Optimize ChatBot loading.

This commit is contained in:
BruceChen 2022-12-21 14:01:05 +08:00
parent 096ea0c70c
commit 87026e1bfb
13 changed files with 264 additions and 249 deletions

View file

@ -19,7 +19,7 @@ namespace MinecraftClient.CommandHandler.ArgumentType
McClient? client = CmdResult.currentHandler;
if (client != null)
{
var botList = client.GetLoadedChatBots();
Scripting.ChatBot[] botList = client.GetLoadedChatBots();
foreach (var bot in botList)
builder.Suggest(bot.GetType().Name);
}

View file

@ -21,15 +21,18 @@ namespace MinecraftClient.CommandHandler.ArgumentType
McClient? client = CmdResult.currentHandler;
if (client != null)
{
var bot = (Map?)client.GetLoadedChatBots().Find(bot => bot.GetType().Name == "Map");
if (bot != null)
foreach (var bot in client.GetLoadedChatBots())
{
var mapList = bot.cachedMaps;
foreach (var map in mapList)
if (bot.GetType() == typeof(Map))
{
string mapName = map.Key.ToString();
if (mapName.StartsWith(builder.RemainingLowerCase, StringComparison.InvariantCultureIgnoreCase))
builder.Suggest(mapName);
var mapList = ((Map)bot).cachedMaps;
foreach (var map in mapList)
{
string mapName = map.Key.ToString();
if (mapName.StartsWith(builder.RemainingLowerCase, StringComparison.InvariantCultureIgnoreCase))
builder.Suggest(mapName);
}
break;
}
}
}

View file

@ -53,7 +53,7 @@ namespace MinecraftClient.Commands
private int DoListBot(CmdResult r)
{
McClient handler = CmdResult.currentHandler!;
int length = handler.GetLoadedChatBots().Count;
int length = handler.GetLoadedChatBots().Length;
if (length == 0)
return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_bots_noloaded);
@ -73,7 +73,7 @@ namespace MinecraftClient.Commands
McClient handler = CmdResult.currentHandler!;
if (botName.ToLower().Equals("all", StringComparison.OrdinalIgnoreCase))
{
if (handler.GetLoadedChatBots().Count == 0)
if (handler.GetLoadedChatBots().Length == 0)
return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_bots_noloaded);
else
{
@ -83,12 +83,20 @@ namespace MinecraftClient.Commands
}
else
{
ChatBot? bot = handler.GetLoadedChatBots().Find(bot => bot.GetType().Name.ToLower() == botName.ToLower());
if (bot == null)
ChatBot? target = null;
foreach (ChatBot bot in handler.GetLoadedChatBots())
{
if (bot.GetType().Name.Equals(botName, StringComparison.InvariantCultureIgnoreCase))
{
target = bot;
break;
}
}
if (target == null)
return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_bots_notfound, botName));
else
{
handler.BotUnLoad(bot).Wait();
handler.BotUnLoad(target).Wait();
return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_bots_unloaded, botName));
}
}

View file

@ -47,7 +47,7 @@ namespace MinecraftClient.Commands
if (Settings.Config.Main.SetServerIP(new Settings.MainConfigHealper.MainConfig.ServerInfoConfig(server), true))
{
Program.Restart(keepAccountAndServerSettings: true);
Program.SetRestart(keepAccountAndServerSettings: true);
return r.SetAndReturn(Status.Done);
}
else
@ -64,7 +64,7 @@ namespace MinecraftClient.Commands
if (Settings.Config.Main.SetServerIP(new Settings.MainConfigHealper.MainConfig.ServerInfoConfig(args[0]), true))
{
Program.Restart(keepAccountAndServerSettings: true);
Program.SetRestart(keepAccountAndServerSettings: true);
return string.Empty;
}
else

View file

@ -45,7 +45,7 @@ namespace MinecraftClient.Commands
private int DoExit(CmdResult r, int code = 0)
{
Program.Exit(code);
Program.SetExit(code);
return r.SetAndReturn(CmdResult.Status.Done);
}
}

View file

@ -47,7 +47,7 @@ namespace MinecraftClient.Commands
if (!Settings.Config.Main.Advanced.SetAccount(account))
return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_connect_unknown, account));
}
Program.Restart(keepAccountAndServerSettings: true);
Program.SetRestart(keepAccountAndServerSettings: true);
return r.SetAndReturn(CmdResult.Status.Done);
}
@ -62,7 +62,7 @@ namespace MinecraftClient.Commands
return string.Format(Translations.cmd_connect_unknown, account);
}
}
Program.Restart(keepAccountAndServerSettings: true);
Program.SetRestart(keepAccountAndServerSettings: true);
return string.Empty;
}
}

View file

