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

Jail record gump, invoked with [jailrecord (30 second cooldown)

---
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
zcmVM#HR00000VgLXD00000Te<5s;gU8m;;|)$
z8Gl#RL=6JI&qzcwaZp&sx_4Y#z2YdrP?@h
zSdeB8QU!G>(JV$c?xgR!WcSXFybY1wYjvu-WqoRY2V@*f0~S=2sG3Hn;~t!3aw0r
zWdm#eQ!-$xE{%!6mFm!5m5#I(5G?kZf+G0hyxH#HNUlp&PM}>=38;EKiej&TYk!WL
znwyPh@=pGcJ{T_1HoRXQxc6v(Y4SI>S@DVS0M1^6xuIPlpJDc^zxBHcEHdH>0XHkE
zEH?%;^f
z84Efc^=h}BV4d@2b?v{iA>d7m&hF96iQ?&;4}n*
z@V?z-gZ^d7lgDZ@8@3pRs3Sma?ATG5mh}%NoQG(DXS=i)b??>uNs?b;RWz85pUQ4rAIN+I8cU8wdalS^qq`{)2XS!;#ba4D%`J)5LHA7p0CfrV
zSQyj2pSGf$MnC0%B9#RWFqp-X`KqAjGqh-$Gh!{o%E?z_Qfq!40s4OO%&0o
zIc}QWm!VaC^5A{;Dhh4WFHN?1YAK5r!UR53XErqPsCg2F30D26!+uEsyYtmwrYL0+
zQ;yW(9PmEr0u11U5g0PBHUAu?`qWu9)WdLOC6~YnU5M_ZbcGSVFiFMa^8|dFy&OLBfEJGD!=Tz>&
zb}qv|v_rA;W@X5pC1zJ3=NU_G-2B*8#ua&Y-X-E!$fglyUm|K;1cDfzn;TyAr#K{Z`WAma$s*K9<@9YZTvRbD7SUigey^+)W5R&rS_{Ahp?%l1u+&+$Z(
zEgfoHJAZgzlLTEW_}Qiwg8Qo%&+QWv4Vpo~&l(Y}`9~nx2BgVoW>r~m^GOfs$gb`3
zsEY6S)g~&@1+9o4K3QiR$Z`<@F)+#T58qB?0`GrvAY7$+_CEG##hA{`_2>e=x4dR}
zYIG`?lnjpwU>TYJTpUL=N0$7oWzPvB=b4F+wk8oYQKF({8q?0blGdHx8g8ox!yz-A@BC~BMyd8)sb-cRG2i@c5J6WtDHdd
zxt`dLxvj&6d@4*Q8`{9LnzN-e2M0|D^?#GmQh(}B)xn<{QZL=7rI+GkZCrf8z|VN$
z5Qiv!&%^CX7t2=sj$UVNRZ5tH)0uX?kYF{&fqm)|$;qm`&Ii>3}Xyjf9pj9=={qa(eKmx#&$%!faG
zLKuPX17%?%RLh9T3U0)o71)gU9UAludoXWeH&8$L6=m3HG5u)5*>gUY<|anC7(jC)
zVU6Z`G29(+fW==3Z{ciMQd%;qZGYtQ2>9g*P<>98sampE9flZ|POXu-t9s`I3T5qT
z+BC|O=@1#h-f&DXM8r|_s6WPV5KB^bpyQW3Z>Qvp3TgF+)QT7~@O)dVE4YF)39P(T#FW&+L|xkTd_Xt$m!%;$zimfB
z6`hmYiTIyzTE1F`OKmg9u*zqfY-jX;fD5gLLAi!xl?u{E9W{&(BNwm|v)MrU`gW-s
zpXY9<=!OVxC@3G!+HH|ur+);;@F1Vabsr~7Af4>$QjPm?WEutdq!yDk-q`jJ4tL8e
zL+myt$Tnc;9Z=4=5?XZ+g8=QPump2O@EID%-dTA-v#XK@qfHdVry8S@wZ*j$jl{(W
zWO?aG;_;L>M|3CEY0}xr-f2SJioam>g7URFx91E}jH`cuW=5A3N`I6$xU)4iY(Tc$
z681fuXDiz~Y%>v&Plp_tt2nft9Q2MGdb=7559gW_Rqou;NBI4;VdmK_W!p+VTAU{K
z_}a$LV@jFTlHiGEz3S04yn45+_GJq)w+_e^rV6B)cPVjM{E5b6uc_bjObsNLN%pGa
zo;~-Dpd5#=!I}mNQ-4gO6DuI*Z^mWs^VfV-%-FgLte(F8yx=rI=HO9?a54Ss%xJgK
z7{#WwAHsBF>CRvP271Q>A*2CwKv6@+I6?`yu6Bt(@&D`jXM!yn!Mgux=~l*yu|C>6gf-^ZNjE{%R^ir2g4mMQ(<~{q)A>6r-P;7HTE~E)?&P
zr+E~OtOG;-<$oe~4;cTcl6Px43$+sMXEgQuuXZ~e)or_!1srka5v_^?s195d3TzgR
z@G#_Nj{dcojR2=3icvNm`cPb|BskLjm(BMaUaj|>n3}lKs_}kkZWHOoaCnlS*#&IZ
z37|&{2(Ppvn9a%>Xt5lrDNXPXX*pNLbbja(tS-Y7zJIJCkV=JX!Sb0!PY>Um3T;}w
z%vs2hT|8y5ZX5fOVf7)67}^uJn&W`B4Cus(jQM+eJl1&i(=ObTM#p-&novw
zK$E6xW%Y_|&p$07Wr>d)Gg1Av24B7B>9JfT=fnGQa6_AE$mvkX#R)L+B>(Wrws6_zE(Y45uck5kBU#l<|>yd3THoL6ICi|2T7aF|BXr
zgd+6Paxe?V`;0DABwD>$6d`7dR4hD{!pljIXm!)~hpha13Z2M^Zas;aFB?kaFoxpj
z)MU*g<(Oh3AipLFI?q)4ngEhi^s=WLZ`N4lVt@QZ!)KDB3cMW
z*njoz=0$ov>Jo{yOTE=nr5
zj_3iE39H-iK(U9r`&p4z?{NU`zTNm$hd>qFbVfsEU_kgH8=`wBRMl}&UYzRSnI#Gq
zDJ35@&LC*K4!|rPcUAsA6ZoonbM;IC$bUF<33?+rteqzzE8u;SN5KS@SO!)lHRZwE
z9Y=&<+|22YN~_j0J-~@%jxpx+l>uBxk83Q{7(IZh=XGB5X`rNzM%TOoqFO363hC9hKQO;in?df#!M8@JN%OOl
z>x|Jw-KJRwnmn{Qq|;g7^=|a7R|b&Gl-Yj#o#9>pIx4;Yv|sIpfxci24k`o5Nl0tt
zu`Q8$O;9X?q4s(gkMH}VezS<26MvifaA;%G%}uX+W1u_#Jdwovi=Ef~e>NOOryTK<
z3odNvZ%xO65t26BhI|l`sH7lEeOAP-`o&-$USkG!)fW(58!EUrDjIW0>-cXA(GJ~}
z_T_?T)87g)p#uNyhEaPd=Wpxil?kXCNP)9E9bx_I>tc3@6HFhcD3?uEB?B1$GrW
z!7xKk-GL3>D|Og77gd}r6^^Nrhq;6g=X<*yE@=91^_4vH_=pGrZHcg80i|HTHxBDTTlL;aB;(u=7z$-iWvUc-S
zze^(%(N~LuDcvznR>#EEe(xZh
z`)6jp9xr(2q3OxU>Lca9Zub#yzK%K5))K}8_Podrw64p=u5-cy!m_XaW?O#%RZP01
z67~$`t(Qd)gy3v$MSpOAlm)pYiOkM-7Fqp-SrC}2fnKJ~)vdon{nl~(+O-1Bt9A;*
z9NVStZpk||4b3sITwhBbmogzF>Uo^@NR03?Rvz*)T25je3Orq!p*ovnP5ES}I)J
z_^$H_)z;&N(P04wsu7%-UNnW2^q=z>;!#8Y-@r`tC0-^(ljVRMw*PY3$hV#2WqYA^
z1Im8@bSe4r=U}+}TTKgHG7acTI2})F%44-aZQf>S8;qXmeC=Rnqbw;qWJt?n%guyF
zS2OfJyIB)xzkkk<10s3kD(RYeSZuA9xJpvYVWNM_KQ%Jl#208mTLnw@KK^g$-h2e=
z3ec_7-}p54WcHB04H5ex599L(VvtGMfZ`MNiG>|l3-gER=HtGo>uTNOBrMW@A=n8C
zsZuZ1PlPS8>m~=y30Y0<^@S5MLECzc&L~dJ|6Q}^a)0MIQG7?sH;=V4OV`1G`YN4c
zhBY5veoqpO=x1?R5HiW=|gy3JD#EL%v6(~O1l!|oA9LkwHP_|A{A!;M8Z2|<4InNvZqzfJSAI-3wOfls?
z+M4<6w10Ce3tHBouf+z3I^w8vaX5IF$IIn|GyHT;gfom1;sS15zY5p^}+tjqxWh1oX^8V7R
zvPW%+E~ZM;)kt*ZbtIVJTd)$V?@c1R}l
zv5!F_-$t3aDf+snl_Pp4&o)8A7&sVWE#T3zqi&9TWMSKKUXpih#*I7N<8*tyDKWfu
z@h{DMK5Q{M5lDrgLmGRrxW}`_af6gKQbRosX>bllI7~C!Z*RL>`XF^8wfyH;DJU|^
z0e>@H?H6c>rj-VwqT9NxoX&jsomUi~pI7k$6m~Jd;I%Nl-IHtvmGFUN)o!2$ot(vE
zoSytG%Qh9;q8q@-kq+foEyR
z^@&bMM-1U5Wo%L%C}p&|#`;V;LMD4+Z-0O$V{}C%UudO9R^GGa1SK@Xc5R{1sl+24
z=hmsS9kIJ0V2DbZmVb>K%&;yUC9Q1nrZ=j&YF_40w)f>#a~BcqE}@iE
z({l`b=v1b7srlS$N*Pe1z&%JyFwot*Yz;{gY1mTH_kss0peS|4oj
zoA8_d=E*OJen8SrgUJ5TT}BNEj(rp^<*1oLgUH2@X$tynu7f>SlDb_14}TbI15hbm
z`TVFhO3=)4L|KLZ(ntdbV-ogP|9h$q<^zA0JicoEwOcs5?I|Y?GP3eBntXp-y8no9
zLEyHsS&9Q#a>J^syO4N>3C=);jI@QwykE7tD=*8-`@v&}!*MSOh|%wyV-$wp!6on@
z;AN$VAQm9ayap0m$%d|*hks8n0oc^ibs`Zj4PaMcjmGCQfNOfm?~HNkEm5-^Xh_Rg
z@XNUq6J9fN2Jhf5FVQVuXmH^cHE2UZrn^?~bJPCq0SQiOrnDfhOqz{G(P$;S2?FOI0Q?;&Y1ip=7naQF__#wHe}2x_4A2=#>{&r0MeMI(k_nu
zDIRgMz!Icdic{PuF@J;k;00K?WGiLUEpvkpGVbPw>a+RJK9O|W3iyRl=g-&^=rhmD
zRd7Nik1uw-<#tzH>dcHhtR~ezte9wPJ00AK(0SXMkHaPwFftM}DX62%_S*p!1_1yd00000
E0HkotY5)KL
delta 6008
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