using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using ModernUO.CodeGeneratedEvents; using Server.Collections; using Server.Logging; using Server.Mobiles; namespace Server.Engines.PlayerMurderSystem; public class PlayerMurderSystem : GenericPersistence { private static PlayerMurderSystem _playerMurderPersistence; private static readonly ILogger logger = LogFactory.GetLogger(typeof(PlayerMurderSystem)); // All of the players with murders private static readonly Dictionary _murderContexts = new(); // Only the players that are online private static readonly HashSet _contextTerms = new(MurderContext.EqualityComparer.Default); private static TimeSpan _shortTermMurderDuration; private static TimeSpan _longTermMurderDuration; public static TimeSpan ShortTermMurderDuration => _shortTermMurderDuration; public static TimeSpan LongTermMurderDuration => _longTermMurderDuration; public static void Configure() { _shortTermMurderDuration = ServerConfiguration.GetOrUpdateSetting("murderSystem.shortTermMurderDuration", TimeSpan.FromHours(8)); _longTermMurderDuration = ServerConfiguration.GetOrUpdateSetting("murderSystem.longTermMurderDuration", TimeSpan.FromHours(40)); _playerMurderPersistence = new PlayerMurderSystem(); } public static void Initialize() { EventSink.Disconnected += OnDisconnected; } [OnEvent(nameof(PlayerMobile.PlayerDeletedEvent))] public static void OnPlayerDeleted(Mobile m) { if (m is PlayerMobile pm && _murderContexts.Remove(pm, out var context)) { _contextTerms.Remove(context); } } public PlayerMurderSystem() : base("PlayerMurders", 10) { } // Only used for migrations! public static void MigrateContext(PlayerMobile player, TimeSpan shortTerm, TimeSpan longTerm) { if (!World.Loading) { logger.Error( $"Attempted to call MigrateContext outside of world loading.{Environment.NewLine}{{StackTrace}}", new StackTrace() ); return; } var context = GetOrCreateMurderContext(player); // We make a big assumption that by the time this is called, the Mobile/PlayerMobile info is deserialized if (Mobile.MurderMigrations?.TryGetValue(player, out var shortTermMurders) == true) { context.ShortTermMurders = shortTermMurders; } context.ShortTermElapse = shortTerm; context.LongTermElapse = longTerm; UpdateMurderContext(context); } [OnEvent(nameof(PlayerMobile.PlayerLoginEvent))] public static void OnLogin(PlayerMobile pm) { if (!GetMurderContext(pm, out var context)) { return; } if (context.CheckStart()) { _contextTerms.Add(context); } else { _murderContexts.Remove(pm); _contextTerms.Remove(context); } } private static void OnDisconnected(Mobile m) { if (m is PlayerMobile pm && _murderContexts.Remove(pm, out var context)) { _contextTerms.Remove(context); } } public override void Deserialize(IGenericReader reader) { var version = reader.ReadEncodedInt(); var count = reader.ReadEncodedInt(); for (var i = 0; i < count; ++i) { var context = new MurderContext(reader.ReadEntity()); context.Deserialize(reader); _murderContexts.Add(context.Player, context); } } public override void Serialize(IGenericWriter writer) { writer.WriteEncodedInt(0); // version writer.WriteEncodedInt(_murderContexts.Count); foreach (var (m, context) in _murderContexts) { writer.Write(m); context.Serialize(writer); } } public static bool GetMurderContext(PlayerMobile player, out MurderContext context) { if (player != null && _murderContexts.TryGetValue(player, out context)) { return true; } context = null; return false; } public static MurderContext GetOrCreateMurderContext(PlayerMobile player) { if (player == null) { return null; } ref var context = ref CollectionsMarshal.GetValueRefOrAddDefault(_murderContexts, player, out var exists); if (!exists) { context = new MurderContext(player); } return context; } public static void ManuallySetShortTermMurders(PlayerMobile player, int shortTermMurders) { var context = GetOrCreateMurderContext(player); context.ShortTermMurders = shortTermMurders; UpdateMurderContext(context); } public static void OnPlayerMurder(PlayerMobile player) { var context = GetOrCreateMurderContext(player); context.ShortTermMurders++; player.Kills++; context.ResetKillTime(); UpdateMurderContext(context); } private static void UpdateMurderContext(MurderContext context) { var player = context.Player; if (!context.CheckStart()) { _murderContexts.Remove(player); _contextTerms.Remove(context); } else if (player.NetState != null) { _contextTerms.Add(context); } } private class MurdererTimer : Timer { public MurdererTimer() : base(TimeSpan.FromMinutes(5.0), TimeSpan.FromMinutes(5.0)) { } public static void Initialize() { new MurdererTimer().Start(); } protected override void OnTick() { if (_contextTerms.Count == 0) { return; } using var queue = PooledRefQueue.Create(); foreach (var context in _contextTerms) { context.DecayKills(); if (!context.CheckStart()) { queue.Enqueue(context.Player); } } while (queue.Count > 0) { if (_murderContexts.Remove((PlayerMobile)queue.Dequeue(), out var context)) { _contextTerms.Remove(context); } } } ~MurdererTimer() { PlayerMurderSystem.logger.Error($"{nameof(MurdererTimer)} is no longer running!"); } } }