From 2d381dad91ee0c8a3daa91e3273429a41e5c897a Mon Sep 17 00:00:00 2001
From: Bohica <53943479+Bohicatv@users.noreply.github.com>
Date: Thu, 13 Nov 2025 06:03:16 -0800
Subject: [PATCH 1/7] feat: Adds anti-botting system for resource harvesting
---
Projects/UOContent/Engines/Harvest/Fishing.cs | 11 ++
.../Engines/Harvest/Lumberjacking.cs | 11 ++
Projects/UOContent/Engines/Harvest/Mining.cs | 11 ++
.../Systems/AntiBotSystem/AntiBotGump.cs | 62 ++++++++
.../Systems/AntiBotSystem/AntiBotSystem.cs | 132 ++++++++++++++++++
5 files changed, 227 insertions(+)
create mode 100644 Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
create mode 100644 Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
diff --git a/Projects/UOContent/Engines/Harvest/Fishing.cs b/Projects/UOContent/Engines/Harvest/Fishing.cs
index df46fe9fc..99de3d11d 100644
--- a/Projects/UOContent/Engines/Harvest/Fishing.cs
+++ b/Projects/UOContent/Engines/Harvest/Fishing.cs
@@ -492,6 +492,17 @@ namespace Server.Engines.Harvest
return false;
}
+ if (Utility.Random(100) < 1)
+ {
+ if (!AntiBotSystem.CheckPlayer(from, () =>
+ {
+ from.Target = new HarvestTarget(tool, this);
+ }))
+ {
+ return false; // antibot challenge sent
+ }
+ }
+
from.SendLocalizedMessage(500974); // What water do you want to fish in?
return true;
}
diff --git a/Projects/UOContent/Engines/Harvest/Lumberjacking.cs b/Projects/UOContent/Engines/Harvest/Lumberjacking.cs
index 9640f68d8..95f4c0fa1 100644
--- a/Projects/UOContent/Engines/Harvest/Lumberjacking.cs
+++ b/Projects/UOContent/Engines/Harvest/Lumberjacking.cs
@@ -185,6 +185,17 @@ namespace Server.Engines.Harvest
{
from.RevealingAction();
}
+
+ if (Utility.Random(100) < 1)
+ {
+ if (!AntiBotSystem.CheckPlayer(from, () =>
+ {
+ from.Target = new HarvestTarget(tool, this);
+ }))
+ {
+ // antibot challenge sent
+ }
+ }
}
public static void Initialize()
diff --git a/Projects/UOContent/Engines/Harvest/Mining.cs b/Projects/UOContent/Engines/Harvest/Mining.cs
index dd7f5c747..60c734343 100644
--- a/Projects/UOContent/Engines/Harvest/Mining.cs
+++ b/Projects/UOContent/Engines/Harvest/Mining.cs
@@ -444,6 +444,17 @@ namespace Server.Engines.Harvest
return false;
}
+ if (Utility.Random(100) < 1)
+ {
+ if (!AntiBotSystem.CheckPlayer(from, () =>
+ {
+ from.Target = new HarvestTarget(tool, this);
+ }))
+ {
+ // antibot challenge sent
+ }
+ }
+
from.SendLocalizedMessage(503033); // Where do you wish to dig?
return true;
}
diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
new file mode 100644
index 000000000..cfe607894
--- /dev/null
+++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
@@ -0,0 +1,62 @@
+using Server.Gumps;
+using Server.Network;
+
+namespace Server.Engines.AntiBot
+{
+ public class AntiBotGump : Gump
+ {
+ public readonly Mobile _mobile;
+ public readonly int _code;
+
+ public AntiBotGump(Mobile mobile, int code) : base(150, 150)
+ {
+ _mobile = mobile;
+ _code = code;
+
+ Closable = false;
+ Disposable = false;
+ Draggable = true;
+ Resizable = false;
+
+ AddPage(0);
+ AddBackground(0, 0, 350, 220, 9270);
+
+ AddHtml(20, 20, 310, 25, "
Anti-Bot Verification", false, false);
+ AddHtml(20, 50, 310, 40, $"Please enter the following number:", false, false);
+ AddHtml(20, 55, 310, 40, $"
{code}", false, false);
+ AddHtml(20, 100, 310, 50, $"You have 2 minutes to input the correct number.
Incorrect numbers, cancellations, or timeouts will disconnect you from the server.", false, false);
+
+ AddBackground(20, 160, 200, 25, 3000);
+ AddTextEntry(25, 165, 190, 20, 0, 0, "");
+
+ AddButton(230, 160, 4005, 4007, 1, GumpButtonType.Reply, 0); // Submit
+ AddButton(290, 160, 4017, 4019, 0, GumpButtonType.Reply, 0); // Cancel
+
+ AddHtml(230, 190, 40, 20, "Submit", false, false);
+ AddHtml(290, 190, 40, 20, "Cancel", false, false);
+ }
+
+ public override void OnResponse(NetState sender, in RelayInfo info)
+ {
+ var from = sender?.Mobile;
+ if (from == null)
+ {
+ return;
+ }
+
+ bool cancelled = info.ButtonID == 0;
+ int enteredCode = 0;
+
+ if (!cancelled)
+ {
+ var textEntry = info.GetTextEntry(0);
+ if (textEntry != null && !int.TryParse(textEntry.Trim(), out enteredCode))
+ {
+ enteredCode = -1; // Invalid input - will cause disconnect
+ }
+ }
+
+ AntiBotSystem.ProcessResponse(from, enteredCode, cancelled);
+ }
+ }
+}
diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
new file mode 100644
index 000000000..b36af735a
--- /dev/null
+++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using Server.Gumps;
+using Server.Mobiles;
+
+namespace Server.Engines.AntiBot
+{
+ public static class AntiBotSystem
+ {
+ private class AntiBotChallenge
+ {
+ public int Code { get; set; }
+ public DateTime ChallengeExpiry { get; set; }
+ public Action SuccessCallback { get; set; }
+ public Timer TimeoutTimer { get; set; }
+ }
+
+ private static readonly Dictionary _activeChallenges = new();
+
+ public static bool Enabled { get; set; } = true;
+ public static int MaxAttempts { get; set; } = 1;
+ public static TimeSpan ChallengeTimeout { get; set; } = TimeSpan.FromMinutes(2);
+
+ public static bool CheckPlayer(Mobile from, Action onSuccess)
+ {
+ if (!Enabled || from is not PlayerMobile)
+ {
+ return true;
+ }
+
+ lock (_activeChallenges)
+ {
+ CleanupExpiredChallenges();
+
+ if (_activeChallenges.TryGetValue(from, out var existing))
+ {
+ from.CloseGump();
+ from.SendGump(new AntiBotGump(from, existing.Code));
+ return false;
+ }
+
+ var challenge = new AntiBotChallenge
+ {
+ Code = Utility.RandomMinMax(1000, 9999),
+ ChallengeExpiry = Core.Now.Add(ChallengeTimeout),
+ SuccessCallback = onSuccess
+ };
+
+ challenge.TimeoutTimer = Timer.DelayCall(ChallengeTimeout, () =>
+ {
+ lock (_activeChallenges)
+ {
+ if (_activeChallenges.ContainsKey(from))
+ {
+ _activeChallenges.Remove(from);
+ from.SendMessage("Anti-Bot: Verification timed out. Disconnecting...");
+ from.NetState?.Disconnect("Anti-Bot: Verification failed by timing out.");
+ }
+ }
+ });
+
+ _activeChallenges[from] = challenge;
+ from.CloseGump();
+ from.SendGump(new AntiBotGump(from, challenge.Code));
+ return false;
+ }
+ }
+
+ internal static void ProcessResponse(Mobile from, int enteredCode, bool cancelled)
+ {
+ lock (_activeChallenges)
+ {
+ if (!_activeChallenges.TryGetValue(from, out var challenge))
+ {
+ return;
+ }
+
+ challenge.TimeoutTimer?.Stop();
+ _activeChallenges.Remove(from);
+
+ if (cancelled)
+ {
+ from.SendMessage("Anti-Bot: Verification cancelled. Disconnecting...");
+ from.NetState?.Disconnect("Anti-Bot: Verification failed by cancellation.");
+ return;
+ }
+
+ if (enteredCode == challenge.Code)
+ {
+ from.SendMessage("Anti-Bot: Verification successful!!!");
+ }
+ else
+ {
+ from.SendMessage("Anti-Bot: Incorrect number. Disconnecting...");
+ from.NetState?.Disconnect("Anti-Bot: Verification failed by incorrect number.");
+ }
+ }
+ }
+
+ private static void CleanupExpiredChallenges()
+ {
+ var now = Core.Now;
+ var toRemove = new List();
+
+ foreach (var kvp in _activeChallenges)
+ {
+ if (kvp.Value.ChallengeExpiry < now)
+ {
+ kvp.Value.TimeoutTimer?.Stop();
+ toRemove.Add(kvp.Key);
+ }
+ }
+
+ foreach (var mobile in toRemove)
+ {
+ _activeChallenges.Remove(mobile);
+ }
+ }
+
+ public static void CancelChallenge(Mobile from)
+ {
+ lock (_activeChallenges)
+ {
+ if (_activeChallenges.TryGetValue(from, out var challenge))
+ {
+ challenge.TimeoutTimer?.Stop();
+ _activeChallenges.Remove(from);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
From 534a7f11c7e2739610dc181bdea21c95dcbfc0da Mon Sep 17 00:00:00 2001
From: Bohica <53943479+Bohicatv@users.noreply.github.com>
Date: Thu, 13 Nov 2025 06:11:53 -0800
Subject: [PATCH 2/7] adds namespace
---
Projects/UOContent/Engines/Harvest/Fishing.cs | 1 +
Projects/UOContent/Engines/Harvest/Lumberjacking.cs | 1 +
Projects/UOContent/Engines/Harvest/Mining.cs | 1 +
3 files changed, 3 insertions(+)
diff --git a/Projects/UOContent/Engines/Harvest/Fishing.cs b/Projects/UOContent/Engines/Harvest/Fishing.cs
index 99de3d11d..920f8e3bd 100644
--- a/Projects/UOContent/Engines/Harvest/Fishing.cs
+++ b/Projects/UOContent/Engines/Harvest/Fishing.cs
@@ -3,6 +3,7 @@ using Server.Engines.Quests.Collector;
using Server.Items;
using Server.Mobiles;
using Server.Spells;
+using Server.Engines.AntiBot;
namespace Server.Engines.Harvest
{
diff --git a/Projects/UOContent/Engines/Harvest/Lumberjacking.cs b/Projects/UOContent/Engines/Harvest/Lumberjacking.cs
index 95f4c0fa1..b850d07dd 100644
--- a/Projects/UOContent/Engines/Harvest/Lumberjacking.cs
+++ b/Projects/UOContent/Engines/Harvest/Lumberjacking.cs
@@ -1,6 +1,7 @@
using System;
using Server.Items;
using Server.Targeting;
+using Server.Engines.AntiBot;
namespace Server.Engines.Harvest
{
diff --git a/Projects/UOContent/Engines/Harvest/Mining.cs b/Projects/UOContent/Engines/Harvest/Mining.cs
index 60c734343..33970951d 100644
--- a/Projects/UOContent/Engines/Harvest/Mining.cs
+++ b/Projects/UOContent/Engines/Harvest/Mining.cs
@@ -2,6 +2,7 @@ using System;
using Server.Items;
using Server.Mobiles;
using Server.Targeting;
+using Server.Engines.AntiBot;
namespace Server.Engines.Harvest
{
From 535a194c885872720c5d31b4553d4754c1aa8bda Mon Sep 17 00:00:00 2001
From: Bohica <53943479+Bohicatv@users.noreply.github.com>
Date: Thu, 13 Nov 2025 06:13:01 -0800
Subject: [PATCH 3/7] adds copyright
---
.../Systems/AntiBotSystem/AntiBotGump.cs | 15 +++++++++++++++
.../Systems/AntiBotSystem/AntiBotSystem.cs | 15 +++++++++++++++
2 files changed, 30 insertions(+)
diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
index cfe607894..f7d397c8d 100644
--- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
+++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
@@ -1,3 +1,18 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2025 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: AntiBotGump.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 Server.Gumps;
using Server.Network;
diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
index b36af735a..e9ff316cb 100644
--- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
+++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
@@ -1,3 +1,18 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2025 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: AntiBotSystem.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.Gumps;
From 316f25d9af6034ad6db5363dce716885d90650ca Mon Sep 17 00:00:00 2001
From: Bohica <53943479+Bohicatv@users.noreply.github.com>
Date: Thu, 13 Nov 2025 07:12:19 -0800
Subject: [PATCH 4/7] removes locks
---
.../Systems/AntiBotSystem/AntiBotSystem.cs | 108 ++++++++----------
1 file changed, 48 insertions(+), 60 deletions(-)
diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
index e9ff316cb..fbf1dff15 100644
--- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
+++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
@@ -43,72 +43,63 @@ namespace Server.Engines.AntiBot
return true;
}
- lock (_activeChallenges)
+ CleanupExpiredChallenges();
+
+ if (_activeChallenges.TryGetValue(from, out var existing))
{
- CleanupExpiredChallenges();
-
- if (_activeChallenges.TryGetValue(from, out var existing))
- {
- from.CloseGump();
- from.SendGump(new AntiBotGump(from, existing.Code));
- return false;
- }
-
- var challenge = new AntiBotChallenge
- {
- Code = Utility.RandomMinMax(1000, 9999),
- ChallengeExpiry = Core.Now.Add(ChallengeTimeout),
- SuccessCallback = onSuccess
- };
-
- challenge.TimeoutTimer = Timer.DelayCall(ChallengeTimeout, () =>
- {
- lock (_activeChallenges)
- {
- if (_activeChallenges.ContainsKey(from))
- {
- _activeChallenges.Remove(from);
- from.SendMessage("Anti-Bot: Verification timed out. Disconnecting...");
- from.NetState?.Disconnect("Anti-Bot: Verification failed by timing out.");
- }
- }
- });
-
- _activeChallenges[from] = challenge;
from.CloseGump();
- from.SendGump(new AntiBotGump(from, challenge.Code));
+ from.SendGump(new AntiBotGump(from, existing.Code));
return false;
}
+
+ var challenge = new AntiBotChallenge
+ {
+ Code = Utility.RandomMinMax(1000, 9999),
+ ChallengeExpiry = Core.Now.Add(ChallengeTimeout),
+ SuccessCallback = onSuccess
+ };
+
+ challenge.TimeoutTimer = Timer.DelayCall(ChallengeTimeout, () =>
+ {
+ if (_activeChallenges.ContainsKey(from))
+ {
+ _activeChallenges.Remove(from);
+ from.SendMessage("Anti-Bot: Verification timed out. Disconnecting...");
+ from.NetState?.Disconnect("Anti-Bot: Verification failed by timing out.");
+ }
+ });
+
+ _activeChallenges[from] = challenge;
+ from.CloseGump();
+ from.SendGump(new AntiBotGump(from, challenge.Code));
+ return false;
}
internal static void ProcessResponse(Mobile from, int enteredCode, bool cancelled)
{
- lock (_activeChallenges)
+ if (!_activeChallenges.TryGetValue(from, out var challenge))
{
- if (!_activeChallenges.TryGetValue(from, out var challenge))
- {
- return;
- }
+ return;
+ }
- challenge.TimeoutTimer?.Stop();
- _activeChallenges.Remove(from);
+ challenge.TimeoutTimer?.Stop();
+ _activeChallenges.Remove(from);
- if (cancelled)
- {
- from.SendMessage("Anti-Bot: Verification cancelled. Disconnecting...");
- from.NetState?.Disconnect("Anti-Bot: Verification failed by cancellation.");
- return;
- }
+ if (cancelled)
+ {
+ from.SendMessage("Anti-Bot: Verification cancelled. Disconnecting...");
+ from.NetState?.Disconnect("Anti-Bot: Verification failed by cancellation.");
+ return;
+ }
- if (enteredCode == challenge.Code)
- {
- from.SendMessage("Anti-Bot: Verification successful!!!");
- }
- else
- {
- from.SendMessage("Anti-Bot: Incorrect number. Disconnecting...");
- from.NetState?.Disconnect("Anti-Bot: Verification failed by incorrect number.");
- }
+ if (enteredCode == challenge.Code)
+ {
+ from.SendMessage("Anti-Bot: Verification successful!!!");
+ }
+ else
+ {
+ from.SendMessage("Anti-Bot: Incorrect number. Disconnecting...");
+ from.NetState?.Disconnect("Anti-Bot: Verification failed by incorrect number.");
}
}
@@ -134,13 +125,10 @@ namespace Server.Engines.AntiBot
public static void CancelChallenge(Mobile from)
{
- lock (_activeChallenges)
+ if (_activeChallenges.TryGetValue(from, out var challenge))
{
- if (_activeChallenges.TryGetValue(from, out var challenge))
- {
- challenge.TimeoutTimer?.Stop();
- _activeChallenges.Remove(from);
- }
+ challenge.TimeoutTimer?.Stop();
+ _activeChallenges.Remove(from);
}
}
}
From da2ac1f26853f33b8f3091988a149d5cd473f8e4 Mon Sep 17 00:00:00 2001
From: Bohica <53943479+Bohicatv@users.noreply.github.com>
Date: Thu, 13 Nov 2025 10:28:50 -0800
Subject: [PATCH 5/7] adds gump for cloudflare turnstile
---
.../AntiBotSystem/AntiBotTurnstileGump.cs | 85 +++++++++++++++++++
1 file changed, 85 insertions(+)
create mode 100644 Projects/UOContent/Systems/AntiBotSystem/AntiBotTurnstileGump.cs
diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotTurnstileGump.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotTurnstileGump.cs
new file mode 100644
index 000000000..22cc4c3ab
--- /dev/null
+++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotTurnstileGump.cs
@@ -0,0 +1,85 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2025 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: AntiBotTurnstileGump.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 Server.Gumps;
+using Server.Network;
+using System.Diagnostics;
+
+namespace Server.Engines.AntiBot
+{
+ public class AntiBotTurnstileGump : Gump
+ {
+ private readonly Mobile _from;
+ private readonly string _challengeId;
+
+ public AntiBotTurnstileGump(Mobile from, string challengeId) : base(150, 150)
+ {
+ _from = from;
+ _challengeId = challengeId;
+
+ Closable = false;
+ Disposable = false;
+ Draggable = true;
+ Resizable = false;
+
+ AddPage(0);
+ AddBackground(0, 0, 350, 220, 9270);
+
+ AddHtml(20, 20, 310, 25, "Anti-Bot Verification", false, false);
+ AddHtml(20, 50, 310, 40, "Please verify by web browser:", false, false);
+ AddHtml(20, 75, 310, 40, $"{AntiBotSystem.VerificationUrl}?id={challengeId}", false, false);
+ AddHtml(20, 100, 310, 50, "You have 5 minutes to complete verification.
Cancelling or timing out will disconnect you from the server.", false, false);
+
+ AddButton(30, 170, 4005, 4007, 1, GumpButtonType.Reply, 0); // Open Browser
+ AddHtml(60, 173, 100, 20, "Open Browser", false, false);
+
+ AddButton(230, 170, 4017, 4019, 0, GumpButtonType.Reply, 0); // Cancel
+ AddHtml(260, 173, 60, 20, "Cancel", false, false);
+ }
+
+ public override void OnResponse(NetState sender, in RelayInfo info)
+ {
+ var from = sender?.Mobile;
+ if (from == null)
+ {
+ return;
+ }
+
+ switch (info.ButtonID)
+ {
+ case 1: // Open Browser
+ try
+ {
+ var url = $"{AntiBotSystem.VerificationUrl}?id={_challengeId}";
+ Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
+ _from.SendMessage("Browser opened. Complete verification and return to game.");
+ _from.SendGump(this); // Keep gump open
+ }
+ catch
+ {
+ _from.SendMessage("Could not open browser. Please visit the verification URL manually.");
+ _from.SendGump(this); // Keep gump open
+ }
+ break;
+
+ case 0: // Cancel
+ AntiBotSystem.CancelChallenge(_from);
+ _from.SendMessage("Anti-Bot: Verification cancelled. Disconnecting...");
+ _from.NetState?.Disconnect("Anti-Bot: Verification cancelled.");
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
From ac028c7b56bdd6ad139ffcd9e63c320c54379ea2 Mon Sep 17 00:00:00 2001
From: Bohica <53943479+Bohicatv@users.noreply.github.com>
Date: Thu, 13 Nov 2025 10:30:25 -0800
Subject: [PATCH 6/7] adds cloudflare turnstile verification
---
.../Systems/AntiBotSystem/AntiBotSystem.cs | 122 ++++++++++++++++--
1 file changed, 110 insertions(+), 12 deletions(-)
diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
index fbf1dff15..c2224d105 100644
--- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
+++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotSystem.cs
@@ -15,6 +15,9 @@
using System;
using System.Collections.Generic;
+using System.Net.Http;
+using System.Text.Json;
+using System.Threading.Tasks;
using Server.Gumps;
using Server.Mobiles;
@@ -24,17 +27,34 @@ namespace Server.Engines.AntiBot
{
private class AntiBotChallenge
{
- public int Code { get; set; }
+ public string ChallengeId { get; set; }
public DateTime ChallengeExpiry { get; set; }
public Action SuccessCallback { get; set; }
public Timer TimeoutTimer { get; set; }
+ public bool UseTurnstile { get; set; }
+ public int FallbackCode { get; set; }
}
private static readonly Dictionary _activeChallenges = new();
+ private static readonly HttpClient _httpClient = new();
+ // enable or disable the entire anti-bot verification system
public static bool Enabled { get; set; } = true;
- public static int MaxAttempts { get; set; } = 1;
- public static TimeSpan ChallengeTimeout { get; set; } = TimeSpan.FromMinutes(2);
+
+ // if set to false (default) = uses a number matching verification
+ // if set to true = uses Cloudflare's Turnstile verification
+ public static bool UseTurnstile { get; set; } = false;
+
+ // Cloudflare Turnstile
+ // secret key from your Cloudflare account (https://dash.cloudflare.com/login)
+ public static string TurnstileSecretKey { get; set; } = "YOUR_SECRET_KEY";
+
+ // the base URL where the widget is hosted (must support HTTPS)
+ // view the docs here: https://developers.cloudflare.com/turnstile/
+ public static string VerificationUrl { get; set; } = "https://yourwebserver.com/verify";
+
+ // timeout before disconnecting the user (applies to both Turnstile and number match verification)
+ public static TimeSpan ChallengeTimeout { get; set; } = TimeSpan.FromMinutes(5);
public static bool CheckPlayer(Mobile from, Action onSuccess)
{
@@ -45,18 +65,19 @@ namespace Server.Engines.AntiBot
CleanupExpiredChallenges();
- if (_activeChallenges.TryGetValue(from, out var existing))
+ if (_activeChallenges.ContainsKey(from))
{
- from.CloseGump();
- from.SendGump(new AntiBotGump(from, existing.Code));
return false;
}
+ var challengeId = Guid.NewGuid().ToString("N")[..8];
var challenge = new AntiBotChallenge
{
- Code = Utility.RandomMinMax(1000, 9999),
+ ChallengeId = challengeId,
ChallengeExpiry = Core.Now.Add(ChallengeTimeout),
- SuccessCallback = onSuccess
+ SuccessCallback = onSuccess,
+ UseTurnstile = UseTurnstile,
+ FallbackCode = Utility.RandomMinMax(1000, 9999)
};
challenge.TimeoutTimer = Timer.DelayCall(ChallengeTimeout, () =>
@@ -70,11 +91,40 @@ namespace Server.Engines.AntiBot
});
_activeChallenges[from] = challenge;
- from.CloseGump();
- from.SendGump(new AntiBotGump(from, challenge.Code));
+
+ if (UseTurnstile)
+ {
+ from.CloseGump();
+ from.SendGump(new AntiBotTurnstileGump(from, challengeId));
+ }
+ else
+ {
+ from.CloseGump();
+ from.SendGump(new AntiBotGump(from, challenge.FallbackCode));
+ }
+
return false;
}
+ public static async Task VerifyTurnstileToken(string token)
+ {
+ var formData = new List>
+ {
+ new("secret", TurnstileSecretKey),
+ new("response", token)
+ };
+
+ var response = await _httpClient.PostAsync(
+ "https://challenges.cloudflare.com/turnstile/v0/siteverify",
+ new FormUrlEncodedContent(formData)
+ );
+
+ var jsonResponse = await response.Content.ReadAsStringAsync();
+ var result = JsonSerializer.Deserialize(jsonResponse);
+
+ return result?.Success == true;
+ }
+
internal static void ProcessResponse(Mobile from, int enteredCode, bool cancelled)
{
if (!_activeChallenges.TryGetValue(from, out var challenge))
@@ -92,9 +142,10 @@ namespace Server.Engines.AntiBot
return;
}
- if (enteredCode == challenge.Code)
+ if (enteredCode == challenge.FallbackCode)
{
- from.SendMessage("Anti-Bot: Verification successful!!!");
+ from.SendMessage("Anti-Bot: Verification successful!");
+ challenge.SuccessCallback?.Invoke();
}
else
{
@@ -103,6 +154,48 @@ namespace Server.Engines.AntiBot
}
}
+ internal static async void ProcessTurnstileResponse(Mobile from, string token)
+ {
+ if (!_activeChallenges.TryGetValue(from, out var challenge))
+ {
+ return;
+ }
+
+ challenge.TimeoutTimer?.Stop();
+ _activeChallenges.Remove(from);
+
+ var isValid = await VerifyTurnstileToken(token);
+
+ if (isValid)
+ {
+ from.SendMessage("Anti-Bot: Verification successful!");
+ challenge.SuccessCallback?.Invoke();
+ }
+ else
+ {
+ from.SendMessage("Anti-Bot: Verification failed. Disconnecting...");
+ from.NetState?.Disconnect("Anti-Bot: Verification failed.");
+ }
+ }
+
+ public static void ProcessTurnstileVerification(string challengeId, string token)
+ {
+ Mobile targetMobile = null;
+ foreach (var kvp in _activeChallenges)
+ {
+ if (kvp.Value.ChallengeId == challengeId)
+ {
+ targetMobile = kvp.Key;
+ break;
+ }
+ }
+
+ if (targetMobile != null)
+ {
+ ProcessTurnstileResponse(targetMobile, token);
+ }
+ }
+
private static void CleanupExpiredChallenges()
{
var now = Core.Now;
@@ -131,5 +224,10 @@ namespace Server.Engines.AntiBot
_activeChallenges.Remove(from);
}
}
+
+ private class TurnstileResponse
+ {
+ public bool Success { get; set; }
+ }
}
}
\ No newline at end of file
From 9bdc255ae5ccc085d5c97dc746e5005699ec2617 Mon Sep 17 00:00:00 2001
From: Bohica <53943479+Bohicatv@users.noreply.github.com>
Date: Thu, 13 Nov 2025 10:31:45 -0800
Subject: [PATCH 7/7] adjusts antibot gump
---
.../UOContent/Systems/AntiBotSystem/AntiBotGump.cs | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
index f7d397c8d..559692a39 100644
--- a/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
+++ b/Projects/UOContent/Systems/AntiBotSystem/AntiBotGump.cs
@@ -37,17 +37,17 @@ namespace Server.Engines.AntiBot
AddBackground(0, 0, 350, 220, 9270);
AddHtml(20, 20, 310, 25, "Anti-Bot Verification", false, false);
- AddHtml(20, 50, 310, 40, $"Please enter the following number:", false, false);
+ AddHtml(20, 50, 310, 40, "Please enter the following number:", false, false);
AddHtml(20, 55, 310, 40, $"
{code}", false, false);
- AddHtml(20, 100, 310, 50, $"You have 2 minutes to input the correct number.
Incorrect numbers, cancellations, or timeouts will disconnect you from the server.", false, false);
+ AddHtml(20, 100, 310, 50, "You have 5 minutes to input the correct number.
Incorrect numbers, cancellations, or timeouts will disconnect you from the server.", false, false);
AddBackground(20, 160, 200, 25, 3000);
AddTextEntry(25, 165, 190, 20, 0, 0, "");
-
+
AddButton(230, 160, 4005, 4007, 1, GumpButtonType.Reply, 0); // Submit
- AddButton(290, 160, 4017, 4019, 0, GumpButtonType.Reply, 0); // Cancel
-
AddHtml(230, 190, 40, 20, "Submit", false, false);
+
+ AddButton(290, 160, 4017, 4019, 0, GumpButtonType.Reply, 0); // Cancel
AddHtml(290, 190, 40, 20, "Cancel", false, false);
}
@@ -67,7 +67,7 @@ namespace Server.Engines.AntiBot
var textEntry = info.GetTextEntry(0);
if (textEntry != null && !int.TryParse(textEntry.Trim(), out enteredCode))
{
- enteredCode = -1; // Invalid input - will cause disconnect
+ enteredCode = -1;
}
}