#region References
using Server.Gumps;
using Server.Network;
using System;
using System.Collections.Generic;
#endregion References
namespace Server.Misc
{
public static class Assistants
{
[Flags]
public enum Features : ulong
{
None = 0,
FilterWeather = 1ul << 0, // Weather Filter
FilterLight = 1ul << 1, // Light Filter
SmartTarget = 1ul << 2, // Smart Last Target
RangedTarget = 1ul << 3, // Range Check Last Target
AutoOpenDoors = 1ul << 4, // Automatically Open Doors
DequipOnCast = 1ul << 5, // Unequip Weapon on spell cast
AutoPotionEquip = 1ul << 6, // Un/Re-equip weapon on potion use
PoisonedChecks = 1ul << 7, // Block heal If poisoned/Macro If Poisoned condition/Heal or Cure self
LoopedMacros = 1ul << 8, // Disallow Looping macros, For loops, and macros that call other macros
UseOnceAgent = 1ul << 9, // The use once agent
RestockAgent = 1ul << 10, // The restock agent
SellAgent = 1ul << 11, // The sell agent
BuyAgent = 1ul << 12, // The buy agent
PotionHotkeys = 1ul << 13, // All potion hotkeys
RandomTargets = 1ul << 14, // All random target hotkeys (not target next, last target, target self)
ClosestTargets = 1ul << 15, // All closest target hotkeys
OverheadHealth = 1ul << 16, // Health and Mana/Stam messages shown over player's heads
// AssistUO Only
AutolootAgent = 1ul << 17, // The autoloot agent
BoneCutterAgent = 1ul << 18, // The bone cutter agent
JScriptMacros = 1ul << 19, // Javascript macro engine
AutoRemount = 1ul << 20, // Auto remount after dismount
All = ~None // Every feature possible
}
public class Settings
{
///
/// Enable assistant negotiator?
///
public static bool Enabled { get; set; } = false;
///
/// When true, this will cause anyone who does not negotiate.
/// (include those not running allowed assistants at all) to be disconnected from the server.
///
public static bool KickOnFailure { get; set; } = true;
public static Features DisallowedFeatures { get; private set; } = Features.None;
///
/// How long to wait for a handshake response before showing warning and disconnecting.
///
public static TimeSpan HandshakeTimeout { get; set; } = TimeSpan.FromSeconds(30.0);
///
/// How long to show warning message before they are disconnected.
///
public static TimeSpan DisconnectDelay { get; set; } = TimeSpan.FromSeconds(15.0);
public static string WarningMessage { get; set; } = "The server was unable to negotiate features with your assistant. You must download and run an updated version of AssistUO or Razor.
Make sure you've checked the option Negotiate features with server, once you have this box checked you may log in and play normally.
You will be disconnected shortly.";
public static void Configure()
{
//DisallowFeature( Features.FilterLight );
}
public static void DisallowFeature(Features feature)
{
SetDisallowed(feature, true);
}
public static void AllowFeature(Features feature)
{
SetDisallowed(feature, false);
}
public static void SetDisallowed(Features feature, bool value)
{
if (value)
DisallowedFeatures |= feature;
else
DisallowedFeatures &= ~feature;
}
}
public class Negotiator
{
private static readonly Dictionary _Handshakes = new Dictionary();
public static void Initialize()
{
if (!Settings.Enabled) return;
EventSink.Login += OnLogin;
ProtocolExtensions.Register(0xFF, true, OnResponse);
}
private static void OnLogin(LoginEventArgs e)
{
var m = e.Mobile;
if (m == null || m.NetState == null || !m.NetState.Running || m.NetState.IsEnhancedClient)
return;
m.Send(new BeginHandshake());
if (_Handshakes.TryGetValue(m, out var t))
t?.Stop();
_Handshakes[m] = Timer.DelayCall(Settings.HandshakeTimeout, OnTimeout, m);
}
private static void OnResponse(NetState state, PacketReader pvSrc)
{
if (state == null || state.Mobile == null || !state.Running) return;
Mobile m = state.Mobile;
if (!_Handshakes.TryGetValue(m, out var t))
return;
t?.Stop();
_Handshakes.Remove(m);
}
private static void OnTimeout(Mobile m)
{
if (m == null || !_Handshakes.TryGetValue(m, out var t)) return;
t?.Stop();
_Handshakes.Remove(m);
if (Settings.KickOnFailure)
{
if (m.NetState == null || !m.NetState.Running)
return;
m.SendGump(new WarningGump(1060635, 30720, Settings.WarningMessage, 0xFFC000, 420, 250, null, null));
if (m.AccessLevel <= AccessLevel.Player)
_Handshakes[m] = Timer.DelayCall(Settings.DisconnectDelay, OnForceDisconnect, m);
}
else
Console.WriteLine($"Player '{m}' failed to negotiate features.");
}
private static void OnForceDisconnect(Mobile m)
{
if (m == null) return;
m.NetState?.Dispose();
_Handshakes.Remove(m);
Console.WriteLine($"Player {m} kicked (Failed assistant handshake)");
}
private sealed class BeginHandshake : ProtocolExtension
{
public BeginHandshake()
: base(0xFE, 8)
{
m_Stream.Write((uint)((ulong)Settings.DisallowedFeatures >> 32));
m_Stream.Write((uint)((ulong)Settings.DisallowedFeatures & 0xFFFFFFFF));
}
}
}
}
}