@ -103,9 +103,9 @@ namespace MinecraftClient
private double sampleSum = 0;
// ChatBot
private ChatBot[] chatbots = Array.Empty<ChatBot>();
private static ChatBot[] botsOnHold = Array.Empty<ChatBot>();
private bool OldChatBotUpdateTrigger = false;
private readonly List<ChatBot> bots = new();
private static readonly List<ChatBot> botsOnHold = new();
private bool networkPacketCaptureEnabled = false;
public int GetServerPort() { return port; }
@ -131,17 +131,33 @@ namespace MinecraftClient
public int GetProtocolVersion() { return protocolversion; }
public ILogger GetLogger() { return Log; }
public int GetPlayerEntityID() { return playerEntityID; }
public List<ChatBot> GetLoadedChatBots() { return new List<ChatBot>(bots); }
public ChatBot[] GetLoadedChatBots() { return chatbots; }
private TcpClient? tcpClient;
private IMinecraftCom? handler;
private CancellationTokenSource? cmdprompt;
private readonly CancellationToken CancelToken;
private Task TimeoutDetectorTask = Task.CompletedTask;
private readonly CancellationTokenSource CancelTokenSource;
public ILogger Log;
public static void LoadCommandsAndChatbots()
{
/* Load commands from the 'Commands' namespace */
Type[] cmds_classes = Program.GetTypesInNamespace("MinecraftClient.Commands");
foreach (Type type in cmds_classes)
{
if (type.IsSubclassOf(typeof(Command)))
{
Command cmd = (Command)Activator.CreateInstance(type)!;
cmd.RegisterCommand(dispatcher);
}
}
/* Load ChatBots */
botsOnHold = GetBotsToRegister();
foreach (ChatBot bot in botsOnHold)
bot.Initialize();
}
/// <summary>
/// Starts the main chat client, wich will login to the server using the MinecraftCom class.
/// </summary>
@ -151,11 +167,10 @@ namespace MinecraftClient
/// <param name="serverPort">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param>
/// <param name="forgeInfo">ForgeInfo item stating that Forge is enabled</param>
public McClient(CancellationToken cancelToken, string serverHost, ushort serverPort)
public McClient(string serverHost, ushort serverPort, CancellationTokenSource cancelTokenSource)
{
CancelToken = cancelToken;
CancelTokenSource = cancelTokenSource;
dispatcher = new();
CmdResult.currentHandler = this;
terrainAndMovementsEnabled = Config.Main.Advanced.TerrainAndMovements;
inventoryHandlingEnabled = Config.Main.Advanced.InventoryHandling;
@ -179,12 +194,6 @@ namespace MinecraftClient
Log.ChatEnabled = Config.Logging.ChatMessages;
Log.WarnEnabled = Config.Logging.WarningMessages;
Log.ErrorEnabled = Config.Logging.ErrorMessages;
/* Load commands from Commands namespace */
LoadCommands();
if (botsOnHold.Count == 0)
RegisterBots();
}
public async Task Login(HttpClient httpClient, SessionToken session, PlayerKeyPair? playerKeyPair, int protocolversion, ForgeInfo? forgeInfo)
@ -204,26 +213,22 @@ namespace MinecraftClient
tcpClient.ReceiveBufferSize = 1024 * 1024;
tcpClient.ReceiveTimeout = Config.Main.Advanced.TcpTimeout * 1000; // Default: 30 seconds
handler = ProtocolHandler.GetProtocolHandler(CancelToken, tcpClient, protocolversion, forgeInfo, this);
handler = ProtocolHandler.GetProtocolHandler(CancelTokenSource.Token, tcpClient, protocolversion, forgeInfo, this);
Log.Info(Translations.mcc_version_supported);
TimeoutDetectorTask = Task.Run(TimeoutDetector, CancelToken);
_ = Task.Run(TimeoutDetector, CancelTokenSource.Token);
try
{
if (await handler.Login(httpClient, this.playerKeyPair, session))
{
foreach (ChatBot bot in botsOnHold)
BotLoad(bot, false);
botsOnHold.Clear();
Log.Info(string.Format(Translations.mcc_joined, Config.Main.Advanced.InternalCmdChar.ToLogString()));
cmdprompt = new CancellationTokenSource();
ConsoleInteractive.ConsoleReader.BeginReadThread(cmdprompt);
ConsoleInteractive.ConsoleReader.MessageReceived += ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange += ConsoleIO.AutocompleteHandler;
chatbots = botsOnHold;
botsOnHold = Array.Empty<ChatBot>();
foreach (ChatBot bot in chatbots)
{
bot.SetHandler(this);
bot.AfterGameJoined();
}
return;
}
@ -249,14 +254,11 @@ namespace MinecraftClient
Log.Info(string.Format(Translations.mcc_reconnect, ReconnectionAttemptsLeft));
Thread.Sleep(5000);
ReconnectionAttemptsLeft--;
Program.Restart();
Program.SetRestart();
}
else if (InternalConfig.InteractiveMode)
else
{
ConsoleInteractive.ConsoleReader.StopReadThread();
ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler;
Program.Exit();
Program.SetExit();
}
throw new Exception("Initialization failed.");
@ -264,38 +266,55 @@ namespace MinecraftClient
public async Task StartUpdating()
{
Log.Info(string.Format(Translations.mcc_joined, Config.Main.Advanced.InternalCmdChar.ToLogString()));
ConsoleInteractive.ConsoleReader.MessageReceived += ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange += ConsoleIO.AutocompleteHandler;
ConsoleInteractive.ConsoleReader.BeginReadThread();
await handler!.StartUpdating();
ConsoleInteractive.ConsoleReader.StopReadThread();
ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler;
ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived;
ConsoleIO.CancelAutocomplete();
ConsoleIO.WriteLine(string.Empty);
}
/// <summary>
/// Register bots
/// </summary>
private void RegisterBots(bool reload = false)
private static ChatBot[] GetBotsToRegister(bool reload = false)
{
if (Config.ChatBot.Alerts.Enabled) { BotLoad(new Alerts()); }
if (Config.ChatBot.AntiAFK.Enabled) { BotLoad(new AntiAFK()); }
if (Config.ChatBot.AutoAttack.Enabled) { BotLoad(new AutoAttack()); }
if (Config.ChatBot.AutoCraft.Enabled) { BotLoad(new AutoCraft()); }
if (Config.ChatBot.AutoDig.Enabled) { BotLoad(new AutoDig()); }
if (Config.ChatBot.AutoDrop.Enabled) { BotLoad(new AutoDrop()); }
if (Config.ChatBot.AutoEat.Enabled) { BotLoad(new AutoEat()); }
if (Config.ChatBot.AutoFishing.Enabled) { BotLoad(new AutoFishing()); }
if (Config.ChatBot.AutoRelog.Enabled) { BotLoad(new AutoRelog()); }
if (Config.ChatBot.AutoRespond.Enabled) { BotLoad(new AutoRespond()); }
if (Config.ChatBot.ChatLog.Enabled) { BotLoad(new ChatLog()); }
if (Config.ChatBot.DiscordBridge.Enabled) { BotLoad(new DiscordBridge()); }
if (Config.ChatBot.Farmer.Enabled) { BotLoad(new Farmer()); }
if (Config.ChatBot.FollowPlayer.Enabled) { BotLoad(new FollowPlayer()); }
if (Config.ChatBot.HangmanGame.Enabled) { BotLoad(new HangmanGame()); }
if (Config.ChatBot.Mailer.Enabled) { BotLoad(new Mailer()); }
if (Config.ChatBot.Map.Enabled) { BotLoad(new Map()); }
if (Config.ChatBot.PlayerListLogger.Enabled) { BotLoad(new PlayerListLogger()); }
if (Config.ChatBot.RemoteControl.Enabled) { BotLoad(new RemoteControl()); }
if (Config.ChatBot.ReplayCapture.Enabled && reload) { BotLoad(new ReplayCapture()); }
if (Config.ChatBot.ScriptScheduler.Enabled) { BotLoad(new ScriptScheduler()); }
if (Config.ChatBot.TelegramBridge.Enabled) { BotLoad(new TelegramBridge()); }
List<ChatBot> chatbotList = new();
if (Config.ChatBot.Alerts.Enabled) { chatbotList.Add(new Alerts()); }
if (Config.ChatBot.AntiAFK.Enabled) { chatbotList.Add(new AntiAFK()); }
if (Config.ChatBot.AutoAttack.Enabled) { chatbotList.Add(new AutoAttack()); }
if (Config.ChatBot.AutoCraft.Enabled) { chatbotList.Add(new AutoCraft()); }
if (Config.ChatBot.AutoDig.Enabled) { chatbotList.Add(new AutoDig()); }
if (Config.ChatBot.AutoDrop.Enabled) { chatbotList.Add(new AutoDrop()); }
if (Config.ChatBot.AutoEat.Enabled) { chatbotList.Add(new AutoEat()); }
if (Config.ChatBot.AutoFishing.Enabled) { chatbotList.Add(new AutoFishing()); }
if (Config.ChatBot.AutoRelog.Enabled) { chatbotList.Add(new AutoRelog()); }
if (Config.ChatBot.AutoRespond.Enabled) { chatbotList.Add(new AutoRespond()); }
if (Config.ChatBot.ChatLog.Enabled) { chatbotList.Add(new ChatLog()); }
if (Config.ChatBot.DiscordBridge.Enabled) { chatbotList.Add(new DiscordBridge()); }
if (Config.ChatBot.Farmer.Enabled) { chatbotList.Add(new Farmer()); }
if (Config.ChatBot.FollowPlayer.Enabled) { chatbotList.Add(new FollowPlayer()); }
if (Config.ChatBot.HangmanGame.Enabled) { chatbotList.Add(new HangmanGame()); }
if (Config.ChatBot.Mailer.Enabled) { chatbotList.Add(new Mailer()); }
if (Config.ChatBot.Map.Enabled) { chatbotList.Add(new Map()); }
if (Config.ChatBot.PlayerListLogger.Enabled) { chatbotList.Add(new PlayerListLogger()); }
if (Config.ChatBot.RemoteControl.Enabled) { chatbotList.Add(new RemoteControl()); }
// if (Config.ChatBot.ReplayCapture.Enabled && reload) { chatbotList.Add(new ReplayCapture()); }
if (Config.ChatBot.ScriptScheduler.Enabled) { chatbotList.Add(new ScriptScheduler()); }
if (Config.ChatBot.TelegramBridge.Enabled) { chatbotList.Add(new TelegramBridge()); }
// Add your ChatBot here by uncommenting and adapting
// BotLoad(new ChatBots.YourBot());
return chatbotList.ToArray();
}
/// <summary>
@ -304,10 +323,13 @@ namespace MinecraftClient
/// </summary>
private async Task TrySendMessageToServer()
{
while (nextMessageSendTime < DateTime.Now && chatQueue.TryDequeue(out string? text))
if (handler != null)
{
await handler!.SendChatMessage(text, playerKeyPair);
nextMessageSendTime = DateTime.Now + TimeSpan.FromSeconds(Config.Main.Advanced.MessageCooldown);
while (nextMessageSendTime < DateTime.Now && chatQueue.TryDequeue(out string? text))
{
await handler.SendChatMessage(text, playerKeyPair);
nextMessageSendTime = DateTime.Now + TimeSpan.FromSeconds(Config.Main.Advanced.MessageCooldown);
}
}
}
@ -317,7 +339,7 @@ namespace MinecraftClient
public async Task OnUpdate()
{
OldChatBotUpdateTrigger = !OldChatBotUpdateTrigger;
foreach (ChatBot bot in bots.ToArray())
foreach (ChatBot bot in chatbots)
{
await bot.OnClientTickAsync();
if (OldChatBotUpdateTrigger)
@ -390,15 +412,24 @@ namespace MinecraftClient
private async Task TimeoutDetector()
{
UpdateKeepAlive();
var periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(Config.Main.Advanced.TcpTimeout));
while (await periodicTimer.WaitForNextTickAsync(CancelToken) && !CancelToken.IsCancellationRequested)
using PeriodicTimer periodicTimer = new(TimeSpan.FromSeconds(Config.Main.Advanced.TcpTimeout));
try
{
if (lastKeepAlive.AddSeconds(Config.Main.Advanced.TcpTimeout) < DateTime.Now)
while (await periodicTimer.WaitForNextTickAsync(CancelTokenSource.Token) && !CancelTokenSource.IsCancellationRequested)
{
OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, Translations.error_timeout);
return;
if (lastKeepAlive.AddSeconds(Config.Main.Advanced.TcpTimeout) < DateTime.Now)
{
OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, Translations.error_timeout);
return;
}
}
}
catch (AggregateException e)
{
if (e.InnerException is not OperationCanceledException)
throw;
}
catch (OperationCanceledException) { }
}
/// <summary>
@ -414,10 +445,10 @@ namespace MinecraftClient
/// </summary>
public void Disconnect()
{
DispatchBotEvent(bot => bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, ""));
DispatchBotEvent(bot => bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, string.Empty));
botsOnHold.Clear();
botsOnHold.AddRange(bots);
botsOnHold = chatbots;
chatbots = Array.Empty<ChatBot>();
if (handler != null)
{
@ -425,15 +456,7 @@ namespace MinecraftClient
handler.Dispose();
}
if (cmdprompt != null)
{
cmdprompt.Cancel();
cmdprompt = null;
}
tcpClient?.Close();
TimeoutDetectorTask.Wait();
}
/// <summary>
@ -441,12 +464,6 @@ namespace MinecraftClient
/// </summary>
public void OnConnectionLost(ChatBot.DisconnectReason reason, string message)
{
ConsoleInteractive.ConsoleReader.StopReadThread();
ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler;
ConsoleIO.CancelAutocomplete();
switch (reason)
{
case ChatBot.DisconnectReason.ConnectionLost:
@ -469,8 +486,8 @@ namespace MinecraftClient
}
// Process AutoRelog last to make sure other bots can perform their cleanup tasks first (issue #1517)
List<ChatBot> onDisconnectBotList = bots.Where(bot => bot is not AutoRelog).ToList();
onDisconnectBotList.AddRange(bots.Where(bot => bot is AutoRelog));
List<ChatBot> onDisconnectBotList = chatbots.Where(bot => bot is not AutoRelog).ToList();
onDisconnectBotList.AddRange(chatbots.Where(bot => bot is AutoRelog));
int restartDelay = -1;
foreach (ChatBot bot in onDisconnectBotList)
@ -490,9 +507,9 @@ namespace MinecraftClient
}
if (restartDelay < 0)
Program.Exit(handleFailure: true);
Program.SetExit(handleFailure: true);
else
Program.Restart(restartDelay, true);
Program.SetRestart(restartDelay, true);
handler!.Dispose();
@ -601,11 +618,11 @@ namespace MinecraftClient
{
dispatcher.Execute(parse);
foreach (ChatBot bot in bots.ToArray())
foreach (ChatBot bot in chatbots)
{
try
{
bot.OnInternalCommand(command, string.Join(" ", Command.GetArgs(command)), result);
bot.OnInternalCommand(command, string.Join(' ', Command.GetArgs(command)), result);
}
catch (Exception e)
{
@ -634,32 +651,6 @@ namespace MinecraftClient
}
}
public void LoadCommands()
{
/* Load commands from the 'Commands' namespace */
if (!commandsLoaded)
{
Type[] cmds_classes = Program.GetTypesInNamespace("MinecraftClient.Commands");
foreach (Type type in cmds_classes)
{
if (type.IsSubclassOf(typeof(Command)))
{
try
{
Command cmd = (Command)Activator.CreateInstance(type)!;
cmd.RegisterCommand(dispatcher);
}
catch (Exception e)
{
Log.Warn(e.Message);
}
}
}
commandsLoaded = true;
}
}
/// <summary>
/// Reload settings and bots
/// </summary>
@ -676,10 +667,16 @@ namespace MinecraftClient
public async Task ReloadBots()
{
await UnloadAllBots();
RegisterBots(true);
if (tcpClient!.Client.Connected)
bots.ForEach(bot => bot.AfterGameJoined());
ChatBot[] bots = GetBotsToRegister(true);
foreach (ChatBot bot in bots)
{
bot.SetHandler(this);
bot.Initialize();
if (handler != null)
bot.AfterGameJoined();
}
chatbots = bots;
}
/// <summary>
@ -687,8 +684,11 @@ namespace MinecraftClient
/// </summary>
public async Task UnloadAllBots()
{
foreach (ChatBot bot in GetLoadedChatBots())
await BotUnLoad(bot);
foreach (ChatBot bot in chatbots)
bot.OnUnload();
chatbots = Array.Empty<ChatBot>();
registeredBotPluginChannels.Clear();
await Task.CompletedTask;
}
#endregion
@ -698,14 +698,14 @@ namespace MinecraftClient
/// <summary>
/// Load a new bot
/// </summary>
public void BotLoad(ChatBot b, bool init = true)
public void BotLoad(ChatBot bot, bool init = true)
{
b.SetHandler(this);
bots.Add(b);
bot.SetHandler(this);
chatbots = new List<ChatBot>(chatbots) { bot }.ToArray();
if (init)
DispatchBotEvent(bot => bot.Initialize(), new ChatBot[] { b });
bot.Initialize();
if (handler != null)
DispatchBotEvent(bot => bot.AfterGameJoined(), new ChatBot[] { b });
bot.AfterGameJoined();
}
/// <summary>
@ -715,7 +715,11 @@ namespace MinecraftClient
{
b.OnUnload();
bots.RemoveAll(item => ReferenceEquals(item, b));
List<ChatBot> botList = new();
botList.AddRange(from bot in chatbots
where !ReferenceEquals(bot, b)
select bot);
chatbots = botList.ToArray();
// ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon.
var botRegistrations = registeredBotPluginChannels.Where(entry => entry.Value.Contains(b)).ToList();
@ -725,14 +729,6 @@ namespace MinecraftClient
}
}
/// <summary>
/// Clear bots
/// </summary>
public void BotClear()
{
bots.Clear();
}
/// <summary>
/// Get Terrain and Movements status.
/// </summary>
@ -2156,18 +2152,8 @@ namespace MinecraftClient
/// <param name="botList">Only fire the event for the specified bot list (default: all bots)</param>
private void DispatchBotEvent(Action<ChatBot> action, IEnumerable<ChatBot>? botList = null)
{
ChatBot[] selectedBots;
if (botList != null)
{
selectedBots = botList.ToArray();
}
else
{
selectedBots = bots.ToArray();
}
foreach (ChatBot bot in selectedBots)
botList ??= chatbots;
foreach (ChatBot bot in botList)
{
try
{
@ -3000,9 +2986,8 @@ namespace MinecraftClient
/// <param name="latency">Latency</param>
public void OnLatencyUpdate(Guid uuid, int latency)
{
if (onlinePlayers.ContainsKey(uuid))
if (onlinePlayers.TryGetValue(uuid, out PlayerInfo? player))
{
PlayerInfo player = onlinePlayers[uuid];
player.Ping = latency;
string playerName = player.Name;
foreach (KeyValuePair<int, Entity> ent in entities)

View file

@ -59,7 +59,9 @@ namespace MinecraftClient
private static int CurrentThreadId;
private static bool RestartKeepSettings = false;
private static int RestartAfter = -1, Exitcode = 0;
private static CancellationTokenSource McClientCancelToken = new();
private static Task McClientInit = Task.CompletedTask;
private static CancellationTokenSource McClientCancelTokenSource = new();
/// <summary>
/// The main entry point of Minecraft Console Client
@ -126,6 +128,11 @@ namespace MinecraftClient
}
}
// Setup exit cleaning code
ExitCleanUp.Add(() => { DoExit(); });
McClientInit = Task.Run(McClient.LoadCommandsAndChatbots);
if (!string.IsNullOrWhiteSpace(Config.Main.Advanced.ConsoleTitle))
{
InternalConfig.Username = "New Window";
@ -167,9 +174,6 @@ namespace MinecraftClient
}
}
// Setup exit cleaning code
ExitCleanUp.Add(() => { DoExit(); });
ConsoleIO.SuppressPrinting(true);
// Asking the user to type in missing data such as Username and Password
bool useBrowser = Config.Main.General.AccountType == LoginType.microsoft && Config.Main.General.Method == LoginMethod.browser;
@ -233,8 +237,6 @@ namespace MinecraftClient
McClient = null;
}
ConsoleInteractive.ConsoleReader.StopReadThread();
if (RestartAfter < 0 && FailureInfo.hasFailure)
RestartAfter = HandleFailure();
@ -255,7 +257,7 @@ namespace MinecraftClient
RestartAfter = -1;
RestartKeepSettings = false;
FailureInfo.hasFailure = false;
McClientCancelToken = new CancellationTokenSource();
McClientCancelTokenSource = new CancellationTokenSource();
}
DoExit();
@ -721,7 +723,6 @@ namespace MinecraftClient
var loginTask = LoginAsync(loginHttpClient);
var getServerInfoTask = GetServerInfoAsync(loginHttpClient, loginTask);
var refreshPlayerKeyTask = RefreshPlayerKeyPair(loginHttpClient, loginTask);
var initMcClientTask = Task.Run(() => { return new McClient(McClientCancelToken.Token, InternalConfig.ServerIP, InternalConfig.ServerPort); });
(result, session, playerKeyPair) = await loginTask;
if (result == ProtocolHandler.LoginResult.Success && session != null)
@ -732,7 +733,7 @@ namespace MinecraftClient
Console.Title = Config.AppVar.ExpandVars(Config.Main.Advanced.ConsoleTitle);
if (Config.Main.Advanced.PlayerHeadAsIcon && OperatingSystem.IsWindows())
_ = Task.Run(async () => { await ConsoleIcon.SetPlayerIconAsync(loginHttpClient, InternalConfig.Username); });
_ = Task.Run(async () => { await ConsoleIcon.SetPlayerIconAsync(loginHttpClient, InternalConfig.Username); });
if (Config.Logging.DebugMessages)
ConsoleIO.WriteLine(string.Format(Translations.debug_session_id, session.ID));
@ -754,8 +755,10 @@ namespace MinecraftClient
{
try
{
await McClientInit;
McClient = new McClient(InternalConfig.ServerIP, InternalConfig.ServerPort, McClientCancelTokenSource);
// Start the main TCP client
McClient = await initMcClientTask;
await McClient.Login(loginHttpClient, session, playerKeyPair, protocolversion, forgeInfo);
}
catch (NotSupportedException)
@ -835,23 +838,23 @@ namespace MinecraftClient
/// Disconnect the current client from the server and restart it
/// </summary>
/// <param name="delaySeconds">Optional delay, in seconds, before restarting</param>
public static void Restart(int delayMilliseconds = 0, bool keepAccountAndServerSettings = false)
public static void SetRestart(int delayMilliseconds = 0, bool keepAccountAndServerSettings = false)
{
RestartAfter = Math.Max(0, delayMilliseconds);
RestartKeepSettings = keepAccountAndServerSettings;
McClientCancelToken.Cancel();
McClientCancelTokenSource.Cancel();
}
/// <summary>
/// Disconnect the current client from the server and exit the app
/// </summary>
public static void Exit(int exitcode = 0, bool handleFailure = false)
public static void SetExit(int exitcode = 0, bool handleFailure = false)
{
McClientCancelToken.Cancel();
Exitcode = exitcode;
RestartAfter = -1;
Exitcode = exitcode;
if (handleFailure)
FailureInfo.Record();
McClientCancelTokenSource.Cancel();
}
public static void DoExit()
@ -904,7 +907,6 @@ namespace MinecraftClient
}
}
ConsoleIO.WriteLine(string.Empty);
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_disconnected, Config.Main.Advanced.InternalCmdChar.ToLogString()));
ConsoleIO.WriteLineFormatted(Translations.mcc_press_exit, acceptnewlines: true);

