From 5f0561b7fc0ac275b5151b306518d2be80815acf Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Wed, 16 Jul 2025 20:41:21 -0700 Subject: [PATCH] feat: New Jail System (#2215) New Jail System for MUO ---------------------------- Commands: [jail [player] [reason] - Jail with time escalation per offense (5 to 120 minutes, GM only) [unjail [player] - Manual release from jail regardless of time (GM only) [jailinfo [player] - Check jail status and history (GM only) [jailrecord - Checks their own jail record stats (player access) Jail/Unjail can be found in the client view of a player in Admin Gump ![Screenshot 2025-06-12 030226](https://github.com/user-attachments/assets/a9f1a736-0316-464c-8958-c589ea0a1dcb) Jail record gump, invoked with [jailrecord (30 second cooldown) ![Screenshot 2025-06-12 030320](https://github.com/user-attachments/assets/c19b3e76-3042-48ae-a00d-c70ef564bc84) --- Projects/Server/Utilities/Utility.cs | 45 ++ Projects/UOContent/Gumps/AdminGump.cs | 43 +- ...rver.Systems.JailSystem.JailRecord.v0.json | 43 ++ .../Systems/JailSystem/JailRecord.cs | 25 + .../Systems/JailSystem/JailRecordGump.cs | 90 ++++ .../JailSystem}/JailRegion.cs | 0 .../Systems/JailSystem/JailSystem.cs | 427 ++++++++++++++++++ docs/commands/commands.7z | Bin 12531 -> 12652 bytes 8 files changed, 669 insertions(+), 4 deletions(-) create mode 100644 Projects/UOContent/Migrations/Server.Systems.JailSystem.JailRecord.v0.json create mode 100644 Projects/UOContent/Systems/JailSystem/JailRecord.cs create mode 100644 Projects/UOContent/Systems/JailSystem/JailRecordGump.cs rename Projects/UOContent/{Regions => Systems/JailSystem}/JailRegion.cs (100%) create mode 100644 Projects/UOContent/Systems/JailSystem/JailSystem.cs diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 6ef4aa72c..448e9adf1 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -1479,4 +1479,49 @@ public static partial class Utility return DateTime.SpecifyKind(local - tz.GetUtcOffset(local), DateTimeKind.Utc); } + + public static string FormatTimeCompact(this TimeSpan ts, bool showSeconds = false) + { + using var sb = ValueStringBuilder.Create(); + if (ts.Days >= 1) + { + sb.Append($"{ts.Days}d"); + } + + if (sb.Length > 0) + { + sb.Append($" {ts.Hours}h"); + } + else if (ts.Hours >= 1) + { + sb.Append($"{ts.Hours}h"); + } + + if (sb.Length > 0) + { + sb.Append($" {ts.Minutes}m"); + } + else if (ts.Minutes >= 1) + { + sb.Append($"{ts.Minutes}m"); + } + + if (showSeconds) + { + if (sb.Length > 0) + { + sb.Append($" {ts.Seconds}s"); + } + else if (ts.Seconds >= 1) + { + sb.Append($"{ts.Seconds}s"); + } + } + else if (sb.Length == 0) + { + sb.Append("0m"); + } + + return sb.ToString(); + } } diff --git a/Projects/UOContent/Gumps/AdminGump.cs b/Projects/UOContent/Gumps/AdminGump.cs index fa22ceca6..e9c91dba3 100644 --- a/Projects/UOContent/Gumps/AdminGump.cs +++ b/Projects/UOContent/Gumps/AdminGump.cs @@ -92,12 +92,12 @@ namespace Server.Gumps AddPage(0); - AddBackground(0, 0, 420, 440, 5054); + AddBackground(0, 0, 420, 480, 5054); AddBlackAlpha(10, 10, 170, 100); AddBlackAlpha(190, 10, 220, 100); - AddBlackAlpha(10, 120, 400, 260); - AddBlackAlpha(10, 390, 400, 40); + AddBlackAlpha(10, 120, 400, 300); + AddBlackAlpha(10, 430, 400, 40); AddPageButton( 10, @@ -141,7 +141,7 @@ namespace Server.Gumps if (notice != null) { - AddHtml(12, 392, 396, 36, notice.Color(LabelColor32)); + AddHtml(20, 440, 396, 36, notice.Color(LabelColor32)); } switch (pageType) @@ -619,6 +619,14 @@ namespace Server.Gumps AddButtonLabeled(20, y, GetButtonID(7, 12), "Kill"); AddButtonLabeled(200, y, GetButtonID(7, 13), "Resurrect"); + y += 20; + + AddButtonLabeled(20, y, GetButtonID(7, 15), "Jail"); + AddButtonLabeled(200, y, GetButtonID(7, 16), "Unjail"); + y += 25; + + AddLabel(20, y, LabelHue, "Jail Reason:"); + AddTextField(100, y, 300, 20, 1); break; } @@ -3801,6 +3809,33 @@ namespace Server.Gumps sendGump = false; break; } + case 15: + { + var reason = info.GetTextEntry(1)?.Trim(); + + if (string.IsNullOrWhiteSpace(reason)) + { + reason = ""; + } + + CommandLogging.WriteLine( + from, + $"{from.AccessLevel} {CommandLogging.Format(from)} jailing {CommandLogging.Format(m)} - Reason: {reason}" + ); + InvokeCommand($"Jail {m.Name} \"{reason}\""); + notice = $"Player has been sent to jail. Reason: {reason}"; + break; + } + case 16: + { + CommandLogging.WriteLine( + from, + $"{from.AccessLevel} {CommandLogging.Format(from)} unjailing {CommandLogging.Format(m)}" + ); + InvokeCommand($"Unjail {m.Name}"); + notice = "Player has been unjailed."; + break; + } } if (sendGump) diff --git a/Projects/UOContent/Migrations/Server.Systems.JailSystem.JailRecord.v0.json b/Projects/UOContent/Migrations/Server.Systems.JailSystem.JailRecord.v0.json new file mode 100644 index 000000000..acbdc3297 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Systems.JailSystem.JailRecord.v0.json @@ -0,0 +1,43 @@ +{ + "version": 0, + "type": "Server.Systems.JailSystem.JailRecord", + "properties": [ + { + "name": "JailCount", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "LastJailed", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "JailEndTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "LastJailReason", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "JailedBy", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Systems/JailSystem/JailRecord.cs b/Projects/UOContent/Systems/JailSystem/JailRecord.cs new file mode 100644 index 000000000..c57ef8622 --- /dev/null +++ b/Projects/UOContent/Systems/JailSystem/JailRecord.cs @@ -0,0 +1,25 @@ +using System; +using ModernUO.Serialization; + +namespace Server.Systems.JailSystem; + +[SerializationGenerator(0)] +public partial class JailRecord +{ + [SerializableField(0)] + private int _jailCount; + + [SerializableField(1)] + private DateTime _lastJailed; + + [SerializableField(2)] + private DateTime _jailEndTime; + + [SerializableField(3)] + private string _lastJailReason; + + [SerializableField(4)] + private Mobile _jailedBy; + + public bool IsCurrentlyJailed => JailEndTime > Core.Now; +} diff --git a/Projects/UOContent/Systems/JailSystem/JailRecordGump.cs b/Projects/UOContent/Systems/JailSystem/JailRecordGump.cs new file mode 100644 index 000000000..45b9ce172 --- /dev/null +++ b/Projects/UOContent/Systems/JailSystem/JailRecordGump.cs @@ -0,0 +1,90 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: JailRecordGump.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Server.Gumps; +using Server.Mobiles; + +namespace Server.Systems.JailSystem; + +public class JailRecordGump : StaticGump +{ + private readonly PlayerMobile _player; + private readonly JailRecord _record; + + public JailRecordGump(PlayerMobile player, JailRecord record) : base(0, 0) + { + _player = player; + _record = record; + } + + protected override void BuildLayout(ref StaticGumpBuilder builder) + { + builder.SetNoResize(); + + builder.AddPage(); + builder.AddBackground(0, 0, 330, 300, 9200); + builder.AddAlphaRegion(10, 10, 310, 280); + + builder.AddHtml(20, 20, 360, 30, "JAIL RECORD"); + builder.AddHtmlPlaceholder(20, 60, 360, 25, "playerName"); + builder.AddHtmlPlaceholder(20, 80, 360, 25, "jailCount"); + builder.AddHtmlPlaceholder(20, 100, 360, 25, "lastJailed"); + builder.AddHtmlPlaceholder(20, 120, 290, 25, "jailReason"); + + builder.AddHtmlPlaceholder(20, 150, 360, 25, "jailStatus"); + builder.AddHtmlPlaceholder(20, 170, 360, 25, "jailTime"); + + builder.AddHtml(20, 200, 360, 25, "If you believe you were jailed in error,"); + builder.AddHtml(20, 220, 360, 25, "please contact staff through normal channels."); + builder.AddHtml(20, 240, 360, 25, "Each time you are jailed, the wait increases."); + builder.AddHtml(20, 260, 360, 25, "Follow all shard rules to avoid future jail time."); + } + + protected override void BuildStrings(ref GumpStringsBuilder builder) + { + builder.SetHtmlText("playerName", $"Player: {_player.Name}", 0xFFFF00, 5); + builder.SetHtmlText("jailCount", $"Jail Count: {_record.JailCount}", 0xFFFFFF, 5); + + if (_record.LastJailed > DateTime.MinValue) + { + builder.SetHtmlText("lastJailed", $"Last Jailed: {_record.LastJailed:yyyy/MM/dd HH:mm}", 0xFFFFFF, 5); + } + else + { + builder.SetHtmlText("lastJailed", "Last Jailed: Never", 0xFFFFFF, 5); + } + + if (string.IsNullOrWhiteSpace(_record.LastJailReason)) + { + builder.SetHtmlText("jailReason", "Jail Reason: None given.", 0xFFFFFF, 4); + } + else + { + builder.SetHtmlText("jailReason", $"Jail Reason: {_record.LastJailReason}", 0xFFFFFF, 4); + } + + if (_record.IsCurrentlyJailed) + { + builder.SetHtmlText("jailStatus", "Status: Currently Jailed", 0xFF6666, 5); + builder.SetHtmlText("jailTime", $"Jail Time: {(_record.JailEndTime - Core.Now).FormatTimeCompact()}", 0xFF6666, 5); + } + else + { + builder.SetHtmlText("jailStatus", "Status: Not Jailed", 0x00FF00, 5); + builder.SetStringSlot("jailTime", ""); + } + } +} diff --git a/Projects/UOContent/Regions/JailRegion.cs b/Projects/UOContent/Systems/JailSystem/JailRegion.cs similarity index 100% rename from Projects/UOContent/Regions/JailRegion.cs rename to Projects/UOContent/Systems/JailSystem/JailRegion.cs diff --git a/Projects/UOContent/Systems/JailSystem/JailSystem.cs b/Projects/UOContent/Systems/JailSystem/JailSystem.cs new file mode 100644 index 000000000..a2592470e --- /dev/null +++ b/Projects/UOContent/Systems/JailSystem/JailSystem.cs @@ -0,0 +1,427 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2025 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: JailSystem.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using Server.Mobiles; +using Server.Network; +using Server.Commands; +using Server.Gumps; + +namespace Server.Systems.JailSystem; + +public class JailSystem : GenericPersistence +{ + // Jail locations (modify if using custom maps) + // felucca void + private static readonly Point3D[] JailLocations = + [ + new(5276, 1164, 0), + new(5286, 1164, 0), + new(5296, 1164, 0), + new(5306, 1164, 0), + new(5276, 1174, 0), + new(5286, 1174, 0), + new(5296, 1174, 0), + new(5306, 1174, 0), + new(5283, 1184, 0), + new(5304, 1184, 0) + ]; + + // Jail map, change this for custom maps + public static readonly Map JailMap = Map.Felucca; + private static readonly JailRecord EmptyRecord = new(); + + private static readonly HashSet CurrentlyBeingJailed = []; + private static readonly Dictionary PlayerJailRecords = []; + private static readonly Dictionary JailTimers = []; + + // Jail time scales from 5 minutes to 12 hours based on the number of offenses + private static readonly TimeSpan MinJailTime = TimeSpan.FromMinutes(5); + private static readonly TimeSpan MaxJailTime = TimeSpan.FromHours(12); + + // Release location, change this for custom maps + private static readonly Point3D ReleaseLocation = new(1444, 1697, 10); // Britain Bank + public static readonly Map ReleaseMap = Map.Felucca; + + private static JailSystem Instance; + + // [jail [reason] - Jail with time escalation per offense (GM only) + // [unjail - Manual release from jail regardless of time (GM only) + // [jailinfo - Check jail status and history (GM only) + // [jailrecord - Checks their own jail record stats (player access) + + public static void Configure() + { + Instance = new JailSystem(); + CommandSystem.Register("Jail", AccessLevel.GameMaster, Jail_OnCommand); + CommandSystem.Register("Unjail", AccessLevel.GameMaster, Unjail_OnCommand); + CommandSystem.Register("JailInfo", AccessLevel.Counselor, JailInfo_OnCommand); + CommandSystem.Register("JailRecord", AccessLevel.Player, MyJailInfo_OnCommand); + } + + public JailSystem() : base("Jail", 3) + { + } + + // Can be used by other systems to check player jail status + public static bool IsPlayerJailed(PlayerMobile player) => + PlayerJailRecords.GetValueOrDefault(player)?.IsCurrentlyJailed == true; + + private static TimeSpan CalculateJailTime(int jailCount) + { + var totalMinutes = MinJailTime + (jailCount - 1) * (MaxJailTime - MinJailTime) / 9.0; + + return totalMinutes.Clamp(MinJailTime, MaxJailTime); + } + + public static void JailPlayer(Mobile from, PlayerMobile player, string reason = "") + { + if (!CurrentlyBeingJailed.Add(player)) + { + return; + } + + if (!PlayerJailRecords.TryGetValue(player, out var record)) + { + PlayerJailRecords[player] = record = new JailRecord(); + } + + record.JailCount++; + record.LastJailed = Core.Now; + record.LastJailReason = reason; + record.JailedBy = from; + + var jailTime = CalculateJailTime(record.JailCount); + record.JailEndTime = Core.Now + jailTime; + + player.Frozen = true; + player.SendMessage(0x35, "You are being sent to jail!"); + player.PlaySound(0x204); + + CommandLogging.WriteLine(from, $"Player {player.Name} jailed for: {reason} (Offense #{record.JailCount}, {jailTime.TotalMinutes} minutes)"); + + foreach (var ns in NetState.Instances) + { + if (ns.Mobile is PlayerMobile staff && staff.AccessLevel >= AccessLevel.Counselor) + { + staff.SendMessage(0x35, $"Player {player.Name} has been jailed for {jailTime.TotalMinutes} minutes. Reason: {reason} (Offense #{record.JailCount})"); + } + } + + Timer.DelayCall(TimeSpan.FromSeconds(2.0), DismountPlayer, from, player, jailTime); + } + + private static void DismountPlayer(Mobile from, PlayerMobile player, TimeSpan jailTime) + { + if (player.Mount != null) + { + var mount = player.Mount; + mount.Rider = null; + player.SendMessage(0x35, "You have been dismounted."); + if (mount is BaseCreature bc) + { + if (bc.Summoned) + { + bc.Dispel(bc); + } + else // Stable the pet + { + bc.ControlTarget = null; + bc.ControlOrder = OrderType.Stay; + bc.Internalize(); + + bc.SetControlMaster(null); + + bc.IsStabled = true; + bc.StabledBy = from; + player.AddStabled(bc); + } + } + } + + CommandLogging.WriteLine(from, $"Player {player.Name} dismounted before jail teleport"); + + Timer.DelayCall(TimeSpan.FromSeconds(3.0), TeleportToJail, from, player, jailTime); + } + + private static void TeleportToJail(Mobile from, PlayerMobile player, TimeSpan jailTime) + { + var jailLocation = JailLocations.RandomElement(); + + player.MoveToWorld(jailLocation, JailMap); + + player.SendMessage(0x35, "Use [jailrecord to pull up your record."); + player.SendMessage(0x35, "Please contact staff if you believe this was a mistake."); + + CommandLogging.WriteLine(from, $"Player {player.Name} teleported to jail at {jailLocation}"); + + Timer.DelayCall(TimeSpan.FromSeconds(5.0), UnfreezePlayer, from, player, jailTime); + } + + private static void UnfreezePlayer(Mobile from, PlayerMobile player, TimeSpan jailTime) + { + player.Frozen = false; + + CommandLogging.WriteLine(from, $"Player {player.Name} unfrozen in jail"); + + CurrentlyBeingJailed.Remove(player); + + var releaseTimer = Timer.DelayCall(jailTime, ReleasePlayer, from, player); + JailTimers[player] = releaseTimer; + } + + private static void ReleasePlayer(Mobile from, PlayerMobile player) + { + if (PlayerJailRecords.TryGetValue(player, out var record)) + { + record.JailEndTime = Core.Now; + } + + JailTimers.Remove(player); + + // Freeze player for release sequence + player.Frozen = true; + player.SendMessage(0x35, "You have been released from jail!"); + player.PlaySound(0x1FF); + + if (from != null) + { + CommandLogging.WriteLine(from, $"Player {player.Name} released from jail, starting teleport sequence"); + } + + foreach (var ns in NetState.Instances) + { + if (ns.Mobile is PlayerMobile staff && staff.AccessLevel >= AccessLevel.Counselor) + { + staff.SendMessage(0x35, $"Player {player.Name} has been released from jail."); + } + } + + Timer.DelayCall(TimeSpan.FromSeconds(5.0), TeleportFromJail, from, player); + } + + private static void TeleportFromJail(Mobile from, PlayerMobile player) + { + player.MoveToWorld(ReleaseLocation, ReleaseMap); + + if (from != null) + { + CommandLogging.WriteLine(player, $"Player {player.Name} teleported from jail to {ReleaseLocation}"); + } + + Timer.DelayCall(TimeSpan.FromSeconds(5.0), UnfreezeFromRelease, from, player); + } + + private static void UnfreezeFromRelease(Mobile from, PlayerMobile player) + { + player.Frozen = false; + player.SendMessage(0x35, "Welcome back!"); + player.SendMessage(0x35, "Please follow the shard rules."); + player.SendMessage(0x35, "Have a nice day!"); + + if (from != null) + { + CommandLogging.WriteLine(from, $"Player {player.Name} unfrozen after jail release"); + } + } + + [Usage("Jail [reason]")] + [Description("Jails a player with freeze/teleport/unfreeze sequence.")] + private static void Jail_OnCommand(CommandEventArgs e) + { + if (e.Length < 1) + { + e.Mobile.SendMessage(0x35, "Usage: [jail [reason]"); + return; + } + + var reason = e.GetString(1); + var playerName = e.GetString(0); + var playerSerial = Serial.TryParse(playerName, null, out var serial) ? serial : Serial.MinusOne; + + PlayerMobile player = null; + + foreach (var m in World.Mobiles.Values) + { + if (m is PlayerMobile pm && (pm.Serial == playerSerial || m.RawName.InsensitiveEquals(playerName))) + { + player = pm; + break; + } + } + + if (player == null) + { + e.Mobile.SendMessage(0x35, $"Player '{playerName}' not found."); + return; + } + + if (player.AccessLevel > AccessLevel.Player) + { + e.Mobile.SendMessage(0x35, "You cannot jail staff members."); + return; + } + + if (IsPlayerJailed(player)) + { + e.Mobile.SendMessage(0x35, $"Player {player.Name} is already jailed."); + return; + } + + JailPlayer(e.Mobile, player, reason); + e.Mobile.SendMessage(0x35, $"Player {player.Name} is being jailed. Reason: {reason}"); + } + + [Usage("Unjail ")] + [Description("Manually releases a player from jail.")] + private static void Unjail_OnCommand(CommandEventArgs e) + { + var from = e.Mobile; + if (e.Length < 1) + { + from.SendMessage(0x35, "Usage: [unjail "); + return; + } + + var playerName = e.GetString(0); + var playerSerial = Serial.TryParse(playerName, null, out var serial) ? serial : Serial.MinusOne; + + PlayerMobile player = null; + + foreach (var m in World.Mobiles.Values) + { + if (m is PlayerMobile pm && (pm.Serial == playerSerial || m.RawName.InsensitiveEquals(playerName))) + { + player = pm; + break; + } + } + + if (player == null) + { + from.SendMessage(0x35, $"Player '{playerName}' not found."); + return; + } + + if (!IsPlayerJailed(player)) + { + from.SendMessage(0x35, $"Player {player.Name} is not currently jailed."); + return; + } + + if (JailTimers.Remove(player, out var value)) + { + value.Stop(); + } + + ReleasePlayer(from, player); + from.SendMessage(0x35, $"Player {player.Name} has been manually released from jail."); + } + + [Usage("JailInfo ")] + [Description("Shows jail information for a player.")] + private static void JailInfo_OnCommand(CommandEventArgs e) + { + if (e.Length < 1) + { + e.Mobile.SendMessage(0x35, "Usage: [jailinfo "); + return; + } + + var playerName = e.GetString(0); + var playerSerial = Serial.TryParse(playerName, null, out var serial) ? serial : Serial.MinusOne; + + PlayerMobile player = null; + + foreach (var m in World.Mobiles.Values) + { + if (m is PlayerMobile pm && (pm.Serial == playerSerial || m.RawName.InsensitiveEquals(playerName))) + { + player = pm; + break; + } + } + + if (player == null) + { + e.Mobile.SendMessage(0x35, $"Player '{playerName}' not found."); + return; + } + + e.Mobile.SendGump(new JailRecordGump(player, PlayerJailRecords.GetValueOrDefault(player, EmptyRecord))); + } + + private static readonly Dictionary JailRecordCooldowns = new(); + private static readonly TimeSpan JailRecordCooldown = TimeSpan.FromSeconds(30); + + [Usage("JailRecord")] + [Description("Shows your own jail information.")] + private static void MyJailInfo_OnCommand(CommandEventArgs e) + { + var player = (PlayerMobile)e.Mobile; + + if (JailRecordCooldowns.TryGetValue(player, out var cooldown)) + { + var timeSinceLastUse = Core.Now - cooldown; + + if (timeSinceLastUse < JailRecordCooldown) + { + var remaining = JailRecordCooldown - timeSinceLastUse; + e.Mobile.SendMessage(0x35, $"You must wait {remaining.TotalSeconds:F0} seconds."); + return; + } + } + + JailRecordCooldowns[player] = Core.Now; + + player.SendGump(new JailRecordGump(player, PlayerJailRecords.GetValueOrDefault(player, EmptyRecord))); + } + + public override void Serialize(IGenericWriter writer) + { + writer.WriteEncodedInt(0); // version + writer.WriteEncodedInt(PlayerJailRecords.Count); + foreach (var (m, record) in PlayerJailRecords) + { + writer.Write(m); + record.Serialize(writer); + } + } + + public override void Deserialize(IGenericReader reader) + { + var version = reader.ReadEncodedInt(); + + var count = reader.ReadEncodedInt(); + for (var i = 0; i < count; i++) + { + var player = reader.ReadEntity(); + var record = new JailRecord(); + record.Deserialize(reader); + + if (player != null) + { + PlayerJailRecords[player] = record; + if (record.IsCurrentlyJailed) + { + CurrentlyBeingJailed.Add(player); + var jailTime = record.JailEndTime - Core.Now; + JailTimers[player] = Timer.DelayCall(jailTime, ReleasePlayer, record.JailedBy, player); + } + } + } + } +} diff --git a/docs/commands/commands.7z b/docs/commands/commands.7z index 0da10ed6dbbce289bc2c1f848a1a9f7b51288213..6db187201c68989111c3554f565e67943a071219 100644 GIT binary patch delta 6130 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 E0HkotY5)KL delta 6008 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