From 053dbcbad05990f2cb5a13707b1d9643313d7e03 Mon Sep 17 00:00:00 2001 From: mark1145 Date: Mon, 31 Oct 2022 22:16:50 -0700 Subject: [PATCH] fix: Fixes ignoring mobs, champions, party crash, slayer rarity, boat speedhack, etc (#1222) * Movie ignore mobiles to Mobile class * Allow necromancer familiars to ignore mobiles * ChampionSpawn should not quietly fail when creating new spawn * Fix client party crash bug: 2 people partied, the leader logs out, other client is hung * Fix spellbooks creating with magery/meditation only and creating magery multiple times * Fix BaseRunicTool.GetRandomSlayer() creating more undead slayers than intended * Do not allow players to dismount each other whilst mounted * Fix AOS onwards damage increase tooltip and wrong formula in AOS * Fix monsters killing other monster revenants + animate dead should not attack player pets + other animates * Fix fire steed having loot pack added twice * Fix oaks spawn not able to create Unicorns + Kirins via Activator.CreateInstace() due to constructor having name as parametr * Fix ignoreMobiles flag not propagated to client for non-players. * Fix harrower tents not leeching from players * Fix boats speedhacking around the map * Fix bless, agility etc. not renewing duration * Fix reveal always worked * Fix empty constructor for steeds so they don't throw now. --- Projects/Server/Mobiles/Mobile.cs | 20 + .../Accounting/Account.Migrations.cs | 2 + .../Engines/CannedEvil/ChampionSpawn.cs | 32 +- .../UOContent/Engines/CannedEvil/GenChamps.cs | 10 +- .../Ethics/Evil/Mobiles/UnholySteed.cs | 5 +- .../Engines/Ethics/Hero/Mobiles/HolySteed.cs | 4 +- .../Factions/Mobiles/FactionWarHorse.cs | 5 +- Projects/UOContent/Engines/Party/Party.cs | 40 +- .../UOContent/Engines/Party/PartyPackets.cs | 19 +- Projects/UOContent/Engines/Virtues/Honor.cs | 4 +- Projects/UOContent/Engines/Virtues/Valor.cs | 1 + .../Items/Skill Items/Tools/BaseRunicTool.cs | 733 ++++++++++++------ .../Items/Special/ML/GrizzledMareStatuette.cs | 17 +- .../Items/Weapons/Abilities/Dismount.cs | 5 +- .../Weapons/Abilities/WeaponAbilityPackets.cs | 7 + .../UOContent/Items/Weapons/BaseWeapon.cs | 37 +- .../UOContent/Items/Weapons/SlayerGroup.cs | 4 +- Projects/UOContent/Misc/ProfessionInfo.cs | 2 +- Projects/UOContent/Mobiles/AI/BaseAI.cs | 18 +- .../Mobiles/Animals/Mounts/BaseMount.cs | 3 +- .../Mobiles/Animals/Mounts/Beetle.cs | 4 +- .../Mobiles/Animals/Mounts/DesertOstard.cs | 4 +- .../Mobiles/Animals/Mounts/FireSteed.cs | 9 +- .../Mobiles/Animals/Mounts/ForestOstard.cs | 4 +- .../Mobiles/Animals/Mounts/FrenziedOstard.cs | 4 +- .../Mobiles/Animals/Mounts/HellSteed.cs | 4 +- .../UOContent/Mobiles/Animals/Mounts/Hiryu.cs | 4 +- .../UOContent/Mobiles/Animals/Mounts/Horse.cs | 4 +- .../UOContent/Mobiles/Animals/Mounts/Kirin.cs | 4 +- .../Mobiles/Animals/Mounts/LesserHiryu.cs | 4 +- .../Mobiles/Animals/Mounts/Nightmare.cs | 9 +- .../Mobiles/Animals/Mounts/RidableLlama.cs | 4 +- .../Mobiles/Animals/Mounts/Ridgeback.cs | 4 +- .../Mobiles/Animals/Mounts/SavageRidgeback.cs | 4 +- .../Animals/Mounts/ScaledSwampDragon.cs | 4 +- .../Mobiles/Animals/Mounts/SeaHorse.cs | 4 +- .../Mobiles/Animals/Mounts/SilverSteed.cs | 4 +- .../Mobiles/Animals/Mounts/SkeletalMount.cs | 2 +- .../Mobiles/Animals/Mounts/SwampDragon.cs | 4 +- .../Mobiles/Animals/Mounts/Unicorn.cs | 4 +- .../Animals/Mounts/War Horses/BaseWarHorse.cs | 3 +- .../Mobiles/Familiars/BaseFamiliar.cs | 1 + .../Mobiles/Monsters/AOS/Revenant.cs | 2 + .../Mobiles/Monsters/ML/Animal/CuSidhe.cs | 2 +- .../Mobiles/Monsters/ML/Bedlam/RedDeath.cs | 4 +- .../Monsters/ML/Misc/Melee/Reptalon.cs | 4 +- .../Mobiles/Monsters/SE/FireBeetle.cs | 4 +- Projects/UOContent/Mobiles/PlayerMobile.cs | 34 +- .../Mobiles/Special/HarrowerTentacles.cs | 9 +- Projects/UOContent/Multis/Boats/BaseBoat.cs | 129 ++- Projects/UOContent/Spells/Base/SpellHelper.cs | 2 +- .../Spells/Necromancy/PoisonStrike.cs | 33 +- .../UOContent/Spells/Necromancy/Wither.cs | 30 +- Projects/UOContent/Spells/Sixth/Reveal.cs | 12 +- 54 files changed, 871 insertions(+), 454 deletions(-) diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 95be9b3f1..61b7e43aa 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -329,6 +329,7 @@ public class Mobile : IHued, IComparable, ISpawnable, IObjectPropertyLis private int m_WarmodeChanges; private bool _warmodeSpamValue; private IWeapon m_Weapon; + private bool _ignoreMobiles; private bool m_YellowHealthbar; private List _statMods; @@ -405,6 +406,20 @@ public class Mobile : IHued, IComparable, ISpawnable, IObjectPropertyLis public virtual bool NewGuildDisplay => false; + [CommandProperty(AccessLevel.GameMaster)] + public virtual bool IgnoreMobiles + { + get => _ignoreMobiles; + set + { + if (_ignoreMobiles != value) + { + _ignoreMobiles = value; + Delta(MobileDelta.Flags); + } + } + } + public List Stabled { get; private set; } [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] @@ -6974,6 +6989,11 @@ public class Mobile : IHued, IComparable, ISpawnable, IObjectPropertyLis flags |= 0x08; } + if (IgnoreMobiles) + { + flags |= 0x10; + } + if (m_Warmode) { flags |= 0x40; diff --git a/Projects/UOContent/Accounting/Account.Migrations.cs b/Projects/UOContent/Accounting/Account.Migrations.cs index dd9148b01..b1c487a5c 100644 --- a/Projects/UOContent/Accounting/Account.Migrations.cs +++ b/Projects/UOContent/Accounting/Account.Migrations.cs @@ -94,7 +94,9 @@ namespace Server.Accounting _totalGameTime = reader.ReadTimeSpan(); if (version > 1) + { _email = reader.ReadString(); + } Timer.StartTimer(AfterDeserialization); } diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs index 57762bad9..c57471a79 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs @@ -20,11 +20,14 @@ using Server.Gumps; using Server.Items; using Server.Mobiles; using Server.Regions; +using Server.Logging; +using Server.Utilities; namespace Server.Engines.CannedEvil { public class ChampionSpawn : Item { + private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionSpawn)); private bool m_Active; private ChampionSpawnType m_Type; private List m_Creatures; @@ -597,11 +600,11 @@ namespace Server.Engines.CannedEvil { if (gainedPath) { - m.SendLocalizedMessage(1054032); // You have gained a path in Valor! + pm.SendLocalizedMessage(1054032); // You have gained a path in Valor! } else { - m.SendLocalizedMessage(1054030); // You have gained in Valor! + pm.SendLocalizedMessage(1054030); // You have gained in Valor! } // No delay on Valor gains @@ -682,10 +685,18 @@ namespace Server.Engines.CannedEvil try { - Champion = Activator.CreateInstance(ChampionSpawnInfo.GetInfo(m_Type).Champion) as Mobile; + Champion = ChampionSpawnInfo.GetInfo(m_Type).Champion.CreateInstance(); } catch (Exception e) - { Console.WriteLine($"Exception creating champion {m_Type}: {e}"); } + { + logger.Error( + e, + "Failed to spawn champion \"{MobileType}\" at {Location} ({Map}).", + m_Type, + Location, + Map + ); + } if (Champion != null) { @@ -851,12 +862,21 @@ namespace Server.Engines.CannedEvil public Mobile Spawn(params Type[] types) { + var type = types[Utility.Random(types.Length)]; try { - return Activator.CreateInstance(types[Utility.Random(types.Length)]) as Mobile; + return type.CreateInstance(); } - catch + catch (Exception e) { + logger.Error( + e, + "Failed to spawn minion \"{Type}\" for champion {ChampionSpawnType} at {Location} ({Map}).", + type, + m_Type, + Location, + Map + ); return null; } } diff --git a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs index a21547a24..e01711383 100644 --- a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs +++ b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs @@ -106,9 +106,15 @@ namespace Server.Engines.CannedEvil } } } - catch + catch (Exception e) { - logger.Warning($"Failed to generate champion spawn {entry.m_ChampType.FullName} at {entry.m_SignLocation} ({entry.m_Map})"); + logger.Error( + e, + "Failed to generate champion \"{Type}\" at {Location} ({Map}).", + entry.m_ChampType.FullName, + entry.m_SignLocation, + entry.m_Map + ); } } } diff --git a/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholySteed.cs b/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholySteed.cs index 33d6aeb85..b20cfb1bd 100644 --- a/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholySteed.cs +++ b/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholySteed.cs @@ -4,9 +4,10 @@ namespace Server.Mobiles { public class UnholySteed : BaseMount { + public override string DefaultName => "a dark steed"; + [Constructible] - public UnholySteed() - : base("a dark steed", 0x74, 0x3EA7, AIType.AI_Melee, FightMode.Aggressor) + public UnholySteed() : base(0x74, 0x3EA7, AIType.AI_Melee, FightMode.Aggressor) { SetStr(496, 525); SetDex(86, 105); diff --git a/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolySteed.cs b/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolySteed.cs index ada515e9d..1f9c06a18 100644 --- a/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolySteed.cs +++ b/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolySteed.cs @@ -4,9 +4,9 @@ namespace Server.Mobiles { public class HolySteed : BaseMount { + public override string DefaultName => "a silver steed"; [Constructible] - public HolySteed() - : base("a silver steed", 0x75, 0x3EA8, AIType.AI_Melee, FightMode.Aggressor) + public HolySteed() : base(0x75, 0x3EA8, AIType.AI_Melee, FightMode.Aggressor) { SetStr(496, 525); SetDex(86, 105); diff --git a/Projects/UOContent/Engines/Factions/Mobiles/FactionWarHorse.cs b/Projects/UOContent/Engines/Factions/Mobiles/FactionWarHorse.cs index e0aa13256..896c79546 100644 --- a/Projects/UOContent/Engines/Factions/Mobiles/FactionWarHorse.cs +++ b/Projects/UOContent/Engines/Factions/Mobiles/FactionWarHorse.cs @@ -8,9 +8,10 @@ namespace Server.Factions public const int GoldPrice = 3000; private Faction m_Faction; + public override string DefaultName => "a war horse"; + [Constructible] - public FactionWarHorse(Faction faction = null) - : base("a war horse", 0xE2, 0x3EA0, AIType.AI_Melee, FightMode.Aggressor) + public FactionWarHorse(Faction faction = null) : base(0xE2, 0x3EA0, AIType.AI_Melee, FightMode.Aggressor) { BaseSoundID = 0xA8; diff --git a/Projects/UOContent/Engines/Party/Party.cs b/Projects/UOContent/Engines/Party/Party.cs index f9e8c3f57..c6ce94636 100644 --- a/Projects/UOContent/Engines/Party/Party.cs +++ b/Projects/UOContent/Engines/Party/Party.cs @@ -195,7 +195,7 @@ namespace Server.Engines.PartySystem m.Party = this; Span memberList = - stackalloc byte[PartyPackets.GetPartyMemberListPacketLength(this)].InitializePacket(); + stackalloc byte[PartyPackets.GetPartyMemberListPacketLength(Count)].InitializePacket(); Span attrsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributesPacketLength].InitializePacket(); for (var i = 0; i < Members.Count; ++i) @@ -216,12 +216,7 @@ namespace Server.Engines.PartySystem } } - public void OnAccept(Mobile from) - { - OnAccept(from, false); - } - - public void OnAccept(Mobile from, bool force) + public void OnAccept(Mobile from, bool force = false) { var ourFaction = Faction.Find(Leader); var theirFaction = Faction.Find(from); @@ -289,28 +284,33 @@ namespace Server.Engines.PartySystem return; } - Span removeMember = - stackalloc byte[PartyPackets.GetPartyRemoveMemberPacketLength(this)].InitializePacket(); - + var removed = false; for (var i = 0; i < Members.Count; i++) { if (Members[i].Mobile == m) { Members.RemoveAt(i); - - m.NetState.SendPartyRemoveMember(m.Serial); - m.Party = null; - - m.SendLocalizedMessage(1005451); // You have been removed from the party. - - PartyPackets.CreatePartyRemoveMember(removeMember, m.Serial, this); - SendToAll(removeMember); - SendToAll(1005452); // A player has been removed from your party. - + removed = true; break; } } + if (removed) + { + Span removeMember = + stackalloc byte[PartyPackets.GetPartyRemoveMemberPacketLength(Count)].InitializePacket(); + + // Send empty party to the player removed + m.NetState.SendPartyRemoveMember(m.Serial); + m.Party = null; + + m.SendLocalizedMessage(1005451); // You have been removed from the party. + + PartyPackets.CreatePartyRemoveMember(removeMember, m.Serial, this); + SendToAll(removeMember); + SendToAll(1005452); // A player has been removed from your party. + } + if (Members.Count == 1) { SendToAll(1005450); // The last person has left the party... diff --git a/Projects/UOContent/Engines/Party/PartyPackets.cs b/Projects/UOContent/Engines/Party/PartyPackets.cs index 3b2e7a5dc..1c206955c 100644 --- a/Projects/UOContent/Engines/Party/PartyPackets.cs +++ b/Projects/UOContent/Engines/Party/PartyPackets.cs @@ -23,7 +23,7 @@ namespace Server.Engines.PartySystem public static class PartyPackets { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetPartyMemberListPacketLength(Party p) => 7 + p.Count * 4; + public static int GetPartyMemberListPacketLength(int partyCount) => 7 + partyCount * 4; public static void CreatePartyMemberList(Span buffer, Party p) { @@ -32,15 +32,16 @@ namespace Server.Engines.PartySystem return; } - var length = GetPartyMemberListPacketLength(p); + var count = p.Count; + var length = GetPartyMemberListPacketLength(count); var writer = new SpanWriter(buffer); writer.Write((byte)0xBF); // Packet ID writer.Write((ushort)length); writer.Write((ushort)0x06); // Sub-packet writer.Write((byte)0x01); // Command - writer.Write((byte)p.Count); + writer.Write((byte)count); - for (var i = 0; i < p.Count; ++i) + for (var i = 0; i < count; ++i) { writer.Write(p[i].Mobile.Serial); } @@ -53,14 +54,14 @@ namespace Server.Engines.PartySystem return; } - Span buffer = stackalloc byte[GetPartyMemberListPacketLength(p)]; + Span buffer = stackalloc byte[GetPartyMemberListPacketLength(p.Count)]; CreatePartyMemberList(buffer, p); ns.Send(buffer); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetPartyRemoveMemberPacketLength(Party p) => 11 + (p?.Count * 4 ?? 0); + public static int GetPartyRemoveMemberPacketLength(int partyCount) => 11 + partyCount * 4; public static void SendPartyRemoveMember(this NetState ns, Serial m, Party p = null) { @@ -69,7 +70,7 @@ namespace Server.Engines.PartySystem return; } - Span buffer = stackalloc byte[GetPartyRemoveMemberPacketLength(p)]; + Span buffer = stackalloc byte[GetPartyRemoveMemberPacketLength(p?.Count ?? 0)]; CreatePartyRemoveMember(buffer, m, p); ns.Send(buffer); @@ -82,14 +83,14 @@ namespace Server.Engines.PartySystem return; } - var length = GetPartyRemoveMemberPacketLength(p); + var count = p?.Count ?? 0; + var length = GetPartyRemoveMemberPacketLength(count); var writer = new SpanWriter(buffer); writer.Write((byte)0xBF); // Packet ID writer.Write((ushort)length); writer.Write((ushort)0x06); // Sub-packet writer.Write((byte)0x02); // Command - var count = p?.Count ?? 0; writer.Write((byte)count); writer.Write(removed); diff --git a/Projects/UOContent/Engines/Virtues/Honor.cs b/Projects/UOContent/Engines/Virtues/Honor.cs index de1636ac4..4b72b7468 100644 --- a/Projects/UOContent/Engines/Virtues/Honor.cs +++ b/Projects/UOContent/Engines/Virtues/Honor.cs @@ -218,13 +218,13 @@ namespace Server m_Timer = new InternalTimer(this); m_Timer.Start(); - source.m_hontime = Core.Now + TimeSpan.FromMinutes(40); + source._honorTime = Core.Now + TimeSpan.FromMinutes(40); Timer.StartTimer( TimeSpan.FromMinutes(40), () => { - if (source.m_hontime < Core.Now && source.SentHonorContext != null) + if (source._honorTime < Core.Now && source.SentHonorContext != null) { Cancel(); } diff --git a/Projects/UOContent/Engines/Virtues/Valor.cs b/Projects/UOContent/Engines/Virtues/Valor.cs index b49a8a597..c2efaef39 100644 --- a/Projects/UOContent/Engines/Virtues/Valor.cs +++ b/Projects/UOContent/Engines/Virtues/Valor.cs @@ -109,6 +109,7 @@ public static class ValorVirtue // Your challenge is heard by the Champion of this region! Beware its wrath! from.SendLocalizedMessage(1054037); idol.Spawn.Start(); + idol.Spawn.ActivatedByValor = true; // Uncomment to not allow advancing level after starting with valor. // idol.Spawn.HasBeenAdvanced = true; } diff --git a/Projects/UOContent/Items/Skill Items/Tools/BaseRunicTool.cs b/Projects/UOContent/Items/Skill Items/Tools/BaseRunicTool.cs index 90657b25f..60fbd4815 100644 --- a/Projects/UOContent/Items/Skill Items/Tools/BaseRunicTool.cs +++ b/Projects/UOContent/Items/Skill Items/Tools/BaseRunicTool.cs @@ -207,9 +207,10 @@ namespace Server.Items private static void ApplySkillBonus(AosSkillBonuses attrs, int min, int max, int index, int low, int high) { + var isSpellBook = attrs.Owner is Spellbook; var possibleSkills = - new List(attrs.Owner is Spellbook ? m_PossibleSpellbookSkills : m_PossibleBonusSkills); - var count = Core.SE ? possibleSkills.Count : possibleSkills.Count - 2; + new List(isSpellBook ? m_PossibleSpellbookSkills : m_PossibleBonusSkills); + var count = Core.SE || isSpellBook ? possibleSkills.Count : possibleSkills.Count - 2; SkillName sk; bool found; @@ -234,20 +235,30 @@ namespace Server.Items switch (res) { case ResistanceType.Physical: - ar.PhysicalBonus += Scale(min, max, low, high); - break; + { + ar.PhysicalBonus += Scale(min, max, low, high); + break; + } case ResistanceType.Fire: - ar.FireBonus += Scale(min, max, low, high); - break; + { + ar.FireBonus += Scale(min, max, low, high); + break; + } case ResistanceType.Cold: - ar.ColdBonus += Scale(min, max, low, high); - break; + { + ar.ColdBonus += Scale(min, max, low, high); + break; + } case ResistanceType.Poison: - ar.PoisonBonus += Scale(min, max, low, high); - break; + { + ar.PoisonBonus += Scale(min, max, low, high); + break; + } case ResistanceType.Energy: - ar.EnergyBonus += Scale(min, max, low, high); - break; + { + ar.EnergyBonus += Scale(min, max, low, high); + break; + } } } @@ -332,20 +343,30 @@ namespace Server.Items switch (Utility.Random(5)) { case 0: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitPhysicalArea, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitPhysicalArea, 2, 50, 2); + break; + } case 1: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitFireArea, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitFireArea, 2, 50, 2); + break; + } case 2: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitColdArea, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitColdArea, 2, 50, 2); + break; + } case 3: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitPoisonArea, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitPoisonArea, 2, 50, 2); + break; + } case 4: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitEnergyArea, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitEnergyArea, 2, 50, 2); + break; + } } break; @@ -355,17 +376,25 @@ namespace Server.Items switch (Utility.Random(4)) { case 0: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitMagicArrow, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitMagicArrow, 2, 50, 2); + break; + } case 1: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitHarm, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitHarm, 2, 50, 2); + break; + } case 2: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitFireball, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitFireball, 2, 50, 2); + break; + } case 3: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLightning, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLightning, 2, 50, 2); + break; + } } break; @@ -375,81 +404,129 @@ namespace Server.Items switch (Utility.Random(2)) { case 0: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.UseBestSkill, 1, 1); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.UseBestSkill, 1, 1); + break; + } case 1: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.MageWeapon, 1, 10); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.MageWeapon, 1, 10); + break; + } } break; } case 3: - ApplyAttribute(primary, min, max, AosAttribute.WeaponDamage, 1, 50); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.WeaponDamage, 1, 50); + break; + } case 4: - ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); + break; + } case 5: - ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); + break; + } case 6: - ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); + break; + } case 7: - ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); + break; + } case 8: - ApplyAttribute(primary, min, max, AosAttribute.WeaponSpeed, 5, 30, 5); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.WeaponSpeed, 5, 30, 5); + break; + } case 9: - ApplyAttribute(primary, min, max, AosAttribute.SpellChanneling, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.SpellChanneling, 1, 1); + break; + } case 10: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitDispel, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitDispel, 2, 50, 2); + break; + } case 11: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechHits, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechHits, 2, 50, 2); + break; + } case 12: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLowerAttack, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLowerAttack, 2, 50, 2); + break; + } case 13: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLowerDefend, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLowerDefend, 2, 50, 2); + break; + } case 14: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechMana, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechMana, 2, 50, 2); + break; + } case 15: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechStam, 2, 50, 2); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.HitLeechStam, 2, 50, 2); + break; + } case 16: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.LowerStatReq, 10, 100, 10); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.LowerStatReq, 10, 100, 10); + break; + } case 17: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistPhysicalBonus, 1, 15); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistPhysicalBonus, 1, 15); + break; + } case 18: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistFireBonus, 1, 15); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistFireBonus, 1, 15); + break; + } case 19: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistColdBonus, 1, 15); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistColdBonus, 1, 15); + break; + } case 20: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistPoisonBonus, 1, 15); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistPoisonBonus, 1, 15); + break; + } case 21: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistEnergyBonus, 1, 15); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.ResistEnergyBonus, 1, 15); + break; + } case 22: - ApplyAttribute(secondary, min, max, AosWeaponAttribute.DurabilityBonus, 10, 100, 10); - break; + { + ApplyAttribute(secondary, min, max, AosWeaponAttribute.DurabilityBonus, 10, 100, 10); + break; + } case 23: - weapon.Slayer = GetRandomSlayer(); - break; + { + weapon.Slayer = GetRandomSlayer(); + break; + } case 24: - GetElementalDamages(weapon); - break; + { + GetElementalDamages(weapon); + break; + } } } } @@ -529,14 +606,14 @@ namespace Server.Items return SlayerName.None; } - // -1 To Exclude the Fey Slayer which appears ONLY on a certain artifact. - var group = groups[Utility.Random(groups.Length - 1)]; + // 10% chance of a super - skip fey + if (Utility.RandomDouble() < 0.10) + { + return groups[Utility.Random(groups.Length - 1)].Super.Name; + } - // 10% chance for a super slayer - var entry = group.Entries.Length == 0 || Utility.Random(10) == 0 - ? group.Super : group.Entries.RandomElement(); - - return entry.Name; + // Minor slayer - skip fey and undead + return groups[Utility.Random(groups.Length - 2)].Entries.RandomElement().Name; } public void ApplyAttributesTo(BaseArmor armor) @@ -611,87 +688,135 @@ namespace Server.Items { /* Begin Shields */ case 0: - ApplyAttribute(primary, min, max, AosAttribute.SpellChanneling, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.SpellChanneling, 1, 1); + break; + } case 1: - ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); + break; + } case 2: - if (Core.ML) { - ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); - } - else - { - ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); - } + if (Core.ML) + { + ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); + } + else + { + ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); + } - break; + break; + } case 3: - ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); + break; + } /* Begin Armor */ case 4: - ApplyAttribute(secondary, min, max, AosArmorAttribute.LowerStatReq, 10, 100, 10); - break; + { + ApplyAttribute(secondary, min, max, AosArmorAttribute.LowerStatReq, 10, 100, 10); + break; + } case 5: - ApplyAttribute(secondary, min, max, AosArmorAttribute.SelfRepair, 1, 5); - break; + { + ApplyAttribute(secondary, min, max, AosArmorAttribute.SelfRepair, 1, 5); + break; + } case 6: - ApplyAttribute(secondary, min, max, AosArmorAttribute.DurabilityBonus, 10, 100, 10); - break; + { + ApplyAttribute(secondary, min, max, AosArmorAttribute.DurabilityBonus, 10, 100, 10); + break; + } /* End Shields */ case 7: - ApplyAttribute(secondary, min, max, AosArmorAttribute.MageArmor, 1, 1); - break; + { + ApplyAttribute(secondary, min, max, AosArmorAttribute.MageArmor, 1, 1); + break; + } case 8: - ApplyAttribute(primary, min, max, AosAttribute.RegenHits, 1, 2); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.RegenHits, 1, 2); + break; + } case 9: - ApplyAttribute(primary, min, max, AosAttribute.RegenStam, 1, 3); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.RegenStam, 1, 3); + break; + } case 10: - ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); + break; + } case 11: - ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); + break; + } case 12: - ApplyAttribute(primary, min, max, AosAttribute.BonusHits, 1, 5); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusHits, 1, 5); + break; + } case 13: - ApplyAttribute(primary, min, max, AosAttribute.BonusStam, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusStam, 1, 8); + break; + } case 14: - ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); + break; + } case 15: - ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); + break; + } case 16: - ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); + break; + } case 17: - ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); + break; + } case 18: - ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); + break; + } case 19: - ApplyResistance(armor, min, max, ResistanceType.Physical, 1, 15); - break; + { + ApplyResistance(armor, min, max, ResistanceType.Physical, 1, 15); + break; + } case 20: - ApplyResistance(armor, min, max, ResistanceType.Fire, 1, 15); - break; + { + ApplyResistance(armor, min, max, ResistanceType.Fire, 1, 15); + break; + } case 21: - ApplyResistance(armor, min, max, ResistanceType.Cold, 1, 15); - break; + { + ApplyResistance(armor, min, max, ResistanceType.Cold, 1, 15); + break; + } case 22: - ApplyResistance(armor, min, max, ResistanceType.Poison, 1, 15); - break; + { + ApplyResistance(armor, min, max, ResistanceType.Poison, 1, 15); + break; + } case 23: - ApplyResistance(armor, min, max, ResistanceType.Energy, 1, 15); - break; + { + ApplyResistance(armor, min, max, ResistanceType.Energy, 1, 15); + break; + } /* End Armor */ } } @@ -728,62 +853,100 @@ namespace Server.Items switch (random) { case 0: - ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.ReflectPhysical, 1, 15); + break; + } case 1: - ApplyAttribute(primary, min, max, AosAttribute.RegenHits, 1, 2); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.RegenHits, 1, 2); + break; + } case 2: - ApplyAttribute(primary, min, max, AosAttribute.RegenStam, 1, 3); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.RegenStam, 1, 3); + break; + } case 3: - ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); + break; + } case 4: - ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); + break; + } case 5: - ApplyAttribute(primary, min, max, AosAttribute.BonusHits, 1, 5); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusHits, 1, 5); + break; + } case 6: - ApplyAttribute(primary, min, max, AosAttribute.BonusStam, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusStam, 1, 8); + break; + } case 7: - ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); + break; + } case 8: - ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); + break; + } case 9: - ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); + break; + } case 10: - ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); + break; + } case 11: - ApplyAttribute(secondary, min, max, AosArmorAttribute.LowerStatReq, 10, 100, 10); - break; + { + ApplyAttribute(secondary, min, max, AosArmorAttribute.LowerStatReq, 10, 100, 10); + break; + } case 12: - ApplyAttribute(secondary, min, max, AosArmorAttribute.SelfRepair, 1, 5); - break; + { + ApplyAttribute(secondary, min, max, AosArmorAttribute.SelfRepair, 1, 5); + break; + } case 13: - ApplyAttribute(secondary, min, max, AosArmorAttribute.DurabilityBonus, 10, 100, 10); - break; + { + ApplyAttribute(secondary, min, max, AosArmorAttribute.DurabilityBonus, 10, 100, 10); + break; + } case 14: - ApplyAttribute(resists, min, max, AosElementAttribute.Physical, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Physical, 1, 15); + break; + } case 15: - ApplyAttribute(resists, min, max, AosElementAttribute.Fire, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Fire, 1, 15); + break; + } case 16: - ApplyAttribute(resists, min, max, AosElementAttribute.Cold, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Cold, 1, 15); + break; + } case 17: - ApplyAttribute(resists, min, max, AosElementAttribute.Poison, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Poison, 1, 15); + break; + } case 18: - ApplyAttribute(resists, min, max, AosElementAttribute.Energy, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Energy, 1, 15); + break; + } } } } @@ -819,77 +982,125 @@ namespace Server.Items switch (random) { case 0: - ApplyAttribute(resists, min, max, AosElementAttribute.Physical, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Physical, 1, 15); + break; + } case 1: - ApplyAttribute(resists, min, max, AosElementAttribute.Fire, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Fire, 1, 15); + break; + } case 2: - ApplyAttribute(resists, min, max, AosElementAttribute.Cold, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Cold, 1, 15); + break; + } case 3: - ApplyAttribute(resists, min, max, AosElementAttribute.Poison, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Poison, 1, 15); + break; + } case 4: - ApplyAttribute(resists, min, max, AosElementAttribute.Energy, 1, 15); - break; + { + ApplyAttribute(resists, min, max, AosElementAttribute.Energy, 1, 15); + break; + } case 5: - ApplyAttribute(primary, min, max, AosAttribute.WeaponDamage, 1, 25); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.WeaponDamage, 1, 25); + break; + } case 6: - ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.DefendChance, 1, 15); + break; + } case 7: - ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.AttackChance, 1, 15); + break; + } case 8: - ApplyAttribute(primary, min, max, AosAttribute.BonusStr, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusStr, 1, 8); + break; + } case 9: - ApplyAttribute(primary, min, max, AosAttribute.BonusDex, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusDex, 1, 8); + break; + } case 10: - ApplyAttribute(primary, min, max, AosAttribute.BonusInt, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusInt, 1, 8); + break; + } case 11: - ApplyAttribute(primary, min, max, AosAttribute.EnhancePotions, 5, 25, 5); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.EnhancePotions, 5, 25, 5); + break; + } case 12: - ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); + break; + } case 13: - ApplyAttribute(primary, min, max, AosAttribute.CastRecovery, 1, 3); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.CastRecovery, 1, 3); + break; + } case 14: - ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); + break; + } case 15: - ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); + break; + } case 16: - ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.Luck, 1, 100); + break; + } case 17: - ApplyAttribute(primary, min, max, AosAttribute.SpellDamage, 1, 12); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.SpellDamage, 1, 12); + break; + } case 18: - ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.NightSight, 1, 1); + break; + } case 19: - ApplySkillBonus(skills, min, max, 0, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 0, 1, 15); + break; + } case 20: - ApplySkillBonus(skills, min, max, 1, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 1, 1, 15); + break; + } case 21: - ApplySkillBonus(skills, min, max, 2, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 2, 1, 15); + break; + } case 22: - ApplySkillBonus(skills, min, max, 3, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 3, 1, 15); + break; + } case 23: - ApplySkillBonus(skills, min, max, 4, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 4, 1, 15); + break; + } } } } @@ -938,41 +1149,65 @@ namespace Server.Items break; } case 4: - ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.BonusMana, 1, 8); + break; + } case 5: - ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.CastSpeed, 1, 1); + break; + } case 6: - ApplyAttribute(primary, min, max, AosAttribute.CastRecovery, 1, 3); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.CastRecovery, 1, 3); + break; + } case 7: - ApplyAttribute(primary, min, max, AosAttribute.SpellDamage, 1, 12); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.SpellDamage, 1, 12); + break; + } case 8: - ApplySkillBonus(skills, min, max, 0, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 0, 1, 15); + break; + } case 9: - ApplySkillBonus(skills, min, max, 1, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 1, 1, 15); + break; + } case 10: - ApplySkillBonus(skills, min, max, 2, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 2, 1, 15); + break; + } case 11: - ApplySkillBonus(skills, min, max, 3, 1, 15); - break; + { + ApplySkillBonus(skills, min, max, 3, 1, 15); + break; + } case 12: - ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.LowerRegCost, 1, 20); + break; + } case 13: - ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.LowerManaCost, 1, 8); + break; + } case 14: - ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); - break; + { + ApplyAttribute(primary, min, max, AosAttribute.RegenMana, 1, 2); + break; + } case 15: - spellbook.Slayer = GetRandomSlayer(); - break; + { + spellbook.Slayer = GetRandomSlayer(); + break; + } } } } diff --git a/Projects/UOContent/Items/Special/ML/GrizzledMareStatuette.cs b/Projects/UOContent/Items/Special/ML/GrizzledMareStatuette.cs index a451dfbe5..1a2bb8ea1 100644 --- a/Projects/UOContent/Items/Special/ML/GrizzledMareStatuette.cs +++ b/Projects/UOContent/Items/Special/ML/GrizzledMareStatuette.cs @@ -35,11 +35,10 @@ namespace Server.Mobiles { public class GrizzledMare : HellSteed { - private static readonly string m_Myname = "a grizzled mare"; + public override string DefaultName => "a grizzled mare"; [Constructible] public GrizzledMare() - : base(m_Myname) { } @@ -49,18 +48,11 @@ namespace Server.Mobiles public override bool DeleteOnRelease => true; - public virtual void OnAfterDeserialize_Callback() - { - SetStats(this); - - Name = m_Myname; - } - public override void Serialize(IGenericWriter writer) { base.Serialize(writer); - writer.Write(1); // version + writer.Write(0); // version } public override void Deserialize(IGenericReader reader) @@ -68,11 +60,6 @@ namespace Server.Mobiles base.Deserialize(reader); var version = reader.ReadInt(); - - if (version < 1) - { - Timer.StartTimer(TimeSpan.FromSeconds(0), OnAfterDeserialize_Callback); - } } } } diff --git a/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs b/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs index ddcb9b389..a53945086 100644 --- a/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs +++ b/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs @@ -23,6 +23,8 @@ public class Dismount : WeaponAbility return; } + ClearCurrentAbility(attacker); + if (defender is ChaosDragoon or ChaosDragoonElite) { return; @@ -33,11 +35,10 @@ public class Dismount : WeaponAbility if (attacker.Weapon is not Lance || !defender.Mounted && !defender.Flying && defender.Weapon is not Lance) { attacker.SendLocalizedMessage(1061283); // You cannot perform that attack while mounted! + return; } } - ClearCurrentAbility(attacker); - var mount = defender.Mount; if (mount == null && !AnimalForm.UnderTransformation(defender)) diff --git a/Projects/UOContent/Items/Weapons/Abilities/WeaponAbilityPackets.cs b/Projects/UOContent/Items/Weapons/Abilities/WeaponAbilityPackets.cs index ae2f0be8f..a192f6da9 100644 --- a/Projects/UOContent/Items/Weapons/Abilities/WeaponAbilityPackets.cs +++ b/Projects/UOContent/Items/Weapons/Abilities/WeaponAbilityPackets.cs @@ -18,6 +18,13 @@ namespace Server.Items if (index >= 1 && index < WeaponAbility.Abilities.Length) { + if (m.Mounted && index == 6) //dismount + { + WeaponAbility.ClearCurrentAbility(m); + m.SendLocalizedMessage(1061283); // You cannot perform that attack while mounted! + return; + } + WeaponAbility.SetCurrentAbility(m, WeaponAbility.Abilities[index]); } else diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 8f835d62d..bbd6fedcb 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -487,14 +487,7 @@ namespace Server.Items if (Quality == WeaponQuality.Exceptional) { - if (Attributes.WeaponDamage > 35) - { - Attributes.WeaponDamage -= 20; - } - else - { - Attributes.WeaponDamage = 15; - } + Attributes.WeaponDamage = 35; if (Core.ML) { @@ -2385,27 +2378,8 @@ namespace Server.Items }; } - public virtual int GetDamageBonus() - { - var quality = m_Quality switch - { - WeaponQuality.Low => -20, - WeaponQuality.Exceptional => 20, - _ => 0 - }; - - var damageLevel = m_DamageLevel switch - { - WeaponDamageLevel.Ruin => 15, - WeaponDamageLevel.Might => 20, - WeaponDamageLevel.Force => 25, - WeaponDamageLevel.Power => 30, - WeaponDamageLevel.Vanq => 35, - _ => 0 - }; - - return VirtualDamageBonus + quality + damageLevel; - } + // Note: AOS quality/damage bonuses removed since they are incorporated into the crafting already + public virtual int GetDamageBonus() => VirtualDamageBonus; public virtual double ScaleDamageAOS(Mobile attacker, double damage, bool checkSkills) { @@ -2477,10 +2451,9 @@ namespace Server.Items damageBonus = 100; } - var totalBonus = strengthBonus + anatomyBonus + tacticsBonus + lumberBonus + - (GetDamageBonus() + damageBonus) / 100.0; + var totalBonus = strengthBonus + anatomyBonus + tacticsBonus + lumberBonus + damageBonus + GetDamageBonus(); - return damage + (int)(damage * totalBonus); + return damage + damage * totalBonus / 100.0; } public virtual int ComputeDamageAOS(Mobile attacker, Mobile defender) => diff --git a/Projects/UOContent/Items/Weapons/SlayerGroup.cs b/Projects/UOContent/Items/Weapons/SlayerGroup.cs index 7e9d5d7d7..b358431d0 100644 --- a/Projects/UOContent/Items/Weapons/SlayerGroup.cs +++ b/Projects/UOContent/Items/Weapons/SlayerGroup.cs @@ -402,11 +402,13 @@ namespace Server.Items Groups = new[] { humanoid, - undead, elemental, abyss, arachnid, reptilian, + // Skip undead for minor slayers + undead, + // Skip fey for random slayer fey }; diff --git a/Projects/UOContent/Misc/ProfessionInfo.cs b/Projects/UOContent/Misc/ProfessionInfo.cs index 0c945556b..ad9f4253c 100644 --- a/Projects/UOContent/Misc/ProfessionInfo.cs +++ b/Projects/UOContent/Misc/ProfessionInfo.cs @@ -105,7 +105,7 @@ namespace Server var key = cols[0].ToLowerInvariant(); var value = cols[1].Trim('"'); - if (key == "type" && value != "profession") + if (key == "type" && !value.InsensitiveEquals("profession")) { break; } diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index 93d4f0338..c886b2ae2 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -2633,8 +2633,8 @@ public abstract class BaseAI var bc = m as BaseCreature; var pm = m as PlayerMobile; - // Monster don't attack it's own summon - if (Core.AOS && bc != null && bc.Summoned && bc.SummonMaster == m_Mobile) + // Monster don't attack it's own summon or the summon of another monster + if (Core.AOS && bc != null && bc.Summoned && (bc.SummonMaster == m_Mobile || (!bc.SummonMaster.Player && IsHostile(bc.SummonMaster)))) { continue; } @@ -2658,6 +2658,18 @@ public abstract class BaseAI { continue; } + + // Animated creatures cannot attack other animated creatures + if (m_Mobile.IsAnimatedDead && bc?.IsAnimatedDead == true) + { + continue; + } + + // Animated creatures cannot attack pets of other players + if (m_Mobile.IsAnimatedDead && bc?.Controlled == true) + { + continue; + } } // If we only want faction friends, make sure it's one. @@ -2726,7 +2738,7 @@ public abstract class BaseAI newFocusMob = m; val = theirVal; } - // The summon is targeted when nothing else around. Otherwise this monster enters idle mode. + // The summon is targeted when nothing else around. Otherwise this monster enters idle mode. // Do a check for this edge case so players cannot abuse by casting EVs offscreen to kill an idle monster. else if (Core.AOS && theirVal > enemySummonVal && m_Mobile.InLOS(m) && bc?.Summoned == true && bc?.Controlled != true) { diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/BaseMount.cs b/Projects/UOContent/Mobiles/Animals/Mounts/BaseMount.cs index fcffd9646..76ca5b300 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/BaseMount.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/BaseMount.cs @@ -11,11 +11,10 @@ namespace Server.Mobiles private Mobile m_Rider; public BaseMount( - string name, int bodyID, int itemID, AIType aiType, FightMode fightMode = FightMode.Closest, + int bodyID, int itemID, AIType aiType, FightMode fightMode = FightMode.Closest, int rangePerception = 10, int rangeFight = 1 ) : base(aiType, fightMode, rangePerception, rangeFight) { - Name = name; Body = bodyID; InternalItem = new MountItem(this, itemID); diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/Beetle.cs b/Projects/UOContent/Mobiles/Animals/Mounts/Beetle.cs index 053385ab0..303e88b10 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/Beetle.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/Beetle.cs @@ -6,8 +6,10 @@ namespace Server.Mobiles { public class Beetle : BaseMount { + public override string DefaultName => "a giant beetle"; + [Constructible] - public Beetle(string name = "a giant beetle") : base(name, 0x317, 0x3EBC, AIType.AI_Melee) + public Beetle() : base( 0x317, 0x3EBC, AIType.AI_Melee) { SetStr(300); SetDex(100); diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/DesertOstard.cs b/Projects/UOContent/Mobiles/Animals/Mounts/DesertOstard.cs index e2e7516f6..6201e2d56 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/DesertOstard.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/DesertOstard.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class DesertOstard : BaseMount { + public override string DefaultName => "a desert ostard"; + [Constructible] - public DesertOstard(string name = "a desert ostard") : base(name, 0xD2, 0x3EA3, AIType.AI_Animal, FightMode.Aggressor) + public DesertOstard() : base(0xD2, 0x3EA3, AIType.AI_Animal, FightMode.Aggressor) { BaseSoundID = 0x270; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/FireSteed.cs b/Projects/UOContent/Mobiles/Animals/Mounts/FireSteed.cs index 86b017f36..ec3cf46cb 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/FireSteed.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/FireSteed.cs @@ -5,8 +5,10 @@ namespace Server.Mobiles { public class FireSteed : BaseMount { + public override string DefaultName => "a fire steed"; + [Constructible] - public FireSteed(string name = "a fire steed") : base(name, 0xBE, 0x3E9E, AIType.AI_Melee) + public FireSteed() : base(0xBE, 0x3E9E, AIType.AI_Melee) { BaseSoundID = 0xA8; @@ -37,6 +39,9 @@ namespace Server.Mobiles Tamable = true; ControlSlots = 2; MinTameSkill = 106.0; + + PackItem(new SulfurousAsh(Utility.RandomMinMax(151, 300))); + PackItem(new Ruby(Utility.RandomMinMax(16, 30))); } public FireSteed(Serial serial) : base(serial) @@ -52,8 +57,6 @@ namespace Server.Mobiles public override void GenerateLoot() { - PackItem(new SulfurousAsh(Utility.RandomMinMax(151, 300))); - PackItem(new Ruby(Utility.RandomMinMax(16, 30))); } public override void Serialize(IGenericWriter writer) diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/ForestOstard.cs b/Projects/UOContent/Mobiles/Animals/Mounts/ForestOstard.cs index c7b764afe..c277c6744 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/ForestOstard.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/ForestOstard.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class ForestOstard : BaseMount { + public override string DefaultName => "a forest ostard"; + [Constructible] - public ForestOstard(string name = "a forest ostard") : base(name, 0xDB, 0x3EA5, AIType.AI_Animal, FightMode.Aggressor) + public ForestOstard() : base(0xDB, 0x3EA5, AIType.AI_Animal, FightMode.Aggressor) { Hue = Utility.RandomSlimeHue() | 0x8000; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/FrenziedOstard.cs b/Projects/UOContent/Mobiles/Animals/Mounts/FrenziedOstard.cs index 98425336e..e6dc45ab7 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/FrenziedOstard.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/FrenziedOstard.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class FrenziedOstard : BaseMount { + public override string DefaultName => "a frenzied ostard"; + [Constructible] - public FrenziedOstard(string name = "a frenzied ostard") : base(name, 0xDA, 0x3EA4, AIType.AI_Melee) + public FrenziedOstard() : base(0xDA, 0x3EA4, AIType.AI_Melee) { Hue = Race.Human.RandomHairHue() | 0x8000; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/HellSteed.cs b/Projects/UOContent/Mobiles/Animals/Mounts/HellSteed.cs index 094510075..d6a127aae 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/HellSteed.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/HellSteed.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class HellSteed : BaseMount { + public override string DefaultName => "a frenzied ostard"; + [Constructible] - public HellSteed(string name = "a hellsteed") : base(name, 793, 0x3EBB, AIType.AI_Animal, FightMode.Aggressor) + public HellSteed() : base(793, 0x3EBB, AIType.AI_Animal, FightMode.Aggressor) { SetStats(this); } diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/Hiryu.cs b/Projects/UOContent/Mobiles/Animals/Mounts/Hiryu.cs index 8b068a014..dacf3d919 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/Hiryu.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/Hiryu.cs @@ -6,8 +6,10 @@ namespace Server.Mobiles; public class Hiryu : BaseMount { + public override string DefaultName => "a hiryu"; + [Constructible] - public Hiryu() : base("a hiryu", 243, 0x3E94, AIType.AI_Melee) + public Hiryu() : base(243, 0x3E94, AIType.AI_Melee) { Hue = GetHue(); diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/Horse.cs b/Projects/UOContent/Mobiles/Animals/Mounts/Horse.cs index 297215d03..37407c63d 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/Horse.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/Horse.cs @@ -16,8 +16,10 @@ namespace Server.Mobiles 0xCC, 0x3EA2 }; + public override string DefaultName => "a horse"; + [Constructible] - public Horse(string name = "a horse") : base(name, 0xE2, 0x3EA0, AIType.AI_Animal, FightMode.Aggressor) + public Horse() : base(0xE2, 0x3EA0, AIType.AI_Animal, FightMode.Aggressor) { var random = Utility.Random(4); diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/Kirin.cs b/Projects/UOContent/Mobiles/Animals/Mounts/Kirin.cs index a496b9816..7010f6d7c 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/Kirin.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/Kirin.cs @@ -6,8 +6,10 @@ namespace Server.Mobiles { public class Kirin : BaseMount { + public override string DefaultName => "a ki-rin"; + [Constructible] - public Kirin(string name = "a ki-rin") : base(name, 132, 0x3EAD, AIType.AI_Mage, FightMode.Evil) + public Kirin() : base(132, 0x3EAD, AIType.AI_Mage, FightMode.Evil) { BaseSoundID = 0x3C5; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/LesserHiryu.cs b/Projects/UOContent/Mobiles/Animals/Mounts/LesserHiryu.cs index b188156e4..5f44ad263 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/LesserHiryu.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/LesserHiryu.cs @@ -6,8 +6,10 @@ namespace Server.Mobiles; public class LesserHiryu : BaseMount { + public override string DefaultName => "a lesser hiryu"; + [Constructible] - public LesserHiryu() : base("a lesser hiryu", 243, 0x3E94, AIType.AI_Melee) + public LesserHiryu() : base(243, 0x3E94, AIType.AI_Melee) { Hue = GetHue(); diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/Nightmare.cs b/Projects/UOContent/Mobiles/Animals/Mounts/Nightmare.cs index 721a031ed..6ac0e4d15 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/Nightmare.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/Nightmare.cs @@ -4,13 +4,10 @@ namespace Server.Mobiles { public class Nightmare : BaseMount { + public override string DefaultName => "a nightmare"; + [Constructible] - public Nightmare(string name = "a nightmare") : base( - name, - 0x74, - 0x3EA7, - AIType.AI_Mage - ) + public Nightmare() : base(0x74, 0x3EA7, AIType.AI_Mage) { BaseSoundID = Core.AOS ? 0xA8 : 0x16A; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/RidableLlama.cs b/Projects/UOContent/Mobiles/Animals/Mounts/RidableLlama.cs index 7ba192f45..f16230394 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/RidableLlama.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/RidableLlama.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class RidableLlama : BaseMount { + public override string DefaultName => "a ridable llama"; + [Constructible] - public RidableLlama(string name = "a ridable llama") : base(name, 0xDC, 0x3EA6, AIType.AI_Animal, FightMode.Aggressor + public RidableLlama() : base(0xDC, 0x3EA6, AIType.AI_Animal, FightMode.Aggressor ) { BaseSoundID = 0x3F3; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/Ridgeback.cs b/Projects/UOContent/Mobiles/Animals/Mounts/Ridgeback.cs index 93345b7c2..15ca4d8c7 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/Ridgeback.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/Ridgeback.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class Ridgeback : BaseMount { + public override string DefaultName => "a ridgeback"; + [Constructible] - public Ridgeback(string name = "a ridgeback") : base(name, 187, 0x3EBA, AIType.AI_Animal, FightMode.Aggressor) + public Ridgeback() : base(187, 0x3EBA, AIType.AI_Animal, FightMode.Aggressor) { BaseSoundID = 0x3F3; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/SavageRidgeback.cs b/Projects/UOContent/Mobiles/Animals/Mounts/SavageRidgeback.cs index b8bfc6615..8132fcb1b 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/SavageRidgeback.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/SavageRidgeback.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class SavageRidgeback : BaseMount { + public override string DefaultName => "a savage ridgeback"; + [Constructible] - public SavageRidgeback(string name = "a savage ridgeback") : base(name, 188, 0x3EB8, AIType.AI_Melee, FightMode.Aggressor) + public SavageRidgeback() : base(188, 0x3EB8, AIType.AI_Melee, FightMode.Aggressor) { BaseSoundID = 0x3F3; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/ScaledSwampDragon.cs b/Projects/UOContent/Mobiles/Animals/Mounts/ScaledSwampDragon.cs index 4705c668a..9bf19a1cb 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/ScaledSwampDragon.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/ScaledSwampDragon.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class ScaledSwampDragon : BaseMount { + public override string DefaultName => "a swamp dragon"; + [Constructible] - public ScaledSwampDragon(string name = "a swamp dragon") : base(name, 0x31F, 0x3EBE, AIType.AI_Melee, FightMode.Aggressor) + public ScaledSwampDragon() : base(0x31F, 0x3EBE, AIType.AI_Melee, FightMode.Aggressor) { SetStr(201, 300); SetDex(66, 85); diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/SeaHorse.cs b/Projects/UOContent/Mobiles/Animals/Mounts/SeaHorse.cs index 4f799c9ad..13ee38114 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/SeaHorse.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/SeaHorse.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class SeaHorse : BaseMount { + public override string DefaultName => "a sea horse"; + [Constructible] - public SeaHorse(string name = "a sea horse") : base(name, 0x90, 0x3EB3, AIType.AI_Animal, FightMode.Aggressor) + public SeaHorse() : base(0x90, 0x3EB3, AIType.AI_Animal, FightMode.Aggressor) { InitStats(Utility.Random(50, 30), Utility.Random(50, 30), 10); Skills.MagicResist.Base = 25.0 + Utility.RandomDouble() * 5.0; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/SilverSteed.cs b/Projects/UOContent/Mobiles/Animals/Mounts/SilverSteed.cs index 43d32abe8..1e302e011 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/SilverSteed.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/SilverSteed.cs @@ -2,8 +2,10 @@ namespace Server.Mobiles { public class SilverSteed : BaseMount { + public override string DefaultName => "a silver steed"; + [Constructible] - public SilverSteed(string name = "a silver steed") : base(name, 0x75, 0x3EA8, AIType.AI_Animal, FightMode.Aggressor) + public SilverSteed() : base(0x75, 0x3EA8, AIType.AI_Animal, FightMode.Aggressor) { InitStats(Utility.Random(50, 30), Utility.Random(50, 30), 10); Skills.MagicResist.Base = 25.0 + Utility.RandomDouble() * 5.0; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/SkeletalMount.cs b/Projects/UOContent/Mobiles/Animals/Mounts/SkeletalMount.cs index 2b2f76a3b..1592dd935 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/SkeletalMount.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/SkeletalMount.cs @@ -3,7 +3,7 @@ namespace Server.Mobiles public class SkeletalMount : BaseMount { [Constructible] - public SkeletalMount(string name = null) : base(name, 793, 0x3EBB, AIType.AI_Animal, FightMode.Aggressor) + public SkeletalMount() : base(793, 0x3EBB, AIType.AI_Animal, FightMode.Aggressor) { SetStr(91, 100); SetDex(46, 55); diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/SwampDragon.cs b/Projects/UOContent/Mobiles/Animals/Mounts/SwampDragon.cs index 984e8bedf..a4da036ef 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/SwampDragon.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/SwampDragon.cs @@ -4,6 +4,8 @@ namespace Server.Mobiles { public class SwampDragon : BaseMount { + public override string DefaultName => "a swamp dragon"; + private string _bardingCraftedBy; private bool m_BardingExceptional; private int m_BardingHP; @@ -11,7 +13,7 @@ namespace Server.Mobiles private bool m_HasBarding; [Constructible] - public SwampDragon(string name = "a swamp dragon") : base(name, 0x31A, 0x3EBD, AIType.AI_Melee, FightMode.Aggressor) + public SwampDragon() : base(0x31A, 0x3EBD, AIType.AI_Melee, FightMode.Aggressor) { BaseSoundID = 0x16A; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/Unicorn.cs b/Projects/UOContent/Mobiles/Animals/Mounts/Unicorn.cs index 23b101675..9dd36b7b8 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/Unicorn.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/Unicorn.cs @@ -6,8 +6,10 @@ namespace Server.Mobiles { public class Unicorn : BaseMount { + public override string DefaultName => "a unicorn"; + [Constructible] - public Unicorn(string name = "a unicorn") : base(name, 0x7A, 0x3EB4, AIType.AI_Mage, FightMode.Evil) + public Unicorn() : base(0x7A, 0x3EB4, AIType.AI_Mage, FightMode.Evil) { BaseSoundID = 0x4BC; diff --git a/Projects/UOContent/Mobiles/Animals/Mounts/War Horses/BaseWarHorse.cs b/Projects/UOContent/Mobiles/Animals/Mounts/War Horses/BaseWarHorse.cs index 26d9d646c..f189f2460 100644 --- a/Projects/UOContent/Mobiles/Animals/Mounts/War Horses/BaseWarHorse.cs +++ b/Projects/UOContent/Mobiles/Animals/Mounts/War Horses/BaseWarHorse.cs @@ -2,11 +2,12 @@ namespace Server.Mobiles { public abstract class BaseWarHorse : BaseMount { + public override string DefaultName => "a war horse"; + public BaseWarHorse( int bodyID, int itemID, AIType aiType = AIType.AI_Melee, FightMode fightMode = FightMode.Aggressor, int rangePerception = 10, int rangeFight = 1 ) : base( - "a war horse", bodyID, itemID, aiType, diff --git a/Projects/UOContent/Mobiles/Familiars/BaseFamiliar.cs b/Projects/UOContent/Mobiles/Familiars/BaseFamiliar.cs index 8cad6a780..b382ff755 100644 --- a/Projects/UOContent/Mobiles/Familiars/BaseFamiliar.cs +++ b/Projects/UOContent/Mobiles/Familiars/BaseFamiliar.cs @@ -20,6 +20,7 @@ namespace Server.Mobiles public override bool BardImmune => true; public override Poison PoisonImmune => Poison.Lethal; public override bool Commandable => false; + public override bool IgnoreMobiles => true; public override bool PlayerRangeSensitive => false; diff --git a/Projects/UOContent/Mobiles/Monsters/AOS/Revenant.cs b/Projects/UOContent/Mobiles/Monsters/AOS/Revenant.cs index 057ad6c58..52d8ea97f 100644 --- a/Projects/UOContent/Mobiles/Monsters/AOS/Revenant.cs +++ b/Projects/UOContent/Mobiles/Monsters/AOS/Revenant.cs @@ -73,6 +73,8 @@ namespace Server.Mobiles public override bool BardImmune => true; public override Poison PoisonImmune => Poison.Lethal; + public override bool IgnoreMobiles => true; + public override void DisplayPaperdollTo(Mobile to) { // Do nothing diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Animal/CuSidhe.cs b/Projects/UOContent/Mobiles/Monsters/ML/Animal/CuSidhe.cs index b6ab6b169..baa3c630c 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Animal/CuSidhe.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Animal/CuSidhe.cs @@ -5,7 +5,7 @@ namespace Server.Mobiles public class CuSidhe : BaseMount { [Constructible] - public CuSidhe(string name = null) : base(name, 277, 0x3E91, AIType.AI_Animal, FightMode.Aggressor) + public CuSidhe() : base(277, 0x3E91, AIType.AI_Animal, FightMode.Aggressor) { var chance = Utility.RandomDouble() * 23301; diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Bedlam/RedDeath.cs b/Projects/UOContent/Mobiles/Monsters/ML/Bedlam/RedDeath.cs index 60afe619a..e9b0d1584 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Bedlam/RedDeath.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Bedlam/RedDeath.cs @@ -5,7 +5,7 @@ namespace Server.Mobiles public class RedDeath : SkeletalMount { [Constructible] - public RedDeath() : base("Red Death") + public RedDeath() : base() { IsParagon = true; @@ -55,6 +55,8 @@ namespace Server.Mobiles { } + public override string DefaultName => "Red Death"; + public override string CorpseName => "a Red Death corpse"; public override bool GivesMLMinorArtifact => true; diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Misc/Melee/Reptalon.cs b/Projects/UOContent/Mobiles/Monsters/ML/Misc/Melee/Reptalon.cs index 8fb9b180b..99bb3244d 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Misc/Melee/Reptalon.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Misc/Melee/Reptalon.cs @@ -4,8 +4,10 @@ namespace Server.Mobiles { public class Reptalon : BaseMount { + public override string DefaultName => "a reptalon"; + [Constructible] - public Reptalon() : base("a reptalon", 0x114, 0x3E90, AIType.AI_Melee) + public Reptalon() : base(0x114, 0x3E90, AIType.AI_Melee) { BaseSoundID = 0x16A; diff --git a/Projects/UOContent/Mobiles/Monsters/SE/FireBeetle.cs b/Projects/UOContent/Mobiles/Monsters/SE/FireBeetle.cs index 7f4daea42..5b4e3aac4 100644 --- a/Projects/UOContent/Mobiles/Monsters/SE/FireBeetle.cs +++ b/Projects/UOContent/Mobiles/Monsters/SE/FireBeetle.cs @@ -6,8 +6,10 @@ namespace Server.Mobiles [Forge] public class FireBeetle : BaseMount { + public override string DefaultName => "a fire beetle"; + [Constructible] - public FireBeetle() : base("a fire beetle", 0xA9, 0x3E95, AIType.AI_Melee) + public FireBeetle() : base(0xA9, 0x3E95, AIType.AI_Melee) { SetStam(100); SetStr(300); diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 2b4a3973e..3eb7e4d44 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -165,9 +165,7 @@ namespace Server.Mobiles private int m_HairModID = -1, m_HairModHue; - public DateTime m_hontime; - - private bool m_IgnoreMobiles; // IgnoreMobiles should be moved to Server.Mobiles + public DateTime _honorTime; private Mobile m_InsuranceAward; private int m_InsuranceBonus; @@ -420,20 +418,6 @@ namespace Server.Mobiles set; } - [CommandProperty(AccessLevel.GameMaster)] - public bool IgnoreMobiles // IgnoreMobiles should be moved to Server.Mobiles - { - get => m_IgnoreMobiles; - set - { - if (m_IgnoreMobiles != value) - { - m_IgnoreMobiles = value; - Delta(MobileDelta.Flags); - } - } - } - [CommandProperty(AccessLevel.GameMaster)] public NpcGuild NpcGuild { get; set; } @@ -952,18 +936,6 @@ namespace Server.Mobiles return true; } - public override int GetPacketFlags(bool stygianAbyss) - { - var flags = base.GetPacketFlags(stygianAbyss); - - if (m_IgnoreMobiles) - { - flags |= 0x10; - } - - return flags; - } - public bool GetFlag(PlayerFlag flag) => (Flags & flag) != 0; public void SetFlag(PlayerFlag flag, bool value) @@ -2336,7 +2308,7 @@ namespace Server.Mobiles pm.DuelPlayer.Eliminated) || base.OnMoveOver(m); public override bool CheckShove(Mobile shoved) => - m_IgnoreMobiles || TransformationSpellHelper.UnderTransformation(shoved, typeof(WraithFormSpell)) || + IgnoreMobiles || shoved.IgnoreMobiles || TransformationSpellHelper.UnderTransformation(shoved, typeof(WraithFormSpell)) || base.CheckShove(shoved); protected override void OnMapChange(Map oldMap) @@ -3228,7 +3200,7 @@ namespace Server.Mobiles if (AccessLevel > AccessLevel.Player) { - m_IgnoreMobiles = true; + IgnoreMobiles = true; } var list = Stabled; diff --git a/Projects/UOContent/Mobiles/Special/HarrowerTentacles.cs b/Projects/UOContent/Mobiles/Special/HarrowerTentacles.cs index eb98639d4..1ccebbfa1 100644 --- a/Projects/UOContent/Mobiles/Special/HarrowerTentacles.cs +++ b/Projects/UOContent/Mobiles/Special/HarrowerTentacles.cs @@ -146,12 +146,17 @@ namespace Server.Mobiles foreach (var m in eable) { - if (m == m_Owner || !(m_Owner.CanBeHarmful(m) || m.Player && m.Alive)) + if (m == m_Owner || !m_Owner.CanBeHarmful(m)) { continue; } - if (m is not BaseCreature bc || !(bc.Controlled || bc.Summoned || bc.Team != m_Owner.Team)) + if (m.Player && !m.Alive) + { + continue; + } + + if (m is BaseCreature bc && !(bc.Controlled || bc.IsAnimatedDead || bc.Summoned || bc.Team != m_Owner.Team)) { continue; } diff --git a/Projects/UOContent/Multis/Boats/BaseBoat.cs b/Projects/UOContent/Multis/Boats/BaseBoat.cs index 1d080c268..59c936fac 100644 --- a/Projects/UOContent/Multis/Boats/BaseBoat.cs +++ b/Projects/UOContent/Multis/Boats/BaseBoat.cs @@ -67,11 +67,12 @@ namespace Server.Multis private DateTime m_DecayTime; private Direction m_Facing; - private TimerExecutionToken _moveTimerToken; private string m_ShipName; - private TimerExecutionToken _turnTimerToken; + + private TurnTimer _turnTimer; + private MoveTimer _moveTimer; public BaseBoat() : base(0x0) { @@ -125,7 +126,7 @@ namespace Server.Multis public Direction Moving { get; set; } [CommandProperty(AccessLevel.GameMaster)] - public bool IsMoving => _moveTimerToken.Running; + public bool IsMoving => _moveTimer?.Running == true; [CommandProperty(AccessLevel.GameMaster)] public int Speed { get; set; } @@ -459,8 +460,8 @@ namespace Server.Multis Hold?.Delete(); PPlank?.Delete(); SPlank?.Delete(); - _turnTimerToken.Cancel(); - _moveTimerToken.Cancel(); + _turnTimer?.Stop(); + _moveTimer?.Stop(); Boats.Remove(this); } @@ -1091,8 +1092,10 @@ namespace Server.Multis Speed = FastSpeed; Order = single ? BoatOrder.Single : BoatOrder.Course; - _moveTimerToken.Cancel(); - Timer.StartTimer(FastInterval, FastInterval, StopBoat, out _moveTimerToken); + if (!SafelyStartMoveTimer(FastInterval, single)) + { + return false; + } if (message) { @@ -1279,11 +1282,27 @@ namespace Server.Multis if (Order != BoatOrder.Move) { - _moveTimerToken.Cancel(); + _turnTimer?.Stop(); } - _turnTimerToken.Cancel(); - Timer.StartTimer(TimeSpan.FromMilliseconds(500), () => Turn(offset), out _turnTimerToken); + _turnTimer ??= new TurnTimer(offset, this, TimeSpan.FromMilliseconds(500), message); + + if (_turnTimer.Running && _turnTimer._turn == offset) + { + // Dont let them spin around in circles like crazy + return true; + } + + // Have they issued a new command too soon? Ignore it if so + var turnTimerNext = (_turnTimer.Next - Core.Now).TotalMilliseconds; + if (turnTimerNext is < 0 and > -250) + { + return true; + } + + _turnTimer._turn = offset; + _turnTimer.Stop(); + _turnTimer.Start(); if (message) { @@ -1295,7 +1314,7 @@ namespace Server.Multis public bool Turn(int offset, bool message = true) { - _turnTimerToken.Cancel(); + _turnTimer?.Stop(); if (CheckDecay()) { @@ -1347,12 +1366,51 @@ namespace Server.Multis m_ClientSpeed = clientSpeed; Order = BoatOrder.Move; - _moveTimerToken.Cancel(); - Timer.StartTimer(interval, single ? 1 : 0, StopBoat, out _moveTimerToken); + SafelyStartMoveTimer(interval, single); return true; } + private bool SafelyStartMoveTimer(TimeSpan interval, bool single) + { + var singleNum = single ? 1 : 0; + + if (_moveTimer != null) + { + // Do not allow them to travel faster simply by respamming the command + if (_moveTimer.Running && _moveTimer.Interval == interval && _moveTimer.Single == singleNum) + { + return false; + } + + // Have they issued a new command too soon? Ignore it if so + var moverTimerNext = (_moveTimer.Next - Core.Now).TotalMilliseconds; + if (moverTimerNext is < 0 and > -500) + { + return false; + } + + // Changing the interval or flipping between commands: "Forward" and "Forward One" + TimeSpan delay = TimeSpan.Zero; + if (interval.TotalMilliseconds > Math.Abs(moverTimerNext)) + { + var addedDelay = (int)(interval.TotalMilliseconds - Math.Abs(moverTimerNext)); + delay = TimeSpan.FromMilliseconds(addedDelay); + } + + _moveTimer.Stop(); + _moveTimer.Delay = delay; + _moveTimer.Interval = interval; + } + else + { + _moveTimer = new MoveTimer(this, interval, interval, singleNum); + } + + _moveTimer.Start(); + return true; + } + public bool StopMove(bool message) { if (CheckDecay()) @@ -1360,7 +1418,7 @@ namespace Server.Multis return false; } - if (!_moveTimerToken.Running) + if (_moveTimer?.Running == false) { if (message) { @@ -1373,7 +1431,7 @@ namespace Server.Multis Moving = Direction.North; Speed = 0; m_ClientSpeed = 0; - _moveTimerToken.Cancel(); + _moveTimer?.Stop(); if (message) { @@ -1891,7 +1949,7 @@ namespace Server.Multis continue; } - // TODO: Remove visible check and use something better, like check for spawners, or other things in the ocean we shouldn't pick up on accident + // TODO: Remove visible check and use something better, like check for spawners, or other things in the ocean we shouldn't pick up on accident if (_boat.Contains(item) && item.Visible && item.Z >= _boat.Z) { _current = current; @@ -2047,6 +2105,45 @@ namespace Server.Multis EventSink.WorldSave += UpdateAllComponents; } + private class TurnTimer : Timer + { + internal int _turn; + private bool _message; + private BaseBoat _boat; + + public TurnTimer(int turn, BaseBoat boat, TimeSpan delay, bool message = true) : base(delay) + { + _turn = turn; + _message = message; + _boat = boat; + } + protected override void OnTick() + { + _boat?.Turn(_turn, _message); + Stop(); + } + } + + private class MoveTimer : Timer + { + public TimeSpan BoatMoveDelay; + public TimeSpan BoatMoveInterval; + public int Single; + private BaseBoat _boat; + + public MoveTimer(BaseBoat boat, TimeSpan delay, TimeSpan interval, int single = 0) : base(delay, interval, single) + { + BoatMoveInterval = interval; + BoatMoveDelay = delay; + Single = single; + _boat = boat; + } + protected override void OnTick() + { + _boat?.StopBoat(); + } + } + private class DecayTimer : Timer { private readonly BaseBoat m_Boat; diff --git a/Projects/UOContent/Spells/Base/SpellHelper.cs b/Projects/UOContent/Spells/Base/SpellHelper.cs index a41137627..2c891d99b 100644 --- a/Projects/UOContent/Spells/Base/SpellHelper.cs +++ b/Projects/UOContent/Spells/Base/SpellHelper.cs @@ -312,7 +312,7 @@ namespace Server.Spells return true; } - if (mod == null || mod.Offset < offset) + if (mod == null || mod.Offset <= offset) { target.AddStatMod(new StatMod(type, name, offset, duration)); return true; diff --git a/Projects/UOContent/Spells/Necromancy/PoisonStrike.cs b/Projects/UOContent/Spells/Necromancy/PoisonStrike.cs index 37abae773..895d16300 100644 --- a/Projects/UOContent/Spells/Necromancy/PoisonStrike.cs +++ b/Projects/UOContent/Spells/Necromancy/PoisonStrike.cs @@ -77,17 +77,38 @@ namespace Server.Spells.Necromancy pool.Enqueue(m); } - var eable = m.GetMobilesInRange(2); + var cbc = Caster as BaseCreature; + var isMonster = cbc?.Controlled == false && (cbc.IsAnimatedDead || !cbc.Summoned); + var eable = m.GetMobilesInRange(2); foreach (Mobile targ in eable) { - if (!(Caster is BaseCreature && targ is BaseCreature) && - targ != Caster && m != targ && - SpellHelper.ValidIndirectTarget(Caster, targ) && - Caster.CanBeHarmful(targ, false)) + if (targ == Caster || m == targ || !SpellHelper.ValidIndirectTarget(Caster, targ) + || !Caster.CanBeHarmful(targ, false)) { - pool.Enqueue(targ); + continue; } + + if (isMonster && targ.Player) + { + continue; + } + + // Animate dead casting poison strike shouldn't hit: familiars or player or pets + if (targ is BaseCreature bc) + { + if (bc.IsAnimatedDead) + { + continue; + } + + if (isMonster && (bc.Controlled || bc.Summoned || bc.Team == cbc.Team || bc.IsNecroFamiliar)) + { + continue; + } + } + + pool.Enqueue(targ); } eable.Free(); diff --git a/Projects/UOContent/Spells/Necromancy/Wither.cs b/Projects/UOContent/Spells/Necromancy/Wither.cs index b134850af..1ea50fe77 100644 --- a/Projects/UOContent/Spells/Necromancy/Wither.cs +++ b/Projects/UOContent/Spells/Necromancy/Wither.cs @@ -49,33 +49,39 @@ namespace Server.Spells.Necromancy using var pool = PooledRefQueue.Create(); var cbc = Caster as BaseCreature; - var isMonster = cbc?.Controlled == false && !cbc.Summoned; + var isMonster = cbc?.Controlled == false && (cbc.IsAnimatedDead || !cbc.Summoned); var eable = Caster.GetMobilesInRange(Core.ML ? 4 : 5); - foreach (var m in eable) + foreach (var targ in eable) { - if (Caster == m || !Caster.InLOS(m) || (!isMonster && !SpellHelper.ValidIndirectTarget(Caster, m)) || - !Caster.CanBeHarmful(m, false)) + if (targ == Caster + || !Caster.InLOS(targ) + || !isMonster && !SpellHelper.ValidIndirectTarget(Caster, targ) + || !Caster.CanBeHarmful(targ, false)) { continue; } - if (isMonster) + if (isMonster && targ.Player) { - if (m is BaseCreature bc) + continue; + } + + // Animate dead casting poison strike shouldn't hit: familiars or player or pets + if (targ is BaseCreature bc) + { + if (bc.IsAnimatedDead) { - if (!bc.Controlled && !bc.Summoned && bc.Team == cbc.Team) - { - continue; - } + continue; } - else if (!m.Player) + + if (isMonster && (bc.Controlled || bc.Summoned || bc.Team == cbc.Team || bc.IsNecroFamiliar)) { continue; } } - pool.Enqueue(m); + pool.Enqueue(targ); } eable.Free(); diff --git a/Projects/UOContent/Spells/Sixth/Reveal.cs b/Projects/UOContent/Spells/Sixth/Reveal.cs index edaf005a3..8235034d0 100644 --- a/Projects/UOContent/Spells/Sixth/Reveal.cs +++ b/Projects/UOContent/Spells/Sixth/Reveal.cs @@ -36,10 +36,14 @@ namespace Server.Spells.Sixth foreach (var m in eable) { - if (m is ShadowKnight && - (m.X != p.X || m.Y != p.Y || !m.Hidden || m.AccessLevel != AccessLevel.Player && - Caster.AccessLevel <= m.AccessLevel || - !CheckDifficulty(Caster, m))) + if (m is ShadowKnight && (m.X != p.X || m.Y != p.Y)) + { + continue; + } + + if (!m.Hidden + || m.AccessLevel > AccessLevel.Player && Caster.AccessLevel <= m.AccessLevel + || !CheckDifficulty(Caster, m)) { continue; }