diff --git a/Projects/Server/Serialization/GenericEntityPersistence.cs b/Projects/Server/Serialization/GenericEntityPersistence.cs index 54617198f..e9ef932c1 100644 --- a/Projects/Server/Serialization/GenericEntityPersistence.cs +++ b/Projects/Server/Serialization/GenericEntityPersistence.cs @@ -375,7 +375,6 @@ public class GenericEntityPersistence : GenericPersistence, IGenericEntityPer // Skip this entry if (t == null) { - dataReader.Seek(entry.Length, SeekOrigin.Current); continue; } @@ -383,7 +382,9 @@ public class GenericEntityPersistence : GenericPersistence, IGenericEntityPer try { - var pos = dataReader.Position; + dataReader.Seek(entry.Position, SeekOrigin.Begin); + var pos = entry.Position; + t.Deserialize(dataReader); var lengthDeserialized = dataReader.Position - pos; diff --git a/Projects/Server/Text/StringHelpers.cs b/Projects/Server/Text/StringHelpers.cs index 330458727..2b9349f6c 100644 --- a/Projects/Server/Text/StringHelpers.cs +++ b/Projects/Server/Text/StringHelpers.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Server.Buffers; +using Server.Text; namespace Server; @@ -286,4 +287,18 @@ public static class StringHelpers str.CopyTo(chars); return chars; } + + public static void AppendSpaceWithArticle(this ref ValueStringBuilder builder, string text, bool articleAn) + { + if (builder.Length != 0) + { + builder.Append(' '); + } + else + { + builder.Append(articleAn ? "an " : "a "); + } + + builder.Append(text); + } } diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs index ac7da1abe..73519eb04 100644 --- a/Projects/UOContent/Accounting/Account.cs +++ b/Projects/UOContent/Accounting/Account.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Net; using System.Runtime.CompilerServices; using System.Xml; +using ModernUO.CodeGeneratedEvents; using ModernUO.Serialization; using Server.Accounting.Security; using Server.Misc; @@ -768,31 +769,24 @@ public partial class Account : IAccount, IComparable acc.TotalGameTime += Core.Now - pm.SessionStart; } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - if (m is not PlayerMobile pm) + if (pm.Account is not Account acc || !pm.Young || !acc.Young) { return; } - if (m.Account is not Account acc) + var ts = YoungDuration - acc.TotalGameTime; + var hours = Math.Max((int)ts.TotalHours, 0); + + if (hours == 1) { - return; + pm.SendAsciiMessage($"You will enjoy the benefits and relatively safe status of a young player for {hours} more hour."); } - - if (pm.Young && acc.Young) + else { - var ts = YoungDuration - acc.TotalGameTime; - var hours = Math.Max((int)ts.TotalHours, 0); - - if (hours == 1) - { - m.SendAsciiMessage($"You will enjoy the benefits and relatively safe status of a young player for {hours} more hour."); - } - else - { - m.SendAsciiMessage($"You will enjoy the benefits and relatively safe status of a young player for {hours} more hours."); - } + pm.SendAsciiMessage($"You will enjoy the benefits and relatively safe status of a young player for {hours} more hours."); } } diff --git a/Projects/UOContent/Assistants/AssistantHandler.cs b/Projects/UOContent/Assistants/AssistantHandler.cs index 55cf0455d..a42e50224 100644 --- a/Projects/UOContent/Assistants/AssistantHandler.cs +++ b/Projects/UOContent/Assistants/AssistantHandler.cs @@ -1,7 +1,9 @@ using System; using System.Buffers; using System.Collections.Generic; +using ModernUO.CodeGeneratedEvents; using Server.Gumps; +using Server.Mobiles; using Server.Network; namespace Server.Assistants; @@ -45,22 +47,23 @@ public static class AssistantHandler m.NetState.LogInfo("Failed to negotiate assistant features."); } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - if (m?.NetState?.Running != true || !Enabled) + if (pm?.NetState?.Running != true || !Enabled) { return; } - m.NetState.SendAssistVersionReq(); - m.NetState.SendAssistHandshake(); + pm.NetState.SendAssistVersionReq(); + pm.NetState.SendAssistHandshake(); - if (_handshakes.TryGetValue(m, out var t)) + if (_handshakes.TryGetValue(pm, out var t)) { t?.Stop(); } - _handshakes[m] = Timer.DelayCall(TimeSpan.FromSeconds(30), OnTimeout, m); + _handshakes[pm] = Timer.DelayCall(TimeSpan.FromSeconds(30), OnTimeout, pm); } public static void AssistVersion(NetState state, SpanReader reader) diff --git a/Projects/UOContent/Commands/VisibilityList.cs b/Projects/UOContent/Commands/VisibilityList.cs index efc9f010f..0026398bb 100644 --- a/Projects/UOContent/Commands/VisibilityList.cs +++ b/Projects/UOContent/Commands/VisibilityList.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using ModernUO.CodeGeneratedEvents; using Server.Mobiles; using Server.Network; using Server.Targeting; @@ -15,10 +16,8 @@ namespace Server.Commands CommandSystem.Register("VisClear", AccessLevel.Counselor, VisClear_OnCommand); } - public static void OnLogin(Mobile m) - { - (m as PlayerMobile)?.VisibilityList.Clear(); - } + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) => pm.VisibilityList.Clear(); [Usage("Vis")] [Description("Adds or removes a targeted player from your visibility list. Anyone on your visibility list will be able to see you at all times, even when you're hidden.")] diff --git a/Projects/UOContent/Engines/ConPVP/DuelContext.cs b/Projects/UOContent/Engines/ConPVP/DuelContext.cs index 6004b6ba3..15fd66b07 100644 --- a/Projects/UOContent/Engines/ConPVP/DuelContext.cs +++ b/Projects/UOContent/Engines/ConPVP/DuelContext.cs @@ -1291,13 +1291,9 @@ namespace Server.Engines.ConPVP return false; } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - if (m is not PlayerMobile pm) - { - return; - } - var dc = pm.DuelContext; if (dc == null) diff --git a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleRegions.cs b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleRegions.cs index 4f16460d1..6c983e0e9 100644 --- a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleRegions.cs +++ b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleRegions.cs @@ -1,3 +1,4 @@ +using ModernUO.CodeGeneratedEvents; using Server.Mobiles; using Server.Regions; @@ -14,12 +15,13 @@ namespace Server.Engines.Doom Register(); } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { var rect = LeverPuzzleController.lr_Rect; - if (m.X >= rect.X && m.X <= rect.X + 10 && m.Y >= rect.Y && m.Y <= rect.Y + 10 && m.Map == Map.Internal) + if (pm.X >= rect.X && pm.X <= rect.X + 10 && pm.Y >= rect.Y && pm.Y <= rect.Y + 10 && pm.Map == Map.Internal) { - Timer kick = new LeverPuzzleController.LampRoomKickTimer(m); + Timer kick = new LeverPuzzleController.LampRoomKickTimer(pm); kick.Start(); } } diff --git a/Projects/UOContent/Engines/Factions/Core/Faction.cs b/Projects/UOContent/Engines/Factions/Core/Faction.cs index a7171c0c2..7368ebe05 100644 --- a/Projects/UOContent/Engines/Factions/Core/Faction.cs +++ b/Projects/UOContent/Engines/Factions/Core/Faction.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using ModernUO.CodeGeneratedEvents; using Server.Accounting; using Server.Commands.Generic; using Server.Engines.ConPVP; @@ -1259,7 +1261,9 @@ public abstract class Faction : IComparable } } - public static void OnLogin(Mobile m) => CheckLeaveTimer(m); + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void OnLogin(PlayerMobile pm) => CheckLeaveTimer(pm); public static void WriteReference(IGenericWriter writer, Faction fact) { diff --git a/Projects/UOContent/Engines/Help/HelpEvents.cs b/Projects/UOContent/Engines/Help/HelpEvents.cs new file mode 100644 index 000000000..e1738ebfd --- /dev/null +++ b/Projects/UOContent/Engines/Help/HelpEvents.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Server.Engines.Help; + +public static class HelpEvents +{ + public static event Action PageEnqueued; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokePageEnqueued(PageEntry e) => PageEnqueued?.Invoke(e); + + public static event Action PageRemoved; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokePageRemoved(PageEntry e) => PageRemoved?.Invoke(e); + + public static event Action PageHandlerChanged; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokePageHandlerChanged(Mobile old, Mobile value, PageEntry e) => PageHandlerChanged?.Invoke(old, value, e); + + public static event Action PageWaiting; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokePageWaiting(PageEntry e) => PageWaiting?.Invoke(e); + + public static event Action StuckMenu; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeStuckMenu(Mobile m) => StuckMenu?.Invoke(m); +} diff --git a/Projects/UOContent/Engines/Help/PageQueue.cs b/Projects/UOContent/Engines/Help/PageQueue.cs index eac51bf81..ea0c418cd 100644 --- a/Projects/UOContent/Engines/Help/PageQueue.cs +++ b/Projects/UOContent/Engines/Help/PageQueue.cs @@ -103,6 +103,8 @@ namespace Server.Engines.Help { m_Entry.Handler = null; } + + HelpEvents.InvokePageWaiting(m_Entry); } else { @@ -171,6 +173,13 @@ namespace Server.Engines.Help { m_KeyedByHandler[value] = entry; } + + if (old == null || value == null) + { + return; + } + + HelpEvents.InvokePageHandlerChanged(old, value, entry); } [Usage("Pages"), Description("Opens the page queue menu.")] @@ -212,6 +221,8 @@ namespace Server.Engines.Help { m_KeyedByHandler.Remove(e.Handler); } + + HelpEvents.InvokePageRemoved(e); } public static PageEntry GetEntry(Mobile sender) @@ -258,6 +269,8 @@ namespace Server.Engines.Help { Email.SendQueueEmail(entry, GetPageTypeName(entry.Type)); } + + HelpEvents.InvokePageEnqueued(entry); } } } diff --git a/Projects/UOContent/Engines/Help/StuckMenu.cs b/Projects/UOContent/Engines/Help/StuckMenu.cs index f6cab4d90..4fc57aed2 100644 --- a/Projects/UOContent/Engines/Help/StuckMenu.cs +++ b/Projects/UOContent/Engines/Help/StuckMenu.cs @@ -1,4 +1,5 @@ using System; +using Server.Engines.Help; using Server.Factions; using Server.Gumps; using Server.Mobiles; @@ -194,16 +195,16 @@ namespace Server.Menus.Questions m_Mobile.SendLocalizedMessage(1010588); // You choose not to go to any city. } } - else - { - var index = info.ButtonID - 1; - var entries = IsInSecondAgeArea(m_Mobile) ? m_T2AEntries : m_Entries; - if (index >= 0 && index < entries.Length) - { - Teleport(entries[index]); - } + var index = info.ButtonID - 1; + var entries = IsInSecondAgeArea(m_Mobile) ? m_T2AEntries : m_Entries; + + if (index >= 0 && index < entries.Length) + { + Teleport(entries[index]); } + + HelpEvents.InvokeStuckMenu(m_Mobile); } private void Teleport(StuckMenuEntry entry) diff --git a/Projects/UOContent/Engines/Party/Party.cs b/Projects/UOContent/Engines/Party/Party.cs index e474885c8..d20d80948 100644 --- a/Projects/UOContent/Engines/Party/Party.cs +++ b/Projects/UOContent/Engines/Party/Party.cs @@ -160,7 +160,8 @@ namespace Server.Engines.PartySystem } } - public static void OnLogin(Mobile from) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile from) { var p = Get(from); diff --git a/Projects/UOContent/Engines/Plants/PlantSystem.cs b/Projects/UOContent/Engines/Plants/PlantSystem.cs index 3579d31a5..61d017c0f 100644 --- a/Projects/UOContent/Engines/Plants/PlantSystem.cs +++ b/Projects/UOContent/Engines/Plants/PlantSystem.cs @@ -1,7 +1,9 @@ using System; +using ModernUO.CodeGeneratedEvents; using ModernUO.Serialization; using Server.Items; using Server.Misc; +using Server.Mobiles; namespace Server.Engines.Plants { @@ -432,7 +434,8 @@ namespace Server.Engines.Plants } } - public static void OnLogin(Mobile from) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile from) { Container cont = from.Backpack; if (cont != null) diff --git a/Projects/UOContent/Engines/Player Murder System/PlayerMurderSystem.cs b/Projects/UOContent/Engines/Player Murder System/PlayerMurderSystem.cs index 24ecd4543..8a654a697 100644 --- a/Projects/UOContent/Engines/Player Murder System/PlayerMurderSystem.cs +++ b/Projects/UOContent/Engines/Player Murder System/PlayerMurderSystem.cs @@ -80,9 +80,10 @@ public class PlayerMurderSystem : GenericPersistence UpdateMurderContext(context); } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - if (m is not PlayerMobile pm || !GetMurderContext(pm, out var context)) + if (!GetMurderContext(pm, out var context)) { return; } diff --git a/Projects/UOContent/Engines/Veteran Rewards/RewardSystem.cs b/Projects/UOContent/Engines/Veteran Rewards/RewardSystem.cs index 6d486f7fe..7b802f4a3 100644 --- a/Projects/UOContent/Engines/Veteran Rewards/RewardSystem.cs +++ b/Projects/UOContent/Engines/Veteran Rewards/RewardSystem.cs @@ -1,4 +1,5 @@ using System; +using ModernUO.CodeGeneratedEvents; using Server.Accounting; using Server.Gumps; using Server.Items; @@ -562,35 +563,31 @@ namespace Server.Engines.VeteranRewards RewardInterval = ServerConfiguration.GetOrUpdateSetting("vetRewards.rewardInterval", TimeSpan.FromDays(30.0)); } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - if (!Enabled) + if (!Enabled || !pm.Alive) { return; } - if (!m.Alive) - { - return; - } + ComputeRewardInfo(pm, out var cur, out var max, out var level); - ComputeRewardInfo(m, out var cur, out var max, out var level); - - if (m.SkillsCap is 7000 or 7050 or 7100 or 7150 or 7200) + if (pm.SkillsCap is 7000 or 7050 or 7100 or 7150 or 7200) { level = Math.Clamp(level, 0, 4); if (SkillCapRewards) { - m.SkillsCap = 7000 + level * 50; + pm.SkillsCap = 7000 + level * 50; } else { - m.SkillsCap = 7000; + pm.SkillsCap = 7000; } } - if (Core.ML && m is PlayerMobile pm && !pm.HasStatReward && HasHalfLevel(pm)) + if (Core.ML && !pm.HasStatReward && HasHalfLevel(pm)) { pm.HasStatReward = true; pm.StatCap += 5; @@ -598,7 +595,7 @@ namespace Server.Engines.VeteranRewards if (cur < max) { - m.SendGump(new RewardNoticeGump(m)); + pm.SendGump(new RewardNoticeGump(pm)); } } } diff --git a/Projects/UOContent/Gumps/AddDoorGump.cs b/Projects/UOContent/Gumps/AddDoorGump.cs index f7bf0d957..27a7a6861 100644 --- a/Projects/UOContent/Gumps/AddDoorGump.cs +++ b/Projects/UOContent/Gumps/AddDoorGump.cs @@ -52,10 +52,9 @@ public class AddDoorGump : DynamicGump ] ]; - private static int _maxCount; - private Type _type; private int _baseId; + private int _page; public AddDoorGump() : base(50, 40) { @@ -72,66 +71,38 @@ public class AddDoorGump : DynamicGump AddBlueBack(ref builder, 155, 174); builder.AddItem(25, 24, _baseId); - builder.AddButton(26, 37, 0x5782, 0x5782, 1); + builder.AddButton(26, 37, 0x5782, 0x5782, 10); builder.AddItem(47, 45, _baseId + 2); - builder.AddButton(43, 57, 0x5783, 0x5783, 2); + builder.AddButton(43, 57, 0x5783, 0x5783, 11); builder.AddItem(87, 22, _baseId + 10); - builder.AddButton(116, 35, 0x5785, 0x5785, 6); + builder.AddButton(116, 35, 0x5785, 0x5785, 15); builder.AddItem(65, 45, _baseId + 8); - builder.AddButton(96, 55, 0x5784, 0x5784, 5); + builder.AddButton(96, 55, 0x5784, 0x5784, 14); - builder.AddButton(73, 36, 0x2716, 0x2716, 9); + builder.AddButton(73, 36, 0x2716, 0x2716, 18); } else { var pages = _types.Length; - if (_maxCount == 0) - { - for (var i = 0; i < pages; i++) - { - _maxCount = Math.Max(_maxCount, _types[i].Length); - } - } + var types = _types[_page]; - AddBlueBack(ref builder, 20 + (_maxCount + 1) * 50, 165); + AddBlueBack(ref builder, 20 + (types.Length + 1) * 50, 165); builder.AddHtmlLocalized(30, 45, 60, 20, 1043353, 0x7FFF); // Next builder.AddHtmlLocalized(30, 85, 60, 20, 1011393, 0x7FFF); // Back - for (var i = 0; i < pages; ++i) + builder.AddButton(30, 60, 0xFA5, 0xFA7, 1); + builder.AddButton(30, 100, 0xFAE, 0xFB0, 2); + + for (var i = 0; i < types.Length; i++) { - var page = i + 1; + var x = (i + 1) * 50; - builder.AddPage(page); - - if (page < pages) - { - builder.AddButton(30, 60, 0xFA5, 0xFA7, 0, GumpButtonType.Page, page + 1); - } - else - { - builder.AddButton(30, 60, 0xFA5, 0xFA7, 0, GumpButtonType.Page, 1); - } - - if (page > 1) - { - builder.AddButton(30, 100, 0xFAE, 0xFB0, 0, GumpButtonType.Page, page - 1); - } - else - { - builder.AddButton(30, 100, 0xFAE, 0xFB0, 0, GumpButtonType.Page, pages); - } - - for (var j = 0; j < _types[i].Length; j++) - { - var x = (j + 1) * 50; - - builder.AddButton(30 + x, 20, 0x2624, 0x2625, i * _maxCount + j + 1); - builder.AddItem(15 + x, 30, _types[i][j].BaseID); - } + builder.AddButton(30 + x, 20, 0x2624, 0x2625, _page * types.Length + i + 10); + builder.AddItem(15 + x, 30, types[i].BaseID); } } } @@ -147,35 +118,64 @@ public class AddDoorGump : DynamicGump public override void OnResponse(NetState sender, in RelayInfo info) { var from = sender.Mobile; - var button = info.ButtonID - 1; + var button = info.ButtonID; - if (_type == null) + if (button == 0) { - if (button < 0 || button >= _types.Length) + if (_type != null) + { + _type = null; + _baseId = 0; + } + else { return; } - - var page = Math.DivRem(button, _maxCount, out var pos); - var door = _types[page][pos]; - _type = door.Type; - _baseId = door.BaseID; } - else if (button is >= 0 and < 8) + else if (button == 1) // Next { - CommandSystem.Handle( - from, - $"{CommandSystem.Prefix}Add {_type.Name} {(DoorFacing)button}" - ); + _page++; + if (_page >= _types.Length) + { + _page = 0; + } } - else if (button == 8) + else if (button == 2) // Prev { - CommandSystem.Handle(from, $"{CommandSystem.Prefix}Link"); + _page--; + if (_page < 0) + { + _page = _types.Length - 1; + } } else { - _type = null; - _baseId = 0; + button -= 10; + if (_type == null) + { + var types = _types[_page]; + var page = Math.DivRem(button, types.Length, out var pos); + + if (page < 0 || pos >= types.Length) + { + return; + } + + var door = types[pos]; + _type = door.Type; + _baseId = door.BaseID; + } + else if (button is >= 0 and < 8) + { + CommandSystem.Handle( + from, + $"{CommandSystem.Prefix}Add {_type.Name} {(DoorFacing)button}" + ); + } + else if (button == 8) + { + CommandSystem.Handle(from, $"{CommandSystem.Prefix}Link"); + } } from.SendGump(this); diff --git a/Projects/UOContent/Gumps/AdminGump.cs b/Projects/UOContent/Gumps/AdminGump.cs index 43d6c1bef..816d1967b 100644 --- a/Projects/UOContent/Gumps/AdminGump.cs +++ b/Projects/UOContent/Gumps/AdminGump.cs @@ -12,6 +12,7 @@ using Server.Misc; using Server.Multis; using Server.Network; using Server.Prompts; +using Server.Saves; using Server.Text; namespace Server.Gumps @@ -295,6 +296,9 @@ namespace Server.Gumps AddButtonLabeled(20, 230, GetButtonID(3, 203), "Shutdown & Restart (With Save)"); AddButtonLabeled(20, 250, GetButtonID(3, 204), "Shutdown & Restart (Without Save)"); + + AddButtonLabeled(20, 270, GetButtonID(3, 205), "Shutdown (With 15m Delay & Save)"); + /*} else { @@ -2147,6 +2151,12 @@ namespace Server.Gumps Shutdown(true, false); break; } + case 205: // shutdown with delay and save + { + var t = new ShutdownTimer(this); + t.Start(); + break; + } case 210: case 211: { @@ -3927,32 +3937,32 @@ namespace Server.Gumps var availableMaps = ExpansionInfo.CoreExpansion.MapSelectionFlags; if (Core.SA && availableMaps.Includes(MapSelectionFlags.TerMur)) { - InvokeCommand("GenerateSpawners Data/Spawns/post-uoml/termur/*.json"); + InvokeCommand("GenerateSpawners Data/Spawns/post-uoml/termur/**.json"); } if (availableMaps.Includes(MapSelectionFlags.Malas)) { - InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/malas/*.json"); + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/malas/**.json"); } if (availableMaps.Includes(MapSelectionFlags.Tokuno)) { - InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/tokuno/*.json"); + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/tokuno/**.json"); } if (availableMaps.Includes(MapSelectionFlags.Ilshenar)) { - InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/ilshenar/*.json"); + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/ilshenar/**.json"); } if (availableMaps.Includes(MapSelectionFlags.Trammel)) { - InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/trammel/*.json"); + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/trammel/**.json"); } if (availableMaps.Includes(MapSelectionFlags.Felucca)) { - InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/felucca/*.json"); + InvokeCommand($"GenerateSpawners Data/Spawns/{folder}/felucca/**.json"); } } @@ -4251,5 +4261,30 @@ namespace Server.Gumps public AdminNoticeGump(string content, Action callback) : base(callback) => Content = content; } + + public class ShutdownTimer : Timer + { + private readonly AdminGump _adminGump; + + public ShutdownTimer(AdminGump gump) : base(TimeSpan.Zero, TimeSpan.Zero, 8) => + _adminGump = gump; + + protected override void OnTick() + { + if (Index >= 7) + { + AutoSave.SavesEnabled = false; + _adminGump.Shutdown(false, true); + return; + } + + ReadOnlySpan times = [15, 10, 5, 4, 3, 2, 1, 0]; + var time = times[Index]; + _adminGump.m_From.SendMessage( + $"The shard will shutdown in {time} minute{(time == 1 ? "s" : "")} for maintenance." + ); + Interval = TimeSpan.FromMinutes(time - times[Index + 1]); + } + } } } diff --git a/Projects/UOContent/Items/Armor/BaseArmor.cs b/Projects/UOContent/Items/Armor/BaseArmor.cs index 7c5e45446..560c5ea52 100644 --- a/Projects/UOContent/Items/Armor/BaseArmor.cs +++ b/Projects/UOContent/Items/Armor/BaseArmor.cs @@ -1,17 +1,20 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using ModernUO.Serialization; using Server.Engines.Craft; using Server.Ethics; using Server.Factions; using Server.Network; +using Server.Text; using AMA = Server.Items.ArmorMeditationAllowance; using AMT = Server.Items.ArmorMaterialType; namespace Server.Items { [SerializationGenerator(9, false)] - public abstract partial class BaseArmor : Item, IScissorable, IFactionItem, ICraftable, IWearableDurability, IAosItem + public abstract partial class BaseArmor + : Item, IScissorable, IFactionItem, ICraftable, IWearableDurability, IAosItem, IIdentifiable { [SerializedIgnoreDupe] [SerializableField(0, setter: "private")] @@ -101,7 +104,7 @@ namespace Server.Items private string _crafter; [SerializableFieldSaveFlag(10)] - private bool ShouldSerializeCrafter() => _crafter != null; + private bool ShouldSerializeCrafter() => !string.IsNullOrEmpty(_crafter); [SerializableFieldSaveFlag(14)] private bool ShouldSerializeResource() => _resource != DefaultResource; @@ -559,6 +562,7 @@ namespace Server.Items Resource = CraftResources.GetFromType(resourceType); PlayerConstructed = true; + Identified = true; var context = craftSystem.GetContext(from); @@ -1451,6 +1455,12 @@ namespace Server.Items public override void OnSingleClick(Mobile from) { + if (!Core.UOTD) + { + OnSingleClickPreUOTD(from); + return; + } + var attrs = new List(); if (DisplayLootType) @@ -1482,13 +1492,12 @@ namespace Server.Items attrs.Add(new EquipInfoAttribute(1038000 + (int)_durability)); } - if (_protectionLevel > ArmorProtectionLevel.Regular && _protectionLevel <= ArmorProtectionLevel.Invulnerability) + if (_protectionLevel != ArmorProtectionLevel.Regular) { attrs.Add(new EquipInfoAttribute(1038005 + (int)_protectionLevel)); } } - else if (_durability != ArmorDurabilityLevel.Regular || _protectionLevel > ArmorProtectionLevel.Regular && - _protectionLevel <= ArmorProtectionLevel.Invulnerability) + else if (_durability != ArmorDurabilityLevel.Regular || _protectionLevel != ArmorProtectionLevel.Regular) { attrs.Add(new EquipInfoAttribute(1038000)); // Unidentified } @@ -1513,6 +1522,100 @@ namespace Server.Items from.NetState.SendDisplayEquipmentInfo(Serial, number, _crafter, false, attrs); } + public void OnSingleClickPreUOTD(Mobile from) + { + var isMagicItem = _durability != ArmorDurabilityLevel.Regular || + _protectionLevel != ArmorProtectionLevel.Regular; + + if (isMagicItem && !_identified) + { + LabelTo(from, $"an unidentified {Name ?? Localization.GetText(LabelNumber).ToLowerInvariant()}"); + return; + } + + var name = Name; + var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0; + + if (isMagicItem) + { + var builder = ValueStringBuilder.Create(128); + + var durabilityText = DurabilityText(out var articleAnDurability); + if (durabilityText != null) + { + builder.AppendSpaceWithArticle(durabilityText, articleAnDurability); + } + + if (name == null) + { + builder.AppendSpaceWithArticle(Localization.GetText(LabelNumber).ToLowerInvariant(), articleAnName); + } + else if (builder.Length != 0) + { + builder.Append($" {name}"); + } + else + { + builder.Append(name); + } + + var protectionText = ProtectionText; + if (protectionText != null) + { + builder.Append($" of {protectionText}"); + } + + LabelTo(from, builder.ToString()); + builder.Dispose(); + return; + } + + name ??= $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}"; + + if (Crafter == null) + { + LabelTo(from, Quality == ArmorQuality.Exceptional ? $"{name} of exceptional quality" : name); + return; + } + + LabelTo( + from, + Quality == ArmorQuality.Exceptional + ? $"{name} crafted with exceptional quality by {Crafter}" + : $"{name} crafted by {Crafter}" + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string DurabilityText(out bool articleAn) + { + articleAn = _durability is ArmorDurabilityLevel.Indestructible; + return _durability switch + { + ArmorDurabilityLevel.Durable => "durable", + ArmorDurabilityLevel.Substantial => "substantial", + ArmorDurabilityLevel.Massive => "massive", + ArmorDurabilityLevel.Fortified => "fortified", + ArmorDurabilityLevel.Indestructible => "indestructable", + _ => null + }; + } + + private string ProtectionText + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => + _protectionLevel switch + { + ArmorProtectionLevel.Defense => "defense", + ArmorProtectionLevel.Guarding => "guarding", + ArmorProtectionLevel.Hardening => "hardening", + ArmorProtectionLevel.Fortification => "fortification", + ArmorProtectionLevel.Invulnerability => "invulnerability", + _ => null + }; + } + [Flags] private enum OldSaveFlag { diff --git a/Projects/UOContent/Items/Clothing/BaseClothing.cs b/Projects/UOContent/Items/Clothing/BaseClothing.cs index d975ab95f..5b249e490 100644 --- a/Projects/UOContent/Items/Clothing/BaseClothing.cs +++ b/Projects/UOContent/Items/Clothing/BaseClothing.cs @@ -95,7 +95,7 @@ namespace Server.Items private string _crafter; [SerializableFieldSaveFlag(8)] - private bool ShouldSerializeCrafter() => _crafter != null; + private bool ShouldSerializeCrafter() => !string.IsNullOrEmpty(_crafter); [InvalidateProperties] [SerializableField(9)] @@ -907,8 +907,13 @@ namespace Server.Items public override void OnSingleClick(Mobile from) { - var attrs = new List(); + if (!Core.UOTD) + { + OnSingleClickPreUOTD(from); + return; + } + var attrs = new List(); AddEquipInfoAttributes(from, attrs); int number; @@ -931,6 +936,30 @@ namespace Server.Items from.NetState.SendDisplayEquipmentInfo(Serial, number, _crafter, false, attrs); } + public void OnSingleClickPreUOTD(Mobile from) + { + var name = Name; + + if (name == null) + { + var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0; + name = $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}"; + } + + if (Crafter == null) + { + LabelTo(from, Quality == ClothingQuality.Exceptional ? $"{name} of exceptional quality" : name); + return; + } + + LabelTo( + from, + Quality == ClothingQuality.Exceptional + ? $"{name} crafted with exceptional quality by {Crafter}" + : $"{name} crafted by {Crafter}" + ); + } + public virtual void AddEquipInfoAttributes(Mobile from, List attrs) { if (DisplayLootType) diff --git a/Projects/UOContent/Items/Containers/Fillable Containers/FillableContainer.cs b/Projects/UOContent/Items/Containers/Fillable Containers/FillableContainer.cs index dae84dcc8..a661b6e26 100644 --- a/Projects/UOContent/Items/Containers/Fillable Containers/FillableContainer.cs +++ b/Projects/UOContent/Items/Containers/Fillable Containers/FillableContainer.cs @@ -19,7 +19,11 @@ public abstract partial class FillableContainer : LockableContainer } } - public FillableContainer(int itemID) : base(itemID) => Movable = false; + public FillableContainer(int itemID) : base(itemID) + { + Movable = false; + _contentType = FillableContentType.None; + } public virtual int MinRespawnMinutes => 60; public virtual int MaxRespawnMinutes => 90; diff --git a/Projects/UOContent/Items/Food/Beverage.cs b/Projects/UOContent/Items/Food/Beverage.cs index f30c6d21e..dd994bfc9 100644 --- a/Projects/UOContent/Items/Food/Beverage.cs +++ b/Projects/UOContent/Items/Food/Beverage.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using ModernUO.CodeGeneratedEvents; using ModernUO.Serialization; using Server.Collections; using Server.Engines.Plants; @@ -711,10 +713,9 @@ public abstract partial class BaseBeverage : Item, IHasQuantity _quantity = reader.ReadInt(); } - public static void OnLogin(Mobile m) - { - CheckHeaveTimer(m); - } + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void OnLogin(PlayerMobile pm) => CheckHeaveTimer(pm); public static void CheckHeaveTimer(Mobile from) { diff --git a/Projects/UOContent/Items/Quivers/BaseQuiver.cs b/Projects/UOContent/Items/Quivers/BaseQuiver.cs index cd29b6079..dfdc6c5f8 100644 --- a/Projects/UOContent/Items/Quivers/BaseQuiver.cs +++ b/Projects/UOContent/Items/Quivers/BaseQuiver.cs @@ -50,7 +50,7 @@ public partial class BaseQuiver : Container, ICraftable, IAosItem private string _crafter; [SerializableFieldSaveFlag(4)] - private bool ShouldSerializeCrafter() => _crafter != null; + private bool ShouldSerializeCrafter() => !string.IsNullOrEmpty(_crafter); [InvalidateProperties] [SerializableField(5)] diff --git a/Projects/UOContent/Items/Wands/BaseWand.cs b/Projects/UOContent/Items/Wands/BaseWand.cs index 9d6b0c46e..4a0127192 100644 --- a/Projects/UOContent/Items/Wands/BaseWand.cs +++ b/Projects/UOContent/Items/Wands/BaseWand.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using ModernUO.Serialization; using Server.Network; using Server.Spells; @@ -168,6 +169,12 @@ public abstract partial class BaseWand : BaseBashing public override void OnSingleClick(Mobile from) { + if (!Core.UOTD) + { + OnSingleClickPreUOTD(from); + return; + } + var attrs = new List(); if (DisplayLootType) @@ -230,6 +237,45 @@ public abstract partial class BaseWand : BaseBashing from.NetState.SendDisplayEquipmentInfo(Serial, number, Crafter, false, attrs); } + public void OnSingleClickPreUOTD(Mobile from) + { + var isMagicItem = _charges > 0; + var name = Name; + if (name == null) + { + var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0; + name = $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}"; + } + + if (isMagicItem && !Identified) + { + LabelTo(from, $"an unidentified {name}"); + return; + } + + LabelTo(from, isMagicItem ? $"{name} of {WandEffectText}" : name); + } + + private string WandEffectText + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _wandEffect switch + { + WandEffect.Clumsiness => "clumsiness", + WandEffect.Identification => "identification", + WandEffect.Healing => "healing", + WandEffect.Feeblemindedness => "feeblemindedness", + WandEffect.Weakness => "weakness", + WandEffect.MagicArrow => "magic arrow", + WandEffect.Harming => "harming", + WandEffect.Fireball => "fireball", + WandEffect.GreaterHealing => "greater healing", + WandEffect.Lightning => "lightning", + WandEffect.ManaDraining => "mana draining", + _ => null + }; + } + public void Cast(Spell spell) { var m = Movable; diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index ccec00c08..66b54cb30 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -17,6 +17,7 @@ using Server.Spells.Necromancy; using Server.Spells.Ninjitsu; using Server.Spells.Sixth; using Server.Spells.Spellweaving; +using Server.Text; namespace Server.Items; @@ -27,7 +28,8 @@ public interface ISlayer } [SerializationGenerator(10, false)] -public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftable, ISlayer, IDurability, IAosItem +public abstract partial class BaseWeapon + : Item, IWeapon, IFactionItem, ICraftable, ISlayer, IDurability, IAosItem, IIdentifiable { private static bool _enableInstaHit; @@ -104,7 +106,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab [SerializableFieldSaveFlag(9)] [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ShouldSerializeCrafter() => string.IsNullOrEmpty(_crafter); + private bool ShouldSerializeCrafter() => !string.IsNullOrEmpty(_crafter); [InvalidateProperties] [SerializableField(10)] @@ -673,6 +675,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } PlayerConstructed = true; + Identified = true; var resourceType = typeRes ?? craftItem.Resources[0].ItemType; @@ -728,21 +731,18 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab { case CraftResource.DullCopper: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Durable; AccuracyLevel = WeaponAccuracyLevel.Accurate; break; } case CraftResource.ShadowIron: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Durable; DamageLevel = WeaponDamageLevel.Ruin; break; } case CraftResource.Copper: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Fortified; DamageLevel = WeaponDamageLevel.Ruin; AccuracyLevel = WeaponAccuracyLevel.Surpassingly; @@ -750,7 +750,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Bronze: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Fortified; DamageLevel = WeaponDamageLevel.Might; AccuracyLevel = WeaponAccuracyLevel.Surpassingly; @@ -758,7 +757,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Gold: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Indestructible; DamageLevel = WeaponDamageLevel.Force; AccuracyLevel = WeaponAccuracyLevel.Eminently; @@ -766,7 +764,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Agapite: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Indestructible; DamageLevel = WeaponDamageLevel.Power; AccuracyLevel = WeaponAccuracyLevel.Eminently; @@ -774,7 +771,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Verite: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Indestructible; DamageLevel = WeaponDamageLevel.Power; AccuracyLevel = WeaponAccuracyLevel.Exceedingly; @@ -782,7 +778,6 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab } case CraftResource.Valorite: { - Identified = true; DurabilityLevel = WeaponDurabilityLevel.Indestructible; DamageLevel = WeaponDamageLevel.Vanq; AccuracyLevel = WeaponAccuracyLevel.Supremely; @@ -3332,6 +3327,12 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab public override void OnSingleClick(Mobile from) { + if (!Core.UOTD) + { + OnSingleClickPreUOTD(from); + return; + } + var attrs = new List(); if (DisplayLootType) @@ -3423,6 +3424,130 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab from.NetState.SendDisplayEquipmentInfo(Serial, number, _crafter, false, attrs); } + public virtual void OnSingleClickPreUOTD(Mobile from) + { + var isMagicItem = _durabilityLevel > WeaponDurabilityLevel.Regular || + _accuracyLevel > WeaponAccuracyLevel.Regular || + _damageLevel > WeaponDamageLevel.Regular || + _slayer != SlayerName.None; + + if (isMagicItem && !_identified) + { + LabelTo(from, $"an unidentified {Name ?? Localization.GetText(LabelNumber).ToLowerInvariant()}"); + return; + } + + var name = Name; + var articleAnName = (TileData.ItemTable[ItemID].Flags & TileFlag.ArticleAn) != 0; + + if (isMagicItem) + { + var builder = ValueStringBuilder.Create(128); + + var durabilityText = DurabilityText(out var articleAnDurability); + if (durabilityText != null) + { + builder.AppendSpaceWithArticle(durabilityText, articleAnDurability); + } + + var accuracyText = AccuracyText(out var articleAnAccuracy); + if (accuracyText != null) + { + builder.AppendSpaceWithArticle(accuracyText, articleAnAccuracy); + } + + var slayerEntry = SlayerGroup.GetEntryByName(_slayer); + if (slayerEntry != null) + { + builder.AppendSpaceWithArticle(slayerEntry.SlayerText(out var articleAnSlayer), articleAnSlayer); + } + + if (name == null) + { + builder.AppendSpaceWithArticle(Localization.GetText(LabelNumber).ToLowerInvariant(), articleAnName); + } + else if (builder.Length != 0) + { + builder.Append($" {name}"); + } + else + { + builder.Append(name); + } + + var weaponDamageText = WeaponDamageText; + if (weaponDamageText != null) + { + builder.Append($" of {weaponDamageText}"); + } + + // TODO: Spells (of Ghoul's Touch) + + LabelTo(from, builder.ToString()); + builder.Dispose(); + return; + } + + name ??= $"{(articleAnName ? "an" : "a")} {Localization.GetText(LabelNumber).ToLowerInvariant()}"; + + if (Crafter == null) + { + LabelTo(from, Quality == WeaponQuality.Exceptional ? $"{name} of exceptional quality" : name); + return; + } + + LabelTo( + from, + Quality == WeaponQuality.Exceptional + ? $"{name} crafted with exceptional quality by {Crafter}" + : $"{name} crafted by {Crafter}" + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string DurabilityText(out bool articleAn) + { + articleAn = _durabilityLevel is WeaponDurabilityLevel.Indestructible; + return _durabilityLevel switch + { + WeaponDurabilityLevel.Durable => "durable", + WeaponDurabilityLevel.Substantial => "substantial", + WeaponDurabilityLevel.Massive => "massive", + WeaponDurabilityLevel.Fortified => "fortified", + WeaponDurabilityLevel.Indestructible => "indestructible", + _ => null + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string AccuracyText(out bool articleAn) + { + articleAn = _accuracyLevel is WeaponAccuracyLevel.Accurate or WeaponAccuracyLevel.Eminently; + return _accuracyLevel switch + { + WeaponAccuracyLevel.Accurate => "accurate", + WeaponAccuracyLevel.Surpassingly => "surpassingly accurate", + WeaponAccuracyLevel.Eminently => "eminently accurate", + WeaponAccuracyLevel.Exceedingly => "exceedingly accurate", + WeaponAccuracyLevel.Supremely => "supremely accurate", + _ => null + }; + } + + private string WeaponDamageText + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _damageLevel switch + { + WeaponDamageLevel.Ruin => "ruin", + WeaponDamageLevel.Might => "might", + WeaponDamageLevel.Force => "force", + WeaponDamageLevel.Power => "power", + WeaponDamageLevel.Vanq => "vanquishing", + _ => null + }; + } + public virtual int GetHitAttackSound(Mobile attacker, Mobile defender) { var sound = attacker.GetAttackSound(); diff --git a/Projects/UOContent/Items/Weapons/Maces/FireworksWand.cs b/Projects/UOContent/Items/Weapons/Maces/FireworksWand.cs index a755d8426..7c0c64a61 100644 --- a/Projects/UOContent/Items/Weapons/Maces/FireworksWand.cs +++ b/Projects/UOContent/Items/Weapons/Maces/FireworksWand.cs @@ -98,5 +98,10 @@ namespace Server.Items Effects.PlaySound(endLoc, map, Utility.Random(0x11B, 4)); Effects.SendLocationEffect(endLoc, map, 0x373A + 0x10 * Utility.Random(4), 16, 10, hue, renderMode); } + + public override void OnSingleClickPreUOTD(Mobile from) + { + LabelTo(from, Name ?? Localization.GetText(LabelNumber).ToLowerInvariant()); + } } } diff --git a/Projects/UOContent/Items/Weapons/SlayerEntry.cs b/Projects/UOContent/Items/Weapons/SlayerEntry.cs index 1acec3c59..ccd1a7682 100644 --- a/Projects/UOContent/Items/Weapons/SlayerEntry.cs +++ b/Projects/UOContent/Items/Weapons/SlayerEntry.cs @@ -88,6 +88,20 @@ namespace Server.Items } } + public string SlayerText(out bool articleAn) + { + if (Name == SlayerName.None) + { + articleAn = false; + return null; + } + + articleAn = Name is SlayerName.OrcSlaying or SlayerName.OgreTrashing or SlayerName.Exorcism or SlayerName.Ophidian + or SlayerName.ArachnidDoom or SlayerName.ElementalBan or SlayerName.ElementalHealth or SlayerName.EarthShatter; + + return Localization.GetText(Title)?.ToLowerInvariant(); + } + public bool Slays(Mobile m) { var t = m.GetType(); diff --git a/Projects/UOContent/Misc/BuffIcons.cs b/Projects/UOContent/Misc/BuffIcons.cs index f72fd3c27..46ab80109 100644 --- a/Projects/UOContent/Misc/BuffIcons.cs +++ b/Projects/UOContent/Misc/BuffIcons.cs @@ -1,4 +1,5 @@ using System; +using ModernUO.CodeGeneratedEvents; using Server.Mobiles; using Server.Network; @@ -108,17 +109,15 @@ namespace Server Enabled = ServerConfiguration.GetOrUpdateSetting("buffIcons.enable", Core.ML); } - public static void ResendBuffsOnClientVersionReceived(NetState ns, ClientVersion cv) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { if (!Enabled) { return; } - if (ns.Mobile is PlayerMobile pm) - { - Timer.StartTimer(pm.ResendBuffs); - } + pm.ResendBuffs(); } public static void AddBuff(Mobile m, BuffInfo b) diff --git a/Projects/UOContent/Misc/LightCycle.cs b/Projects/UOContent/Misc/LightCycle.cs index 7277c299b..441fefeb2 100644 --- a/Projects/UOContent/Misc/LightCycle.cs +++ b/Projects/UOContent/Misc/LightCycle.cs @@ -1,5 +1,7 @@ using System; +using ModernUO.CodeGeneratedEvents; using Server.Items; +using Server.Mobiles; using Server.Network; namespace Server @@ -55,10 +57,8 @@ namespace Server } } - public static void OnLogin(Mobile m) - { - m.CheckLightLevels(true); - } + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) => pm.CheckLightLevels(true); public static int ComputeLevelFor(Mobile from) { diff --git a/Projects/UOContent/Misc/LoginStats.cs b/Projects/UOContent/Misc/LoginStats.cs index 44c3e006e..d7f163afd 100644 --- a/Projects/UOContent/Misc/LoginStats.cs +++ b/Projects/UOContent/Misc/LoginStats.cs @@ -1,17 +1,18 @@ +using Server.Mobiles; using Server.Network; namespace Server.Misc { public static class LoginStats { - public static void OnLogin(Mobile m) + public static void OnLogin(PlayerMobile pm) { var userCount = NetState.Instances.Count; var itemCount = World.Items.Count; var mobileCount = World.Mobiles.Count; - m.SendMessage( - $"Welcome, {m.Name}! There {(userCount == 1 ? "is" : "are")} currently {userCount} user{(userCount == 1 ? "" : "s")} " + + pm.SendMessage( + $"Welcome, {pm.Name}! There {(userCount == 1 ? "is" : "are")} currently {userCount} user{(userCount == 1 ? "" : "s")} " + $"online, with {itemCount} item{(itemCount == 1 ? "" : "s")} and {mobileCount} mobile{(mobileCount == 1 ? "" : "s")} in the world." ); } diff --git a/Projects/UOContent/Misc/ShardPoller.cs b/Projects/UOContent/Misc/ShardPoller.cs index 2d408f945..682003c58 100644 --- a/Projects/UOContent/Misc/ShardPoller.cs +++ b/Projects/UOContent/Misc/ShardPoller.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.Net; using System.Text.RegularExpressions; +using ModernUO.CodeGeneratedEvents; using ModernUO.Serialization; using Server.Gumps; +using Server.Mobiles; using Server.Network; using Server.Prompts; @@ -125,14 +127,15 @@ public partial class ShardPoller : Item Options[^1] = option; } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { if (_activePollers.Count == 0) { return; } - Timer.StartTimer(TimeSpan.FromSeconds(1.0), () => EventSink_Login_Callback(m)); + Timer.StartTimer(TimeSpan.FromSeconds(1.0), () => EventSink_Login_Callback(pm)); } private static void EventSink_Login_Callback(Mobile from) diff --git a/Projects/UOContent/Misc/StaminaSystem.cs b/Projects/UOContent/Misc/StaminaSystem.cs index 845496c2e..e4704e310 100644 --- a/Projects/UOContent/Misc/StaminaSystem.cs +++ b/Projects/UOContent/Misc/StaminaSystem.cs @@ -82,17 +82,19 @@ public static class StaminaSystem RemoveEntry(m as IHasSteps); } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { + bool exists; if (EnableMountStamina) { // Start idle for mount - ref var stepsTaken = ref GetStepsTaken(m.Mount, out var exists); + ref var stepsTaken = ref GetStepsTaken(pm.Mount, out exists); if (exists) { if (stepsTaken.Steps <= 0 || Core.Now >= stepsTaken.IdleStartTime + ResetDuration) { - _stepsTaken.Remove(m.Mount); + _stepsTaken.Remove(pm.Mount); } else { @@ -100,16 +102,13 @@ public static class StaminaSystem } } - _resetHash.Remove(m.Mount); + _resetHash.Remove(pm.Mount); } - if (m is PlayerMobile pm) + ref var regenStepsTaken = ref RegenSteps(pm, out exists); + if (exists) { - ref var stepsTaken = ref RegenSteps(pm, out var exists); - if (exists) - { - stepsTaken.IdleStartTime = Core.Now; - } + regenStepsTaken.IdleStartTime = Core.Now; } } diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index f2ea4fa28..abf2d5c67 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -1220,6 +1220,10 @@ namespace Server.Mobiles } } + [GeneratedEvent(nameof(PlayerLoginEvent))] + public static partial void PlayerLoginEvent(PlayerMobile pm); + + [OnEvent(nameof(PlayerLoginEvent))] public static void OnLogin(PlayerMobile from) { if (AccountHandler.LockdownLevel > AccessLevel.Player) @@ -4474,20 +4478,39 @@ namespace Server.Mobiles public void SendAddBuffPacket(BuffInfo buffInfo) { - if (buffInfo == null) + if (buffInfo == null || NetState?.BuffIcon != true) { return; } + // Synchronize the buff icon as close to _on the second_ as we can. + var msecs = buffInfo.TimeLength.Milliseconds; + if (msecs >= 8) + { + Timer.DelayCall(TimeSpan.FromMilliseconds(msecs), () => + { + // They are still online, we still have the buff icon in the table, and it is the same buff icon + if (NetState != null && m_BuffTable?.GetValueOrDefault(buffInfo.ID) == buffInfo) + { + SendAddBuffPacket(buffInfo, (long)buffInfo.TimeLength.TotalMilliseconds - msecs); + } + }); + } + else + { + SendAddBuffPacket(buffInfo, (long)buffInfo.TimeLength.TotalMilliseconds); + } + } + + private void SendAddBuffPacket(BuffInfo buffInfo, long ticks) + { NetState.SendAddBuffPacket( Serial, buffInfo.ID, buffInfo.TitleCliloc, buffInfo.SecondaryCliloc, buffInfo.Args, - buffInfo.TimeStart == 0 - ? 0 - : Math.Max(buffInfo.TimeStart + (long)buffInfo.TimeLength.TotalMilliseconds - Core.TickCount, 0) + ticks ); } @@ -4512,29 +4535,9 @@ namespace Server.Mobiles RemoveBuff(b); // Check & subsequently remove the old one. m_BuffTable ??= new Dictionary(); - m_BuffTable.Add(b.ID, b); - if (NetState?.BuffIcon == true) - { - // Synchronize the buff icon as close to _on the second_ as we can. - var msecs = b.TimeLength.Milliseconds; - if (msecs >= 8) - { - Timer.DelayCall(TimeSpan.FromMilliseconds(msecs), (buffInfo, pm) => - { - // They are still online, we still have the buff icon in the table, and it is the same buff icon - if (pm.NetState != null && pm.m_BuffTable?.GetValueOrDefault(buffInfo.ID) == buffInfo) - { - pm.SendAddBuffPacket(buffInfo); - } - }, b, this); - } - else - { - SendAddBuffPacket(b); - } - } + SendAddBuffPacket(b); } public void RemoveBuff(BuffInfo b) diff --git a/Projects/UOContent/Multis/Boats/Strandedness.cs b/Projects/UOContent/Multis/Boats/Strandedness.cs index 5dd6f742f..4779289b8 100644 --- a/Projects/UOContent/Multis/Boats/Strandedness.cs +++ b/Projects/UOContent/Multis/Boats/Strandedness.cs @@ -1,3 +1,6 @@ +using ModernUO.CodeGeneratedEvents; +using Server.Mobiles; + namespace Server.Misc { public static class Strandedness @@ -112,7 +115,8 @@ namespace Server.Misc return false; } - public static void OnLogin(Mobile from) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile from) { if (!IsStranded(from)) { diff --git a/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs b/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs index 099072b36..3a658effe 100644 --- a/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs +++ b/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs @@ -17,23 +17,9 @@ using System; using System.Buffers; using System.Collections.Generic; using System.IO; -using Server.Accounting; -using Server.Assistants; -using Server.Commands; using Server.Engines.CharacterCreation; -using Server.Engines.ConPVP; -using Server.Engines.Doom; -using Server.Engines.PartySystem; -using Server.Engines.Plants; -using Server.Engines.PlayerMurderSystem; -using Server.Engines.VeteranRewards; -using Server.Factions; -using Server.Items; using Server.Misc; using Server.Mobiles; -using Server.Regions; -using Server.Spells.Ninjitsu; -using Server.Spells.Spellweaving; namespace Server.Network; @@ -314,34 +300,10 @@ public static class IncomingAccountPackets state.SendPlayMusic(m.Region.Music); - StaminaSystem.OnLogin(m); - DuelContext.OnLogin(m); - LightCycle.OnLogin(m); - LoginStats.OnLogin(m); - AnimalForm.OnLogin(m); - BaseBeverage.OnLogin(m); - AntiMacroSystem.OnLogin(m); - Strandedness.OnLogin(m); - ShardPoller.OnLogin(m); - ReaperFormSpell.OnLogin(m); - Party.OnLogin(m); - PlantSystem.OnLogin(m); - LampRoomRegion.OnLogin(m); - HouseRegion.OnLogin(m); - Faction.OnLogin(m); - PlayerMurderSystem.OnLogin(m); - AssistantHandler.OnLogin(m); - VisibilityList.OnLogin(m); - if (m is PlayerMobile pm) { - PlayerMobile.OnLogin(pm); + PlayerMobile.PlayerLoginEvent(pm); } - Account.OnLogin(m); - GiftGiving.OnLogin(m); - PreventInaccess.OnLogin(m); - TwistedWealdDesertRegion.OnLogin(m); - RewardSystem.OnLogin(m); } private static int GenerateAuthID(this NetState state) diff --git a/Projects/UOContent/Regions/HouseRegion.cs b/Projects/UOContent/Regions/HouseRegion.cs index cd533c776..6c7ec59b0 100644 --- a/Projects/UOContent/Regions/HouseRegion.cs +++ b/Projects/UOContent/Regions/HouseRegion.cs @@ -1,4 +1,5 @@ using System; +using ModernUO.CodeGeneratedEvents; using Server.Gumps; using Server.Items; using Server.Mobiles; @@ -29,13 +30,14 @@ public class HouseRegion : BaseRegion { } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - var house = BaseHouse.FindHouseAt(m); + var house = BaseHouse.FindHouseAt(pm); - if (house?.Public == false && !house.IsFriend(m)) + if (house?.Public == false && !house.IsFriend(pm)) { - m.Location = house.BanLocation; + pm.Location = house.BanLocation; } } diff --git a/Projects/UOContent/Regions/TwistedWealdDesertRegion.cs b/Projects/UOContent/Regions/TwistedWealdDesertRegion.cs index 558148283..7d4e9b156 100644 --- a/Projects/UOContent/Regions/TwistedWealdDesertRegion.cs +++ b/Projects/UOContent/Regions/TwistedWealdDesertRegion.cs @@ -1,4 +1,6 @@ using System.Text.Json.Serialization; +using ModernUO.CodeGeneratedEvents; +using Server.Mobiles; using Server.Network; using Server.Spells; using Server.Spells.Ninjitsu; @@ -41,11 +43,12 @@ public class TwistedWealdDesertRegion : MondainRegion } } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - if (m.Region.IsPartOf() && m.AccessLevel == AccessLevel.Player) + if (pm.AccessLevel == AccessLevel.Player && pm.Region.IsPartOf()) { - m.NetState.SendSpeedControl(SpeedControlSetting.Walk); + pm.NetState.SendSpeedControl(SpeedControlSetting.Walk); } } } diff --git a/Projects/UOContent/Skills/AntiMacroSystem.cs b/Projects/UOContent/Skills/AntiMacroSystem.cs index 1ba547775..ffc4e0bc0 100644 --- a/Projects/UOContent/Skills/AntiMacroSystem.cs +++ b/Projects/UOContent/Skills/AntiMacroSystem.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Text.Json.Serialization; +using ModernUO.CodeGeneratedEvents; using Server.Collections; using Server.Json; using Server.Mobiles; @@ -138,10 +139,11 @@ public static class AntiMacroSystem } } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { // Stop the clear out timer - if (_logoutCleanup?.Remove(m, out var timer) == true) + if (_logoutCleanup?.Remove(pm, out var timer) == true) { timer.Stop(); } diff --git a/Projects/UOContent/Skills/ItemIdentification.cs b/Projects/UOContent/Skills/ItemIdentification.cs index 0724bce80..9d09208fb 100644 --- a/Projects/UOContent/Skills/ItemIdentification.cs +++ b/Projects/UOContent/Skills/ItemIdentification.cs @@ -2,62 +2,62 @@ using System; using Server.Mobiles; using Server.Targeting; -namespace Server.Items +namespace Server.Items; + +public interface IIdentifiable { - public static class ItemIdentification + bool Identified { get; set; } +} + +public static class ItemIdentification +{ + public static void Initialize() { - public static void Initialize() + SkillInfo.Table[(int)SkillName.ItemID].Callback = OnUse; + } + + public static TimeSpan OnUse(Mobile from) + { + from.SendLocalizedMessage(500343); // What do you wish to appraise and identify? + from.Target = new InternalTarget(); + + return TimeSpan.FromSeconds(1.0); + } + + [PlayerVendorTarget] + private class InternalTarget : Target + { + public InternalTarget() : base(8, false, TargetFlags.None) => AllowNonlocal = true; + + protected override void OnTarget(Mobile from, object o) { - SkillInfo.Table[(int)SkillName.ItemID].Callback = OnUse; - } - - public static TimeSpan OnUse(Mobile from) - { - from.SendLocalizedMessage(500343); // What do you wish to appraise and identify? - from.Target = new InternalTarget(); - - return TimeSpan.FromSeconds(1.0); - } - - [PlayerVendorTarget] - private class InternalTarget : Target - { - public InternalTarget() : base(8, false, TargetFlags.None) => AllowNonlocal = true; - - protected override void OnTarget(Mobile from, object o) + if (o is Mobile mobile) { - if (o is Item item) - { - if (from.CheckTargetSkill(SkillName.ItemID, item, 0, 100)) - { - if (item is BaseWeapon weapon) - { - weapon.Identified = true; - } - else if (item is BaseArmor armor) - { - armor.Identified = true; - } + mobile.OnSingleClick(from); + return; + } - if (!Core.AOS) - { - item.OnSingleClick(from); - } - } - else - { - from.SendLocalizedMessage(500353); // You are not certain... - } - } - else if (o is Mobile mobile) + bool identified = false; + + if (o is Item item) + { + if (item is IIdentifiable identifiable && from.CheckTargetSkill(SkillName.ItemID, item, 0, 100)) { - mobile.OnSingleClick(from); + identifiable.Identified = true; + identified = true; } - else + + if (!Core.AOS) { - from.SendLocalizedMessage(500353); // You are not certain... + item.OnSingleClick(from); } } + + + if (!identified) + { + from.SendLocalizedMessage(500353); // You are not certain... + } } } } diff --git a/Projects/UOContent/Special Systems/Engines/GiftGiving.cs b/Projects/UOContent/Special Systems/Engines/GiftGiving.cs index efe4a9ace..763a5efcf 100644 --- a/Projects/UOContent/Special Systems/Engines/GiftGiving.cs +++ b/Projects/UOContent/Special Systems/Engines/GiftGiving.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using ModernUO.CodeGeneratedEvents; using Server.Accounting; +using Server.Mobiles; namespace Server.Misc { @@ -19,9 +21,10 @@ namespace Server.Misc m_Givers.Add(giver); } - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - if (m.Account is not Account acct) + if (pm.Account is not Account acct) { return; } @@ -47,7 +50,7 @@ namespace Server.Misc continue; // already got one } - giver.DelayGiveGift(TimeSpan.FromSeconds(5.0), m); + giver.DelayGiveGift(TimeSpan.FromSeconds(5.0), pm); } acct.LastLogin = now; diff --git a/Projects/UOContent/Special Systems/Engines/PreventInaccess.cs b/Projects/UOContent/Special Systems/Engines/PreventInaccess.cs index ccf96144e..8e45c53a7 100644 --- a/Projects/UOContent/Special Systems/Engines/PreventInaccess.cs +++ b/Projects/UOContent/Special Systems/Engines/PreventInaccess.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using ModernUO.CodeGeneratedEvents; +using Server.Mobiles; namespace Server.Misc { @@ -34,7 +36,8 @@ namespace Server.Misc m_MoveHistory = new Dictionary(); } - public static void OnLogin(Mobile from) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile from) { if (!Enabled) { diff --git a/Projects/UOContent/Spells/Ninjitsu/AnimalForm.cs b/Projects/UOContent/Spells/Ninjitsu/AnimalForm.cs index 24cbeea0a..abe0847d9 100644 --- a/Projects/UOContent/Spells/Ninjitsu/AnimalForm.cs +++ b/Projects/UOContent/Spells/Ninjitsu/AnimalForm.cs @@ -64,11 +64,12 @@ public class AnimalForm : NinjaSpell new(typeof(Reptalon), 1075202, 11669, 0, 1075222, 90.0, 0x114, 0, 0, false, false) }; - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - if (GetContext(m)?.SpeedBoost == true) + if (GetContext(pm)?.SpeedBoost == true) { - m.NetState.SendSpeedControl(SpeedControlSetting.Mount); + pm.NetState.SendSpeedControl(SpeedControlSetting.Mount); } } diff --git a/Projects/UOContent/Spells/Spellweaving/ReaperForm.cs b/Projects/UOContent/Spells/Spellweaving/ReaperForm.cs index d853c4d70..59ec73dc1 100644 --- a/Projects/UOContent/Spells/Spellweaving/ReaperForm.cs +++ b/Projects/UOContent/Spells/Spellweaving/ReaperForm.cs @@ -1,4 +1,6 @@ using System; +using ModernUO.CodeGeneratedEvents; +using Server.Mobiles; using Server.Network; namespace Server.Spells.Spellweaving @@ -27,13 +29,14 @@ namespace Server.Spells.Spellweaving public virtual int SwingSpeedBonus => 10 + FocusLevel; public virtual int SpellDamageBonus => 10 + FocusLevel; - public static void OnLogin(Mobile m) + [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] + public static void OnLogin(PlayerMobile pm) { - var context = TransformationSpellHelper.GetContext(m); + var context = TransformationSpellHelper.GetContext(pm); if (context?.Type == typeof(ReaperFormSpell)) { - m.NetState.SendSpeedControl(SpeedControlSetting.Walk); + pm.NetState.SendSpeedControl(SpeedControlSetting.Walk); } }