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; McClient? client = CmdResult.currentHandler;
if (client != null) if (client != null)
{ {
var botList = client.GetLoadedChatBots(); Scripting.ChatBot[] botList = client.GetLoadedChatBots();
foreach (var bot in botList) foreach (var bot in botList)
builder.Suggest(bot.GetType().Name); builder.Suggest(bot.GetType().Name);
} }

View file

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

View file

@ -53,7 +53,7 @@ namespace MinecraftClient.Commands
private int DoListBot(CmdResult r) private int DoListBot(CmdResult r)
{ {
McClient handler = CmdResult.currentHandler!; McClient handler = CmdResult.currentHandler!;
int length = handler.GetLoadedChatBots().Count; int length = handler.GetLoadedChatBots().Length;
if (length == 0) if (length == 0)
return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_bots_noloaded); return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_bots_noloaded);
@ -73,7 +73,7 @@ namespace MinecraftClient.Commands
McClient handler = CmdResult.currentHandler!; McClient handler = CmdResult.currentHandler!;
if (botName.ToLower().Equals("all", StringComparison.OrdinalIgnoreCase)) 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); return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_bots_noloaded);
else else
{ {
@ -83,12 +83,20 @@ namespace MinecraftClient.Commands
} }
else else
{ {
ChatBot? bot = handler.GetLoadedChatBots().Find(bot => bot.GetType().Name.ToLower() == botName.ToLower()); ChatBot? target = null;
if (bot == 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)); return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_bots_notfound, botName));
else else
{ {
handler.BotUnLoad(bot).Wait(); handler.BotUnLoad(target).Wait();
return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_bots_unloaded, botName)); 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)) 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); return r.SetAndReturn(Status.Done);
} }
else else
@ -64,7 +64,7 @@ namespace MinecraftClient.Commands
if (Settings.Config.Main.SetServerIP(new Settings.MainConfigHealper.MainConfig.ServerInfoConfig(args[0]), true)) if (Settings.Config.Main.SetServerIP(new Settings.MainConfigHealper.MainConfig.ServerInfoConfig(args[0]), true))
{ {
Program.Restart(keepAccountAndServerSettings: true); Program.SetRestart(keepAccountAndServerSettings: true);
return string.Empty; return string.Empty;
} }
else else

View file

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

View file

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

View file