View file

@ -540,30 +540,27 @@ namespace MinecraftClient.Protocol.Handlers
{
ConsoleIO.WriteLine(Translations.mcc_session);
bool needCheckSession = true;
string serverHash = CryptoHandler.GetServerHash(serverIDhash, serverPublicKey, secretKey);
if (session.SessionPreCheckTask != null && session.ServerInfoHash != null && serverHash == session.ServerInfoHash)
{
try
(bool preCheckResult, string? error) = await session.SessionPreCheckTask;
if (!preCheckResult)
{
bool preCheckResult = await session.SessionPreCheckTask;
if (preCheckResult) // PreCheck Successed
needCheckSession = false;
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected,
string.IsNullOrEmpty(error) ? Translations.mcc_session_fail : $"{Translations.mcc_session_fail} Error: {error}.");
return false;
}
catch (HttpRequestException) { }
session.SessionPreCheckTask = null;
}
if (needCheckSession)
else
{
var sessionCheck = await ProtocolHandler.SessionCheckAsync(httpClient, uuid, sessionID, serverHash);
(bool sessionCheck, string? error) = await ProtocolHandler.SessionCheckAsync(httpClient, uuid, sessionID, serverHash);
if (sessionCheck)
{
SessionCache.StoreServerInfo($"{InternalConfig.ServerIP}:{InternalConfig.ServerPort}", serverIDhash, serverPublicKey);
}
else
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, Translations.mcc_session_fail);
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected,
string.IsNullOrEmpty(error) ? Translations.mcc_session_fail : $"{Translations.mcc_session_fail} Error: {error}.");
return false;
}
}

