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 0da10ed6d..6db187201 100644
Binary files a/docs/commands/commands.7z and b/docs/commands/commands.7z differ