@ -103,9 +103,9 @@ namespace MinecraftClient
private double sampleSum = 0; private double sampleSum = 0;
// ChatBot // ChatBot
private ChatBot[] chatbots = Array.Empty<ChatBot>();
private static ChatBot[] botsOnHold = Array.Empty<ChatBot>();
private bool OldChatBotUpdateTrigger = false; private bool OldChatBotUpdateTrigger = false;
private readonly List<ChatBot> bots = new();
private static readonly List<ChatBot> botsOnHold = new();
private bool networkPacketCaptureEnabled = false; private bool networkPacketCaptureEnabled = false;
public int GetServerPort() { return port; } public int GetServerPort() { return port; }
@ -131,17 +131,33 @@ namespace MinecraftClient
public int GetProtocolVersion() { return protocolversion; } public int GetProtocolVersion() { return protocolversion; }
public ILogger GetLogger() { return Log; } public ILogger GetLogger() { return Log; }
public int GetPlayerEntityID() { return playerEntityID; } public int GetPlayerEntityID() { return playerEntityID; }
public List<ChatBot> GetLoadedChatBots() { return new List<ChatBot>(bots); } public ChatBot[] GetLoadedChatBots() { return chatbots; }
private TcpClient? tcpClient; private TcpClient? tcpClient;
private IMinecraftCom? handler; private IMinecraftCom? handler;
private CancellationTokenSource? cmdprompt; private readonly CancellationTokenSource CancelTokenSource;
private readonly CancellationToken CancelToken;
private Task TimeoutDetectorTask = Task.CompletedTask;
public ILogger Log; 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> /// <summary>
/// Starts the main chat client, wich will login to the server using the MinecraftCom class. /// Starts the main chat client, wich will login to the server using the MinecraftCom class.
/// </summary> /// </summary>
@ -151,11 +167,10 @@ namespace MinecraftClient
/// <param name="serverPort">The server port to use</param> /// <param name="serverPort">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param> /// <param name="protocolversion">Minecraft protocol version to use</param>
/// <param name="forgeInfo">ForgeInfo item stating that Forge is enabled</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; CmdResult.currentHandler = this;
terrainAndMovementsEnabled = Config.Main.Advanced.TerrainAndMovements; terrainAndMovementsEnabled = Config.Main.Advanced.TerrainAndMovements;
inventoryHandlingEnabled = Config.Main.Advanced.InventoryHandling; inventoryHandlingEnabled = Config.Main.Advanced.InventoryHandling;
@ -179,12 +194,6 @@ namespace MinecraftClient
Log.ChatEnabled = Config.Logging.ChatMessages; Log.ChatEnabled = Config.Logging.ChatMessages;
Log.WarnEnabled = Config.Logging.WarningMessages; Log.WarnEnabled = Config.Logging.WarningMessages;
Log.ErrorEnabled = Config.Logging.ErrorMessages; 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) 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.ReceiveBufferSize = 1024 * 1024;
tcpClient.ReceiveTimeout = Config.Main.Advanced.TcpTimeout * 1000; // Default: 30 seconds 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); Log.Info(Translations.mcc_version_supported);
TimeoutDetectorTask = Task.Run(TimeoutDetector, CancelToken); _ = Task.Run(TimeoutDetector, CancelTokenSource.Token);
try try
{ {
if (await handler.Login(httpClient, this.playerKeyPair, session)) if (await handler.Login(httpClient, this.playerKeyPair, session))
{ {
foreach (ChatBot bot in botsOnHold) chatbots = botsOnHold;
BotLoad(bot, false); botsOnHold = Array.Empty<ChatBot>();
foreach (ChatBot bot in chatbots)
botsOnHold.Clear(); {
bot.SetHandler(this);
Log.Info(string.Format(Translations.mcc_joined, Config.Main.Advanced.InternalCmdChar.ToLogString())); bot.AfterGameJoined();
}
cmdprompt = new CancellationTokenSource();
ConsoleInteractive.ConsoleReader.BeginReadThread(cmdprompt);
ConsoleInteractive.ConsoleReader.MessageReceived += ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange += ConsoleIO.AutocompleteHandler;
return; return;
} }
@ -249,14 +254,11 @@ namespace MinecraftClient
Log.Info(string.Format(Translations.mcc_reconnect, ReconnectionAttemptsLeft)); Log.Info(string.Format(Translations.mcc_reconnect, ReconnectionAttemptsLeft));
Thread.Sleep(5000); Thread.Sleep(5000);
ReconnectionAttemptsLeft--; ReconnectionAttemptsLeft--;
Program.Restart(); Program.SetRestart();
} }
else if (InternalConfig.InteractiveMode) else
{ {
ConsoleInteractive.ConsoleReader.StopReadThread(); Program.SetExit();
ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler;
Program.Exit();
} }
throw new Exception("Initialization failed."); throw new Exception("Initialization failed.");
@ -264,38 +266,55 @@ namespace MinecraftClient
public async Task StartUpdating() 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(); await handler!.StartUpdating();
ConsoleInteractive.ConsoleReader.StopReadThread();
ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler;
ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived;
ConsoleIO.CancelAutocomplete();
ConsoleIO.WriteLine(string.Empty);
} }
/// <summary> /// <summary>
/// Register bots /// Register bots
/// </summary> /// </summary>
private void RegisterBots(bool reload = false) private static ChatBot[] GetBotsToRegister(bool reload = false)
{ {
if (Config.ChatBot.Alerts.Enabled) { BotLoad(new Alerts()); } List<ChatBot> chatbotList = new();
if (Config.ChatBot.AntiAFK.Enabled) { BotLoad(new AntiAFK()); }
if (Config.ChatBot.AutoAttack.Enabled) { BotLoad(new AutoAttack()); } if (Config.ChatBot.Alerts.Enabled) { chatbotList.Add(new Alerts()); }
if (Config.ChatBot.AutoCraft.Enabled) { BotLoad(new AutoCraft()); } if (Config.ChatBot.AntiAFK.Enabled) { chatbotList.Add(new AntiAFK()); }
if (Config.ChatBot.AutoDig.Enabled) { BotLoad(new AutoDig()); } if (Config.ChatBot.AutoAttack.Enabled) { chatbotList.Add(new AutoAttack()); }
if (Config.ChatBot.AutoDrop.Enabled) { BotLoad(new AutoDrop()); } if (Config.ChatBot.AutoCraft.Enabled) { chatbotList.Add(new AutoCraft()); }
if (Config.ChatBot.AutoEat.Enabled) { BotLoad(new AutoEat()); } if (Config.ChatBot.AutoDig.Enabled) { chatbotList.Add(new AutoDig()); }
if (Config.ChatBot.AutoFishing.Enabled) { BotLoad(new AutoFishing()); } if (Config.ChatBot.AutoDrop.Enabled) { chatbotList.Add(new AutoDrop()); }
if (Config.ChatBot.AutoRelog.Enabled) { BotLoad(new AutoRelog()); } if (Config.ChatBot.AutoEat.Enabled) { chatbotList.Add(new AutoEat()); }
if (Config.ChatBot.AutoRespond.Enabled) { BotLoad(new AutoRespond()); } if (Config.ChatBot.AutoFishing.Enabled) { chatbotList.Add(new AutoFishing()); }
if (Config.ChatBot.ChatLog.Enabled) { BotLoad(new ChatLog()); } if (Config.ChatBot.AutoRelog.Enabled) { chatbotList.Add(new AutoRelog()); }
if (Config.ChatBot.DiscordBridge.Enabled) { BotLoad(new DiscordBridge()); } if (Config.ChatBot.AutoRespond.Enabled) { chatbotList.Add(new AutoRespond()); }
if (Config.ChatBot.Farmer.Enabled) { BotLoad(new Farmer()); } if (Config.ChatBot.ChatLog.Enabled) { chatbotList.Add(new ChatLog()); }
if (Config.ChatBot.FollowPlayer.Enabled) { BotLoad(new FollowPlayer()); } if (Config.ChatBot.DiscordBridge.Enabled) { chatbotList.Add(new DiscordBridge()); }
if (Config.ChatBot.HangmanGame.Enabled) { BotLoad(new HangmanGame()); } if (Config.ChatBot.Farmer.Enabled) { chatbotList.Add(new Farmer()); }
if (Config.ChatBot.Mailer.Enabled) { BotLoad(new Mailer()); } if (Config.ChatBot.FollowPlayer.Enabled) { chatbotList.Add(new FollowPlayer()); }
if (Config.ChatBot.Map.Enabled) { BotLoad(new Map()); } if (Config.ChatBot.HangmanGame.Enabled) { chatbotList.Add(new HangmanGame()); }
if (Config.ChatBot.PlayerListLogger.Enabled) { BotLoad(new PlayerListLogger()); } if (Config.ChatBot.Mailer.Enabled) { chatbotList.Add(new Mailer()); }
if (Config.ChatBot.RemoteControl.Enabled) { BotLoad(new RemoteControl()); } if (Config.ChatBot.Map.Enabled) { chatbotList.Add(new Map()); }
if (Config.ChatBot.ReplayCapture.Enabled && reload) { BotLoad(new ReplayCapture()); } if (Config.ChatBot.PlayerListLogger.Enabled) { chatbotList.Add(new PlayerListLogger()); }
if (Config.ChatBot.ScriptScheduler.Enabled) { BotLoad(new ScriptScheduler()); } if (Config.ChatBot.RemoteControl.Enabled) { chatbotList.Add(new RemoteControl()); }
if (Config.ChatBot.TelegramBridge.Enabled) { BotLoad(new TelegramBridge()); } // 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 // Add your ChatBot here by uncommenting and adapting
// BotLoad(new ChatBots.YourBot()); // BotLoad(new ChatBots.YourBot());
return chatbotList.ToArray();
} }
/// <summary> /// <summary>
@ -303,13 +322,16 @@ namespace MinecraftClient
/// Note: requires external locking. /// Note: requires external locking.
/// </summary> /// </summary>
private async Task TrySendMessageToServer() private async Task TrySendMessageToServer()
{
if (handler != null)
{ {
while (nextMessageSendTime < DateTime.Now && chatQueue.TryDequeue(out string? text)) while (nextMessageSendTime < DateTime.Now && chatQueue.TryDequeue(out string? text))
{ {
await handler!.SendChatMessage(text, playerKeyPair); await handler.SendChatMessage(text, playerKeyPair);
nextMessageSendTime = DateTime.Now + TimeSpan.FromSeconds(Config.Main.Advanced.MessageCooldown); nextMessageSendTime = DateTime.Now + TimeSpan.FromSeconds(Config.Main.Advanced.MessageCooldown);
} }
} }
}
/// <summary> /// <summary>
/// Called ~20 times per second by the protocol handler /// Called ~20 times per second by the protocol handler
@ -317,7 +339,7 @@ namespace MinecraftClient
public async Task OnUpdate() public async Task OnUpdate()
{ {
OldChatBotUpdateTrigger = !OldChatBotUpdateTrigger; OldChatBotUpdateTrigger = !OldChatBotUpdateTrigger;
foreach (ChatBot bot in bots.ToArray()) foreach (ChatBot bot in chatbots)
{ {
await bot.OnClientTickAsync(); await bot.OnClientTickAsync();
if (OldChatBotUpdateTrigger) if (OldChatBotUpdateTrigger)
@ -390,8 +412,10 @@ namespace MinecraftClient
private async Task TimeoutDetector() private async Task TimeoutDetector()
{ {
UpdateKeepAlive(); UpdateKeepAlive();
var periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(Config.Main.Advanced.TcpTimeout)); using PeriodicTimer periodicTimer = new(TimeSpan.FromSeconds(Config.Main.Advanced.TcpTimeout));
while (await periodicTimer.WaitForNextTickAsync(CancelToken) && !CancelToken.IsCancellationRequested) try
{
while (await periodicTimer.WaitForNextTickAsync(CancelTokenSource.Token) && !CancelTokenSource.IsCancellationRequested)
{ {
if (lastKeepAlive.AddSeconds(Config.Main.Advanced.TcpTimeout) < DateTime.Now) if (lastKeepAlive.AddSeconds(Config.Main.Advanced.TcpTimeout) < DateTime.Now)
{ {
@ -400,6 +424,13 @@ namespace MinecraftClient
} }
} }
} }
catch (AggregateException e)
{
if (e.InnerException is not OperationCanceledException)
throw;
}
catch (OperationCanceledException) { }
}
/// <summary> /// <summary>
/// Update last keep alive to current time /// Update last keep alive to current time
@ -414,10 +445,10 @@ namespace MinecraftClient
/// </summary> /// </summary>
public void Disconnect() public void Disconnect()
{ {
DispatchBotEvent(bot => bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, "")); DispatchBotEvent(bot => bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, string.Empty));
botsOnHold.Clear(); botsOnHold = chatbots;
botsOnHold.AddRange(bots); chatbots = Array.Empty<ChatBot>();
if (handler != null) if (handler != null)
{ {
@ -425,15 +456,7 @@ namespace MinecraftClient
handler.Dispose(); handler.Dispose();
} }
if (cmdprompt != null)
{
cmdprompt.Cancel();
cmdprompt = null;
}
tcpClient?.Close(); tcpClient?.Close();
TimeoutDetectorTask.Wait();
} }
/// <summary> /// <summary>
@ -441,12 +464,6 @@ namespace MinecraftClient
/// </summary> /// </summary>
public void OnConnectionLost(ChatBot.DisconnectReason reason, string message) public void OnConnectionLost(ChatBot.DisconnectReason reason, string message)
{ {
ConsoleInteractive.ConsoleReader.StopReadThread();
ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler;
ConsoleIO.CancelAutocomplete();
switch (reason) switch (reason)
{ {
case ChatBot.DisconnectReason.ConnectionLost: 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) // 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(); List<ChatBot> onDisconnectBotList = chatbots.Where(bot => bot is not AutoRelog).ToList();
onDisconnectBotList.AddRange(bots.Where(bot => bot is AutoRelog)); onDisconnectBotList.AddRange(chatbots.Where(bot => bot is AutoRelog));
int restartDelay = -1; int restartDelay = -1;
foreach (ChatBot bot in onDisconnectBotList) foreach (ChatBot bot in onDisconnectBotList)
@ -490,9 +507,9 @@ namespace MinecraftClient
} }
if (restartDelay < 0) if (restartDelay < 0)
Program.Exit(handleFailure: true); Program.SetExit(handleFailure: true);
else else
Program.Restart(restartDelay, true); Program.SetRestart(restartDelay, true);
handler!.Dispose(); handler!.Dispose();
@ -601,11 +618,11 @@ namespace MinecraftClient
{ {
dispatcher.Execute(parse); dispatcher.Execute(parse);
foreach (ChatBot bot in bots.ToArray()) foreach (ChatBot bot in chatbots)
{ {
try try
{ {
bot.OnInternalCommand(command, string.Join(" ", Command.GetArgs(command)), result); bot.OnInternalCommand(command, string.Join(' ', Command.GetArgs(command)), result);
} }
catch (Exception e) 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> /// <summary>
/// Reload settings and bots /// Reload settings and bots
/// </summary> /// </summary>
@ -676,10 +667,16 @@ namespace MinecraftClient
public async Task ReloadBots() public async Task ReloadBots()
{ {
await UnloadAllBots(); await UnloadAllBots();
RegisterBots(true);
if (tcpClient!.Client.Connected) ChatBot[] bots = GetBotsToRegister(true);
bots.ForEach(bot => bot.AfterGameJoined()); foreach (ChatBot bot in bots)
{
bot.SetHandler(this);
bot.Initialize();
if (handler != null)
bot.AfterGameJoined();
}
chatbots = bots;
} }
/// <summary> /// <summary>
@ -687,8 +684,11 @@ namespace MinecraftClient
/// </summary> /// </summary>
public async Task UnloadAllBots() public async Task UnloadAllBots()
{ {
foreach (ChatBot bot in GetLoadedChatBots()) foreach (ChatBot bot in chatbots)
await BotUnLoad(bot); bot.OnUnload();
chatbots = Array.Empty<ChatBot>();
registeredBotPluginChannels.Clear();
await Task.CompletedTask;
} }
#endregion #endregion
@ -698,14 +698,14 @@ namespace MinecraftClient
/// <summary> /// <summary>
/// Load a new bot /// Load a new bot
/// </summary> /// </summary>
public void BotLoad(ChatBot b, bool init = true) public void BotLoad(ChatBot bot, bool init = true)
{ {
b.SetHandler(this); bot.SetHandler(this);
bots.Add(b); chatbots = new List<ChatBot>(chatbots) { bot }.ToArray();
if (init) if (init)
DispatchBotEvent(bot => bot.Initialize(), new ChatBot[] { b }); bot.Initialize();
if (handler != null) if (handler != null)
DispatchBotEvent(bot => bot.AfterGameJoined(), new ChatBot[] { b }); bot.AfterGameJoined();
} }
/// <summary> /// <summary>
@ -715,7 +715,11 @@ namespace MinecraftClient
{ {
b.OnUnload(); 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. // 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(); 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> /// <summary>
/// Get Terrain and Movements status. /// Get Terrain and Movements status.
/// </summary> /// </summary>
@ -2156,18 +2152,8 @@ namespace MinecraftClient
/// <param name="botList">Only fire the event for the specified bot list (default: all bots)</param> /// <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) private void DispatchBotEvent(Action<ChatBot> action, IEnumerable<ChatBot>? botList = null)
{ {
ChatBot[] selectedBots; botList ??= chatbots;
foreach (ChatBot bot in botList)
if (botList != null)
{
selectedBots = botList.ToArray();
}
else
{
selectedBots = bots.ToArray();
}
foreach (ChatBot bot in selectedBots)
{ {
try try
{ {
@ -3000,9 +2986,8 @@ namespace MinecraftClient
/// <param name="latency">Latency</param> /// <param name="latency">Latency</param>
public void OnLatencyUpdate(Guid uuid, int latency) 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; player.Ping = latency;
string playerName = player.Name; string playerName = player.Name;
foreach (KeyValuePair<int, Entity> ent in entities) foreach (KeyValuePair<int, Entity> ent in entities)

View file

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

View file

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

View file

@ -95,7 +95,7 @@ namespace MinecraftClient.Protocol.Handlers
private readonly CancellationToken CancelToken; 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; CancelToken = cancelToken;
ConsoleIO.SetAutoCompleteEngine(this); ConsoleIO.SetAutoCompleteEngine(this);
@ -213,7 +213,9 @@ namespace MinecraftClient.Protocol.Handlers
private async Task MainTicker() private async Task MainTicker()
{ {
var periodicTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(1000 / 20)); using PeriodicTimer periodicTimer = new(TimeSpan.FromMilliseconds(1000 / 20));
try
{
while (await periodicTimer.WaitForNextTickAsync(CancelToken) && !CancelToken.IsCancellationRequested) while (await periodicTimer.WaitForNextTickAsync(CancelToken) && !CancelToken.IsCancellationRequested)
{ {
try try
@ -231,6 +233,13 @@ namespace MinecraftClient.Protocol.Handlers
} }
} }
} }
catch (AggregateException e)
{
if (e.InnerException is not OperationCanceledException)
throw;
}
catch (OperationCanceledException) { }
}
/// <summary> /// <summary>
/// Start the updating thread. Should be called after login success. /// Start the updating thread. Should be called after login success.
@ -252,6 +261,11 @@ namespace MinecraftClient.Protocol.Handlers
catch (ObjectDisposedException) { break; } catch (ObjectDisposedException) { break; }
catch (OperationCanceledException) { break; } catch (OperationCanceledException) { break; }
catch (NullReferenceException) { break; } catch (NullReferenceException) { break; }
catch (AggregateException e)
{
if (e.InnerException is TaskCanceledException)
break;
}
} }
if (!CancelToken.IsCancellationRequested) if (!CancelToken.IsCancellationRequested)
@ -1760,14 +1774,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary> /// <summary>
/// Disconnect from the server, cancel network reading. /// Disconnect from the server, cancel network reading.
/// </summary> /// </summary>
public void Dispose() public void Dispose() { }
{
try
{
socketWrapper.Disconnect();
}
catch { }
}
/// <summary> /// <summary>
/// Send a packet to the server. Packet ID, compression, and encryption will be handled automatically. /// 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); log.Info(Translations.mcc_session);
bool needCheckSession = true;
string serverHash = CryptoHandler.GetServerHash(serverIDhash, serverPublicKey, secretKey); string serverHash = CryptoHandler.GetServerHash(serverIDhash, serverPublicKey, secretKey);
if (session.SessionPreCheckTask != null && session.ServerInfoHash != null && serverHash == session.ServerInfoHash) 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; handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected,
if (preCheckResult) // PreCheck Successed string.IsNullOrEmpty(error) ? Translations.mcc_session_fail : $"{Translations.mcc_session_fail} Error: {error}.");
needCheckSession = false; return false;
} }
catch (HttpRequestException) { }
session.SessionPreCheckTask = null; session.SessionPreCheckTask = null;
} }
if (needCheckSession)
{
var sessionCheck = await ProtocolHandler.SessionCheckAsync(httpClient, uuid, sessionID, serverHash);
if (sessionCheck)
{
SessionCache.StoreServerInfo($"{InternalConfig.ServerIP}:{InternalConfig.ServerPort}", serverIDhash, serverPublicKey);
}
else else
{ {
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, Translations.mcc_session_fail); (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,
string.IsNullOrEmpty(error) ? Translations.mcc_session_fail : $"{Translations.mcc_session_fail} Error: {error}.");
return false; return false;
} }
} }

View file

@ -4,6 +4,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -20,6 +21,7 @@ using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Protocol.Session; using MinecraftClient.Protocol.Session;
using MinecraftClient.Proxy; using MinecraftClient.Proxy;
using PInvoke; using PInvoke;
using static MinecraftClient.Json;
using static MinecraftClient.Settings; using static MinecraftClient.Settings;
using static MinecraftClient.Settings.MainConfigHealper.MainConfig.GeneralConfig; 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 }; 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) 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)); throw new NotSupportedException(string.Format(Translations.exception_version_unsupport, ProtocolVersion));
} }
@ -743,6 +745,12 @@ namespace MinecraftClient.Protocol
public string? serverId { init; get; } public string? serverId { init; get; }
} }
private record SessionCheckFailResult
{
public string? error { init; get; }
public string? path { init; get; }
}
/// <summary> /// <summary>
/// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server /// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server
/// </summary> /// </summary>
@ -750,7 +758,7 @@ namespace MinecraftClient.Protocol
/// <param name="accesstoken">Session ID</param> /// <param name="accesstoken">Session ID</param>
/// <param name="serverhash">Server ID</param> /// <param name="serverhash">Server ID</param>
/// <returns>TRUE if session was successfully checked</returns> /// <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() SessionCheckPayload payload = new()
{ {
@ -759,6 +767,8 @@ namespace MinecraftClient.Protocol
serverId = serverhash, serverId = serverhash,
}; };
try
{
using HttpRequestMessage request = new(HttpMethod.Post, "https://sessionserver.mojang.com/session/minecraft/join"); 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");
@ -768,15 +778,22 @@ namespace MinecraftClient.Protocol
using HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); using HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
return response.IsSuccessStatusCode; if (response.IsSuccessStatusCode)
return new(true, null);
//try else
//{ {
// string json_request = $"{{\"accessToken\":\"{accesstoken}\",\"selectedProfile\":\"{uuid}\",\"serverId\":\"{serverhash}\"}}"; SessionCheckFailResult jsonData = (await response.Content.ReadFromJsonAsync<SessionCheckFailResult>())!;
// (int code, _) = await DoHTTPSPost("sessionserver.mojang.com", "/session/minecraft/join", json_request); return new(false, jsonData.error);
// return (code >= 200 && code < 300); }
//} }
//catch { return false; } catch (HttpRequestException e)
{
return new(false, $"HttpRequestException: {e.Message}");
}
catch (JsonException e)
{
return new(false, $"JsonException: {e.Message}");
}
} }
/// <summary> /// <summary>

View file

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

View file

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