View file

@ -95,7 +95,7 @@ namespace MinecraftClient.Protocol.Handlers
private readonly CancellationToken CancelToken;
public Protocol18Handler(CancellationToken cancelToken, TcpClient Client, int protocolVersion, IMinecraftComHandler handler, ForgeInfo? forgeInfo)
public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler, ForgeInfo? forgeInfo, CancellationToken cancelToken)
{
CancelToken = cancelToken;
ConsoleIO.SetAutoCompleteEngine(this);
@ -213,23 +213,32 @@ namespace MinecraftClient.Protocol.Handlers
private async Task MainTicker()
{
var periodicTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(1000 / 20));
while (await periodicTimer.WaitForNextTickAsync(CancelToken) && !CancelToken.IsCancellationRequested)
using PeriodicTimer periodicTimer = new(TimeSpan.FromMilliseconds(1000 / 20));
try
{
try
while (await periodicTimer.WaitForNextTickAsync(CancelToken) && !CancelToken.IsCancellationRequested)
{
await handler.OnUpdate();
}
catch (Exception e)
{
if (Config.Logging.DebugMessages)
try
{
ConsoleIO.WriteLine($"{e.GetType().Name} when ticking: {e.Message}");
if (e.StackTrace != null)
ConsoleIO.WriteLine(e.StackTrace);
await handler.OnUpdate();
}
catch (Exception e)
{
if (Config.Logging.DebugMessages)
{
ConsoleIO.WriteLine($"{e.GetType().Name} when ticking: {e.Message}");
if (e.StackTrace != null)
ConsoleIO.WriteLine(e.StackTrace);
}
}
}
}
catch (AggregateException e)
{
if (e.InnerException is not OperationCanceledException)
throw;
}
catch (OperationCanceledException) { }
}
/// <summary>
@ -252,6 +261,11 @@ namespace MinecraftClient.Protocol.Handlers
catch (ObjectDisposedException) { break; }
catch (OperationCanceledException) { break; }
catch (NullReferenceException) { break; }
catch (AggregateException e)
{
if (e.InnerException is TaskCanceledException)
break;
}
}
if (!CancelToken.IsCancellationRequested)
@ -1760,14 +1774,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Disconnect from the server, cancel network reading.
/// </summary>
public void Dispose()
{
try
{
socketWrapper.Disconnect();
}
catch { }
}
public void Dispose() { }
/// <summary>
/// Send a packet to the server. Packet ID, compression, and encryption will be handled automatically.
@ -1934,31 +1941,27 @@ namespace MinecraftClient.Protocol.Handlers
{
log.Info(Translations.mcc_session);
bool needCheckSession = true;
string serverHash = CryptoHandler.GetServerHash(serverIDhash, serverPublicKey, secretKey);
if (session.SessionPreCheckTask != null && session.ServerInfoHash != null && serverHash == session.ServerInfoHash)
{
try
(bool preCheckResult, string? error) = await session.SessionPreCheckTask;
if (!preCheckResult)
{
bool preCheckResult = await session.SessionPreCheckTask;
if (preCheckResult) // PreCheck Successed
needCheckSession = false;
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected,
string.IsNullOrEmpty(error) ? Translations.mcc_session_fail : $"{Translations.mcc_session_fail} Error: {error}.");
return false;
}
catch (HttpRequestException) { }
session.SessionPreCheckTask = null;
}
if (needCheckSession)
else
{
var sessionCheck = await ProtocolHandler.SessionCheckAsync(httpClient, uuid, sessionID, serverHash);
(bool sessionCheck, string? error) = await ProtocolHandler.SessionCheckAsync(httpClient, uuid, sessionID, serverHash);
if (sessionCheck)
{
SessionCache.StoreServerInfo($"{InternalConfig.ServerIP}:{InternalConfig.ServerPort}", serverIDhash, serverPublicKey);
}
else
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, Translations.mcc_session_fail);
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected,
string.IsNullOrEmpty(error) ? Translations.mcc_session_fail : $"{Translations.mcc_session_fail} Error: {error}.");
return false;
}
}

