diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml deleted file mode 100644 index e7bbc2ebe..000000000 --- a/.github/workflows/code_quality.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: # Specify your branches here - - main # The 'main' branch - -jobs: - qodana: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - checks: write - steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit - fetch-depth: 0 # a full history is required for pull request analysis - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2025.1 - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} diff --git a/Projects/Server/Buffers/SpanReader.cs b/Projects/Server/Buffers/SpanReader.cs index c127327c5..31c5fc51d 100644 --- a/Projects/Server/Buffers/SpanReader.cs +++ b/Projects/Server/Buffers/SpanReader.cs @@ -294,13 +294,17 @@ public ref struct SpanReader } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Read(Span bytes) + public int Read(Span bytes) { - if (bytes.Length < Length) + if (bytes.Length == 0) { - throw new ArgumentOutOfRangeException(nameof(bytes)); + return 0; } - return _buffer.TryCopyTo(bytes); + var bytesWritten = Math.Min(bytes.Length, Remaining); + _buffer.Slice(Position, bytesWritten).CopyTo(bytes); + + Position += bytesWritten; + return bytesWritten; } } diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 9726e1d34..9703fc55f 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -792,6 +792,7 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt public virtual void GetProperties(IPropertyList list) { AddNameProperties(list); + AppendChildNameProperties(list); } [IgnoreDupe] @@ -1943,8 +1944,6 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt { AddQuestItemProperty(list); } - - AppendChildNameProperties(list); } /// diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 5b4e7a992..f217b0fe1 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -661,6 +661,7 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } } + [CommandProperty(AccessLevel.Administrator)] public long NextActionTime { get; set; } public long NextActionMessage { get; set; } @@ -675,6 +676,7 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro public virtual bool CanRegenStam => Alive; public virtual bool CanRegenMana => Alive; + [CommandProperty(AccessLevel.Administrator)] public long NextSkillTime { get; set; } public List Aggressors { get; private set; } diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index 137070973..e30a521e1 100755 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -343,7 +343,28 @@ public partial class NetState : IComparable, IValueLinkListNode FindTrade(m)?.From.Container; + public SecureTradeContainer FindTradeContainer(Mobile m) + { + for (var i = 0; i < Trades.Count; ++i) + { + var trade = Trades[i]; + + var from = trade.From; + var to = trade.To; + + if (from.Mobile == Mobile && to.Mobile == m) + { + return from.Container; + } + + if (from.Mobile == m && to.Mobile == Mobile) + { + return to.Container; + } + } + + return null; + } public SecureTradeContainer AddTrade(NetState state) { diff --git a/Projects/UOContent.Tests/Tests/Misc/NameVerificationTests.cs b/Projects/UOContent.Tests/Tests/Misc/NameVerificationTests.cs index ab0971904..a9e4f5c27 100644 --- a/Projects/UOContent.Tests/Tests/Misc/NameVerificationTests.cs +++ b/Projects/UOContent.Tests/Tests/Misc/NameVerificationTests.cs @@ -119,7 +119,7 @@ public class NameVerificationTests public void Validate_TooManyExceptions_ReturnsFalse() { var exceptions = SearchValues.Create(' ', '-', '.'); - Assert.False(NameVerification.Validate("A-B.C D-E", 2, 16, true, false, false, 3, exceptions)); + Assert.False(NameVerification.Validate("A-B.C D-E", 2, 16, true, false, false, 1, exceptions)); } [Fact] diff --git a/Projects/UOContent/Commands/Generic/Extensions/Compilers/ConditionalCompiler.cs b/Projects/UOContent/Commands/Generic/Extensions/Compilers/ConditionalCompiler.cs index 18e2a6505..416d4bc46 100644 --- a/Projects/UOContent/Commands/Generic/Extensions/Compilers/ConditionalCompiler.cs +++ b/Projects/UOContent/Commands/Generic/Extensions/Compilers/ConditionalCompiler.cs @@ -279,53 +279,25 @@ namespace Server.Commands.Generic } case StringOperator.Equal: { - if (m_IgnoreCase) - { - methodName = "InsensitiveEquals"; - } - else - { - methodName = "EqualsOrdinal"; - } + methodName = m_IgnoreCase ? "InsensitiveEquals" : "EqualsOrdinal"; break; } case StringOperator.Contains: { - if (m_IgnoreCase) - { - methodName = "InsensitiveContains"; - } - else - { - methodName = "ContainsOrdinal"; - } + methodName = m_IgnoreCase ? "InsensitiveContains" : "ContainsOrdinal"; break; } case StringOperator.StartsWith: { - if (m_IgnoreCase) - { - methodName = "InsensitiveStartsWith"; - } - else - { - methodName = "StartsWithOrdinal"; - } + methodName = m_IgnoreCase ? "InsensitiveStartsWith" : "StartsWithOrdinal"; break; } case StringOperator.EndsWith: { - if (m_IgnoreCase) - { - methodName = "InsensitiveEndsWith"; - } - else - { - methodName = "EndsWithOrdinal"; - } + methodName = m_IgnoreCase ? "InsensitiveEndsWith" : "EndsWithOrdinal"; break; } @@ -342,11 +314,7 @@ namespace Server.Commands.Generic methodName, BindingFlags.Public | BindingFlags.Static, null, - new[] - { - typeof(string), - typeof(string) - }, + [typeof(string), typeof(string)], null ) ); @@ -380,12 +348,9 @@ namespace Server.Commands.Generic emitter.BeginCall( type.GetMethod( methodName, - BindingFlags.Public | BindingFlags.Instance, + BindingFlags.Public | BindingFlags.Static, null, - new[] - { - typeof(string) - }, + [typeof(string), typeof(string)], null ) ); diff --git a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs index d7ca9e1b7..2109f09ec 100644 --- a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs +++ b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using Server.Collections; using Server.Misc; namespace Server.Engines.CannedEvil @@ -29,8 +30,8 @@ namespace Server.Engines.CannedEvil Instance.OnTick(); } - private static readonly HashSet _dungeonSpawns = new(); - private static readonly HashSet _lostLandsSpawns = new(); + private static readonly HashSet _dungeonSpawns = new(); + private static readonly HashSet _lostLandsSpawns = new(); private static DateTime _sliceTime; public static CannedEvilTimer Instance { get; private set; } @@ -38,25 +39,25 @@ namespace Server.Engines.CannedEvil public static void AddSpawn(DungeonChampionSpawn spawn) { _dungeonSpawns.Add(spawn); - Instance?.OnSlice(_dungeonSpawns, false); + OnSlice(_dungeonSpawns, false); } public static void AddSpawn(LLChampionSpawn spawn) { _lostLandsSpawns.Add(spawn); - Instance?.OnSlice(_lostLandsSpawns, false); + OnSlice(_lostLandsSpawns, false); } public static void RemoveSpawn(DungeonChampionSpawn spawn) { _dungeonSpawns.Remove(spawn); - Instance?.OnSlice(_dungeonSpawns, false); + OnSlice(_dungeonSpawns, false); } public static void RemoveSpawn(LLChampionSpawn spawn) { _lostLandsSpawns.Remove(spawn); - Instance?.OnSlice(_lostLandsSpawns, false); + OnSlice(_lostLandsSpawns, false); } public CannedEvilTimer() : base(TimeSpan.Zero, TimeSpan.FromMinutes(1.0)) @@ -64,32 +65,34 @@ namespace Server.Engines.CannedEvil _sliceTime = Core.Now; } - public void OnSlice(ICollection list, bool rotate = true) where T : ChampionSpawn + public static void OnSlice(HashSet spawns, bool rotate = true) { - if (list.Count > 0) + if (spawns.Count <= 0) { - List valid = new List(); + return; + } - foreach (T spawn in list) + using var queue = rotate ? PooledRefQueue.Create() : default; + + foreach (var spawn in spawns) + { + if (spawn.AlwaysActive && !spawn.Active) { - if (spawn.AlwaysActive && !spawn.Active) - { - spawn.ReadyToActivate = true; - } - else if (rotate && (!spawn.Active || spawn.Kills == 0 && spawn.Level == 0)) - { - spawn.Active = false; - spawn.ReadyToActivate = false; - - valid.Add(spawn); - } + spawn.ReadyToActivate = true; } - - if (valid.Count > 0) + else if (rotate && (!spawn.Active || spawn.Kills == 0 && spawn.Level == 0)) { - valid[Utility.Random(valid.Count)].ReadyToActivate = true; + spawn.Active = false; + spawn.ReadyToActivate = false; + + queue.Enqueue(spawn); } } + + if (rotate && queue.Count > 0) + { + ((ChampionSpawn)queue.PeekRandom()).ReadyToActivate = true; + } } protected override void OnTick() diff --git a/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs index 534168848..280afe924 100644 --- a/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs @@ -26,11 +26,6 @@ public partial class DungeonChampionSpawn : ChampionSpawn CannedEvilTimer.AddSpawn(this); } - public DungeonChampionSpawn(Serial serial) : base(serial) - { - CannedEvilTimer.AddSpawn(this); - } - public override bool ProximitySpawn => true; public override bool AlwaysActive => false; @@ -39,4 +34,7 @@ public partial class DungeonChampionSpawn : ChampionSpawn base.OnAfterDelete(); CannedEvilTimer.RemoveSpawn(this); } + + [AfterDeserialization] + private void AfterDeserialization() => CannedEvilTimer.AddSpawn(this); } diff --git a/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs b/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs index 148ac59d7..dc106d22c 100644 --- a/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs +++ b/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs @@ -15,35 +15,34 @@ using System; -namespace Server.Engines.CannedEvil +namespace Server.Engines.CannedEvil; + +public class ChampionEntry { - public record ChampionEntry + public readonly bool _randomizeType; + public readonly ChampionSpawnType _type; + public readonly Point3D _signLocation; + public readonly Type _champType; + public readonly Map _map; + public readonly Point3D _ejectLocation; + public readonly Map _ejectMap; + + public ChampionEntry(Type champtype, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap) : + this(champtype, ChampionSpawnType.Abyss, signloc, map, ejectloc, ejectmap, true) { - public readonly bool m_RandomizeType; - public readonly ChampionSpawnType m_Type; - public readonly Point3D m_SignLocation; - public readonly Type m_ChampType; - public readonly Map m_Map; - public readonly Point3D m_EjectLocation; - public readonly Map m_EjectMap; + } - public ChampionEntry(Type champtype, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap) : - this(champtype, ChampionSpawnType.Abyss, signloc, map, ejectloc, ejectmap, true) - { - } - - public ChampionEntry( - Type champtype, ChampionSpawnType type, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap, - bool randomizetype = false - ) - { - m_ChampType = champtype; - m_RandomizeType = randomizetype; - m_Type = type; - m_SignLocation = signloc; - m_Map = map; - m_EjectLocation = ejectloc; - m_EjectMap = ejectmap; - } + public ChampionEntry( + Type champtype, ChampionSpawnType type, Point3D signloc, Map map, Point3D ejectloc, Map ejectmap, + bool randomizetype = false + ) + { + _champType = champtype; + _randomizeType = randomizetype; + _type = type; + _signLocation = signloc; + _map = map; + _ejectLocation = ejectloc; + _ejectMap = ejectmap; } } diff --git a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs index 0a3b526ea..22a7f03a0 100644 --- a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs +++ b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs @@ -17,105 +17,106 @@ using System; using System.Collections.Generic; using Server.Logging; -namespace Server.Engines.CannedEvil +namespace Server.Engines.CannedEvil; + +public static class ChampionGenerator { - public static class ChampionGenerator + private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionGenerator)); + + public static void Configure() { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionGenerator)); + CommandSystem.Register("GenChamps", AccessLevel.Developer, ChampGen_OnCommand); + } - public static void Configure() + private static readonly ChampionEntry[] LLLocations = + [ + new(typeof(LLChampionSpawn), new Point3D(5511, 2360, 42), Map.Felucca, new Point3D(5439, 2323, 26), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(6038, 2401, 47), Map.Felucca, new Point3D(5988, 2340, 24), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5549, 2640, 16), Map.Felucca, new Point3D(5645, 2696, -8), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5636, 2916, 37), Map.Felucca, new Point3D(5721, 2949, 28), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(6035, 2943, 50), Map.Felucca, new Point3D(6098, 2997, 17), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5265, 3171, 105), Map.Felucca, new Point3D(5314, 3232, 2), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5282, 3368, 50), Map.Felucca, new Point3D(5215, 3318, 3), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5207, 3637, 20), Map.Felucca, new Point3D(5263, 3687, 0), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5954, 3475, 25), Map.Felucca, new Point3D(6013, 3529, 0), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5982, 3882, 20), Map.Felucca, new Point3D(5929, 3820, -1), Map.Felucca), + new(typeof(LLChampionSpawn), new Point3D(5724, 3991, 41), Map.Felucca, new Point3D(5774, 4041, 26), Map.Felucca), + new(typeof(LLChampionSpawn), ChampionSpawnType.ForestLord, new Point3D(5559, 3757, 21), Map.Felucca, new Point3D(5513, 3878, 3), Map.Felucca) + ]; + + private static readonly ChampionEntry[] DungeonLocations = + [ + new(typeof(DungeonChampionSpawn), ChampionSpawnType.UnholyTerror, new Point3D(5179, 709, 20), Map.Felucca, new Point3D(4111, 432, 5), Map.Felucca), + new(typeof(DungeonChampionSpawn), ChampionSpawnType.VerminHorde, new Point3D(5557, 827, 65), Map.Felucca, new Point3D(5580, 632, 30), Map.Felucca), + new(typeof(DungeonChampionSpawn), ChampionSpawnType.ColdBlood, new Point3D(5259, 837, 64), Map.Felucca, new Point3D(1176, 2637, 0), Map.Felucca), + new(typeof(DungeonChampionSpawn), ChampionSpawnType.Abyss, new Point3D(5815, 1352, 5), Map.Felucca, new Point3D(2923, 3406, 8), Map.Felucca), + new(typeof(DungeonChampionSpawn), ChampionSpawnType.Arachnid, new Point3D(5190, 1607, 20), Map.Felucca, new Point3D(5482, 3161, -54), Map.Felucca) + ]; + + [Usage("GenChamps")] + [Description("Generates champions for Felucca Dungeons & Lost Lands.")] + private static void ChampGen_OnCommand(CommandEventArgs e) + { + /* + //We take the assumption that we are spawning managed champions + for (int i = CannedEvilTimer.DungeonSpawns.Count - 1; i >= 0; i--) + CannedEvilTimer.DungeonSpawns[i].Delete(); + + for (int i = CannedEvilTimer.LLSpawns.Count - 1; i >= 0; i--) + CannedEvilTimer.LLSpawns[i].Delete(); + */ + + //We assume that all champion spawns are generated here. + List spawns = []; + foreach (Item item in World.Items.Values) { - CommandSystem.Register("GenChamps", AccessLevel.Developer, ChampGen_OnCommand); + if (item is ChampionSpawn spawn) + { + spawns.Add(spawn); + } } - private static readonly ChampionEntry[] LLLocations = { - new(typeof(LLChampionSpawn), new Point3D(5511, 2360, 42), Map.Felucca, new Point3D(5439, 2323, 26 ), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(6038, 2401, 47), Map.Felucca, new Point3D(5988, 2340, 24), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(5549, 2640, 16), Map.Felucca, new Point3D(5645, 2696, -8), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(5636, 2916, 37), Map.Felucca, new Point3D(5721, 2949, 28), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(6035, 2943, 50), Map.Felucca, new Point3D(6098, 2997, 17), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(5265, 3171, 105), Map.Felucca, new Point3D(5314, 3232, 2), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(5282, 3368, 50), Map.Felucca, new Point3D(5215, 3318, 3), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(5207, 3637, 20), Map.Felucca, new Point3D(5263, 3687, 0), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(5954, 3475, 25), Map.Felucca, new Point3D(6013, 3529, 0), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(5982, 3882, 20), Map.Felucca, new Point3D(5929, 3820, -1), Map.Felucca), - new(typeof(LLChampionSpawn), new Point3D(5724, 3991, 41), Map.Felucca, new Point3D(5774, 4041, 26), Map.Felucca), - new(typeof(LLChampionSpawn), ChampionSpawnType.ForestLord, new Point3D(5559, 3757, 21), Map.Felucca, new Point3D(5513, 3878, 3), Map.Felucca), - }; - - private static readonly ChampionEntry[] DungeonLocations = { - new(typeof(DungeonChampionSpawn), ChampionSpawnType.UnholyTerror, new Point3D(5179, 709, 20), Map.Felucca, new Point3D(4111, 432, 5), Map.Felucca), - new(typeof(DungeonChampionSpawn), ChampionSpawnType.VerminHorde, new Point3D(5557, 827, 65), Map.Felucca, new Point3D(5580, 632, 30), Map.Felucca), - new(typeof(DungeonChampionSpawn), ChampionSpawnType.ColdBlood, new Point3D(5259, 837, 64), Map.Felucca, new Point3D(1176, 2637, 0), Map.Felucca), - new(typeof(DungeonChampionSpawn), ChampionSpawnType.Abyss, new Point3D(5815, 1352, 5), Map.Felucca, new Point3D(2923, 3406, 8), Map.Felucca), - new(typeof(DungeonChampionSpawn), ChampionSpawnType.Arachnid, new Point3D(5190, 1607, 20), Map.Felucca, new Point3D(5482, 3161, -54), Map.Felucca), - }; - - [Usage("GenChamps")] - [Description("Generates champions for Felucca Dungeons & Lost Lands.")] - private static void ChampGen_OnCommand(CommandEventArgs e) + for (int i = spawns.Count - 1; i >= 0; i--) { - /* - //We take the assumption that we are spawning managed champions - for (int i = CannedEvilTimer.DungeonSpawns.Count - 1; i >= 0; i--) - CannedEvilTimer.DungeonSpawns[i].Delete(); - - for (int i = CannedEvilTimer.LLSpawns.Count - 1; i >= 0; i--) - CannedEvilTimer.LLSpawns[i].Delete(); - */ - - //We assume that all champion spawns are generated here. - List spawns = new List(); - foreach (Item item in World.Items.Values) - { - if (item is ChampionSpawn spawn) - { - spawns.Add(spawn); - } - } - - for (int i = spawns.Count - 1; i >= 0; i--) - { - spawns[i].Delete(); - } - - Process(DungeonLocations); - Process(LLLocations); - //ProcessIlshenar(); - //ProcessTokuno(); + spawns[i].Delete(); } - private static void Process(ChampionEntry[] entries) - { - for (int i = 0; i < entries.Length; i++) - { - ChampionEntry entry = entries[i]; + Process(DungeonLocations); + Process(LLLocations); + //ProcessIlshenar(); + //ProcessTokuno(); + } - try + private static void Process(ChampionEntry[] entries) + { + for (int i = 0; i < entries.Length; i++) + { + ChampionEntry entry = entries[i]; + + try + { + if (Activator.CreateInstance(entry._champType) is ChampionSpawn spawn) { - if (Activator.CreateInstance(entry.m_ChampType) is ChampionSpawn spawn) + spawn.RandomizeType = entry._randomizeType; + spawn.Type = entry._type; + spawn.MoveToWorld(entry._signLocation, entry._map); + spawn.EjectLocation = entry._ejectLocation; + spawn.EjectMap = entry._ejectMap; + if (spawn.AlwaysActive) { - spawn.RandomizeType = entry.m_RandomizeType; - spawn.Type = entry.m_Type; - spawn.MoveToWorld(entry.m_SignLocation, entry.m_Map); - spawn.EjectLocation = entry.m_EjectLocation; - spawn.EjectMap = entry.m_EjectMap; - if (spawn.AlwaysActive) - { - spawn.ReadyToActivate = true; - } + spawn.ReadyToActivate = true; } } - catch (Exception e) - { - logger.Error( - e, - "Failed to generate champion \"{Type}\" at {Location} ({Map}).", - entry.m_ChampType.FullName, - entry.m_SignLocation, - entry.m_Map - ); - } + } + catch (Exception e) + { + logger.Error( + e, + "Failed to generate champion \"{Type}\" at {Location} ({Map}).", + entry._champType.FullName, + entry._signLocation, + entry._map + ); } } } diff --git a/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs index 17768f81e..d7cb89c4d 100644 --- a/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs @@ -28,11 +28,6 @@ public partial class LLChampionSpawn : ChampionSpawn CannedEvilTimer.AddSpawn(this); } - public LLChampionSpawn(Serial serial) : base(serial) - { - CannedEvilTimer.AddSpawn(this); - } - public override bool AlwaysActive => false; public override void OnAfterDelete() @@ -40,4 +35,7 @@ public partial class LLChampionSpawn : ChampionSpawn base.OnAfterDelete(); CannedEvilTimer.RemoveSpawn(this); } + + [AfterDeserialization] + private void AfterDeserialization() => CannedEvilTimer.AddSpawn(this); } diff --git a/Projects/UOContent/Engines/Craft/DefBowFletching.cs b/Projects/UOContent/Engines/Craft/DefBowFletching.cs index eb6e04166..cd75829a1 100644 --- a/Projects/UOContent/Engines/Craft/DefBowFletching.cs +++ b/Projects/UOContent/Engines/Craft/DefBowFletching.cs @@ -258,5 +258,15 @@ public class DefBowFletching : CraftSystem MarkOption = true; Repair = Core.AOS; + + SetSubRes(typeof(Log), 1072643); + + AddSubRes(typeof(Log), 1072643, 0.0, 1044041, 1072652); + AddSubRes(typeof(OakLog), 1072644, 65.0, 1044041, 1072652); + AddSubRes(typeof(AshLog), 1072645, 80.0, 1044041, 1072652); + AddSubRes(typeof(YewLog), 1072646, 95.0, 1044041, 1072652); + AddSubRes(typeof(HeartwoodLog), 1072647, 100.0, 1044041, 1072652); + AddSubRes(typeof(BloodwoodLog), 1072648, 100.0, 1044041, 1072652); + AddSubRes(typeof(FrostwoodLog), 1072649, 100.0, 1044041, 1072652); } } diff --git a/Projects/UOContent/Engines/Player Murder System/MurderContext.cs b/Projects/UOContent/Engines/Player Murder System/MurderContext.cs index b9584dd8a..93c1f67c7 100644 --- a/Projects/UOContent/Engines/Player Murder System/MurderContext.cs +++ b/Projects/UOContent/Engines/Player Murder System/MurderContext.cs @@ -79,11 +79,7 @@ public partial class MurderContext if (_player.Kills > 0) { - var timeUntilLong = now + (LongTermElapse - gameTime); - if (_nextElapse > timeUntilLong) - { - _nextElapse = timeUntilLong; - } + _nextElapse = Utility.Min(_nextElapse, now + (LongTermElapse - gameTime)); } return _nextElapse != DateTime.MaxValue; diff --git a/Projects/UOContent/Engines/Player Murder System/PlayerMurderSystem.cs b/Projects/UOContent/Engines/Player Murder System/PlayerMurderSystem.cs index 8a654a697..5fb39a4d8 100644 --- a/Projects/UOContent/Engines/Player Murder System/PlayerMurderSystem.cs +++ b/Projects/UOContent/Engines/Player Murder System/PlayerMurderSystem.cs @@ -101,9 +101,17 @@ public class PlayerMurderSystem : GenericPersistence private static void OnDisconnected(Mobile m) { - if (m is PlayerMobile pm && _murderContexts.Remove(pm, out var context)) + if (m is not PlayerMobile pm || !_murderContexts.TryGetValue(pm, out var context)) { - _contextTerms.Remove(context); + return; + } + + context.DecayKills(); + _contextTerms.Remove(context); + + if (pm.Kills <= 0 && context.ShortTermMurders <= 0) + { + _murderContexts.Remove(pm); } } @@ -164,6 +172,8 @@ public class PlayerMurderSystem : GenericPersistence { var context = GetOrCreateMurderContext(player); context.ShortTermMurders = shortTermMurders; + + context.ResetKillTime(); UpdateMurderContext(context); } diff --git a/Projects/UOContent/Items/Addons/AddonComponent.cs b/Projects/UOContent/Items/Addons/AddonComponent.cs index ce155cec9..e19914998 100644 --- a/Projects/UOContent/Items/Addons/AddonComponent.cs +++ b/Projects/UOContent/Items/Addons/AddonComponent.cs @@ -104,7 +104,7 @@ namespace Server.Items } [SerializableField(0)] - [SerializedCommandProperty(AccessLevel.GameMaster)] + [SerializedCommandProperty(AccessLevel.GameMaster, readOnly: true)] public BaseAddon _addon; [SerializableField(1)] diff --git a/Projects/UOContent/Items/Addons/BaseAddon.cs b/Projects/UOContent/Items/Addons/BaseAddon.cs index 340a93065..6b2abbc66 100644 --- a/Projects/UOContent/Items/Addons/BaseAddon.cs +++ b/Projects/UOContent/Items/Addons/BaseAddon.cs @@ -270,7 +270,8 @@ namespace Server.Items foreach (var c in Components) { - c.Delete(); + // Component can become null if the Addon property is somehow deleted, then the component itself is deleted. + c?.Delete(); } } @@ -283,5 +284,12 @@ namespace Server.Items _resource = (CraftResource)reader.ReadEncodedInt(); } } + + [AfterDeserialization] + private void AfterDeserialization() + { + // We have had issues in the past, so let's tidy it up. + _components?.Tidy(); + } } } diff --git a/Projects/UOContent/Items/Farming/FarmableCabbage.cs b/Projects/UOContent/Items/Farming/FarmableCabbage.cs index 3b4e32d59..51ccff825 100644 --- a/Projects/UOContent/Items/Farming/FarmableCabbage.cs +++ b/Projects/UOContent/Items/Farming/FarmableCabbage.cs @@ -10,7 +10,7 @@ public partial class FarmableCabbage : FarmableCrop { } - public static int GetCropID() => 3254; + public static int GetCropID() => 0x0C7B; public override Item GetCropObject() => new Cabbage diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 85489edf3..badb4ce1d 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -88,7 +88,7 @@ public abstract partial class BaseWeapon [SerializableFieldSaveFlag(7)] [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ShouldSerializePoison() => _poison?.Level > 0; + private bool ShouldSerializePoison() => _poison != null; [InvalidateProperties] [SerializableField(8)] @@ -890,6 +890,11 @@ public abstract partial class BaseWeapon if (attacker is BaseCreature bc) { + if (bc.TriggerAbility(MonsterAbilityTrigger.CombatAction, defender)) + { + return GetDelay(attacker); + } + // Only change direction if they are not a player. attacker.Direction = attacker.GetDirectionTo(defender); var ab = bc.GetWeaponAbility(); @@ -1350,7 +1355,7 @@ public abstract partial class BaseWeapon theirValue = Math.Max(0.1, defValue + 50.0); } - var chance = ourValue / (theirValue * 2.0) * 1.0 + (double)bonus / 100;; + var chance = ourValue / (theirValue * 2.0) * 1.0 + (double)bonus / 100; if (Core.AOS && chance < 0.02) { diff --git a/Projects/UOContent/Items/Weapons/Fists.cs b/Projects/UOContent/Items/Weapons/Fists.cs index 69f265356..c041b5c82 100644 --- a/Projects/UOContent/Items/Weapons/Fists.cs +++ b/Projects/UOContent/Items/Weapons/Fists.cs @@ -50,7 +50,7 @@ namespace Server.Items return wresValue > incrValue ? wresValue : incrValue; } - private void CheckPreAOSMoves(Mobile attacker, Mobile defender) + private static void CheckPreAOSMoves(Mobile attacker, Mobile defender) { if (!attacker.CanBeginAction()) { @@ -90,60 +90,67 @@ namespace Server.Items attacker.SendLocalizedMessage(1004010); // You failed in your attempt to stun. defender.SendLocalizedMessage(1004011); // Your opponent tried to stun you and failed. } + + return; } - else if (attacker.DisarmReady) + + if (!attacker.DisarmReady) { - if (!defender.Player && !defender.Body.IsHuman) - { - attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent. - return; - } + return; + } - if (attacker.Skills.ArmsLore.Value < 80.0 || attacker.Skills.Wrestling.Value < 80.0) - { - attacker.SendLocalizedMessage(1004002); // You are not skilled enough to disarm your opponent. - attacker.DisarmReady = false; - return; - } + if (!defender.Player && !defender.Body.IsHuman) + { + attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent. + return; + } - if (attacker.Stam < 15) - { - attacker.SendLocalizedMessage(1004003); // You are too fatigued to attempt anything. - return; - } + if (attacker.Skills.ArmsLore.Value < 80.0 || attacker.Skills.Wrestling.Value < 80.0) + { + attacker.SendLocalizedMessage(1004002); // You are not skilled enough to disarm your opponent. + attacker.DisarmReady = false; + return; + } - var toDisarm = defender.FindItemOnLayer(Layer.OneHanded); + if (attacker.Stam < 15) + { + attacker.SendLocalizedMessage(1004003); // You are too fatigued to attempt anything. + return; + } - if (toDisarm?.Movable == false) - { - toDisarm = defender.FindItemOnLayer(Layer.TwoHanded); - } + var toDisarm = defender.FindItemOnLayer(Layer.OneHanded); - var pack = defender.Backpack; + if (toDisarm?.Movable != true) + { + toDisarm = defender.FindItemOnLayer(Layer.TwoHanded); + } - if (pack == null || toDisarm?.Movable == false) - { - attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent. - } - else if (CheckMove(attacker, SkillName.ArmsLore)) - { - StartMoveDelay(attacker); + var pack = defender.Backpack; - attacker.Stam -= 15; - attacker.DisarmReady = false; + if (pack == null || toDisarm?.Movable != true) + { + attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent. + return; + } - attacker.SendLocalizedMessage(1004006); // You successfully disarm your opponent! - defender.SendLocalizedMessage(1004007); // You have been disarmed! + if (CheckMove(attacker, SkillName.ArmsLore)) + { + StartMoveDelay(attacker); - pack.DropItem(toDisarm); - } - else - { - attacker.Stam -= 15; + attacker.Stam -= 15; + attacker.DisarmReady = false; - attacker.SendLocalizedMessage(1004004); // You failed in your attempt to disarm. - defender.SendLocalizedMessage(1004005); // Your opponent tried to disarm you but failed. - } + attacker.SendLocalizedMessage(1004006); // You successfully disarm your opponent! + defender.SendLocalizedMessage(1004007); // You have been disarmed! + + pack.DropItem(toDisarm); + } + else + { + attacker.Stam -= 15; + + attacker.SendLocalizedMessage(1004004); // You failed in your attempt to disarm. + defender.SendLocalizedMessage(1004005); // Your opponent tried to disarm you but failed. } } diff --git a/Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs b/Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs index bbe80daf6..b50a7b353 100644 --- a/Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs +++ b/Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs @@ -70,6 +70,11 @@ namespace Server.Items attacker.DisruptiveAction(); attacker.NetState.SendSwing(attacker.Serial, defender.Serial); + if (attacker is BaseCreature bc && bc.TriggerAbility(MonsterAbilityTrigger.CombatAction, defender)) + { + return GetDelay(attacker); + } + if (OnFired(attacker, defender)) { if (CheckHit(attacker, defender)) diff --git a/Projects/UOContent/Misc/NameVerification.cs b/Projects/UOContent/Misc/NameVerification.cs index ae934b261..d35c2b883 100644 --- a/Projects/UOContent/Misc/NameVerification.cs +++ b/Projects/UOContent/Misc/NameVerification.cs @@ -216,6 +216,7 @@ public static class NameVerification } var index = name.IndexOfAny(exceptions); + var exceptionCount = 0; while (index != -1) { @@ -229,7 +230,7 @@ public static class NameVerification noExceptionsAtStart = false; } - if (maxExceptions-- <= 0) + if (exceptionCount++ >= maxExceptions) { return true; } @@ -238,6 +239,10 @@ public static class NameVerification { name = name[(index + 1)..]; index = name.IndexOfAny(exceptions); + if (index != 0) + { + exceptionCount = 0; + } } else { diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index d7cce9923..a77e36d8f 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -626,15 +626,13 @@ public abstract class BaseAI { if (m_Mobile.Summoned || m_Mobile is GrizzledMare) { - e.Mobile.SendLocalizedMessage( - 1005481 - ); // Summoned creatures are loyal only to their summoners. + // Summoned creatures are loyal only to their summoners. + e.Mobile.SendLocalizedMessage(1005481); } else if (e.Mobile.HasTrade) { - e.Mobile.SendLocalizedMessage( - 1070947 - ); // You cannot friend a pet with a trade pending + // You cannot friend a pet with a trade pending + e.Mobile.SendLocalizedMessage(1070947); } else { @@ -742,15 +740,13 @@ public abstract class BaseAI { if (m_Mobile.Summoned || m_Mobile is GrizzledMare) { - e.Mobile.SendLocalizedMessage( - 1005487 - ); // You cannot transfer ownership of a summoned creature. + // You cannot transfer ownership of a summoned creature. + e.Mobile.SendLocalizedMessage(1005487); } else if (e.Mobile.HasTrade) { - e.Mobile.SendLocalizedMessage( - 1010507 - ); // You cannot transfer a pet with a trade pending + // You cannot transfer a pet with a trade pending + e.Mobile.SendLocalizedMessage(1010507); } else { @@ -2975,6 +2971,7 @@ public abstract class BaseAI { if (bc.CheckControlChance(from)) { + bc.ControlTarget = null; bc.ControlOrder = _order; } diff --git a/Projects/UOContent/Mobiles/Abilities/FanningFire.cs b/Projects/UOContent/Mobiles/Abilities/FanningFire.cs index a06bb561f..99e65f5c6 100644 --- a/Projects/UOContent/Mobiles/Abilities/FanningFire.cs +++ b/Projects/UOContent/Mobiles/Abilities/FanningFire.cs @@ -5,8 +5,23 @@ namespace Server.Mobiles; public class FanningFire : MonsterAbilitySingleTargetDoT { public override MonsterAbilityType AbilityType => MonsterAbilityType.FanningFire; - public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.GiveDamage; - public override double ChanceToTrigger => 0.05; + public override MonsterAbilityTrigger AbilityTrigger => MonsterAbilityTrigger.CombatAction; + + public FanningFire(double chanceToTrigger, int fireResistMod, int minDamage, int maxDamage) + { + ChanceToTrigger = chanceToTrigger; + FireResistMod = fireResistMod; + MinDamage = minDamage; + MaxDamage = maxDamage; + } + + public sealed override double ChanceToTrigger { get; } + + public int FireResistMod { get; } + + public int MinDamage { get; } + + public int MaxDamage { get; } public const string Name = "FanningFire"; @@ -52,16 +67,12 @@ public class FanningFire : MonsterAbilitySingleTargetDoT */ source.DoHarmful(defender); - var effect = -(defender.FireResistance / 10); + defender.AddResistanceMod(new ResistanceMod(ResistanceType.Fire, Name, FireResistMod)); - var mod = new ResistanceMod(ResistanceType.Fire, Name, effect); - defender.AddResistanceMod(mod); - - defender.FixedParticles(0x37B9, 10, 30, 0x34, EffectLayer.RightFoot); + defender.FixedParticles(0x3709, 10, 30, 0x34, EffectLayer.RightFoot); defender.PlaySound(0x208); - // TODO: Trigger replaces a normal attack. - AOS.Damage(defender, source, Utility.RandomMinMax(35, 45), 0, 100, 0, 0, 0); + AOS.Damage(defender, source, Utility.RandomMinMax(MinDamage, MaxDamage), 0, 100, 0, 0, 0); } protected override void EffectTick(BaseCreature source, Mobile defender, ref TimeSpan nextDelay) diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilities.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilities.cs index 9df7ffd0a..652bc5b05 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbilities.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilities.cs @@ -14,7 +14,7 @@ public static class MonsterAbilities // Resistance Debuffs public static GraspingClaw GraspingClaw => new(); public static RuneCorruption RuneCorruption => new(); - public static FanningFire FanningFire => new(); + public static FanningFire FanningFire => new(0.05, -10, 35, 45); // Summon Undead public static SummonSkeletonsCounter SummonSkeletonsCounter => new(); diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs index 72fb61ec3..fc227507e 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbility.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; +using ModernUO.CodeGeneratedEvents; namespace Server.Mobiles; /// /// Abstract class used to build singletons for managing a specific monster ability. /// -public abstract partial class MonsterAbility +public abstract class MonsterAbility { private Dictionary _nextTriggerTicks; @@ -18,39 +19,34 @@ public abstract partial class MonsterAbility public virtual TimeSpan MinTriggerCooldown => TimeSpan.Zero; public virtual TimeSpan MaxTriggerCooldown => TimeSpan.Zero; - public bool WillTrigger(MonsterAbilityTrigger trigger) => (AbilityTrigger & trigger) != 0; - /// /// Returns true if ability is not on cooldown, and the chance to trigger succeeds. /// /// Boolean indicating the ability can trigger. public virtual bool CanTrigger(BaseCreature source, MonsterAbilityTrigger trigger) { - if (source is not { Alive: true, Deleted: false }) + if ((AbilityTrigger & trigger) == 0 || source is not { Alive: true, Deleted: false }) { return false; } - if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true && nextTrigger - Core.TickCount > 0) + if (_nextTriggerTicks?.TryGetValue(source, out var nextTrigger) == true) { - return false; + if (nextTrigger - Core.TickCount > 0) + { + return false; + } + + _nextTriggerTicks.Remove(source); + if (_nextTriggerTicks.Count == 0) + { + _nextTriggerTicks = null; + } } var c = ChanceToTrigger; - if (c >= 1) - { - return true; - } - - if (c <= 0) - { - return false; - } - - var rnd = Utility.RandomDouble(); - - return c > rnd; + return c >= 1 || c > 0 && c > Utility.RandomDouble(); } /// @@ -99,7 +95,23 @@ public abstract partial class MonsterAbility { } - public virtual void Move(BaseCreature creature, Direction d) + public virtual void Move(BaseCreature source, Direction d) { } + + [OnEvent(nameof(BaseCreature.CreatureDeathEvent))] + [OnEvent(nameof(BaseCreature.CreatureDeletedEvent))] + public static void InvalidateNextAbilityTriggers(BaseCreature source) + { + var abilities = source.GetMonsterAbilities(); + if (abilities == null || abilities.Length == 0) + { + return; + } + + for (var i = 0; i < abilities.Length; i++) + { + abilities[i]._nextTriggerTicks?.Remove(source); + } + } } diff --git a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs index c431d4d35..6555a3682 100644 --- a/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs +++ b/Projects/UOContent/Mobiles/Abilities/MonsterAbilityGroup.cs @@ -1,15 +1,14 @@ using System; -using Server.Random; using WeightedMonsterAbility = Server.Random.WeightedValue; namespace Server.Mobiles; public class MonsterAbilityGroup : MonsterAbility { - private WeightedMonsterAbility[] _weightedAbilities; - private WeightedMonsterAbility[] _availableToTrigger; + private readonly WeightedMonsterAbility[] _weightedAbilities; + private readonly WeightedMonsterAbility[] _availableToTrigger; + private readonly MonsterAbilityTrigger _triggers; private int _availableToTriggerCount; - private MonsterAbilityTrigger _triggers; public MonsterAbilityGroup(params WeightedMonsterAbility[] weightedAbilities) { @@ -18,8 +17,7 @@ public class MonsterAbilityGroup : MonsterAbility for (var i = 0; i < _weightedAbilities.Length; i++) { - var weightedAbility = _weightedAbilities[i]; - _triggers |= weightedAbility.Value.AbilityTrigger; + _triggers |= _weightedAbilities[i].Value.AbilityTrigger; } } @@ -66,7 +64,7 @@ public class MonsterAbilityGroup : MonsterAbility for (var i = 0; i < _weightedAbilities.Length; i++) { var weightedAbility = _weightedAbilities[i]; - if (weightedAbility.Value.WillTrigger(trigger) && weightedAbility.Value.CanTrigger(source, trigger)) + if (weightedAbility.Value.CanTrigger(source, trigger)) { _availableToTrigger[_availableToTriggerCount++] = weightedAbility; } @@ -83,7 +81,7 @@ public class MonsterAbilityGroup : MonsterAbility return; } - var slice = new ReadOnlySpan>(_availableToTrigger, 0, _availableToTriggerCount); + var slice = new ReadOnlySpan(_availableToTrigger, 0, _availableToTriggerCount); var chosenAbility = slice.RandomWeightedElement().Value; // Just in case? diff --git a/Projects/UOContent/Mobiles/Abilities/ReflectPhysicalDamage.cs b/Projects/UOContent/Mobiles/Abilities/ReflectPhysicalDamage.cs index ae66734ba..3aa48960f 100644 --- a/Projects/UOContent/Mobiles/Abilities/ReflectPhysicalDamage.cs +++ b/Projects/UOContent/Mobiles/Abilities/ReflectPhysicalDamage.cs @@ -8,4 +8,8 @@ public class ReflectPhysicalDamage : MonsterAbility public override MonsterAbilityType AbilityType => MonsterAbilityType.ReflectPhysicalDamage; public virtual int PercentReflected => 10; + + public override void Trigger(MonsterAbilityTrigger trigger, BaseCreature source, Mobile target) + { + } } diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index ed37b7be1..22c43ebc4 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -1216,7 +1216,7 @@ namespace Server.Mobiles for (var i = 0; i < abilities.Length; i++) { var ability = abilities[i]; - if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) + if (ability.CanTrigger(this, trigger)) { ability.Trigger(trigger, this, defender); triggered = true; @@ -1238,7 +1238,7 @@ namespace Server.Mobiles for (var i = 0; i < abilities.Length; i++) { var ability = abilities[i]; - if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) + if (ability.CanTrigger(this, trigger)) { ability.Move(this, d); } @@ -1257,7 +1257,7 @@ namespace Server.Mobiles for (var i = 0; i < abilities.Length; i++) { var ability = abilities[i]; - if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) + if (ability.CanTrigger(this, trigger)) { if ((trigger & MonsterAbilityTrigger.GiveMeleeDamage) != 0) { @@ -1298,7 +1298,7 @@ namespace Server.Mobiles for (var i = 0; i < abilities.Length; i++) { var ability = abilities[i]; - if (ability.WillTrigger(trigger) && ability.CanTrigger(this, trigger)) + if (ability.CanTrigger(this, trigger)) { if ((trigger & MonsterAbilityTrigger.GiveSpellDamage) != 0) { @@ -3263,7 +3263,7 @@ namespace Server.Mobiles } [GeneratedEvent(nameof(CreatureDeathEvent))] - public static partial void CreatureDeathEvent(Mobile m); + public static partial void CreatureDeathEvent(BaseCreature bc); public override void OnDeath(Container c) { @@ -3454,8 +3454,13 @@ namespace Server.Mobiles CreatureDeathEvent(this); } + [GeneratedEvent(nameof(CreatureDeletedEvent))] + public static partial void CreatureDeletedEvent(BaseCreature bc); + public override void OnDelete() { + CreatureDeletedEvent(this); + var m = m_ControlMaster; SetControlMaster(null); diff --git a/Projects/UOContent/Mobiles/Hireables/BaseHire.cs b/Projects/UOContent/Mobiles/Hireables/BaseHire.cs index 06c569e25..aa4ba1cda 100644 --- a/Projects/UOContent/Mobiles/Hireables/BaseHire.cs +++ b/Projects/UOContent/Mobiles/Hireables/BaseHire.cs @@ -228,11 +228,6 @@ public partial class BaseHire : BaseCreature if (!Controlled) { - if (CanPaperdollBeOpenedBy(from)) - { - list.Add(new PaperdollEntry()); - } - list.Add(new HireEntry()); } else diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Bedlam/LadyJennifyr.cs b/Projects/UOContent/Mobiles/Monsters/ML/Bedlam/LadyJennifyr.cs index 6b7a89005..f09b345ca 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Bedlam/LadyJennifyr.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Bedlam/LadyJennifyr.cs @@ -1,122 +1,71 @@ using ModernUO.Serialization; -using System; -using System.Collections.Generic; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class LadyJennifyr : SkeletalKnight { - [SerializationGenerator(0, false)] - public partial class LadyJennifyr : SkeletalKnight + [Constructible] + public LadyJennifyr() { - private static readonly Dictionary m_Table = new(); + IsParagon = true; - [Constructible] - public LadyJennifyr() - { - IsParagon = true; + Hue = 0x76D; - Hue = 0x76D; + SetStr(208, 309); + SetDex(91, 118); + SetInt(44, 101); - SetStr(208, 309); - SetDex(91, 118); - SetInt(44, 101); + SetHits(1113, 1285); - SetHits(1113, 1285); + SetDamage(15, 25); - SetDamage(15, 25); + SetDamageType(ResistanceType.Physical, 40); + SetDamageType(ResistanceType.Cold, 60); - SetDamageType(ResistanceType.Physical, 40); - SetDamageType(ResistanceType.Cold, 60); + SetResistance(ResistanceType.Physical, 56, 65); + SetResistance(ResistanceType.Fire, 41, 49); + SetResistance(ResistanceType.Cold, 71, 80); + SetResistance(ResistanceType.Poison, 41, 50); + SetResistance(ResistanceType.Energy, 50, 58); - SetResistance(ResistanceType.Physical, 56, 65); - SetResistance(ResistanceType.Fire, 41, 49); - SetResistance(ResistanceType.Cold, 71, 80); - SetResistance(ResistanceType.Poison, 41, 50); - SetResistance(ResistanceType.Energy, 50, 58); + SetSkill(SkillName.Wrestling, 127.9, 137.1); + SetSkill(SkillName.Tactics, 128.4, 141.9); + SetSkill(SkillName.MagicResist, 102.1, 119.5); + SetSkill(SkillName.Anatomy, 129.0, 137.5); - SetSkill(SkillName.Wrestling, 127.9, 137.1); - SetSkill(SkillName.Tactics, 128.4, 141.9); - SetSkill(SkillName.MagicResist, 102.1, 119.5); - SetSkill(SkillName.Anatomy, 129.0, 137.5); - - Fame = 18000; - Karma = -18000; - } - - public override string CorpseName => "a Lady Jennifyr corpse"; - public override string DefaultName => "Lady Jennifyr"; - - /* - // TODO: Uncomment once added - public override void OnDeath( Container c ) - { - base.OnDeath( c ); - - if (Utility.RandomDouble() < 0.15) - c.DropItem( new DisintegratingThesisNotes() ); - - if (Utility.RandomDouble() < 0.1) - c.DropItem( new ParrotItem() ); - } - */ - - public override bool GivesMLMinorArtifact => true; - - public override void GenerateLoot() - { - AddLoot(LootPack.UltraRich, 3); - } - - public override void OnGaveMeleeAttack(Mobile defender, int damage) - { - base.OnGaveMeleeAttack(defender, damage); - - if (Utility.RandomDouble() < 0.9) - { - return; - } - - if (m_Table.Remove(defender, out var timer)) - { - timer.DoExpire(); - } - - defender.FixedParticles(0x3709, 10, 30, 5052, EffectLayer.LeftFoot); - defender.PlaySound(0x208); - // The creature fans you with fire, reducing your resistance to fire attacks. - defender.SendLocalizedMessage(1070833); - - var mod = new ResistanceMod(ResistanceType.Fire, "FireResistFanningFire", -10); - defender.AddResistanceMod(mod); - - m_Table[defender] = timer = new ExpireTimer(defender, mod); - timer.Start(); - } - - private class ExpireTimer : Timer - { - private readonly Mobile m_Mobile; - private readonly ResistanceMod m_Mod; - - public ExpireTimer(Mobile m, ResistanceMod mod) - : base(TimeSpan.FromSeconds(10)) - { - m_Mobile = m; - m_Mod = mod; - } - - public void DoExpire() - { - m_Mobile.RemoveResistanceMod(m_Mod); - - Stop(); - } - - protected override void OnTick() - { - m_Mobile.SendLocalizedMessage(1070834); // Your resistance to fire attacks has returned. - DoExpire(); - m_Table.Remove(m_Mobile); - } - } + Fame = 18000; + Karma = -18000; } + + public override string CorpseName => "a Lady Jennifyr corpse"; + public override string DefaultName => "Lady Jennifyr"; + + /* + // TODO: Uncomment once added + public override void OnDeath( Container c ) + { + base.OnDeath( c ); + + if (Utility.RandomDouble() < 0.15) + c.DropItem( new DisintegratingThesisNotes() ); + + if (Utility.RandomDouble() < 0.1) + c.DropItem( new ParrotItem() ); + } + */ + + public override bool GivesMLMinorArtifact => true; + + public override void GenerateLoot() + { + AddLoot(LootPack.UltraRich, 3); + } + + private static readonly MonsterAbility[] _abilities = + [ + new FanningFire(0.10, -10, 35, 45) + ]; + + public override MonsterAbility[] GetMonsterAbilities() => _abilities; } diff --git a/Projects/UOContent/Mobiles/Townfolk/Banker.cs b/Projects/UOContent/Mobiles/Townfolk/Banker.cs index d79cc7055..7744c1918 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Banker.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Banker.cs @@ -12,7 +12,7 @@ namespace Server.Mobiles; [SerializationGenerator(0, false)] public partial class Banker : BaseVendor { - private readonly List m_SBInfos = new(); + private readonly List m_SBInfos = []; [Constructible] public Banker() : base("the banker") @@ -41,7 +41,7 @@ public partial class Banker : BaseVendor } } - Container bank = m.FindBankNoCreate(); + var bank = m.FindBankNoCreate(); if (bank != null) { @@ -80,11 +80,11 @@ public partial class Banker : BaseVendor } } - Container bank = m.FindBankNoCreate(); + var bank = m.FindBankNoCreate(); if (bank != null) { - gold = new List(); + gold = []; foreach (var g in bank.FindItemsByType()) { @@ -97,7 +97,7 @@ public partial class Banker : BaseVendor return int.MaxValue; } - checks = new List(); + checks = []; foreach (var bc in bank.FindItemsByType()) { @@ -111,7 +111,7 @@ public partial class Banker : BaseVendor private static bool HasRequiredBalance(int requiredBalance, Mobile m, out PooledRefList gold, out PooledRefList checks) { - Container bank = m.FindBankNoCreate(); + var bank = m.FindBankNoCreate(); if (bank == null) { @@ -382,9 +382,7 @@ public partial class Banker : BaseVendor } else if (amount > 0) { - var box = e.Mobile.FindBankNoCreate(); - - if (box == null || !Withdraw(e.Mobile, amount)) + if (!Withdraw(e.Mobile, amount)) { Say(500384); // Ah, art thou trying to fool me? Thou hast not so much gold! } diff --git a/Projects/UOContent/Mobiles/Townfolk/TownCrier.cs b/Projects/UOContent/Mobiles/Townfolk/TownCrier.cs index a2dd13d14..44eadf509 100644 --- a/Projects/UOContent/Mobiles/Townfolk/TownCrier.cs +++ b/Projects/UOContent/Mobiles/Townfolk/TownCrier.cs @@ -118,7 +118,7 @@ public class TownCrierDurationPrompt : Prompt { if (!TimeSpan.TryParse(text, out var ts)) { - from.SendMessage("Value was not properly formatted. Use: "); + from.SendMessage("Value was not properly formatted. Use: "); from.SendGump(new TownCrierGump(from, m_Owner)); return; } @@ -272,7 +272,7 @@ public class TownCrierGump : Gump { if (info.ButtonID == 1) { - m_From.SendMessage("Enter the duration for the new message. Format: "); + m_From.SendMessage("Enter the duration for the new message. Format: "); m_From.Prompt = new TownCrierDurationPrompt(m_Owner); } else if (info.ButtonID > 1) @@ -433,7 +433,7 @@ public partial class TownCrier : Mobile, ITownCrierEntryList if (tce == null) { - _autoShoutTimer.Stop(); + _autoShoutTimer?.Stop(); _autoShoutTimer = null; } else if (_newsTimer == null) @@ -454,7 +454,7 @@ public partial class TownCrier : Mobile, ITownCrierEntryList var index = _newsTimer.Index; if (index >= tce.Lines.Length) { - _newsTimer.Stop(); + _newsTimer?.Stop(); _newsTimer = null; } else diff --git a/Projects/UOContent/Mobiles/Vendors/NPC/CustomHairstylist.cs b/Projects/UOContent/Mobiles/Vendors/NPC/CustomHairstylist.cs index 819224578..ae45ad6d1 100644 --- a/Projects/UOContent/Mobiles/Vendors/NPC/CustomHairstylist.cs +++ b/Projects/UOContent/Mobiles/Vendors/NPC/CustomHairstylist.cs @@ -5,680 +5,536 @@ using Server.Gumps; using Server.Items; using Server.Network; -namespace Server.Mobiles +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class CustomHairstylist : BaseVendor { - [SerializationGenerator(0, false)] - public partial class CustomHairstylist : BaseVendor + private static readonly HairstylistBuyInfo[] _sellList = + [ + new( + 1018357, // New Hair (50000 gold) + 50000, + false, + (from, vendor, price) => + new ChangeHairstyleGump(from, vendor, price, false, ChangeHairstyleEntry.HairEntries) + ), + new( + 1018358, // New Beard (50000 gold) + 50000, + true, + (from, vendor, price) => + new ChangeHairstyleGump(from, vendor, price, true, ChangeHairstyleEntry.BeardEntries) + ), + new( + 1018359, // Normal Hair Dye (50 gold) + 50, + false, + (_, vendor, price) => + new ChangeHairHueGump(vendor, price, true, true, ChangeHairHueEntry.RegularEntries) + ), + new( + 1018360, // Bright Hair Dye (500000 gold) + 500000, + false, + (_, vendor, price) => + new ChangeHairHueGump(vendor, price, true, true, ChangeHairHueEntry.BrightEntries) + ), + new( + 1018361, // Hair Only Dye (30000 gold) + 30000, + false, + (_, vendor, price) => + new ChangeHairHueGump(vendor, price, true, false, ChangeHairHueEntry.RegularEntries) + ), + new( + 1018362, // Beard Only Dye (30000 gold) + 30000, + true, + (_, vendor, price) => + new ChangeHairHueGump(vendor, price, false, true, ChangeHairHueEntry.RegularEntries) + ), + new( + 1018363, // Bright Hair Only Dye (500000 gold) + 500000, + false, + (_, vendor, price) => + new ChangeHairHueGump(vendor, price, true, false, ChangeHairHueEntry.BrightEntries) + ), + new( + 1018364, // Bright Beard Only Dye (500000 gold) + 500000, + true, + (_, vendor, price) => + new ChangeHairHueGump(vendor, price, false, true, ChangeHairHueEntry.BrightEntries) + ) + ]; + + [Constructible] + public CustomHairstylist() : base("the hairstylist") { - public static readonly object From = new(); - public static readonly object Vendor = new(); - public static readonly object Price = new(); - - private static readonly HairstylistBuyInfo[] m_SellList = - { - new( - 1018357, - 50000, - false, - typeof(ChangeHairstyleGump), - new[] - { From, Vendor, Price, false, ChangeHairstyleEntry.HairEntries } - ), - new( - 1018358, - 50000, - true, - typeof(ChangeHairstyleGump), - new[] - { From, Vendor, Price, true, ChangeHairstyleEntry.BeardEntries } - ), - new( - 1018359, - 50, - false, - typeof(ChangeHairHueGump), - new[] - { From, Vendor, Price, true, true, ChangeHairHueEntry.RegularEntries } - ), - new( - 1018360, - 500000, - false, - typeof(ChangeHairHueGump), - new[] - { From, Vendor, Price, true, true, ChangeHairHueEntry.BrightEntries } - ), - new( - 1018361, - 30000, - false, - typeof(ChangeHairHueGump), - new[] - { From, Vendor, Price, true, false, ChangeHairHueEntry.RegularEntries } - ), - new( - 1018362, - 30000, - true, - typeof(ChangeHairHueGump), - new[] - { From, Vendor, Price, false, true, ChangeHairHueEntry.RegularEntries } - ), - new( - 1018363, - 500000, - false, - typeof(ChangeHairHueGump), - new[] - { From, Vendor, Price, true, false, ChangeHairHueEntry.BrightEntries } - ), - new( - 1018364, - 500000, - true, - typeof(ChangeHairHueGump), - new[] - { From, Vendor, Price, false, true, ChangeHairHueEntry.BrightEntries } - ) - }; - - [Constructible] - public CustomHairstylist() : base("the hairstylist") - { - } - - protected override List SBInfos { get; } = new(); - - public override bool ClickTitle => false; - - public override bool IsActiveBuyer => false; - public override bool IsActiveSeller => true; - - public override VendorShoeType ShoeType => Utility.RandomBool() ? VendorShoeType.Shoes : VendorShoeType.Sandals; - - public override bool OnBuyItems(Mobile buyer, List list) => false; - - public override void VendorBuy(Mobile from) - { - from.SendGump(new HairstylistBuyGump(from, this, m_SellList)); - } - - public override int GetHairHue() => Utility.RandomBrightHue(); - - public override void InitOutfit() - { - base.InitOutfit(); - - AddItem(new Robe(Utility.RandomPinkHue())); - } - - public override void InitSBInfo() - { - } } - public class HairstylistBuyInfo + protected override List SBInfos { get; } = []; + + public override bool ClickTitle => false; + public override bool IsActiveBuyer => false; + public override bool IsActiveSeller => true; + + public override VendorShoeType ShoeType => Utility.RandomBool() ? VendorShoeType.Shoes : VendorShoeType.Sandals; + + public override bool OnBuyItems(Mobile buyer, List list) => false; + + public override void VendorBuy(Mobile from) => from.SendGump(new HairstylistBuyGump(from, this, _sellList)); + + public override int GetHairHue() => Utility.RandomBrightHue(); + + public override void InitOutfit() { - public HairstylistBuyInfo(int title, int price, bool facialHair, Type gumpType, object[] args) - { - Title = title; - Price = price; - FacialHair = facialHair; - GumpType = gumpType; - GumpArgs = args; - } + base.InitOutfit(); - public HairstylistBuyInfo(string title, int price, bool facialHair, Type gumpType, object[] args) - { - TitleString = title; - Price = price; - FacialHair = facialHair; - GumpType = gumpType; - GumpArgs = args; - } - - public int Title { get; } - - public string TitleString { get; } - - public int Price { get; } - - public bool FacialHair { get; } - - public Type GumpType { get; } - - public object[] GumpArgs { get; } + AddItem(new Robe(Utility.RandomPinkHue())); } - public class HairstylistBuyGump : Gump + public override void InitSBInfo() { - private readonly Mobile m_From; - private readonly HairstylistBuyInfo[] m_SellList; - private readonly Mobile m_Vendor; - public override bool Singleton => true; - public HairstylistBuyGump(Mobile from, Mobile vendor, HairstylistBuyInfo[] sellList) : base(50, 50) + } +} + +public class HairstylistBuyInfo +{ + public HairstylistBuyInfo( + TextDefinition title, int price, bool facialHair, Func gumpFunction + ) + { + Title = title; + Price = price; + FacialHair = facialHair; + GumpFactoryFn = gumpFunction; + } + + public TextDefinition Title { get; } + + public int Price { get; } + + public bool FacialHair { get; } + + public Func GumpFactoryFn { get; } +} + +public class HairstylistBuyGump : DynamicGump +{ + private readonly Mobile _from; + private readonly HairstylistBuyInfo[] _sellList; + private readonly Mobile _vendor; + public override bool Singleton => true; + + public HairstylistBuyGump(Mobile from, Mobile vendor, HairstylistBuyInfo[] sellList) : base(50, 50) + { + _from = from; + _vendor = vendor; + _sellList = sellList; + + var gumps = from.GetGumps(); + + gumps.Close(); + gumps.Close(); + } + + protected override void BuildLayout(ref DynamicGumpBuilder builder) + { + var isFemale = _from.Female || _from.Body.IsFemale; + + var balance = Banker.GetBalance(_from); + var canAfford = 0; + + for (var i = 0; i < _sellList.Length; ++i) { - m_From = from; - m_Vendor = vendor; - m_SellList = sellList; - - var gumps = from.GetGumps(); - - gumps.Close(); - gumps.Close(); - - var isFemale = from.Female || from.Body.IsFemale; - - var balance = Banker.GetBalance(from); - var canAfford = 0; - - for (var i = 0; i < sellList.Length; ++i) + var buyInfo = _sellList[i]; + if (balance >= buyInfo.Price && (!buyInfo.FacialHair || !isFemale)) { - if (balance >= sellList[i].Price && (!sellList[i].FacialHair || !isFemale)) - { - ++canAfford; - } - } - - AddPage(0); - - AddBackground(50, 10, 450, 100 + canAfford * 25, 2600); - - AddHtmlLocalized(100, 40, 350, 20, 1018356); // Choose your hairstyle change: - - var index = 0; - - for (var i = 0; i < sellList.Length; ++i) - { - if (balance >= sellList[i].Price && (!sellList[i].FacialHair || !isFemale)) - { - if (sellList[i].TitleString != null) - { - AddHtml(140, 75 + index * 25, 300, 20, sellList[i].TitleString); - } - else - { - AddHtmlLocalized(140, 75 + index * 25, 300, 20, sellList[i].Title); - } - - AddButton(100, 75 + index++ * 25, 4005, 4007, 1 + i); - } + ++canAfford; } } - public override void OnResponse(NetState sender, in RelayInfo info) + builder.AddPage(); + builder.AddBackground(50, 10, 450, 100 + canAfford * 25, 2600); + builder.AddHtmlLocalized(100, 40, 350, 20, 1018356); // Choose your hairstyle change: + + var index = 0; + + for (var i = 0; i < _sellList.Length; ++i) { - var index = info.ButtonID - 1; - - if (index >= 0 && index < m_SellList.Length) + var buyInfo = _sellList[i]; + if (balance >= buyInfo.Price && (!buyInfo.FacialHair || !isFemale)) { - var buyInfo = m_SellList[index]; - - var balance = Banker.GetBalance(m_From); - - var isFemale = m_From.Female || m_From.Body.IsFemale; - - if (buyInfo.FacialHair && isFemale) - { - m_Vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1010639, m_From.NetState); - } - else if (balance >= buyInfo.Price) - { - try - { - var origArgs = buyInfo.GumpArgs; - var args = new object[origArgs.Length]; - - for (var i = 0; i < args.Length; ++i) - { - if (origArgs[i] == CustomHairstylist.Price) - { - args[i] = m_SellList[index].Price; - } - else if (origArgs[i] == CustomHairstylist.From) - { - args[i] = m_From; - } - else if (origArgs[i] == CustomHairstylist.Vendor) - { - args[i] = m_Vendor; - } - else - { - args[i] = origArgs[i]; - } - } - - var g = buyInfo.GumpType.CreateInstance(args); - - m_From.SendGump(g); - } - catch - { - // ignored - } - } - else - { - m_Vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1042293, m_From.NetState); - } + buyInfo.Title.AddHtmlText(ref builder, 140, 75 + index * 25, 300, 20); + builder.AddButton(100, 75 + index++ * 25, 4005, 4007, 1 + i); } } } - public class ChangeHairHueEntry + public override void OnResponse(NetState sender, in RelayInfo info) { - public static readonly ChangeHairHueEntry[] BrightEntries = - { - new("*****", 12, 10), - new("*****", 32, 5), - new("*****", 38, 8), - new("*****", 54, 3), - new("*****", 62, 10), - new("*****", 81, 2), - new("*****", 89, 2), - new("*****", 1153, 2) - }; + var index = info.ButtonID - 1; - public static readonly ChangeHairHueEntry[] RegularEntries = + if (index < 0 || index >= _sellList.Length) { - new("*****", 1602, 26), - new("*****", 1628, 27), - new("*****", 1502, 32), - new("*****", 1302, 32), - new("*****", 1402, 32), - new("*****", 1202, 24), - new("*****", 2402, 29), - new("*****", 2213, 6), - new("*****", 1102, 8), - new("*****", 1110, 8), - new("*****", 1118, 16), - new("*****", 1134, 16) - }; - - public ChangeHairHueEntry(string name, int[] hues) - { - Name = name; - Hues = hues; + return; } - public ChangeHairHueEntry(string name, int start, int count) + var balance = Banker.GetBalance(_from); + var isFemale = _from.Female || _from.Body.IsFemale; + + var buyInfo = _sellList[index]; + if (buyInfo.FacialHair && isFemale) { - Name = name; - - Hues = new int[count]; - - for (var i = 0; i < count; ++i) - { - Hues[i] = start + i; - } + _vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1010639, _from.NetState); } - - public string Name { get; } - - public int[] Hues { get; } - } - - public class ChangeHairHueGump : Gump - { - private readonly ChangeHairHueEntry[] m_Entries; - private readonly bool m_FacialHair; - private readonly Mobile m_From; - private readonly bool m_Hair; - private readonly int m_Price; - private readonly Mobile m_Vendor; - - public override bool Singleton => true; - - public ChangeHairHueGump( - Mobile from, Mobile vendor, int price, bool hair, bool facialHair, - ChangeHairHueEntry[] entries - ) : base(50, 50) + else if (balance >= buyInfo.Price) { - m_From = from; - m_Vendor = vendor; - m_Price = price; - m_Hair = hair; - m_FacialHair = facialHair; - m_Entries = entries; - - AddPage(0); - - AddBackground(100, 10, 350, 370, 2600); - AddBackground(120, 54, 110, 270, 5100); - - AddHtmlLocalized(155, 25, 240, 30, 1011013); //
Hair Color Selection Menu
- - AddHtmlLocalized(150, 330, 220, 35, 1011014); // Dye my hair this color! - AddButton(380, 330, 4005, 4007, 1); - - for (var i = 0; i < entries.Length; ++i) - { - var entry = entries[i]; - - AddLabel(130, 59 + i * 22, entry.Hues[0] - 1, entry.Name); - AddButton(207, 60 + i * 22, 5224, 5224, 0, GumpButtonType.Page, 1 + i); - } - - for (var i = 0; i < entries.Length; ++i) - { - var entry = entries[i]; - var hues = entry.Hues; - var name = entry.Name; - - AddPage(1 + i); - - for (var j = 0; j < hues.Length; ++j) - { - AddLabel(278 + j / 16 * 80, 52 + j % 16 * 17, hues[j] - 1, name); - AddRadio(260 + j / 16 * 80, 52 + j % 16 * 17, 210, 211, false, j * entries.Length + i); - } - } + _from.SendGump(buyInfo.GumpFactoryFn(_from, _vendor, buyInfo.Price)); } - - public override void OnResponse(NetState sender, in RelayInfo info) + else { - if (info.ButtonID == 1) - { - var switches = info.Switches; - - if (switches.Length > 0) - { - var index = switches[0] % m_Entries.Length; - var offset = switches[0] / m_Entries.Length; - - if (index >= 0 && index < m_Entries.Length) - { - if (offset >= 0 && offset < m_Entries[index].Hues.Length) - { - if (m_Hair && m_From.HairItemID > 0 || m_FacialHair && m_From.FacialHairItemID > 0) - { - if (!Banker.Withdraw(m_From, m_Price)) - { - m_Vendor.PrivateOverheadMessage( - MessageType.Regular, - 0x3B2, - 1042293, // You cannot afford my services for that style. - m_From.NetState - ); - return; - } - - var hue = m_Entries[index].Hues[offset]; - - if (m_Hair) - { - m_From.HairHue = hue; - } - - if (m_FacialHair) - { - m_From.FacialHairHue = hue; - } - } - else - { - m_Vendor.PrivateOverheadMessage( - MessageType.Regular, - 0x3B2, - 502623, // You have no hair to dye and you cannot use this. - m_From.NetState - ); - } - } - } - } - else - { - // You decide not to change your hairstyle. - m_Vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1013009, m_From.NetState); - } - } - else - { - // You decide not to change your hairstyle. - m_Vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1013009, m_From.NetState); - } - } - } - - public class ChangeHairstyleEntry - { - public static readonly ChangeHairstyleEntry[] HairEntries = - { - new(50700, 70 - 137, 20 - 60, 0x203B), - new(60710, 193 - 260, 18 - 60, 0x2045), - new(50703, 316 - 383, 25 - 60, 0x2044), - new(60708, 70 - 137, 75 - 125, 0x203C), - new(60900, 193 - 260, 85 - 125, 0x2047), - new(60713, 320 - 383, 85 - 125, 0x204A), - new(60702, 70 - 137, 140 - 190, 0x203D), - new(60707, 193 - 260, 140 - 190, 0x2049), - new(60901, 315 - 383, 150 - 190, 0x2048), - new(0, 0, 0, 0) - }; - - public static readonly ChangeHairstyleEntry[] BeardEntries = - { - new(50800, 120 - 187, 30 - 80, 0x2040), - new(50904, 243 - 310, 33 - 80, 0x204B), - new(50906, 120 - 187, 100 - 150, 0x204D), - new(50801, 243 - 310, 95 - 150, 0x203E), - new(50802, 120 - 187, 173 - 220, 0x203F), - new(50905, 243 - 310, 165 - 220, 0x204C), - new(50808, 120 - 187, 242 - 290, 0x2041), - new(0, 0, 0, 0) - }; - - public ChangeHairstyleEntry(int gumpID, int x, int y, int itemID) - { - GumpID = gumpID; - X = x; - Y = y; - ItemID = itemID; - } - - public int ItemID { get; } - - public int GumpID { get; } - - public int X { get; } - - public int Y { get; } - } - - public class ChangeHairstyleGump : Gump - { - private readonly ChangeHairstyleEntry[] m_Entries; - private readonly bool m_FacialHair; - private readonly Mobile m_From; - private readonly int m_Price; - private readonly Mobile m_Vendor; - - public override bool Singleton => true; - - public ChangeHairstyleGump( - Mobile from, Mobile vendor, int price, bool facialHair, ChangeHairstyleEntry[] entries - ) : base(50, 50) - { - m_From = from; - m_Vendor = vendor; - m_Price = price; - m_FacialHair = facialHair; - m_Entries = entries; - - var gumps = from.GetGumps(); - - gumps.Close(); - gumps.Close(); - - var tableWidth = m_FacialHair ? 2 : 3; - var tableHeight = (entries.Length + tableWidth - (m_FacialHair ? 1 : 2)) / tableWidth; - var offsetWidth = 123; - var offsetHeight = m_FacialHair ? 70 : 65; - - AddPage(0); - - AddBackground(0, 0, 81 + tableWidth * offsetWidth, 105 + tableHeight * offsetHeight, 2600); - - AddButton(45, 45 + tableHeight * offsetHeight, 4005, 4007, 1); - AddHtmlLocalized(77, 45 + tableHeight * offsetHeight, 90, 35, 1006044); // Ok - - AddButton(81 + tableWidth * offsetWidth - 180, 45 + tableHeight * offsetHeight, 4005, 4007, 0); - AddHtmlLocalized( - 81 + tableWidth * offsetWidth - 148, - 45 + tableHeight * offsetHeight, - 90, - 35, - 1006045 - ); // Cancel - - if (!facialHair) - { - AddHtmlLocalized(50, 15, 350, 20, 1018353); //
New Hairstyle
- } - else - { - AddHtmlLocalized(55, 15, 200, 20, 1018354); //
New Beard
- } - - for (var i = 0; i < entries.Length; ++i) - { - var xTable = i % tableWidth; - var yTable = i / tableWidth; - - if (entries[i].GumpID != 0) - { - AddRadio(40 + xTable * offsetWidth, 70 + yTable * offsetHeight, 208, 209, false, i); - AddBackground(87 + xTable * offsetWidth, 50 + yTable * offsetHeight, 50, 50, 2620); - AddImage( - 87 + xTable * offsetWidth + entries[i].X, - 50 + yTable * offsetHeight + entries[i].Y, - entries[i].GumpID - ); - } - else if (!facialHair) - { - AddRadio(40 + (xTable + 1) * offsetWidth, 240, 208, 209, false, i); - AddHtmlLocalized(60 + (xTable + 1) * offsetWidth, 240, 85, 35, 1011064); // Bald - } - else - { - AddRadio(40 + xTable * offsetWidth, 70 + yTable * offsetHeight, 208, 209, false, i); - AddHtmlLocalized(60 + xTable * offsetWidth, 70 + yTable * offsetHeight, 85, 35, 1011064); // Bald - } - } - } - - public override void OnResponse(NetState sender, in RelayInfo info) - { - if (m_FacialHair && (m_From.Female || m_From.Body.IsFemale)) - { - return; - } - - if (m_From.Race == Race.Elf) - { - m_From.SendMessage("This isn't implemented for elves yet. Sorry!"); - return; - } - - if (info.ButtonID == 1) - { - var switches = info.Switches; - - if (switches.Length > 0) - { - var index = switches[0]; - - if (index >= 0 && index < m_Entries.Length) - { - var entry = m_Entries[index]; - - (m_From as PlayerMobile)?.SetHairMods(-1, -1); - - var hairID = m_From.HairItemID; - var facialHairID = m_From.FacialHairItemID; - - if (entry.ItemID == 0) - { - if (m_FacialHair ? facialHairID == 0 : hairID == 0) - { - return; - } - - if (Banker.Withdraw(m_From, m_Price)) - { - if (m_FacialHair) - { - m_From.FacialHairItemID = 0; - } - else - { - m_From.HairItemID = 0; - } - } - else - { - m_Vendor.PrivateOverheadMessage( - MessageType.Regular, - 0x3B2, - 1042293, // You cannot afford my services for that style. - m_From.NetState - ); - } - } - else - { - if (m_FacialHair) - { - if (facialHairID > 0 && facialHairID == entry.ItemID) - { - return; - } - } - else - { - if (hairID > 0 && hairID == entry.ItemID) - { - return; - } - } - - if (Banker.Withdraw(m_From, m_Price)) - { - if (m_FacialHair) - { - m_From.FacialHairItemID = entry.ItemID; - } - else - { - m_From.HairItemID = entry.ItemID; - } - } - else - { - m_Vendor.PrivateOverheadMessage( - MessageType.Regular, - 0x3B2, - 1042293, - m_From.NetState - ); // You cannot afford my services for that style. - } - } - } - } - else - { - // You decide not to change your hairstyle. - m_Vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1013009, m_From.NetState); - } - } - else - { - // You decide not to change your hairstyle. - m_Vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1013009, m_From.NetState); - } + _vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1042293, _from.NetState); + } + } +} + +public class ChangeHairHueEntry +{ + public static readonly ChangeHairHueEntry[] BrightEntries = + [ + new("*****", 12, 10), + new("*****", 32, 5), + new("*****", 38, 8), + new("*****", 54, 3), + new("*****", 62, 10), + new("*****", 81, 2), + new("*****", 89, 2), + new("*****", 1153, 2) + ]; + + public static readonly ChangeHairHueEntry[] RegularEntries = + [ + new("*****", 1602, 26), + new("*****", 1628, 27), + new("*****", 1502, 32), + new("*****", 1302, 32), + new("*****", 1402, 32), + new("*****", 1202, 24), + new("*****", 2402, 29), + new("*****", 2213, 6), + new("*****", 1102, 8), + new("*****", 1110, 8), + new("*****", 1118, 16), + new("*****", 1134, 16) + ]; + + public ChangeHairHueEntry(string name, int[] hues) + { + Name = name; + Hues = hues; + } + + public ChangeHairHueEntry(string name, int start, int count) + { + Name = name; + + Hues = new int[count]; + + for (var i = 0; i < count; ++i) + { + Hues[i] = start + i; + } + } + + public string Name { get; } + + public int[] Hues { get; } +} + +public class ChangeHairHueGump : DynamicGump +{ + private readonly ChangeHairHueEntry[] _entries; + private readonly bool _facialHair; + private readonly bool _hair; + private readonly int _price; + private readonly Mobile _vendor; + + public override bool Singleton => true; + + public ChangeHairHueGump( + Mobile vendor, int price, bool hair, bool facialHair, ChangeHairHueEntry[] entries + ) : base(50, 50) + { + _vendor = vendor; + _price = price; + _hair = hair; + _facialHair = facialHair; + _entries = entries; + } + + protected override void BuildLayout(ref DynamicGumpBuilder builder) + { + builder.AddPage(); + + builder.AddBackground(100, 10, 350, 370, 2600); + builder.AddBackground(120, 54, 110, 270, 5100); + + builder.AddHtmlLocalized(155, 25, 240, 30, 1011013); //
Hair Color Selection Menu
+ + builder.AddHtmlLocalized(150, 330, 220, 35, 1011014); // Dye my hair this color! + builder.AddButton(380, 330, 4005, 4007, 1); + + for (var i = 0; i < _entries.Length; ++i) + { + var entry = _entries[i]; + + builder.AddLabel(130, 59 + i * 22, entry.Hues[0] - 1, entry.Name); + builder.AddButton(207, 60 + i * 22, 5224, 5224, 0, GumpButtonType.Page, 1 + i); + } + + for (var i = 0; i < _entries.Length; ++i) + { + var entry = _entries[i]; + var hues = entry.Hues; + var name = entry.Name; + + builder.AddPage(1 + i); + + for (var j = 0; j < hues.Length; ++j) + { + var page = Math.DivRem(j, 16, out var index); + builder.AddLabel(278 + page * 80, 52 + index * 17, hues[j] - 1, name); + builder.AddRadio(260 + page * 80, 52 + index * 17, 210, 211, false, j * _entries.Length + i); + } + } + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + var from = sender.Mobile; + + var switches = info.Switches; + if (info.ButtonID != 1 || switches.Length <= 0) + { + // You decide not to change your hairstyle. + _vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1013009, from.NetState); + return; + } + + var offset = Math.DivRem(switches[0], _entries.Length, out var index); + + if (index < 0 || index >= _entries.Length || offset < 0 || offset >= _entries[index].Hues.Length) + { + return; + } + + if ((!_hair || from.HairItemID <= 0) && (!_facialHair || from.FacialHairItemID <= 0)) + { + // You have no hair to dye and you cannot use this. + _vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 502623, sender); + return; + } + + if (!Banker.Withdraw(from, _price)) + { + // You cannot afford my services for that style. + _vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1042293, sender); + return; + } + + var hue = _entries[index].Hues[offset]; + + if (_hair) + { + from.HairHue = hue; + } + + if (_facialHair) + { + from.FacialHairHue = hue; + } + } +} + +public class ChangeHairstyleEntry +{ + public static readonly ChangeHairstyleEntry[] HairEntries = + [ + new(50700, 70 - 137, 20 - 60, 0x203B), + new(60710, 193 - 260, 18 - 60, 0x2045), + new(50703, 316 - 383, 25 - 60, 0x2044), + new(60708, 70 - 137, 75 - 125, 0x203C), + new(60900, 193 - 260, 85 - 125, 0x2047), + new(60713, 320 - 383, 85 - 125, 0x204A), + new(60702, 70 - 137, 140 - 190, 0x203D), + new(60707, 193 - 260, 140 - 190, 0x2049), + new(60901, 315 - 383, 150 - 190, 0x2048), + new(0, 0, 0, 0) + ]; + + public static readonly ChangeHairstyleEntry[] BeardEntries = + [ + new(50800, 120 - 187, 30 - 80, 0x2040), + new(50904, 243 - 310, 33 - 80, 0x204B), + new(50906, 120 - 187, 100 - 150, 0x204D), + new(50801, 243 - 310, 95 - 150, 0x203E), + new(50802, 120 - 187, 173 - 220, 0x203F), + new(50905, 243 - 310, 165 - 220, 0x204C), + new(50808, 120 - 187, 242 - 290, 0x2041), + new(0, 0, 0, 0) + ]; + + public ChangeHairstyleEntry(int gumpID, int x, int y, int itemID) + { + GumpID = gumpID; + X = x; + Y = y; + ItemID = itemID; + } + + public int ItemID { get; } + + public int GumpID { get; } + + public int X { get; } + + public int Y { get; } +} + +public class ChangeHairstyleGump : DynamicGump +{ + private readonly ChangeHairstyleEntry[] _entries; + private readonly bool _facialHair; + private readonly Mobile _from; + private readonly int _price; + private readonly Mobile _vendor; + + public override bool Singleton => true; + + public ChangeHairstyleGump(Mobile from, Mobile vendor, int price, bool facialHair, ChangeHairstyleEntry[] entries) + : base(50, 50) + { + _from = from; + _vendor = vendor; + _price = price; + _facialHair = facialHair; + _entries = entries; + + var gumps = from.GetGumps(); + + gumps.Close(); + gumps.Close(); + } + + protected override void BuildLayout(ref DynamicGumpBuilder builder) + { + var tableWidth = _facialHair ? 2 : 3; + var tableHeight = (_entries.Length + tableWidth - (_facialHair ? 1 : 2)) / tableWidth; + const int offsetWidth = 123; + var offsetHeight = _facialHair ? 70 : 65; + var tableWidthOffset = 81 + tableWidth * offsetWidth; + var tableHeightOffset = 45 + tableHeight * offsetHeight; + + builder.AddPage(); + + builder.AddBackground(0, 0, tableWidthOffset, 60 + tableHeightOffset, 2600); + + builder.AddButton(45, tableHeightOffset, 4005, 4007, 1); + builder.AddHtmlLocalized(77, tableHeightOffset, 90, 35, 1006044); // Ok + + builder.AddButton(tableWidthOffset - 180, tableHeightOffset, 4005, 4007, 0); + // Cancel + builder.AddHtmlLocalized(tableWidthOffset - 148, tableHeightOffset, 90, 35, 1006045); + + if (!_facialHair) + { + builder.AddHtmlLocalized(50, 15, 350, 20, 1018353); //
New Hairstyle
+ } + else + { + builder.AddHtmlLocalized(55, 15, 200, 20, 1018354); //
New Beard
+ } + + for (var i = 0; i < _entries.Length; ++i) + { + var yTable = Math.DivRem(i, tableWidth, out var xTable); + var xOffset = xTable * offsetWidth; + var yOffset = yTable * offsetHeight; + var entry = _entries[i]; + + if (entry.GumpID != 0) + { + builder.AddRadio(40 + xOffset, 70 + yOffset, 208, 209, false, i); + builder.AddBackground(87 + xOffset, 50 + yOffset, 50, 50, 2620); + builder.AddImage(87 + xOffset + entry.X, 50 + yOffset + entry.Y, entry.GumpID); + } + else if (!_facialHair) + { + builder.AddRadio(40 + (xTable + 1) * offsetWidth, 240, 208, 209, false, i); + builder. AddHtmlLocalized(60 + (xTable + 1) * offsetWidth, 240, 85, 35, 1011064); // Bald + } + else + { + builder.AddRadio(40 + xOffset, 70 + yOffset, 208, 209, false, i); + builder.AddHtmlLocalized(60 + xOffset, 70 + yOffset, 85, 35, 1011064); // Bald + } + } + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + if (_facialHair && (_from.Female || _from.Body.IsFemale)) + { + return; + } + + if (_from.Race == Race.Elf) + { + _from.SendMessage("This isn't implemented for elves yet. Sorry!"); + return; + } + + var switches = info.Switches; + + if (info.ButtonID != 1 || switches.Length <= 0) + { + // You decide not to change your hairstyle. + _vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1013009, _from.NetState); + return; + } + + var index = switches[0]; + + if (index < 0 || index >= _entries.Length) + { + return; + } + + var entry = _entries[index]; + + (_from as PlayerMobile)?.SetHairMods(-1, -1); + + if ((_facialHair ? _from.FacialHairItemID : _from.HairItemID) == entry.ItemID) + { + return; + } + + if (!Banker.Withdraw(_from, _price)) + { + // You cannot afford my services for that style. + _vendor.PrivateOverheadMessage(MessageType.Regular, 0x3B2, 1042293, _from.NetState); + } + else if (_facialHair) + { + _from.FacialHairItemID = entry.ItemID; + } + else + { + _from.HairItemID = entry.ItemID; } } } diff --git a/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs b/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs index 9cf360aff..cc63701d3 100644 --- a/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs +++ b/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs @@ -171,7 +171,7 @@ public partial class PlayerVendor : Mobile reader.ReadBool(); // New vendor system? _shopName = reader.ReadString(); _nextPayTime = reader.ReadDeltaTime(); - _house = reader.ReadEntity(); + House = reader.ReadEntity(); _owner = reader.ReadEntity(); _bankAccount = reader.ReadInt(); _holdGold = reader.ReadInt(); @@ -201,6 +201,34 @@ public partial class PlayerVendor : Mobile { NameHue = -1; } + + // Do we have a vendor that may have been orphaned? Let's try to recover them and attach them to their house. + if (_house == null) + { + if (_owner == null) + { + Timer.DelayCall(Delete); // Don't try to dismiss, no owner. + } + Timer.DelayCall(() => + { + var house = BaseHouse.FindHouseAt(this); + + if (house != null) + { + House = house; + } + else if (_owner.AccessLevel == AccessLevel.Player) + { + // If we can't find a house, dismiss the vendor. + Dismiss(_owner); + } + } + ); + } + else + { + _house.PlayerVendors.Add(this); + } } public void InitBody() diff --git a/Projects/UOContent/Multis/Boats/BaseBoat.cs b/Projects/UOContent/Multis/Boats/BaseBoat.cs index 9e367b455..6a6d68666 100644 --- a/Projects/UOContent/Multis/Boats/BaseBoat.cs +++ b/Projects/UOContent/Multis/Boats/BaseBoat.cs @@ -175,40 +175,16 @@ namespace Server.Multis [CommandProperty(AccessLevel.GameMaster)] public BoatOrder Order { get; set; } - public int Status - { - get + public int Status => + (Core.Now - (TimeOfDecay - BoatDecayDelay)) switch { - var start = Core.Now - TimeOfDecay - BoatDecayDelay; - - if (start < TimeSpan.FromHours(1.0)) - { - return 1043010; // This structure is like new. - } - - if (start < TimeSpan.FromDays(2.0)) - { - return 1043011; // This structure is slightly worn. - } - - if (start < TimeSpan.FromDays(3.0)) - { - return 1043012; // This structure is somewhat worn. - } - - if (start < TimeSpan.FromDays(4.0)) - { - return 1043013; // This structure is fairly worn. - } - - if (start < TimeSpan.FromDays(5.0)) - { - return 1043014; // This structure is greatly worn. - } - - return 1043015; // This structure is in danger of collapsing. - } - } + var start when start < TimeSpan.FromHours(1) => 1043010, // This structure is like new. + var start when start < TimeSpan.FromDays(2) => 1043011, // This structure is slightly worn. + var start when start < TimeSpan.FromDays(3) => 1043012, // This structure is somewhat worn. + var start when start < TimeSpan.FromDays(4) => 1043013, // This structure is fairly worn. + var start when start < TimeSpan.FromDays(5) => 1043014, // This structure is greatly worn. + _ => 1043015 // This structure is in danger of collapsing. + }; public virtual int NorthID => 0; public virtual int EastID => 0; diff --git a/Projects/UOContent/Multis/Deeds.cs b/Projects/UOContent/Multis/Deeds.cs index b537cec5e..1120a0b55 100644 --- a/Projects/UOContent/Multis/Deeds.cs +++ b/Projects/UOContent/Multis/Deeds.cs @@ -71,6 +71,8 @@ namespace Server.Multis.Deeds [CommandProperty(AccessLevel.GameMaster)] public Point3D Offset { get; set; } + public virtual Direction HouseDirection => Direction.South; + public abstract Rectangle2D[] Area { get; } public override void Serialize(IGenericWriter writer) @@ -153,7 +155,7 @@ namespace Server.Multis.Deeds else { var center = new Point3D(p.X - Offset.X, p.Y - Offset.Y, p.Z - Offset.Z); - var res = HousePlacement.Check(from, MultiID, center, out var toMove); + var res = HousePlacement.Check(from, MultiID, center, out var toMove, HouseDirection); switch (res) { diff --git a/Projects/UOContent/Multis/Houses/BaseHouse.cs b/Projects/UOContent/Multis/Houses/BaseHouse.cs index d49062d01..f1d3954d9 100644 --- a/Projects/UOContent/Multis/Houses/BaseHouse.cs +++ b/Projects/UOContent/Multis/Houses/BaseHouse.cs @@ -100,6 +100,8 @@ namespace Server.Multis [CommandProperty(AccessLevel.GameMaster)] public bool RestrictDecay { get; set; } + public virtual Direction HouseDirection => Direction.South; + public virtual TimeSpan DecayPeriod => TimeSpan.FromDays(5.0); public virtual DecayType DecayType diff --git a/Projects/UOContent/Multis/Houses/HousePlacement.cs b/Projects/UOContent/Multis/Houses/HousePlacement.cs index d97f734ab..972b43f2e 100644 --- a/Projects/UOContent/Multis/Houses/HousePlacement.cs +++ b/Projects/UOContent/Multis/Houses/HousePlacement.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Server.Collections; using Server.Regions; @@ -37,7 +38,9 @@ namespace Server.Multis 0x0150, 0x015C // Furrows }; - public static HousePlacementResult Check(Mobile from, int multiID, Point3D center, out List toMove) + public static HousePlacementResult Check( + Mobile from, int multiID, Point3D center, out List toMove, Direction houseFacing = Direction.South + ) { // If this spot is considered valid, every item and mobile in this list will be moved under the house sign toMove = new List(); @@ -77,7 +80,7 @@ namespace Server.Multis HouseFoundation.AddStairsTo(ref mcl); // this is a AOS house, add the stairs } - // Location of the nortwest-most corner of the house + // Location of the northwest-most corner of the house var start = new Point3D(center.X + mcl.Min.X, center.Y + mcl.Min.Y, center.Z); // These are storage lists. They hold items and mobiles found in the map for further processing @@ -85,7 +88,7 @@ namespace Server.Multis var mobiles = new List(); // These are also storage lists. They hold location values indicating the yard and border locations. - List yard = new(), borders = new(); + List borders = []; /* RULES: * @@ -121,7 +124,7 @@ namespace Server.Multis return HousePlacementResult.BadRegionTemp; } - if (reg.IsPartOf() || reg.IsPartOf()) + if (reg.IsPartOf()) { return HousePlacementResult.BadRegionHidden; } @@ -218,16 +221,18 @@ namespace Server.Multis { var id = item.ItemData; - if (addTileTop > item.Z && item.Z + id.CalcHeight > addTileZ) + if (addTileTop <= item.Z || item.Z + id.CalcHeight <= addTileZ) { - if (item.Movable) - { - toMove.Add(item); - } - else if (id.Impassable || id.Surface && !id.Background) - { - return HousePlacementResult.BadItem; // Broke rule #2 - } + continue; + } + + if (item.Movable) + { + toMove.Add(item); + } + else if (id.Impassable || id.Surface && !id.Background) + { + return HousePlacementResult.BadItem; // Broke rule #2 } } @@ -257,17 +262,9 @@ namespace Server.Multis if (hasFoundation) { - for (var xOffset = -1; xOffset <= 1; ++xOffset) + if (!CheckYard(map, tileX, tileY, YardSize, houseFacing)) { - for (var yOffset = -YardSize; yOffset <= YardSize; ++yOffset) - { - var yardPoint = new Point2D(tileX + xOffset, tileY + yOffset); - - if (!yard.Contains(yardPoint)) - { - yard.Add(yardPoint); - } - } + return HousePlacementResult.BadStatic; // Broke rule #3 } for (var xOffset = -1; xOffset <= 1; ++xOffset) @@ -364,37 +361,73 @@ namespace Server.Multis } } - for (var i = 0; i < yard.Count; i++) - { - var yardPoint = yard[i]; + return HousePlacementResult.Valid; + } - foreach (var house in map.GetMultisInSector(yardPoint)) + private static bool CheckYard(Map map, int tileX, int tileY, int yardSize, Direction houseFacing) + { + var isSouthFacing = (houseFacing & Direction.South) != 0; + var isEastFacing = (houseFacing & Direction.East) != 0; + + for (var xOffset = -yardSize; xOffset <= yardSize; ++xOffset) + { + var absXOffset = Math.Abs(xOffset); + for (var yOffset = -yardSize; yOffset <= yardSize; ++yOffset) { - if (house.Contains(yard[i])) + var absYOffset = Math.Abs(yOffset); + var yardPoint = new Point2D(tileX + xOffset, tileY + yOffset); + + bool inSouthYard = yOffset > 0 && yOffset <= yardSize && absXOffset <= 1; + bool inEastYard = xOffset > 0 && xOffset <= yardSize && absYOffset <= 1; + bool inNorthYard = yOffset < 0 && yOffset >= -yardSize && absXOffset <= 1; + bool inWestYard = xOffset < 0 && xOffset >= -yardSize && absYOffset <= 1; + + // Check each house at this point + foreach (var house in map.GetMultisInSector(yardPoint)) { - return HousePlacementResult.BadStatic; // Broke rule #3 + if (!house.Contains(yardPoint)) + { + continue; + } + + var existingHouseFacing = house.HouseDirection; + var existingHouseIsSouthFacing = (existingHouseFacing & Direction.South) != 0; + var existingHouseIsEastFacing = (existingHouseFacing & Direction.East) != 0; + + // Sub-Rule 1: No houses within immediate proximity (1 tile radius) + if (absXOffset <= 1 && absYOffset <= 1) + { + return false; + } + + // Sub-Rule 2: If we're south facing, protect our south yard + if (isSouthFacing && inSouthYard) + { + return false; + } + + // Sub-Rule 3: If we're east facing, protect our east yard + if (isEastFacing && inEastYard) + { + return false; + } + + // Sub-Rule 4: If there's a south-facing house to our north, respect its yard + if (inNorthYard && existingHouseIsSouthFacing) + { + return false; + } + + // Sub-Rule 5: If there's an east-facing house to our west, respect its yard + if (inWestYard && existingHouseIsEastFacing) + { + return false; + } } } } - // TODO: Should we check for MultiTilesAt each yard point? - // for (var i = 0; i < yard.Count; i++) - // { - // var yardPoint = yard[i]; - // - // foreach (var tiles in map.GetMultiTilesAt(yardPoint)) - // { - // for (int j = 0; j < tiles.Length; ++j) - // { - // if ((TileData.ItemTable[tiles[j].ID & TileData.MaxItemValue].Flags & (TileFlag.Impassable | TileFlag.Surface)) != 0) - // { - // return HousePlacementResult.BadStatic; // Broke rule #3 - // } - // } - // } - // } - - return HousePlacementResult.Valid; + return true; } } } diff --git a/Projects/UOContent/Multis/Houses/HousePlacementTool.cs b/Projects/UOContent/Multis/Houses/HousePlacementTool.cs index c0842066c..be254b01c 100644 --- a/Projects/UOContent/Multis/Houses/HousePlacementTool.cs +++ b/Projects/UOContent/Multis/Houses/HousePlacementTool.cs @@ -303,7 +303,7 @@ public class HousePlacementEntry public HousePlacementEntry( Type type, int description, int storage, int lockdowns, int newStorage, int newLockdowns, - int vendors, int cost, int xOffset, int yOffset, int zOffset, int multiID + int vendors, int cost, int xOffset, int yOffset, int zOffset, int multiID, Direction direction = Direction.South ) { Type = type; @@ -318,6 +318,7 @@ public class HousePlacementEntry Offset = new Point3D(xOffset, yOffset, zOffset); MultiID = multiID; + HouseDirection = direction; } public Type Type { get; } @@ -334,6 +335,8 @@ public class HousePlacementEntry public Point3D Offset { get; } + public Direction HouseDirection { get; } + public static HousePlacementEntry[] ClassicHouses { get; } = { new(typeof(SmallOldHouse), 1011303, 425, 212, 489, 244, 10, 37000, 0, 4, 0, 0x0064), @@ -1981,7 +1984,7 @@ public class HousePlacementEntry prevHouse.Delete(); - var res = HousePlacement.Check(from, MultiID, center, out var toMove); + var res = HousePlacement.Check(from, MultiID, center, out var toMove, HouseDirection); switch (res) { @@ -2008,21 +2011,18 @@ public class HousePlacementEntry $"{Cost} gold would have been withdrawn from your bank if you were not a GM." ); } + else if (Banker.Withdraw(from, Cost)) + { + // ~1_AMOUNT~ gold has been withdrawn from your bank box. + from.SendLocalizedMessage(1060398, Cost.ToString()); + } else { - if (Banker.Withdraw(from, Cost)) - { - // ~1_AMOUNT~ gold has been withdrawn from your bank box. - from.SendLocalizedMessage(1060398, Cost.ToString()); - } - else - { - house.RemoveKeys(from); - house.Delete(); - // You do not have the funds available in your bank box to purchase this house. Try placing a smaller house, or adding gold or checks to your bank box. - from.SendLocalizedMessage(1060646); - return; - } + house.RemoveKeys(from); + house.Delete(); + // You do not have the funds available in your bank box to purchase this house. Try placing a smaller house, or adding gold or checks to your bank box. + from.SendLocalizedMessage(1060646); + return; } house.MoveToWorld(center, from.Map); @@ -2087,7 +2087,7 @@ public class HousePlacementEntry } var center = new Point3D(p.X - Offset.X, p.Y - Offset.Y, p.Z - Offset.Z); - var res = HousePlacement.Check(from, MultiID, center, out var toMove); + var res = HousePlacement.Check(from, MultiID, center, out var toMove, HouseDirection); switch (res) { diff --git a/Projects/UOContent/Network/Packets/IncomingMobilePackets.cs b/Projects/UOContent/Network/Packets/IncomingMobilePackets.cs index 994a2590a..8dab27f0e 100644 --- a/Projects/UOContent/Network/Packets/IncomingMobilePackets.cs +++ b/Projects/UOContent/Network/Packets/IncomingMobilePackets.cs @@ -158,6 +158,8 @@ public static class IncomingMobilePackets trade.To.Plat = plat; trade.UpdateToCurrency(); } + + cont.ClearChecks(); } } } diff --git a/Projects/UOContent/Skills/Begging.cs b/Projects/UOContent/Skills/Begging.cs index 0267aa682..1855050af 100644 --- a/Projects/UOContent/Skills/Begging.cs +++ b/Projects/UOContent/Skills/Begging.cs @@ -21,23 +21,18 @@ namespace Server.SkillHandlers m.SendLocalizedMessage(500397); // To whom do you wish to grovel? - return TimeSpan.FromHours(6.0); + return TimeSpan.FromSeconds(30.0); } private class InternalTarget : Target { - private bool m_SetSkillTime = true; - public InternalTarget() : base(12, false, TargetFlags.None) { } - protected override void OnTargetFinish(Mobile from) + protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType) { - if (m_SetSkillTime) - { - from.NextSkillTime = Core.TickCount; - } + from.NextSkillTime = Core.TickCount; } protected override void OnTarget(Mobile from, object targeted) @@ -81,8 +76,6 @@ namespace Server.SkillHandlers from.Animate(32, 5, 1, true, false, 0); // Bow new InternalTimer(from, targ).Start(); - - m_SetSkillTime = false; } } else // Not a Mobile @@ -125,16 +118,7 @@ namespace Server.SkillHandlers else if (m_From.CheckTargetSkill(SkillName.Begging, m_Target, 0.0, 100.0)) { var toConsume = theirPack.GetAmount(typeof(Gold)) / 10; - var max = 10 + m_From.Fame / 2500; - - if (max > 14) - { - max = 14; - } - else if (max < 10) - { - max = 10; - } + var max = Math.Clamp(10 + m_From.Fame / 2500, 10, 14); if (toConsume > max) { diff --git a/Projects/UOContent/Skills/DetectHidden.cs b/Projects/UOContent/Skills/DetectHidden.cs index a0fcecafd..b0aa0ccbe 100644 --- a/Projects/UOContent/Skills/DetectHidden.cs +++ b/Projects/UOContent/Skills/DetectHidden.cs @@ -18,7 +18,7 @@ namespace Server.SkillHandlers src.SendLocalizedMessage(500819); // Where will you search? src.Target = new InternalTarget(); - return TimeSpan.FromSeconds(6.0); + return TimeSpan.FromSeconds(30.0); } private class InternalTarget : Target @@ -27,6 +27,11 @@ namespace Server.SkillHandlers { } + protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType) + { + from.NextSkillTime = Core.TickCount; + } + protected override void OnTarget(Mobile src, object targ) { var foundAnyone = false; @@ -108,6 +113,8 @@ namespace Server.SkillHandlers { src.SendLocalizedMessage(500817); // You can see nothing hidden there. } + + src.NextSkillTime = Core.TickCount + 6000; // 6 seconds cooldown } } } diff --git a/Projects/UOContent/Skills/Peacemaking.cs b/Projects/UOContent/Skills/Peacemaking.cs index c963395d5..437249bba 100644 --- a/Projects/UOContent/Skills/Peacemaking.cs +++ b/Projects/UOContent/Skills/Peacemaking.cs @@ -27,27 +27,22 @@ namespace Server.SkillHandlers from.RevealingAction(); from.SendLocalizedMessage(1049525); // Whom do you wish to calm? from.Target = new InternalTarget(from, instrument); - from.NextSkillTime = Core.TickCount + 21600000; + from.NextSkillTime = Core.TickCount + 30000; // 30s timeout on the targeter } private class InternalTarget : Target { private readonly BaseInstrument m_Instrument; - private bool m_SetSkillTime = true; public InternalTarget(Mobile from, BaseInstrument instrument) : base( BaseInstrument.GetBardRange(from, SkillName.Peacemaking), false, TargetFlags.None - ) => - m_Instrument = instrument; + ) => m_Instrument = instrument; - protected override void OnTargetFinish(Mobile from) + protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType) { - if (m_SetSkillTime) - { - from.NextSkillTime = Core.TickCount; - } + from.NextSkillTime = Core.TickCount; } protected override void OnTarget(Mobile from, object targeted) @@ -60,21 +55,19 @@ namespace Server.SkillHandlers } else if (from.Region.IsPartOf()) { - from.SendMessage("You may not peacemake in this area."); + from.SendMessage("You may not use peacemaking in this area."); } else if (targ.Region.IsPartOf()) { - from.SendMessage("You may not peacemake there."); + from.SendMessage("You may not use peacemaking there."); } else if (!m_Instrument.IsChildOf(from.Backpack)) { - from.SendLocalizedMessage( - 1062488 - ); // The instrument you are trying to play is no longer in your backpack! + // The instrument you are trying to play is no longer in your backpack! + from.SendLocalizedMessage(1062488); } else { - m_SetSkillTime = false; from.NextSkillTime = Core.TickCount + 10000; if (targeted == from) @@ -149,17 +142,14 @@ namespace Server.SkillHandlers if (!from.CanBeHarmful(targ, false)) { from.SendLocalizedMessage(1049528); - m_SetSkillTime = true; } else if (bc?.Uncalmable == true) { from.SendLocalizedMessage(1049526); // You have no chance of calming that creature. - m_SetSkillTime = true; } else if (bc?.BardPacified == true) { from.SendLocalizedMessage(1049527); // That creature is already being calmed. - m_SetSkillTime = true; } else if (!BaseInstrument.CheckMusicianship(from)) { @@ -193,10 +183,10 @@ namespace Server.SkillHandlers targ.Combatant = null; targ.Warmode = false; + from.SendLocalizedMessage(1049532); // You play hypnotic music, calming your target. if (bc != null) { - from.SendLocalizedMessage(1049532); // You play hypnotic music, calming your target. - + // You play hypnotic music, calming your target. var seconds = 100 - diff / 1.5; if (seconds > 120) @@ -212,8 +202,7 @@ namespace Server.SkillHandlers } else { - from.SendLocalizedMessage(1049532); // You play hypnotic music, calming your target. - + // You play hypnotic music, calming your target. // You hear lovely music, and forget to continue battling! targ.SendLocalizedMessage(500616); } diff --git a/Projects/UOContent/Skills/Stealing.cs b/Projects/UOContent/Skills/Stealing.cs index f2f270b29..92cba48cb 100644 --- a/Projects/UOContent/Skills/Stealing.cs +++ b/Projects/UOContent/Skills/Stealing.cs @@ -12,510 +12,503 @@ using Server.Spells.Ninjitsu; using Server.Spells.Seventh; using Server.Targeting; -namespace Server.SkillHandlers +namespace Server.SkillHandlers; + +public static class Stealing { - public static class Stealing + public static bool ClassicMode { get; private set; } + + public static bool SuspendOnMurder { get; private set; } + + public static int MaxWeightToSteal { get; private set; } + + public static bool CanStealContainers { get; private set; } + + public static void Configure() { - public static readonly bool ClassicMode = false; - public static readonly bool SuspendOnMurder = false; - private const int MaxWeightToSteal = 10; + ClassicMode = ServerConfiguration.GetSetting("stealing.classicMode", !Core.AOS); + SuspendOnMurder = ServerConfiguration.GetSetting("stealing.suspendOnMurder", !Core.AOS); + CanStealContainers = ServerConfiguration.GetSetting("stealing.canStealContainers", !Core.AOS); + MaxWeightToSteal = ServerConfiguration.GetSetting("stealing.maxWeightToSteal", 10); + } - public static void Initialize() + public static void Initialize() + { + SkillInfo.Table[33].Callback = OnUse; + } + + public static bool IsInGuild(Mobile m) => m is PlayerMobile mobile && mobile.NpcGuild == NpcGuild.ThievesGuild; + + public static bool IsInnocentTo(Mobile from, Mobile to) => Notoriety.Compute(from, to) == Notoriety.Innocent; + + public static bool IsEmptyHanded(Mobile from) => + from.FindItemOnLayer(Layer.OneHanded) == null && from.FindItemOnLayer(Layer.TwoHanded) == null; + + public static TimeSpan OnUse(Mobile m) + { + if (!IsEmptyHanded(m)) { - SkillInfo.Table[33].Callback = OnUse; + m.SendLocalizedMessage(1005584); // Both hands must be free to steal. + } + else if (m.Region.IsPartOf()) + { + m.SendMessage("You may not steal in this area."); + } + else + { + m.Target = new StealingTarget(m); + m.RevealingAction(); + + m.SendLocalizedMessage(502698); // Which item do you want to steal? } - public static bool IsInGuild(Mobile m) => m is PlayerMobile mobile && mobile.NpcGuild == NpcGuild.ThievesGuild; + return TimeSpan.FromSeconds(30.0); + } - public static bool IsInnocentTo(Mobile from, Mobile to) => Notoriety.Compute(from, to) == Notoriety.Innocent; + private class StealingTarget : Target + { + private readonly Mobile _thief; - public static bool IsEmptyHanded(Mobile from) + public StealingTarget(Mobile thief) : base(1, false, TargetFlags.None) { - if (from.FindItemOnLayer(Layer.OneHanded) != null) - { - return false; - } - - if (from.FindItemOnLayer(Layer.TwoHanded) != null) - { - return false; - } - - return true; + _thief = thief; + AllowNonlocal = true; } - public static TimeSpan OnUse(Mobile m) + protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType) { - if (!IsEmptyHanded(m)) - { - m.SendLocalizedMessage(1005584); // Both hands must be free to steal. - } - else if (m.Region.IsPartOf()) - { - m.SendMessage("You may not steal in this area."); - } - else - { - m.Target = new StealingTarget(m); - m.RevealingAction(); - - m.SendLocalizedMessage(502698); // Which item do you want to steal? - } - - return TimeSpan.FromSeconds(10.0); + from.NextSkillTime = Core.TickCount; } - private class StealingTarget : Target + private Item TryStealItem(Item toSteal, ref bool caught) { - private readonly Mobile m_Thief; + Item stolen = null; - public StealingTarget(Mobile thief) : base(1, false, TargetFlags.None) + var root = toSteal.RootParent; + var mobRoot = root as Mobile; + var rootIsPlayer = mobRoot?.Player == true; + + var si = toSteal.Parent == null || !toSteal.Movable + ? StealableArtifacts.GetStealableInstance(toSteal) + : null; + + if (!IsEmptyHanded(_thief)) { - m_Thief = thief; - AllowNonlocal = true; + _thief.SendLocalizedMessage(1005584); // Both hands must be free to steal. } - - private Item TryStealItem(Item toSteal, ref bool caught) + else if (_thief.Region.IsPartOf()) { - Item stolen = null; + _thief.SendMessage("You may not steal in this area."); + } + else if ((_thief as PlayerMobile)?.Young == true && (rootIsPlayer || mobRoot is BaseCreature)) + { + _thief.SendLocalizedMessage(502700); // You cannot steal from people or monsters right now. Practice on chests and barrels. + } + else if (rootIsPlayer && !IsInGuild(_thief)) + { + _thief.SendLocalizedMessage(1005596); // You must be in the thieves guild to steal from other players. + } + else if (SuspendOnMurder && rootIsPlayer && IsInGuild(_thief) && _thief.Kills > 0) + { + _thief.SendLocalizedMessage(502706); // You are currently suspended from the thieves guild. + } + else if ((mobRoot as PlayerMobile)?.Young == true) + { + _thief.SendLocalizedMessage(502699); // You cannot steal from the Young. + } + else if ((root as BaseVendor)?.IsInvulnerable == true) + { + _thief.SendLocalizedMessage(1005598); // You can't steal from shopkeepers. + } + else if (root is PlayerVendor) + { + _thief.SendLocalizedMessage(502709); // You can't steal from vendors. + } + else if (!_thief.CanSee(toSteal)) + { + _thief.SendLocalizedMessage(500237); // Target can not be seen. + } + else if (toSteal is Sigil sig) + { + var pl = PlayerState.Find(_thief); + var faction = pl?.Faction; - var root = toSteal.RootParent; - var mobRoot = root as Mobile; - var rootIsPlayer = mobRoot?.Player == true; - - StealableArtifacts.StealableInstance si = toSteal.Parent == null || !toSteal.Movable - ? StealableArtifacts.GetStealableInstance(toSteal) - : null; - - if (!IsEmptyHanded(m_Thief)) + if (!_thief.InRange(sig.GetWorldLocation(), 1)) { - m_Thief.SendLocalizedMessage(1005584); // Both hands must be free to steal. + _thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it. } - else if (m_Thief.Region.IsPartOf()) + else if (root != null) // not on the ground { - m_Thief.SendMessage("You may not steal in this area."); + _thief.SendLocalizedMessage(502710); // You can't steal that! } - else if ((m_Thief as PlayerMobile)?.Young == true && (rootIsPlayer || mobRoot is BaseCreature)) + else if (faction == null) { - m_Thief.SendLocalizedMessage(502700); // You cannot steal from people or monsters right now. Practice on chests and barrels. + _thief.SendLocalizedMessage(1005588); // You must join a faction to do that } - else if (rootIsPlayer && !IsInGuild(m_Thief)) + else if (!_thief.CanBeginAction()) { - m_Thief.SendLocalizedMessage(1005596); // You must be in the thieves guild to steal from other players. + _thief.SendLocalizedMessage(1010581); // You cannot steal the sigil when you are incognito } - else if (SuspendOnMurder && rootIsPlayer && IsInGuild(m_Thief) && m_Thief.Kills > 0) + else if (DisguisePersistence.IsDisguised(_thief)) { - m_Thief.SendLocalizedMessage(502706); // You are currently suspended from the thieves guild. + _thief.SendLocalizedMessage(1010583); // You cannot steal the sigil while disguised } - else if ((mobRoot as PlayerMobile)?.Young == true) + else if (!_thief.CanBeginAction()) { - m_Thief.SendLocalizedMessage(502699); // You cannot steal from the Young. + _thief.SendLocalizedMessage(1010582); // You cannot steal the sigil while polymorphed } - else if ((root as BaseVendor)?.IsInvulnerable == true) + else if (TransformationSpellHelper.UnderTransformation(_thief)) { - m_Thief.SendLocalizedMessage(1005598); // You can't steal from shopkeepers. + _thief.SendLocalizedMessage(1061622); // You cannot steal the sigil while in that form. } - else if (root is PlayerVendor) + else if (AnimalForm.UnderTransformation(_thief)) { - m_Thief.SendLocalizedMessage(502709); // You can't steal from vendors. + _thief.SendLocalizedMessage(1063222); // You cannot steal the sigil while mimicking an animal. } - else if (!m_Thief.CanSee(toSteal)) + else if (pl.IsLeaving) { - m_Thief.SendLocalizedMessage(500237); // Target can not be seen. + // You are currently quitting a faction and cannot steal the town sigil + _thief.SendLocalizedMessage(1005589); } - else if (toSteal is Sigil sig) + else if (sig.IsBeingCorrupted && sig.LastMonolith.Faction == faction) { - var pl = PlayerState.Find(m_Thief); - var faction = pl?.Faction; - - if (!m_Thief.InRange(sig.GetWorldLocation(), 1)) - { - m_Thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it. - } - else if (root != null) // not on the ground - { - m_Thief.SendLocalizedMessage(502710); // You can't steal that! - } - else if (faction != null) - { - if (!m_Thief.CanBeginAction()) - { - m_Thief.SendLocalizedMessage(1010581); // You cannot steal the sigil when you are incognito - } - else if (DisguisePersistence.IsDisguised(m_Thief)) - { - m_Thief.SendLocalizedMessage(1010583); // You cannot steal the sigil while disguised - } - else if (!m_Thief.CanBeginAction()) - { - m_Thief.SendLocalizedMessage(1010582); // You cannot steal the sigil while polymorphed - } - else if (TransformationSpellHelper.UnderTransformation(m_Thief)) - { - m_Thief.SendLocalizedMessage(1061622); // You cannot steal the sigil while in that form. - } - else if (AnimalForm.UnderTransformation(m_Thief)) - { - m_Thief.SendLocalizedMessage(1063222); // You cannot steal the sigil while mimicking an animal. - } - else if (pl.IsLeaving) - { - // You are currently quitting a faction and cannot steal the town sigil - m_Thief.SendLocalizedMessage(1005589); - } - else if (sig.IsBeingCorrupted && sig.LastMonolith.Faction == faction) - { - m_Thief.SendLocalizedMessage(1005590); // You cannot steal your own sigil - } - else if (sig.IsPurifying) - { - m_Thief.SendLocalizedMessage(1005592); // You cannot steal this sigil until it has been purified - } - else if (m_Thief.CheckTargetSkill(SkillName.Stealing, toSteal, 80.0, 80.0)) - { - if (Sigil.ExistsOn(m_Thief)) - { - // The sigil has gone back to its home location because you already have a sigil. - m_Thief.SendLocalizedMessage(1010258); - } - else if (m_Thief.Backpack?.CheckHold(m_Thief, sig, false, true) != true) - { - // The sigil has gone home because your backpack is full - m_Thief.SendLocalizedMessage(1010259); - } - else - { - if (sig.IsBeingCorrupted) - { - sig.GraceStart = Core.Now; // begin grace period - } - - m_Thief.SendLocalizedMessage(1010586); // YOU STOLE THE SIGIL!!! (woah, calm down now) - - if (sig.LastMonolith?.Sigil != null) - { - sig.LastMonolith.Sigil = null; - sig.LastStolen = Core.Now; - } - - return sig; - } - } - else - { - m_Thief.SendLocalizedMessage(1005594); // You do not have enough skill to steal the sigil - } - } - else - { - m_Thief.SendLocalizedMessage(1005588); // You must join a faction to do that - } + _thief.SendLocalizedMessage(1005590); // You cannot steal your own sigil } - else if (m_Thief.Backpack?.CheckHold(m_Thief, toSteal, false, true) != true) + else if (sig.IsPurifying) { - m_Thief.SendLocalizedMessage(1048147); // Your backpack can't hold anything else. + _thief.SendLocalizedMessage(1005592); // You cannot steal this sigil until it has been purified } - else if (si == null && (toSteal.Parent == null || !toSteal.Movable)) + else if (!_thief.CheckTargetSkill(SkillName.Stealing, toSteal, 80.0, 80.0)) { - m_Thief.SendLocalizedMessage(502710); // You can't steal that! + _thief.SendLocalizedMessage(1005594); // You do not have enough skill to steal the sigil } - else if (toSteal.LootType == LootType.Newbied || toSteal.CheckBlessed(mobRoot)) + else if (Sigil.ExistsOn(_thief)) { - m_Thief.SendLocalizedMessage(502710); // You can't steal that! + // The sigil has gone back to its home location because you already have a sigil. + _thief.SendLocalizedMessage(1010258); } - else if (Core.AOS && si == null && toSteal is Container) + else if (_thief.Backpack?.CheckHold(_thief, sig, false, true) != true) { - m_Thief.SendLocalizedMessage(502710); // You can't steal that! - } - else if (!m_Thief.InRange(toSteal.GetWorldLocation(), 1)) - { - m_Thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it. - } - else if (si != null && m_Thief.Skills.Stealing.Value < 100.0) - { - // You're not skilled enough to attempt the theft of this item. - m_Thief.SendLocalizedMessage(1060025, "", 0x66D); - } - else if (toSteal.Parent is Mobile) - { - m_Thief.SendLocalizedMessage(1005585); // You cannot steal items which are equipped. - } - else if (root == m_Thief) - { - m_Thief.SendLocalizedMessage(502704); // You catch yourself red-handed. - } - else if (mobRoot?.AccessLevel > AccessLevel.Player) - { - m_Thief.SendLocalizedMessage(502710); // You can't steal that! - } - else if (mobRoot != null && !m_Thief.CanBeHarmful(mobRoot)) - { - } - else if (root is Corpse) - { - m_Thief.SendLocalizedMessage(502710); // You can't steal that! + // The sigil has gone home because your backpack is full + _thief.SendLocalizedMessage(1010259); } else { - var w = toSteal.Weight + toSteal.TotalWeight; - - if (w > MaxWeightToSteal) + if (sig.IsBeingCorrupted) { - // This item is too heavy to steal from someone's backpack. - m_Thief.SendLocalizedMessage(502722); + sig.GraceStart = Core.Now; // begin grace period } - else + + _thief.SendLocalizedMessage(1010586); // YOU STOLE THE SIGIL!!! (woah, calm down now) + + if (sig.LastMonolith?.Sigil != null) { - if (toSteal.Stackable && toSteal.Amount > 1) + sig.LastMonolith.Sigil = null; + sig.LastStolen = Core.Now; + } + + return sig; + } + } + else if (_thief.Backpack?.CheckHold(_thief, toSteal, false, true) != true) + { + _thief.SendLocalizedMessage(1048147); // Your backpack can't hold anything else. + } + else if (si == null && (toSteal.Parent == null || !toSteal.Movable) || toSteal.LootType == LootType.Newbied || + toSteal.CheckBlessed(mobRoot) || !CanStealContainers && si == null && toSteal is Container) + { + _thief.SendLocalizedMessage(502710); // You can't steal that! + } + else if (!_thief.InRange(toSteal.GetWorldLocation(), 1)) + { + _thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it. + } + else if (si != null && _thief.Skills.Stealing.Value < 100.0) + { + // You're not skilled enough to attempt the theft of this item. + _thief.SendLocalizedMessage(1060025, "", 0x66D); + } + else if (toSteal.Parent is Mobile) + { + _thief.SendLocalizedMessage(1005585); // You cannot steal items which are equipped. + } + else if (root == _thief) + { + _thief.SendLocalizedMessage(502704); // You catch yourself red-handed. + } + else if (mobRoot?.AccessLevel > AccessLevel.Player) + { + _thief.SendLocalizedMessage(502710); // You can't steal that! + } + else if (mobRoot != null && !_thief.CanBeHarmful(mobRoot)) + { + } + else if (root is Corpse) + { + _thief.SendLocalizedMessage(502710); // You can't steal that! + } + else + { + var w = toSteal.Weight + toSteal.TotalWeight; + + if (w > MaxWeightToSteal) + { + // This item is too heavy to steal from someone's backpack. + _thief.SendLocalizedMessage(502722); + } + else + { + if (toSteal.Stackable && toSteal.Amount > 1) + { + var maxAmount = Math.Clamp( + (int)(_thief.Skills.Stealing.Value / 10.0 / toSteal.Weight), + 1, + toSteal.Amount + ); + + var amount = Utility.RandomMinMax(1, maxAmount); + + if (amount >= toSteal.Amount) { - var maxAmount = Math.Clamp( - (int)(m_Thief.Skills.Stealing.Value / 10.0 / toSteal.Weight), - 1, - toSteal.Amount - ); + var pileWeight = (int)Math.Ceiling(toSteal.Weight * toSteal.Amount); + pileWeight *= 10; - var amount = Utility.RandomMinMax(1, maxAmount); - - if (amount >= toSteal.Amount) - { - var pileWeight = (int)Math.Ceiling(toSteal.Weight * toSteal.Amount); - pileWeight *= 10; - - if (m_Thief.CheckTargetSkill( + if (_thief.CheckTargetSkill( SkillName.Stealing, toSteal, pileWeight - 22.5, pileWeight + 27.5 )) - { - stolen = toSteal; - } - } - else - { - var pileWeight = (int)Math.Ceiling(toSteal.Weight * amount); - pileWeight *= 10; - - if (m_Thief.CheckTargetSkill( - SkillName.Stealing, - toSteal, - pileWeight - 22.5, - pileWeight + 27.5 - )) - { - stolen = Mobile.LiftItemDupe(toSteal, toSteal.Amount - amount) ?? toSteal; - } - } - } - else - { - var iw = (int)Math.Ceiling(w); - iw *= 10; - - if (m_Thief.CheckTargetSkill(SkillName.Stealing, toSteal, iw - 22.5, iw + 27.5)) { stolen = toSteal; } } - - if (stolen != null) - { - m_Thief.SendLocalizedMessage(502724); // You successfully steal the item. - - if (si != null) - { - toSteal.Movable = true; - si.Item = null; - } - } else { - m_Thief.SendLocalizedMessage(502723); // You fail to steal the item. - } + var pileWeight = (int)Math.Ceiling(toSteal.Weight * amount); + pileWeight *= 10; - caught = m_Thief.Skills.Stealing.Value < Utility.Random(150); - } - } - - return stolen; - } - - protected override void OnTarget(Mobile from, object target) - { - from.RevealingAction(); - - Item stolen = null; - IEntity root = null; - var caught = false; - - if (target is Item item) - { - root = item.RootParent; - stolen = TryStealItem(item, ref caught); - } - else if (target is Mobile mobile) - { - var pack = mobile.Backpack; - - if (pack?.Items.Count > 0) - { - root = mobile; - stolen = TryStealItem(pack.Items.RandomElement(), ref caught); - } - } - else - { - m_Thief.SendLocalizedMessage(502710); // You can't steal that! - } - - var mobRoot = root as Mobile; - - if (stolen != null) - { - from.AddToBackpack(stolen); - - if (!(stolen is Container || stolen.Stackable)) - { - StolenItem.Add(stolen, m_Thief, mobRoot); - } - } - - var corpse = root as Corpse; - - if (caught) - { - if (root == null || corpse?.IsCriminalAction(m_Thief) == true) - { - m_Thief.CriminalAction(false); - } - else if (mobRoot != null) - { - if (!IsInGuild(mobRoot) && IsInnocentTo(m_Thief, mobRoot)) - { - m_Thief.CriminalAction(false); - } - - var message = $"You notice {m_Thief.Name} trying to steal from {mobRoot.Name}."; - - foreach (var ns in m_Thief.GetClientsInRange(8)) - { - if (ns.Mobile != m_Thief) + if (_thief.CheckTargetSkill( + SkillName.Stealing, + toSteal, + pileWeight - 22.5, + pileWeight + 27.5 + )) { - ns.Mobile.SendMessage(message); + stolen = Mobile.LiftItemDupe(toSteal, toSteal.Amount - amount) ?? toSteal; } } } - } - else if (corpse?.IsCriminalAction(m_Thief) == true) - { - m_Thief.CriminalAction(false); - } - - if (mobRoot?.Player == true && m_Thief is PlayerMobile pm && - IsInnocentTo(pm, mobRoot) && !IsInGuild(mobRoot)) - { - pm.PermaFlags.Add(mobRoot); - pm.Delta(MobileDelta.Noto); - } - } - } - } - - public class StolenItem - { - public static readonly TimeSpan StealTime = TimeSpan.FromMinutes(2.0); - - private static readonly Queue m_Queue = new(); - - public StolenItem(Item stolen, Mobile thief, Mobile victim) - { - Stolen = stolen; - Thief = thief; - Victim = victim; - - Expires = Core.Now + StealTime; - } - - public Item Stolen { get; } - - public Mobile Thief { get; } - - public Mobile Victim { get; } - - public DateTime Expires { get; private set; } - - public bool IsExpired => Core.Now >= Expires; - - public static void Add(Item item, Mobile thief, Mobile victim) - { - Clean(); - - m_Queue.Enqueue(new StolenItem(item, thief, victim)); - } - - public static bool IsStolen(Item item) - { - Mobile victim = null; - - return IsStolen(item, ref victim); - } - - public static bool IsStolen(Item item, ref Mobile victim) - { - Clean(); - - foreach (var si in m_Queue) - { - if (si.Stolen == item && !si.IsExpired) - { - victim = si.Victim; - return true; - } - } - - return false; - } - - [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))] - public static void ReturnOnDeath(Mobile killed) - { - Clean(); - - var corpse = killed.Corpse; - - foreach (var si in m_Queue) - { - if (si.Stolen.RootParent == corpse && si.Victim != null && !si.IsExpired) - { - if (si.Victim.AddToBackpack(si.Stolen)) - { - si.Victim.SendLocalizedMessage(1010464); // the item that was stolen is returned to you. - } else { - si.Victim.SendLocalizedMessage(1010463); // the item that was stolen from you falls to the ground. + var iw = (int)Math.Ceiling(w); + iw *= 10; + + if (_thief.CheckTargetSkill(SkillName.Stealing, toSteal, iw - 22.5, iw + 27.5)) + { + stolen = toSteal; + } } - si.Expires = Core.Now; // such a hack + if (stolen != null) + { + _thief.SendLocalizedMessage(502724); // You successfully steal the item. + + if (si != null) + { + toSteal.Movable = true; + si.Item = null; + } + } + else + { + _thief.SendLocalizedMessage(502723); // You fail to steal the item. + } + + caught = _thief.Skills.Stealing.Value < Utility.Random(150); } } + + return stolen; } - public static void Clean() + protected override void OnTarget(Mobile from, object target) { - while (m_Queue.Count > 0) - { - var si = m_Queue.Peek(); + from.RevealingAction(); - if (si.IsExpired) + Item stolen = null; + IEntity root = null; + var caught = false; + + if (target is Item item) + { + root = item.RootParent; + stolen = TryStealItem(item, ref caught); + } + else if (target is Mobile mobile) + { + var pack = mobile.Backpack; + + if (pack?.Items.Count > 0) { - m_Queue.Dequeue(); - } - else - { - break; + root = mobile; + stolen = TryStealItem(pack.Items.RandomElement(), ref caught); } } + else + { + _thief.SendLocalizedMessage(502710); // You can't steal that! + } + + var mobRoot = root as Mobile; + + if (stolen != null) + { + from.AddToBackpack(stolen); + + if (!(stolen is Container || stolen.Stackable)) + { + StolenItem.Add(stolen, _thief, mobRoot); + } + } + + var corpse = root as Corpse; + + if (caught) + { + if (root == null || corpse?.IsCriminalAction(_thief) == true) + { + _thief.CriminalAction(false); + } + else if (mobRoot != null) + { + if (!IsInGuild(mobRoot) && IsInnocentTo(_thief, mobRoot)) + { + _thief.CriminalAction(false); + } + + var message = $"You notice {_thief.Name} trying to steal from {mobRoot.Name}."; + + foreach (var ns in _thief.GetClientsInRange(8)) + { + if (ns.Mobile != _thief) + { + ns.Mobile.SendMessage(message); + } + } + } + } + else if (corpse?.IsCriminalAction(_thief) == true) + { + _thief.CriminalAction(false); + } + + if (mobRoot?.Player == true && _thief is PlayerMobile pm && + IsInnocentTo(pm, mobRoot) && !IsInGuild(mobRoot)) + { + pm.PermaFlags.Add(mobRoot); + pm.Delta(MobileDelta.Noto); + } + + from.NextSkillTime = Core.TickCount + 10000; // 10 seconds cooldown + } + } +} + +public class StolenItem +{ + public static readonly TimeSpan StealTime = TimeSpan.FromMinutes(2.0); + + private static readonly Queue _queue = []; + + public StolenItem(Item stolen, Mobile thief, Mobile victim) + { + Stolen = stolen; + Thief = thief; + Victim = victim; + + Expires = Core.Now + StealTime; + } + + public Item Stolen { get; } + + public Mobile Thief { get; } + + public Mobile Victim { get; } + + public DateTime Expires { get; private set; } + + public bool IsExpired => Core.Now >= Expires; + + public static void Add(Item item, Mobile thief, Mobile victim) + { + Clean(); + + _queue.Enqueue(new StolenItem(item, thief, victim)); + } + + public static bool IsStolen(Item item) + { + Mobile victim = null; + + return IsStolen(item, ref victim); + } + + public static bool IsStolen(Item item, ref Mobile victim) + { + Clean(); + + foreach (var si in _queue) + { + if (si.Stolen == item && !si.IsExpired) + { + victim = si.Victim; + return true; + } + } + + return false; + } + + [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))] + public static void ReturnOnDeath(Mobile killed) + { + Clean(); + + var corpse = killed.Corpse; + + foreach (var si in _queue) + { + if (si.Stolen.RootParent != corpse || si.Victim == null || si.IsExpired) + { + continue; + } + + if (si.Victim.AddToBackpack(si.Stolen)) + { + si.Victim.SendLocalizedMessage(1010464); // the item that was stolen is returned to you. + } + else + { + si.Victim.SendLocalizedMessage(1010463); // the item that was stolen from you falls to the ground. + } + + si.Expires = Core.Now; // such a hack + } + } + + public static void Clean() + { + while (_queue.Count > 0) + { + var si = _queue.Peek(); + + if (!si.IsExpired) + { + break; + } + + _queue.Dequeue(); } } } diff --git a/Projects/UOContent/Spells/Fourth/Curse.cs b/Projects/UOContent/Spells/Fourth/Curse.cs index 2f70789ea..b85337e96 100644 --- a/Projects/UOContent/Spells/Fourth/Curse.cs +++ b/Projects/UOContent/Spells/Fourth/Curse.cs @@ -54,7 +54,7 @@ namespace Server.Spells.Fourth var oldDexOffset = SpellHelper.GetCurse(caster, m, StatType.Dex); var oldIntOffset = SpellHelper.GetCurse(caster, m, StatType.Int); - if (oldStrOffset <= newStrOffset && oldDexOffset <= newDexOffset && oldIntOffset <= newIntOffset) + if (oldStrOffset > newStrOffset && oldDexOffset > newDexOffset && oldIntOffset > newIntOffset) { return false; } diff --git a/README.md b/README.md index c04c17279..2184b2181 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,6 @@ Thank you for supporting us! You can find out how by visiting the [sponsors](./S - [Karasho](https://github.com/andreakarasho), [Jaedan](https://github.com/jaedan) and the ClassicUO Community

-

Development Tools & Plugins provided with ♥ by
JetBrains +

Development Tools & Plugins provided with ♥ by
JetBrains
Material Theme

diff --git a/SPONSORS.md b/SPONSORS.md index 315169097..4264670ca 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -3,6 +3,7 @@ Thank you to all of our generous sponsors that make ModernUO possible. **A special thank you to the following sponsors for their considerable contributions:** +* [Age of Shadows](https://ageofshadows.gg) * [UO Outlands](https://uooutlands.com) * Prayer ([MagnUm-Opus](https://discord.gg/CzDEq3vv2N)) @@ -10,7 +11,7 @@ Thank you to all of our generous sponsors that make ModernUO possible. We greatly appreciate the support! Use one of the following platforms below: #### GitHub -[Github Sponsors | ModernUO](https://github.com/sponsors/modernuo) +[Github Sponsors | ModernUO](https://github.com/sponsors/modernuo) #### Patreon [Patreon | ModernUO](https://muo.gg/patreon)