diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..a3b68bd30a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/MinecraftClient.v11.suo +/MinecraftClient.suo \ No newline at end of file diff --git a/MinecraftClient/.gitignore b/MinecraftClient/.gitignore new file mode 100644 index 0000000000..4ded7c4c7a --- /dev/null +++ b/MinecraftClient/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/obj/ diff --git a/MinecraftClient/Bots.cs b/MinecraftClient/Bots.cs index e9187c18cd..f5c16d737d 100644 --- a/MinecraftClient/Bots.cs +++ b/MinecraftClient/Bots.cs @@ -77,16 +77,19 @@ protected void SendText(string text) protected static string getVerbatim(string text) { - string verbatim = ""; - for (int i = 0; i < text.Length; i++) - { - if (text[i] == '§') - { - i++; //Will skip also the next char - } - else verbatim += text[i]; //Add the char - } - return verbatim; + if ( String.IsNullOrEmpty(text) ) + return String.Empty; + + int idx = 0; + var data = new char[text.Length]; + + for ( int i = 0; i < text.Length; i++ ) + if ( text[i] != '§' ) + data[idx++] = text[i]; + else + i++; + + return new string(data, 0, idx); } /// @@ -95,32 +98,17 @@ protected static string getVerbatim(string text) protected static bool isValidName(string username) { - if (username == "") { return false; } - string validchars = - "abcdefghijklmnopqrstuvwxyz" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - + "1234567890_"; - - bool passe = false; - bool ok = true; - for (int i = 0; i < username.Length; i++) - { - passe = false; - for (int j = 0; j < validchars.Length; j++) - { - if (username[i] == validchars[j]) - { - passe = true; - break; - } - } - if (!passe) - { - ok = false; - break; - } - } - return ok; + if ( String.IsNullOrEmpty(username) ) + return false; + + foreach ( char c in username ) + if ( !((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '_') ) + return false; + + return true; } /// @@ -229,6 +217,15 @@ protected void ReconnectToTheServer(int ExtraAttempts) Program.Restart(); } + /// + /// Disconnect from the server and exit the program + /// + + protected void DisconnectAndExit() + { + Program.Exit(); + } + /// /// Unload the chatbot, and release associated memory. /// @@ -486,14 +483,14 @@ private void start() private string chooseword() { - if (System.IO.File.Exists(English ? "words.txt" : "mots.txt")) + if (System.IO.File.Exists(English ? Settings.Hangman_FileWords_EN : Settings.Hangman_FileWords_FR)) { - string[] dico = System.IO.File.ReadAllLines(English ? "words.txt" : "mots.txt"); + string[] dico = System.IO.File.ReadAllLines(English ? Settings.Hangman_FileWords_EN : Settings.Hangman_FileWords_FR); return dico[new Random().Next(dico.Length)]; } else { - LogToConsole(English ? "Cannot find words.txt !" : "Fichier mots.txt introuvable !"); + LogToConsole(English ? "File not found: " + Settings.Hangman_FileWords_EN : "Fichier introuvable : " + Settings.Hangman_FileWords_FR); return English ? "WORDSAREMISSING" : "DICOMANQUANT"; } } @@ -502,14 +499,14 @@ private string[] getowners() { List owners = new List(); owners.Add("CONSOLE"); - if (System.IO.File.Exists("bot-owners.txt")) + if (System.IO.File.Exists(Settings.Bots_OwnersFile)) { - foreach (string s in System.IO.File.ReadAllLines("bot-owners.txt")) + foreach (string s in System.IO.File.ReadAllLines(Settings.Bots_OwnersFile)) { owners.Add(s.ToUpper()); } } - else LogToConsole(English ? "Cannot find bot-owners.txt !" : "Fichier bot-owners.txt introuvable !"); + else LogToConsole(English ? "File not found: " + Settings.Bots_OwnersFile : "Fichier introuvable : " + Settings.Bots_OwnersFile); return owners.ToArray(); } @@ -552,39 +549,39 @@ private bool winner public class Alerts : ChatBot { - private string[] dictionnary = new string[0]; + private string[] dictionary = new string[0]; private string[] excludelist = new string[0]; public override void Initialize() { - if (System.IO.File.Exists("alerts.txt")) + if (System.IO.File.Exists(Settings.Alerts_MatchesFile)) { - dictionnary = System.IO.File.ReadAllLines("alerts.txt"); + dictionary = System.IO.File.ReadAllLines(Settings.Alerts_MatchesFile); - for (int i = 0; i < dictionnary.Length; i++) + for (int i = 0; i < dictionary.Length; i++) { - dictionnary[i] = dictionnary[i].ToLower(); + dictionary[i] = dictionary[i].ToLower(); } } - else LogToConsole("Cannot find alerts.txt !"); + else LogToConsole("File not found: " + Settings.Alerts_MatchesFile); - if (System.IO.File.Exists("alerts-exclude.txt")) + if (System.IO.File.Exists(Settings.Alerts_ExcludesFile)) { - excludelist = System.IO.File.ReadAllLines("alerts-exclude.txt"); + excludelist = System.IO.File.ReadAllLines(Settings.Alerts_ExcludesFile); for (int i = 0; i < excludelist.Length; i++) { excludelist[i] = excludelist[i].ToLower(); } } - else LogToConsole("Cannot find alerts-exclude.txt !"); + else LogToConsole("File not found : " + Settings.Alerts_ExcludesFile); } public override void GetText(string text) { text = getVerbatim(text); string comp = text.ToLower(); - foreach (string alert in dictionnary) + foreach (string alert in dictionary) { if (comp.Contains(alert)) { @@ -711,6 +708,18 @@ public ChatLog(string file, MessageFilter filter, bool AddDateAndTime) } } + public static MessageFilter str2filter(string filtername) + { + switch (filtername.ToLower()) + { + case "all": return MessageFilter.AllText; + case "messages": return MessageFilter.AllMessages; + case "chat": return MessageFilter.OnlyChat; + case "private": return MessageFilter.OnlyWhispers; + default: return MessageFilter.AllText; + } + } + public override void GetText(string text) { text = getVerbatim(text); @@ -764,7 +773,7 @@ private void save(string tosave) public class AutoRelog : ChatBot { - private string[] dictionnary = new string[0]; + private string[] dictionary = new string[0]; private int attempts; private int delay; @@ -786,23 +795,23 @@ public AutoRelog(int DelayBeforeRelog, int retries) public override void Initialize() { McTcpClient.AttemptsLeft = attempts; - if (System.IO.File.Exists("kickmessages.txt")) + if (System.IO.File.Exists(Settings.AutoRelog_KickMessagesFile)) { - dictionnary = System.IO.File.ReadAllLines("kickmessages.txt"); + dictionary = System.IO.File.ReadAllLines(Settings.AutoRelog_KickMessagesFile); - for (int i = 0; i < dictionnary.Length; i++) + for (int i = 0; i < dictionary.Length; i++) { - dictionnary[i] = dictionnary[i].ToLower(); + dictionary[i] = dictionary[i].ToLower(); } } - else LogToConsole("Cannot find kickmessages.txt !"); + else LogToConsole("File not found: " + Settings.AutoRelog_KickMessagesFile); } public override bool OnDisconnect(DisconnectReason reason, string message) { message = getVerbatim(message); string comp = message.ToLower(); - foreach (string msg in dictionnary) + foreach (string msg in dictionary) { if (comp.Contains(msg)) { @@ -841,5 +850,90 @@ public override void Update() } } } + + /// + /// Runs a list of commands + /// Usage: bot:scripting:filename + /// Script must be placed in the config directory + /// + + public class Scripting : ChatBot + { + private string file; + private string[] lines = new string[0]; + private int sleepticks = 10; + private int sleepticks_interval = 10; + private int nextline = 0; + public Scripting(string filename) + { + file = filename; + } + + public override void Initialize() + { + // Loads the given file from the startup parameters + if (System.IO.File.Exists(file)) + { + lines = System.IO.File.ReadAllLines(file); // Load the given bot text file (containing commands) + } + else + { + LogToConsole("File not found: " + file); + UnloadBot(); //No need to keep the bot active + } + } + + public override void Update() + { + if (sleepticks > 0) { sleepticks--; } + else + { + if (nextline < lines.Length) //Is there an instruction left to interpret? + { + string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line + nextline++; //Move the cursor so that the next time the following line will be interpreted + sleepticks = sleepticks_interval; //Used to delay next command sending and prevent from beign kicked for spamming + + if (instruction_line.Length > 0) + { + if (!instruction_line.StartsWith("//") && !instruction_line.StartsWith("#")) + { + string instruction_name = instruction_line.Split(' ')[0]; + switch (instruction_name.ToLower()) + { + case "send": + SendText(instruction_line.Substring(5, instruction_line.Length - 5)); + break; + case "wait": + int ticks = 10; + try + { + ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5)); + } + catch { } + sleepticks = ticks; + break; + case "disconnect": + DisconnectAndExit(); + break; + case "exit": //Exit bot & stay connected to the server + UnloadBot(); + break; + default: + sleepticks = 0; Update(); //Unknown command : process next line immediately + break; + } + } + else { sleepticks = 0; Update(); } //Comment: process next line immediately + } + } + else + { + //No more instructions to interpret + UnloadBot(); + } + } + } + } } } diff --git a/MinecraftClient/ChatParser.cs b/MinecraftClient/ChatParser.cs index b0d07e3f84..203d6bc700 100644 --- a/MinecraftClient/ChatParser.cs +++ b/MinecraftClient/ChatParser.cs @@ -8,7 +8,7 @@ namespace MinecraftClient /// /// This class parses JSON chat data from MC 1.6+ and returns the appropriate string to be printed. /// - + static class ChatParser { /// @@ -21,7 +21,7 @@ public static string ParseText(string json) { int cursorpos = 0; JSONData jsonData = String2Data(json, ref cursorpos); - return JSONData2String(jsonData).Replace("u0027", "'"); + return JSONData2String(jsonData); } /// @@ -54,11 +54,11 @@ public JSONData(DataType datatype) private static string color2tag(string colorname) { - switch(colorname.ToLower()) + switch (colorname.ToLower()) { case "black": return "§0"; case "dark_blue": return "§1"; - case "dark_green" : return "§2"; + case "dark_green": return "§2"; case "dark_cyan": return "§3"; case "dark_cyanred": return "§4"; case "dark_magenta": return "§5"; @@ -96,9 +96,9 @@ private static void InitRules() TranslationRules["commands.message.display.outgoing"] = "§7You whisper to %s: %s"; //Load an external dictionnary of translation rules - if (System.IO.File.Exists("translations.lang")) + if (System.IO.File.Exists(Settings.TranslationsFile)) { - string[] translations = System.IO.File.ReadAllLines("translations.lang"); + string[] translations = System.IO.File.ReadAllLines(Settings.TranslationsFile); foreach (string line in translations) { if (line.Length > 0) @@ -118,9 +118,9 @@ private static void InitRules() else //No external dictionnary found. { Console.ForegroundColor = ConsoleColor.DarkGray; - ConsoleIO.WriteLine("MC 1.6+ warning: Translations file \"translations.lang\" not found." + ConsoleIO.WriteLine("MC 1.6+ warning: Translations file \"" + Settings.TranslationsFile + "\" not found." + "\nYou can pick a translation file from .minecraft\\assets\\lang\\" - + "\nCopy to the same folder as MinecraftClient & rename to \"translations.lang\"" + + "\nCopy to the same folder as MinecraftClient & rename to \"" + Settings.TranslationsFile + "\"" + "\nSome messages won't be properly printed without this file."); Console.ForegroundColor = ConsoleColor.Gray; } @@ -139,7 +139,15 @@ private static string TranslateString(string rulename, List using_data) if (!init) { InitRules(); init = true; } if (TranslationRules.ContainsKey(rulename)) { - string[] syntax = TranslationRules[rulename].Split(new string[2] { "%s", "%d" }, StringSplitOptions.None); + if ((TranslationRules[rulename].IndexOf("%1$s") >= 0 && TranslationRules[rulename].IndexOf("%2$s") >= 0) + && (TranslationRules[rulename].IndexOf("%1$s") > TranslationRules[rulename].IndexOf("%2$s"))) + { + while (using_data.Count < 2) { using_data.Add(""); } + string tmp = using_data[0]; + using_data[0] = using_data[1]; + using_data[1] = tmp; + } + string[] syntax = TranslationRules[rulename].Split(new string[] { "%s", "%d", "%1$s", "%2$s" }, StringSplitOptions.None); while (using_data.Count < syntax.Length - 1) { using_data.Add(""); } string[] using_array = using_data.ToArray(); string translated = ""; @@ -205,7 +213,25 @@ private static JSONData String2Data(string toparse, ref int cursorpos) cursorpos++; while (toparse[cursorpos] != '"') { - if (toparse[cursorpos] == '\\') { cursorpos++; } + if (toparse[cursorpos] == '\\') + { + try //Unicode character \u0123 + { + if (toparse[cursorpos + 1] == 'u' + && isHex(toparse[cursorpos + 2]) + && isHex(toparse[cursorpos + 3]) + && isHex(toparse[cursorpos + 4]) + && isHex(toparse[cursorpos + 5])) + { + //"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string + data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), System.Globalization.NumberStyles.HexNumber)); + cursorpos += 6; continue; + } + else cursorpos++; //Normal character escapement \" + } + catch (IndexOutOfRangeException) { cursorpos++; } // \u01 + catch (ArgumentOutOfRangeException) { cursorpos++; } // Unicode index 0123 was invalid + } data.StringValue += toparse[cursorpos]; cursorpos++; } @@ -278,7 +304,7 @@ private static string JSONData2String(JSONData data) return colorcode + TranslateString(JSONData2String(data.Properties["translate"]), using_data) + colorcode; } else return ""; - + case JSONData.DataType.Array: string result = ""; foreach (JSONData item in data.DataArray) @@ -293,5 +319,13 @@ private static string JSONData2String(JSONData data) return ""; } + + /// + /// Small function for checking if a char is an hexadecimal char (0-9 A-F a-f) + /// + /// Char to test + /// True if hexadecimal + + private static bool isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); } } } diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index d2a181f5eb..1eb9c7163c 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -13,12 +13,14 @@ namespace MinecraftClient public static class ConsoleIO { public static void Reset() { if (reading) { reading = false; Console.Write("\b \b"); } } + public static void SetAutoCompleteEngine(IAutoComplete engine) { autocomplete_engine = engine; } + private static IAutoComplete autocomplete_engine; private static LinkedList previous = new LinkedList(); private static string buffer = ""; private static string buffer2 = ""; - private static bool consolelock = false; private static bool reading = false; - private static bool writing = false; + private static bool reading_lock = false; + private static bool writing_lock = false; #region Read User Input public static string ReadLine() @@ -32,8 +34,8 @@ public static string ReadLine() while (k.Key != ConsoleKey.Enter) { k = Console.ReadKey(true); - while (writing) { } - consolelock = true; + while (writing_lock) { } + reading_lock = true; switch (k.Key) { case ConsoleKey.Escape: @@ -86,13 +88,29 @@ public static string ReadLine() Console.Write(buffer); } break; + case ConsoleKey.Tab: + if (autocomplete_engine != null && buffer.Length > 0) + { + string[] tmp = buffer.Split(' '); + if (tmp.Length > 0) + { + string word_tocomplete = tmp[tmp.Length - 1]; + string word_autocomplete = autocomplete_engine.AutoComplete(word_tocomplete); + if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != word_tocomplete) + { + while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); } + foreach (char c in word_autocomplete) { AddChar(c); } + } + } + } + break; default: AddChar(k.KeyChar); break; } - consolelock = false; + reading_lock = false; } - while (writing) { } + while (writing_lock) { } reading = false; previous.AddLast(buffer + buffer2); return buffer + buffer2; @@ -102,8 +120,8 @@ public static string ReadLine() #region Console Output public static void Write(string text) { - while (consolelock) { } - writing = true; + while (reading_lock) { } + writing_lock = true; if (reading) { ConsoleColor fore = Console.ForegroundColor; @@ -135,7 +153,7 @@ public static void Write(string text) Console.BackgroundColor = back; } else Console.Write(text); - writing = false; + writing_lock = false; } public static void WriteLine(string line) @@ -179,15 +197,12 @@ private static void RemoveOneChar() } private static void GoBack() { - if (buffer.Length > 0) + if (Console.CursorLeft == 0) { - if (Console.CursorLeft == 0) - { - Console.CursorLeft = Console.BufferWidth - 1; - Console.CursorTop--; - } - else Console.Write('\b'); + Console.CursorLeft = Console.BufferWidth - 1; + Console.CursorTop--; } + else Console.Write('\b'); } private static void GoLeft() { @@ -216,4 +231,14 @@ private static void AddChar(char c) } #endregion } + + /// + /// Interface for TAB autocompletion + /// Allows to use any object which has an AutoComplete() method using the IAutocomplete interface + /// + + public interface IAutoComplete + { + string AutoComplete(string BehindCursor); + } } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index e31f280e98..b767631060 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -141,7 +141,7 @@ private void StartClient(string user, string sessionID, string server_port, bool ChatBot.LogToConsole("Waiting 5 seconds (" + AttemptsLeft + " attempts left)..."); Thread.Sleep(5000); AttemptsLeft--; Program.Restart(); } - else if (!singlecommand){ Console.ReadLine(); } + else if (!singlecommand) { Console.ReadLine(); } } } @@ -194,6 +194,7 @@ private void StartTalk() else if (text == "/reco" || text == "/reconnect") { ConsoleIO.WriteLine("You have left the server."); + handler.SendRespawnPacket(); Program.Restart(); } } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index b1b033e61d..a2d8ad1f13 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -52,6 +52,13 @@ false + + resources\appicon.ico + + + MinecraftClient.Program + + False @@ -86,6 +93,7 @@ + @@ -110,11 +118,12 @@ - - - - - + + + + + +