View file

@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
@ -20,6 +21,7 @@ using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Protocol.Session;
using MinecraftClient.Proxy;
using PInvoke;
using static MinecraftClient.Json;
using static MinecraftClient.Settings;
using static MinecraftClient.Settings.MainConfigHealper.MainConfig.GeneralConfig;
@ -139,7 +141,7 @@ namespace MinecraftClient.Protocol
int[] supportedVersions_Protocol18 = { 4, 5, 47, 107, 108, 109, 110, 210, 315, 316, 335, 338, 340, 393, 401, 404, 477, 480, 485, 490, 498, 573, 575, 578, 735, 736, 751, 753, 754, 755, 756, 757, 758, 759, 760 };
if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1)
return new Protocol18Handler(cancelToken, Client, ProtocolVersion, Handler, forgeInfo);
return new Protocol18Handler(Client, ProtocolVersion, Handler, forgeInfo, cancelToken);
throw new NotSupportedException(string.Format(Translations.exception_version_unsupport, ProtocolVersion));
}
@ -743,6 +745,12 @@ namespace MinecraftClient.Protocol
public string? serverId { init; get; }
}
private record SessionCheckFailResult
{
public string? error { init; get; }
public string? path { init; get; }
}
/// <summary>
/// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server
/// </summary>
@ -750,7 +758,7 @@ namespace MinecraftClient.Protocol
/// <param name="accesstoken">Session ID</param>
/// <param name="serverhash">Server ID</param>
/// <returns>TRUE if session was successfully checked</returns>
public static async Task<bool> SessionCheckAsync(HttpClient httpClient, string uuid, string accesstoken, string serverhash)
public static async Task<Tuple<bool, string?>> SessionCheckAsync(HttpClient httpClient, string uuid, string accesstoken, string serverhash)
{
SessionCheckPayload payload = new()
{
@ -759,24 +767,33 @@ namespace MinecraftClient.Protocol
serverId = serverhash,
};
using HttpRequestMessage request = new(HttpMethod.Post, "https://sessionserver.mojang.com/session/minecraft/join");
try
{
using HttpRequestMessage request = new(HttpMethod.Post, "https://sessionserver.mojang.com/session/minecraft/join");
request.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
request.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
request.Headers.UserAgent.Clear();
request.Headers.UserAgent.ParseAdd($"MCC/{Program.Version}");
request.Headers.UserAgent.Clear();
request.Headers.UserAgent.ParseAdd($"MCC/{Program.Version}");
using HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
using HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
return response.IsSuccessStatusCode;
//try
//{
// string json_request = $"{{\"accessToken\":\"{accesstoken}\",\"selectedProfile\":\"{uuid}\",\"serverId\":\"{serverhash}\"}}";
// (int code, _) = await DoHTTPSPost("sessionserver.mojang.com", "/session/minecraft/join", json_request);
// return (code >= 200 && code < 300);
//}
//catch { return false; }
if (response.IsSuccessStatusCode)
return new(true, null);
else
{
SessionCheckFailResult jsonData = (await response.Content.ReadFromJsonAsync<SessionCheckFailResult>())!;
return new(false, jsonData.error);
}
}
catch (HttpRequestException e)
{
return new(false, $"HttpRequestException: {e.Message}");
}
catch (JsonException e)
{
return new(false, $"JsonException: {e.Message}");
}
}
/// <summary>

View file

@ -35,7 +35,7 @@ namespace MinecraftClient.Protocol.Session
public string? ServerInfoHash = null;
[JsonIgnore]
public Task<bool>? SessionPreCheckTask = null;
public Task<Tuple<bool, string?>>? SessionPreCheckTask = null;
public SessionToken()
{

View file

@ -38,7 +38,7 @@ namespace MinecraftClient.Scripting
public void SetHandler(McClient handler) { _handler = handler; }
protected void SetMaster(ChatBot master) { this.master = master; }
protected void LoadBot(ChatBot bot) { Handler.BotUnLoad(bot).Wait(); Handler.BotLoad(bot); }
protected List<ChatBot> GetLoadedChatBots() { return Handler.GetLoadedChatBots(); }
protected ChatBot[] GetLoadedChatBots() { return Handler.GetLoadedChatBots(); }
protected void UnLoadBot(ChatBot bot) { Handler.BotUnLoad(bot).Wait(); }
private McClient? _handler = null;
private ChatBot? master = null;
@ -945,7 +945,7 @@ namespace MinecraftClient.Scripting
ConsoleIO.WriteLogLine(string.Format(Translations.chatbot_reconnect, botName));
}
McClient.ReconnectionAttemptsLeft = ExtraAttempts;
Program.Restart(delaySeconds * 10, keepAccountAndServerSettings);
Program.SetRestart(delaySeconds * 10, keepAccountAndServerSettings);
}
/// <summary>
@ -953,7 +953,7 @@ namespace MinecraftClient.Scripting
/// </summary>
protected void DisconnectAndExit()
{
Program.Exit();
Program.SetExit();
}
/// <summary>