Merge tab-complete

Fri, 15 May 2015 18:36:22 +0300

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Fri, 15 May 2015 18:36:22 +0300
changeset 71
4f7c2c944637
parent 70
0e947b487b18 (diff)
parent 41
9ab869656b9e (current diff)
child 72
1b9c53e0c846

Merge tab-complete

sources/interface.cpp file | annotate | diff | comparison | revisions
sources/interface.h file | annotate | diff | comparison | revisions
sources/list.h file | annotate | diff | comparison | revisions
sources/network/rconsession.cpp file | annotate | diff | comparison | revisions
sources/network/rconsession.h file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgtags	Fri May 15 18:36:22 2015 +0300
@@ -0,0 +1,1 @@
+5e968dc8d552598f5bb5343bd5fa0c24088b6ee9 1.0
--- a/CMakeLists.txt	Mon Dec 15 20:19:18 2014 +0200
+++ b/CMakeLists.txt	Fri May 15 18:36:22 2015 +0300
@@ -2,6 +2,7 @@
 project (zfc9000)
 
 set (SOURCE_FILES
+	sources/coloredline.cpp
 	sources/format.cpp
 	sources/interface.cpp
 	sources/main.cpp
--- a/README.md	Mon Dec 15 20:19:18 2014 +0200
+++ b/README.md	Fri May 15 18:36:22 2015 +0300
@@ -1,1 +1,21 @@
-Text-based RCON utility for Zandronum
+zfc9000
+---------
+
+zfc9000 is a text-based RCON utility for the Zandronum Doom source port.
+
+Features:
+
+* Fully textual, can be used over SSH for instance
+* Supports in-game colorcodes
+* Input history
+* A subset of readline shortcuts
+* a nicklist
+
+To use, either:
+
+* use the command line: ```zfc9000 <address> <password>```
+* use Ctrl-N within the application to create a new connection
+
+To compile, clone the sources off the mercurial repository and check out the version desired. Ensure you have the proper ncurses libraries installed, e.g. ```libncurses5-dev``` and ```libncursesw5-dev``` on Ubuntu. Compile using cmake.
+
+Binaries are provided but these may or may not work on your system.
--- a/sources/basics.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/basics.h	Fri May 15 18:36:22 2015 +0300
@@ -44,6 +44,11 @@
 using std::min;
 using std::max;
 
+template<typename Signature>
+using Function = std::function<Signature>;
+
+// -------------------------------------------------------------------------------------------------
+//
 enum Color
 {
 	BLACK,
@@ -59,16 +64,35 @@
 	NUM_COLORS
 };
 
-template<typename Signature>
-using Function = std::function<Signature>;
+#define TEXTCOLOR_Escape "\x1C"
+#define TEXTCOLOR_Black			TEXTCOLOR_Escape "M"
+#define TEXTCOLOR_Gray			TEXTCOLOR_Escape "U"
+#define TEXTCOLOR_Silver		TEXTCOLOR_Escape "C"
+#define TEXTCOLOR_White			TEXTCOLOR_Escape "J"
+#define TEXTCOLOR_Red			TEXTCOLOR_Escape "R"
+#define TEXTCOLOR_BrightRed		TEXTCOLOR_Escape "G"
+#define TEXTCOLOR_Green			TEXTCOLOR_Escape "Q"
+#define TEXTCOLOR_BrightGreen	TEXTCOLOR_Escape "D"
+#define TEXTCOLOR_Yellow		TEXTCOLOR_Escape "K"
+#define TEXTCOLOR_BrightYellow	TEXTCOLOR_Escape "F"
+#define TEXTCOLOR_Blue			TEXTCOLOR_Escape "H"
+#define TEXTCOLOR_BrightBlue	TEXTCOLOR_Escape "N"
+#define TEXTCOLOR_Magenta		TEXTCOLOR_Escape "T"
+#define TEXTCOLOR_BrightCyan	TEXTCOLOR_Escape "V"
+#define TEXTCOLOR_Reset			TEXTCOLOR_Escape "-"
 
+// -------------------------------------------------------------------------------------------------
+//
 FUNCTION print_to_console (String a) -> void;
-FUNCTION request_exit() -> void;
 
+// -------------------------------------------------------------------------------------------------
+//
 template<typename T> inline FUNCTION
 clamp (T a, T b, T c) -> T
 {
 	return (a < b) ? b : (a > c) ? c : a;
 }
 
+// -------------------------------------------------------------------------------------------------
+//
 struct Exitception {};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sources/coloredline.cpp	Fri May 15 18:36:22 2015 +0300
@@ -0,0 +1,156 @@
+/*
+	Copyright 2014 Teemu Piippo
+	All rights reserved.
+
+	Redistribution and use in source and binary forms, with or without
+	modification, are permitted provided that the following conditions
+	are met:
+
+	1. Redistributions of source code must retain the above copyright
+	   notice, this list of conditions and the following disclaimer.
+	2. Redistributions in binary form must reproduce the above copyright
+	   notice, this list of conditions and the following disclaimer in the
+	   documentation and/or other materials provided with the distribution.
+	3. Neither the name of the copyright holder nor the names of its
+	   contributors may be used to endorse or promote products derived from
+	   this software without specific prior written permission.
+
+	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
+	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "coloredline.h"
+
+static const struct
+{
+	const char* name;
+	Color color;
+	bool bold;
+} g_colorCodes['v' - 'a' + 1] =
+{
+	{ "Brick",        RED,     true  }, // a
+	{ "Tan",          YELLOW,  true  }, // b
+	{ "Gray",         WHITE,   false }, // c
+	{ "Green",        GREEN,   true  }, // d
+	{ "Brown",        YELLOW,  false }, // e
+	{ "Gold",         YELLOW,  true  }, // f
+	{ "Red",          RED,     true  }, // g
+	{ "Blue",         BLUE,    false }, // h
+	{ "Orange",       YELLOW,  false }, // i
+	{ "White",        WHITE,   true  }, // j
+	{ "Yellow",       YELLOW,  true  }, // k
+	{ "Untranslated", DEFAULT, false }, // l
+	{ "Black",        BLACK,   false }, // m
+	{ "Blue",         BLUE,    true  }, // n
+	{ "Cream",        YELLOW,  true  }, // o
+	{ "Olive",        GREEN,   true  }, // p
+	{ "Dark Green",   GREEN,   false }, // q
+	{ "Dark Red",     RED,     false }, // r
+	{ "Dark Brown",   YELLOW,  false }, // s
+	{ "Purple",       MAGENTA, false }, // t
+	{ "Dark Gray",    BLACK,   true  }, // u
+	{ "Cyan",         CYAN,    true  }, // v
+};
+
+// -------------------------------------------------------------------------------------------------
+//
+void ColoredLine::finalize()
+{
+	if (m_activeColor != DEFAULT)
+		set_color (m_activeColor, false);
+
+	if (m_boldActive)
+		m_data << RLINE_OFF_BOLD;
+
+	m_final = true;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+void ColoredLine::add_char (char ch)
+{
+	if (m_final)
+		return; // Don't touch finalized lines.
+
+	if (ch == '\x1C' and m_colorCodeStage == 0)
+	{
+		m_colorCodeStage = 1;
+		return;
+	}
+
+	if (m_colorCodeStage == 1)
+	{
+		if (m_activeColor != DEFAULT)
+			set_color (m_activeColor, false);
+
+		if (m_boldActive)
+			m_data << RLINE_OFF_BOLD;
+
+		// Chars may be in uppercase
+		if (ch >= 'A' and ch <= 'V')
+			ch += 'a' - 'A';
+
+		if (ch >= 'a' and ch <= 'v' and ch != 'l')
+		{
+			auto colorInfo = g_colorCodes[ch - 'a'];
+			m_activeColor = colorInfo.color;
+			m_boldActive = colorInfo.bold;
+			assert (m_activeColor < 8);
+			set_color (m_activeColor, true);
+
+			if (m_boldActive)
+				m_data << RLINE_ON_BOLD;
+		}
+
+		m_colorCodeStage = 0;
+		return;
+	}
+
+	if (isprint (ch))
+	{
+		m_string += ch;
+		m_data << int (ch);
+		++m_length;
+	}
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+void ColoredLine::set_color (Color a, bool on)
+{
+	switch (a)
+	{
+	case BLACK:   m_data << (on ? RLINE_ON_BLACK   : RLINE_OFF_BLACK); break;
+	case RED:     m_data << (on ? RLINE_ON_RED     : RLINE_OFF_RED); break;
+	case GREEN:   m_data << (on ? RLINE_ON_GREEN   : RLINE_OFF_GREEN); break;
+	case YELLOW:  m_data << (on ? RLINE_ON_YELLOW  : RLINE_OFF_YELLOW); break;
+	case BLUE:    m_data << (on ? RLINE_ON_BLUE    : RLINE_OFF_BLUE); break;
+	case MAGENTA: m_data << (on ? RLINE_ON_MAGENTA : RLINE_OFF_MAGENTA); break;
+	case CYAN:    m_data << (on ? RLINE_ON_CYAN    : RLINE_OFF_CYAN); break;
+	case WHITE:   m_data << (on ? RLINE_ON_WHITE   : RLINE_OFF_WHITE); break;
+	case NUM_COLORS:
+	case DEFAULT: assert (false); break;
+	}
+}
+
+// -------------------------------------------------------------------------------------------------
+// How many rows does this line take up?
+//
+int ColoredLine::rows (int cols) const
+{
+	int rows = length() / cols;
+
+	if (length() % cols != 0)
+		rows++;
+
+	return max (rows, 1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sources/coloredline.h	Fri May 15 18:36:22 2015 +0300
@@ -0,0 +1,81 @@
+/*
+	Copyright 2014 Teemu Piippo
+	All rights reserved.
+
+	Redistribution and use in source and binary forms, with or without
+	modification, are permitted provided that the following conditions
+	are met:
+
+	1. Redistributions of source code must retain the above copyright
+	   notice, this list of conditions and the following disclaimer.
+	2. Redistributions in binary form must reproduce the above copyright
+	   notice, this list of conditions and the following disclaimer in the
+	   documentation and/or other materials provided with the distribution.
+	3. Neither the name of the copyright holder nor the names of its
+	   contributors may be used to endorse or promote products derived from
+	   this software without specific prior written permission.
+
+	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
+	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#pragma once
+#include "main.h"
+
+// -------------------------------------------------------------------------------------------------
+//
+enum
+{
+	RLINE_ON_BLACK = 256,
+	RLINE_ON_RED,
+	RLINE_ON_GREEN,
+	RLINE_ON_YELLOW,
+	RLINE_ON_BLUE,
+	RLINE_ON_MAGENTA,
+	RLINE_ON_CYAN,
+	RLINE_ON_WHITE,
+	RLINE_ON_BOLD,
+	RLINE_OFF_BLACK,
+	RLINE_OFF_RED,
+	RLINE_OFF_GREEN,
+	RLINE_OFF_YELLOW,
+	RLINE_OFF_BLUE,
+	RLINE_OFF_MAGENTA,
+	RLINE_OFF_CYAN,
+	RLINE_OFF_WHITE,
+	RLINE_OFF_BOLD,
+};
+
+// -------------------------------------------------------------------------------------------------
+//
+class ColoredLine
+{
+public:
+	ColoredLine() {}
+
+	const Vector<int>& data() const { return m_data; }
+	int length() const { return m_length; }
+	void add_char (char ch);
+	void finalize();
+	int rows (int cols) const;
+
+private:
+	void set_color (Color a, bool on);
+
+	Vector<int> m_data;
+	int m_length = 0;
+	bool m_final = false;
+	Color m_activeColor = DEFAULT;
+	bool m_boldActive = false;
+	int m_colorCodeStage = 0;
+	String m_string;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sources/colors.h	Fri May 15 18:36:22 2015 +0300
@@ -0,0 +1,1 @@
+#pragma once
\ No newline at end of file
--- a/sources/format.cpp	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/format.cpp	Fri May 15 18:36:22 2015 +0300
@@ -32,6 +32,7 @@
 #include "format.h"
 
 // -------------------------------------------------------------------------------------------------
+//
 // Throws an error while formatting the string
 //
 static auto format_error (String fmtstr, const String errdescribe, int pos) -> void
@@ -48,6 +49,7 @@
 }
 
 // -------------------------------------------------------------------------------------------------
+//
 // Main formatter algorithm.
 //
 auto format_args (const String& fmtstr, const Vector<String>& args) -> String
--- a/sources/format.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/format.h	Fri May 15 18:36:22 2015 +0300
@@ -34,18 +34,19 @@
 #include "geometry.h"
 
 #define FORMAT_OVERLOAD(...) \
-	inline FUNCTION make_format_argument (__VA_ARGS__ a) -> String
+	inline String make_format_argument (__VA_ARGS__ a)
 
-//
 // -------------------------------------------------------------------------------------------------
 //
-
 FORMAT_OVERLOAD (String) { return a; }
 FORMAT_OVERLOAD (char) { return String (a); }
+FORMAT_OVERLOAD (short int) { return String::from_number (a); }
 FORMAT_OVERLOAD (int) { return String::from_number (a); }
 FORMAT_OVERLOAD (long int) { return String::from_number (a); }
 FORMAT_OVERLOAD (double) { return String::from_number (a); }
-FORMAT_OVERLOAD (size_t) { return String::from_number (a); }
+FORMAT_OVERLOAD (unsigned short int) { return String::from_number (a); }
+FORMAT_OVERLOAD (unsigned int) { return String::from_number (a); }
+FORMAT_OVERLOAD (unsigned long int) { return String::from_number (a); }
 FORMAT_OVERLOAD (const char*) { return a; }
 FORMAT_OVERLOAD (std::nullptr_t) { (void) a; return "<null pointer>"; }
 FORMAT_OVERLOAD (bool) { return a ? "true" : "false"; }
@@ -96,8 +97,15 @@
 	return "???";
 }
 
-FORMAT_OVERLOAD (Position) { return String ("(") + a.x + ", " + a.y + ")"; }
-FORMAT_OVERLOAD (Size) { return String ("(") + a.width + "x" + a.height + ")"; }
+FORMAT_OVERLOAD (Position)
+{
+	return String ("(") + a.x + ", " + a.y + ")";
+}
+
+FORMAT_OVERLOAD (Size)
+{
+	return String ("(") + a.width + "x" + a.height + ")";
+}
 
 FORMAT_OVERLOAD (Rectangle)
 {
@@ -107,15 +115,17 @@
 }
 
 // -------------------------------------------------------------------------------------------------
+//
 // Formats the given string with the given args.
 //
 FUNCTION format_args (const String& fmtstr, const Vector<String>& args) -> String;
 
 // -------------------------------------------------------------------------------------------------
+//
 // Expands the given arguments into a vector of strings.
 //
-template<typename T, typename... RestTypes> FUNCTION
-expand_format_arguments (Vector<String>& data, const T& arg, const RestTypes& ... rest) -> void
+template<typename T, typename... RestTypes>
+void expand_format_arguments (Vector<String>& data, const T& arg, const RestTypes& ... rest)
 {
 	data.append (make_format_argument (arg));
 	expand_format_arguments (data, rest...);
@@ -149,8 +159,8 @@
 //      argument did not expand into a number in the first place, 0 is used
 //      and 0x0 is printed.
 //
-template<typename... argtypes> FUNCTION
-format (const String& fmtstr, const argtypes&... raw_args) -> String
+template<typename... argtypes>
+String format (const String& fmtstr, const argtypes&... raw_args)
 {
 	Vector<String> args;
 	expand_format_arguments (args, raw_args...);
@@ -158,26 +168,28 @@
 }
 
 // -------------------------------------------------------------------------------------------------
+//
 // This is an overload of format() where no arguments are supplied.
 // It returns the formatter string as-is.
 //
 static String format (const String& fmtstr) __attribute__ ((unused));
-static String // FUNCTION
-format (const String& fmtstr) // -> String
+static String format (const String& fmtstr) // -> String
 {
 	return fmtstr;
 }
 
 // -------------------------------------------------------------------------------------------------
+//
 // Prints the formatting result to the given file handle
 //
-template<typename... Args> FUNCTION
-print_to (FILE* fp, const String& fmtstr, Args const& ...args) -> void
+template<typename... Args>
+void print_to (FILE* fp, const String& fmtstr, Args const& ...args)
 {
 	std::fprintf (fp, "%s", format (fmtstr, args...).chars());
 }
 
 // -------------------------------------------------------------------------------------------------
+//
 // Appends the formatting result to the given filename, if opening it succeeds
 //
 template<typename... argtypes>
@@ -193,6 +205,7 @@
 }
 
 // -------------------------------------------------------------------------------------------------
+//
 // Prints the formatting result to the console
 //
 template<typename... argtypes>
@@ -200,3 +213,23 @@
 {
 	print_to_console (format (fmtstr, args...));
 }
+
+// -------------------------------------------------------------------------------------------------
+//
+// Prints the formatting result as a warning to the console
+//
+template<typename... argtypes>
+void print_warning (const String& fmtstr, const argtypes&... args)
+{
+	print_to_console (TEXTCOLOR_BrightYellow "-!- " + format (fmtstr, args...) + TEXTCOLOR_Reset);
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+// Prints the formatting result as a warning to the console
+//
+template<typename... argtypes>
+void print_error (const String& fmtstr, const argtypes&... args)
+{
+	print_to_console (TEXTCOLOR_BrightRed "!!! " + format (fmtstr, args...) + TEXTCOLOR_Reset);
+}
\ No newline at end of file
--- a/sources/geometry.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/geometry.h	Fri May 15 18:36:22 2015 +0300
@@ -43,12 +43,41 @@
 		x (0),
 		y (0) {}
 
-	inline METHOD operator< (const Position& other) const -> bool;
-	inline METHOD operator> (const Position& other) const -> bool;
-	inline METHOD operator== (const Position& other) const -> bool;
-	inline METHOD operator<= (const Position& other) const -> bool;
-	inline METHOD operator>= (const Position& other) const -> bool;
-	inline METHOD operator!= (const Position& other) const -> bool;
+	bool operator< (const Position& other) const
+	{
+		if (y != other.y)
+			return y < other.y;
+
+		return x < other.x;
+	}
+
+	bool operator> (const Position& other) const
+	{
+		if (y != other.y)
+			return y > other.y;
+
+		return x > other.x;
+	}
+
+	bool operator== (const Position& other) const
+	{
+		return y == other.y and x == other.x;
+	}
+
+	bool operator!= (const Position& other) const
+	{
+		return not operator== (other);
+	}
+
+	bool operator<= (const Position& other) const
+	{
+		return not operator> (other);
+	}
+
+	bool operator>= (const Position& other) const
+	{
+		return not operator< (other);
+	}
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -84,57 +113,3 @@
 		Position(),
 		Size() {}
 };
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-Position::operator< (const Position& other) const -> bool
-{
-	if (y != other.y)
-		return y < other.y;
-
-	return x < other.x;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-Position::operator> (const Position& other) const -> bool
-{
-	if (y != other.y)
-		return y > other.y;
-
-	return x > other.x;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-Position::operator== (const Position& other) const -> bool
-{
-	return y == other.y and x == other.x;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-Position::operator<= (const Position& other) const -> bool
-{
-	return not operator> (other);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-Position::operator>= (const Position& other) const -> bool
-{
-	return not operator< (other);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-Position::operator!= (const Position& other) const -> bool
-{
-	return not operator== (other);
-}
--- a/sources/interface.cpp	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/interface.cpp	Fri May 15 18:36:22 2015 +0300
@@ -29,54 +29,11 @@
 */
 
 #include <string.h>
+#include <time.h>
 #include "interface.h"
 #include "network/rconsession.h"
 #include "network/ipaddress.h"
-
-enum
-{
-	RLINE_ON_BLACK = 256,
-	RLINE_ON_RED,
-	RLINE_ON_GREEN,
-	RLINE_ON_YELLOW,
-	RLINE_ON_BLUE,
-	RLINE_ON_MAGENTA,
-	RLINE_ON_CYAN,
-	RLINE_ON_WHITE,
-	RLINE_ON_BOLD,
-	RLINE_OFF_BLACK,
-	RLINE_OFF_RED,
-	RLINE_OFF_GREEN,
-	RLINE_OFF_YELLOW,
-	RLINE_OFF_BLUE,
-	RLINE_OFF_MAGENTA,
-	RLINE_OFF_CYAN,
-	RLINE_OFF_WHITE,
-	RLINE_OFF_BOLD,
-};
-
-class RendererLine
-{
-public:
-	RendererLine() {}
-
-	METHOD data() const -> const Vector<int>& { return m_data; }
-	METHOD length() const -> int { return m_length; }
-	METHOD add_char (char ch) -> void;
-	METHOD finalize() -> void;
-	METHOD rows (int cols) const -> int;
-
-private:
-	METHOD set_color (Color a, bool on) -> void;
-
-	Vector<int> m_data;
-	int m_length = 0;
-	bool m_final = false;
-	Color m_activeColor = DEFAULT;
-	bool m_boldActive = false;
-	int m_colorCodeStage = 0;
-	String m_string;
-};
+#include "coloredline.h"
 
 static const int g_pageSize = 10;
 
@@ -88,48 +45,25 @@
 	INPUTSTATE_CONFIRM_DISCONNECTION,
 };
 
-static StringList g_input;
-static int g_inputCursor = 0;
-static int g_cursor = 0;
-static int g_pan = 0;
-static bool g_needRefresh = false;
-static bool g_needStatusBarRender = false;
-static bool g_needInputRender = false;
-static bool g_needOutputRender = false;
-static struct { char ch; int x; } g_cursorChar;
-static Vector<RendererLine> g_output;;
-static int g_outputScroll = 0;
-static String g_title;
-static InputState g_inputState = INPUTSTATE_NORMAL;
-static Function<void (void)> g_disconnectConfirmFunction = nullptr;
-static IPAddress g_address;
-static String g_statusBarText;
-
-static const struct { Color color; bool bold; } g_colorCodes['v' - 'a' + 1] =
-{
-	{ RED,     true  }, // a - brick
-	{ YELLOW,  true  }, // b - tan
-	{ WHITE,   false }, // c - gray
-	{ GREEN,   true  }, // d - light green
-	{ YELLOW,  false }, // e - brown
-	{ YELLOW,  true  }, // f - gold yellow
-	{ RED,     true  }, // g - bright red
-	{ BLUE,    false }, // h - dark blue
-	{ YELLOW,  false }, // i - orange
-	{ WHITE,   true  }, // j - white
-	{ YELLOW,  true  }, // k - fire yellow
-	{ DEFAULT, false }, // l - untranslated
-	{ BLACK,   false }, // m - black
-	{ BLUE,    true  }, // n - light blue
-	{ YELLOW,  true  }, // o - cream
-	{ GREEN,   true  }, // p - olive green
-	{ GREEN,   false }, // q - dark green
-	{ RED,     false }, // r - dark red
-	{ YELLOW,  false }, // s - dark brown
-	{ MAGENTA, false }, // t - purple
-	{ BLACK,   true  }, // u - dark gray
-	{ CYAN,    true  }, // v - cyan
-};
+static StringList InputHistory;
+static int InputCursor = 0;
+static int CursorPosition = 0;
+static int InputPanning = 0;
+static bool NeedRefresh = false;
+static bool NeedStatusBarRender = false;
+static bool NeedInputRender = false;
+static bool NeedOutputRender = false;
+static bool NeedNicklistRender = false;
+static struct { char ch; int x; } CursorCharacter;
+static Vector<ColoredLine> OutputLines;
+static int OutputScroll = 0;
+static String Title;
+static InputState CurrentInputState = INPUTSTATE_NORMAL;
+static Function<void (void)> DisconnectConfirmFunction = nullptr;
+static IPAddress CurrentAddress;
+static String StatusBarText;
+static StringList PlayerNames;
+static String PasteBuffer;
 
 // -------------------------------------------------------------------------------------------------
 //
@@ -144,7 +78,7 @@
 static FUNCTION
 current_input() -> const String&
 {
-	return g_input[g_inputCursor];
+	return InputHistory[InputCursor];
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -154,10 +88,10 @@
 static FUNCTION
 detach_input() -> void
 {
-	if (g_inputCursor > 0)
+	if (InputCursor > 0)
 	{
-		g_input[0] = current_input();
-		g_inputCursor = 0;
+		InputHistory[0] = current_input();
+		InputCursor = 0;
 	}
 }
 
@@ -168,7 +102,7 @@
 mutable_current_input() -> String&
 {
 	detach_input();
-	return g_input[g_inputCursor];
+	return InputHistory[InputCursor];
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -177,19 +111,19 @@
 move_input_cursor (int delta) -> void
 {
 	// No input history when inputting addresses or passwords
-	if (g_inputState != INPUTSTATE_NORMAL)
+	if (CurrentInputState != INPUTSTATE_NORMAL)
 	{
-		g_inputCursor = 0;
+		InputCursor = 0;
 		return;
 	}
 
-	int oldcursor = g_inputCursor;
-	g_inputCursor = clamp (g_inputCursor + delta, 0, g_input.size() - 1);
+	int oldcursor = InputCursor;
+	InputCursor = clamp (InputCursor + delta, 0, InputHistory.size() - 1);
 
-	if (g_inputCursor != oldcursor)
+	if (InputCursor != oldcursor)
 	{
-		g_cursor = current_input().length();
-		g_needInputRender = true;
+		CursorPosition = current_input().length();
+		NeedInputRender = true;
 	}
 }
 
@@ -200,7 +134,7 @@
 {
 	String prompt;
 
-	switch (g_inputState)
+	switch (CurrentInputState)
 	{
 	case INPUTSTATE_NORMAL: prompt = ">"; break;
 	case INPUTSTATE_ADDRESS: prompt = "address:"; break;
@@ -218,25 +152,25 @@
 {
 	// Clear the input row (unless going to or from confirm state)
 	if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION
-		and g_inputState != INPUTSTATE_CONFIRM_DISCONNECTION)
+		and CurrentInputState != INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
-		g_inputCursor = 0;
+		InputCursor = 0;
 		mutable_current_input().clear();
 	}
 
 	switch (newstate)
 	{
 	case INPUTSTATE_ADDRESS:
-		if (g_address.host != 0)
-			mutable_current_input() = g_address.to_string (IP_WITH_PORT);
+		if (CurrentAddress.host != 0)
+			mutable_current_input() = CurrentAddress.to_string (IP_WITH_PORT);
 		break;
 
 	default:
 		break;
 	}
 
-	g_inputState = newstate;
-	g_needInputRender = true;
+	CurrentInputState = newstate;
+	NeedInputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -252,11 +186,11 @@
 	::refresh();
 	::timeout (0);
 	::use_default_colors();
-	g_input.clear();
-	g_input << "";
-	g_output.clear();
-	g_output << RendererLine();
-	g_title = format (APPNAME " %1 (%2)", full_version_string(), changeset_date_string());
+	InputHistory.clear();
+	InputHistory << "";
+	OutputLines.clear();
+	OutputLines << ColoredLine();
+	Title = format (APPNAME " %1 (%2)", full_version_string(), changeset_date_string());
 
 	for (int i = 0; i < NUM_COLORS; ++i)
 	for (int j = 0; j < NUM_COLORS; ++j)
@@ -268,7 +202,7 @@
 
 	render_full();
 	refresh();
-	g_needRefresh = false;
+	NeedRefresh = false;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -276,19 +210,19 @@
 static FUNCTION
 interface_render_titlebar() -> void
 {
-	if (g_title.length() <= COLS)
+	if (Title.length() <= COLS)
 	{
 		int pair = interface_color_pair (WHITE, BLUE);
-		int startx = (COLS - g_title.length()) / 2;
-		int endx = startx + g_title.length();
+		int startx = (COLS - Title.length()) / 2;
+		int endx = startx + Title.length();
 		attron (pair);
-		mvprintw (0, startx, "%s", g_title.chars());
+		mvprintw (0, startx, "%s", Title.chars());
 		mvhline (0, 0, ' ', startx);
 		mvhline (0, endx, ' ', COLS - endx);
 		attroff (pair);
 	}
 
-	g_needRefresh = true;
+	NeedRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -296,7 +230,7 @@
 FUNCTION
 Interface::set_title (const String& title) -> void
 {
-	g_title = title;
+	Title = title;
 	interface_render_titlebar();
 }
 
@@ -307,7 +241,7 @@
 {
 	if (RCONSession::get_session()->is_active())
 	{
-		g_disconnectConfirmFunction = afterwards;
+		DisconnectConfirmFunction = afterwards;
 		set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION);
 	}
 	else
@@ -317,16 +251,95 @@
 // -------------------------------------------------------------------------------------------------
 //
 static FUNCTION
+interface_nicklist_width() -> int
+{
+	// Allocate at least 12 characters, at most 24 characters, for the nicklist. If we cannot
+	// afford that (o_O) then we probably shouldn't draw the nicklist at all I think.
+	int nicklistWidth = COLS / 4;
+
+	if (nicklistWidth < 12)
+		return 0;
+
+	return min (nicklistWidth, 24);
+}
+
+// -------------------------------------------------------------------------------------------------
+// Renders the given colored line onto the screen. Will wrap if allowWrap is true. Returns the
+// 'y' value for the next line.
+//
+static FUNCTION
+interface_render_colorline (int y, int x0, int width,
+	const ColoredLine& line, bool allowWrap) -> int
+{
+	int x = x0;
+
+	for (int byte : line.data())
+	{
+		if (x == x0 + width)
+		{
+			if (not allowWrap)
+				return y;
+
+			x = x0;
+			++y;
+		}
+
+		if (isprint (byte))
+		{
+			mvaddch (y, x, char (byte));
+			++x;
+		}
+		else switch (byte)
+		{
+		case RLINE_ON_BLACK:
+		case RLINE_ON_RED:
+		case RLINE_ON_GREEN:
+		case RLINE_ON_YELLOW:
+		case RLINE_ON_BLUE:
+		case RLINE_ON_MAGENTA:
+		case RLINE_ON_CYAN:
+		case RLINE_ON_WHITE:
+			attron (interface_color_pair (Color (byte - RLINE_ON_BLACK), DEFAULT));
+			break;
+
+		case RLINE_OFF_BLACK:
+		case RLINE_OFF_RED:
+		case RLINE_OFF_GREEN:
+		case RLINE_OFF_YELLOW:
+		case RLINE_OFF_BLUE:
+		case RLINE_OFF_MAGENTA:
+		case RLINE_OFF_CYAN:
+		case RLINE_OFF_WHITE:
+			attroff (interface_color_pair (Color (byte - RLINE_OFF_BLACK), DEFAULT));
+			break;
+
+		case RLINE_ON_BOLD:
+			attron (A_BOLD);
+			break;
+
+		case RLINE_OFF_BOLD:
+			attroff (A_BOLD);
+			break;
+		}
+	}
+
+	return y + 1;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+static FUNCTION
 interface_render_output() -> void
 {
-	if (g_output.size() == 1)
+	if (OutputLines.size() == 1)
 		return;
 
-	g_outputScroll = clamp (g_outputScroll, 0, g_output.size() - 1);
+	OutputScroll = clamp (OutputScroll, 0, OutputLines.size() - 1);
 
 	int height = LINES - 3;
+	int width = COLS - interface_nicklist_width();
 	int printOffset = 0;
-	int end = g_output.size() - 1 - g_outputScroll;
+	int end = OutputLines.size() - 1 - OutputScroll;
 	int start = end;
 	int usedHeight = 0;
 	int y = 1;
@@ -335,7 +348,7 @@
 	// Where to start?
 	while (start > 0)
 	{
-		int rows = g_output[start - 1].rows (COLS);
+		int rows = OutputLines[start - 1].rows (width);
 
 		if (usedHeight + rows > height)
 		{
@@ -351,9 +364,9 @@
 	// See if there's any more rows to use (end may be too small)
 	if (not tightFit)
 	{
-		while (end < g_output.size())
+		while (end < OutputLines.size())
 		{
-			int rows = g_output[end].rows (COLS);
+			int rows = OutputLines[end].rows (width);
 
 			if (usedHeight + rows > height)
 			{
@@ -369,7 +382,7 @@
 	if (start > 0)
 		printOffset = height - usedHeight;
 
-	g_outputScroll = g_output.size() - 1 - end;
+	OutputScroll = OutputLines.size() - 1 - end;
 
 	if (start < 0 or start == end or printOffset >= height)
 		return;
@@ -378,62 +391,53 @@
 
 	// Clear the display
 	for (int i = y; i < y + height; ++i)
-		mvhline (i, 0, ' ', COLS);
+		mvhline (i, 0, ' ', width);
 
 	// Print the lines
 	y += printOffset;
 
 	for (int i = start; i < end; ++i)
+		y = interface_render_colorline (y, 0, width, OutputLines[i], true);
+
+	NeedOutputRender = false;
+	NeedRefresh = true;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+static FUNCTION
+interface_render_nicklist() -> void
+{
+	int width = interface_nicklist_width();
+	int height = LINES- 3;
+	int y = 1;
+	int x = COLS - width;
+
+	if (width == 0)
+		return;
+
+	for (int i = 0; i < height; ++i)
 	{
-		int x = 0;
+		mvhline (y, x, ' ', width);
 
-		for (int byte : g_output[i].data())
+		if (i < PlayerNames.size())
 		{
-			if (x == COLS)
+			String displaynick = PlayerNames[i];
+
+			if (displaynick.length() > width)
 			{
-				x = 0;
-				++y;
+				displaynick = displaynick.mid (0, width - 3);
+				displaynick += "...";
 			}
 
-			if (isprint (byte))
-				mvaddch (y, x++, char (byte));
-			else switch (byte)
-			{
-			case RLINE_ON_BLACK:
-			case RLINE_ON_GREEN:
-			case RLINE_ON_YELLOW:
-			case RLINE_ON_BLUE:
-			case RLINE_ON_MAGENTA:
-			case RLINE_ON_CYAN:
-			case RLINE_ON_WHITE:
-				attron (interface_color_pair (Color (byte - RLINE_ON_BLACK), DEFAULT));
-				break;
-
-			case RLINE_OFF_BLACK:
-			case RLINE_OFF_GREEN:
-			case RLINE_OFF_YELLOW:
-			case RLINE_OFF_BLUE:
-			case RLINE_OFF_MAGENTA:
-			case RLINE_OFF_CYAN:
-			case RLINE_OFF_WHITE:
-				attroff (interface_color_pair (Color (byte - RLINE_OFF_BLACK), DEFAULT));
-				break;
-
-			case RLINE_ON_BOLD:
-				attron (A_BOLD);
-				break;
-
-			case RLINE_OFF_BOLD:
-				attroff (A_BOLD);
-				break;
-			}
+			mvprintw (y, x, "%s", displaynick.chars());
 		}
 
-		++y;
+		y++;
 	}
 
-	g_needOutputRender = false;
-	g_needRefresh = true;
+	NeedNicklistRender = false;
+	NeedRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -445,12 +449,13 @@
 
 	// If we're asking the user if they want to disconnect, we don't render any input strings,
 	// just the confirmation message.
-	if (g_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
 		attron (promptColor);
 		mvhline (LINES - 2, 0, ' ', COLS);
 		mvprintw (LINES - 2, 0, "Are you sure you want to disconnect? y/n");
 		attroff (promptColor);
+		NeedRefresh = true;
 		return;
 	}
 
@@ -460,29 +465,29 @@
 	int y = LINES - 2;
 
 	// If we're inputting a password, replace it with asterisks
-	if (g_inputState == INPUTSTATE_PASSWORD)
+	if (CurrentInputState == INPUTSTATE_PASSWORD)
 	{
 		for (char& ch : displayString)
 			ch = '*';
 	}
 
 	// Ensure the cursor is within bounds
-	g_cursor = clamp (g_cursor, 0, displayString.length());
+	CursorPosition = clamp (CursorPosition, 0, displayString.length());
 
 	// Ensure that the cursor is always in view, adjust panning if this is not the case
-	if (g_cursor > g_pan + displayLength)
-		g_pan = g_cursor - displayLength; // cursor went too far right
-	else if (g_cursor < g_pan)
-		g_pan = g_cursor; // cursor went past the pan value to the left
+	if (CursorPosition > InputPanning + displayLength)
+		InputPanning = CursorPosition - displayLength; // cursor went too far right
+	else if (CursorPosition < InputPanning)
+		InputPanning = CursorPosition; // cursor went past the pan value to the left
 
 	// What part of the string to draw?
-	int start = g_pan;
+	int start = InputPanning;
 	int end = min<int> (displayString.length(), start + displayLength);
-	assert (g_cursor >= start and g_cursor <= end);
+	assert (CursorPosition >= start and CursorPosition <= end);
 
 	// Render the input string
 	mvhline (LINES - 2, 0, ' ', COLS);
-	mvprintw (y, prompt.length() + 1, "%s", displayString.chars());
+	mvprintw (y, prompt.length() + 1, "%s", displayString.mid (start, end).chars());
 
 	// Render the prompt
 	attron (promptColor);
@@ -491,10 +496,10 @@
 
 	// Store in memory where the cursor is now (so that we can re-draw it to position the terminal
 	// cursor).
-	g_cursorChar.ch = g_cursor != 0 ? displayString[g_cursor - 1] : '\0';
-	g_cursorChar.x = prompt.length() + (g_cursor - g_pan);
-	g_needRefresh = true;
-	g_needInputRender = false;
+	CursorCharacter.ch = CursorPosition != 0 ? displayString[CursorPosition - 1] : '\0';
+	CursorCharacter.x = prompt.length() + (CursorPosition - InputPanning);
+	NeedRefresh = true;
+	NeedInputRender = false;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -506,10 +511,10 @@
 	int y = LINES - 1;
 	attron (color);
 	mvhline (y, 0, ' ', COLS);
-	mvprintw (y, 0, "%s", g_statusBarText.chars());
+	mvprintw (y, 0, "%s", StatusBarText.chars());
 	attroff (color);
-	g_needRefresh = true;
-	g_needStatusBarRender = false;
+	NeedRefresh = true;
+	NeedStatusBarRender = false;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -546,10 +551,10 @@
 
 	text += "^N to connect, ^Q to quit";
 
-	if (text != g_statusBarText)
+	if (text != StatusBarText)
 	{
-		g_statusBarText = text;
-		g_needStatusBarRender = true;
+		StatusBarText = text;
+		NeedStatusBarRender = true;
 	}
 }
 
@@ -563,6 +568,7 @@
 	interface_render_output();
 	interface_render_statusbar();
 	interface_render_input();
+	interface_render_nicklist();
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -571,19 +577,74 @@
 interface_position_cursor() -> void
 {
 	// This is only relevant if the input string is being drawn
-	if (g_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 		return;
 
 	int y = LINES - 2;
 
-	if (g_cursorChar.ch != '\0')
-		mvprintw (y, g_cursorChar.x, "%c", g_cursorChar.ch);
+	if (CursorCharacter.ch != '\0')
+		mvprintw (y, CursorCharacter.x, "%c", CursorCharacter.ch);
 	else
 		mvprintw (y, interface_prompt_string().length(), " ");
 }
 
 // -------------------------------------------------------------------------------------------------
 //
+static FUNCTION
+interface_find_previous_word() -> int
+{
+	const String& input = current_input();
+	int pos = CursorPosition;
+
+	// Move past whitespace
+	while (pos > 0 and isspace (input[pos - 1]))
+		pos--;
+
+	// Move past the word
+	while (pos > 0 and not isspace (input[pos - 1]))
+		pos--;
+
+	return pos;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+static FUNCTION
+interface_find_next_word() -> int
+{
+	const String& input = current_input();
+	int pos = CursorPosition;
+
+	// Move past current whitespace
+	while (pos < input.length() and isspace (input[pos]))
+		pos++;
+
+	// Move past the word
+	while (input[pos] != '\0' and not isspace (input[pos]))
+		pos++;
+
+	return pos;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+static FUNCTION
+yank (int a, int b) -> void
+{
+	if (a >= b)
+		return;
+
+	if (CursorPosition > a and CursorPosition <= b)
+		CursorPosition = a;
+
+	String& input = mutable_current_input();
+	PasteBuffer = input.mid (a, b);
+	input.remove (a, b - a);
+	NeedInputRender = true;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
 FUNCTION
 Interface::handle_input() -> void
 {
@@ -596,12 +657,12 @@
 		return;
 	}
 
-	if (g_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
 		if (ch == 'y' or ch == 'Y')
 		{
 			RCONSession::get_session()->disconnect();
-			g_disconnectConfirmFunction();
+			DisconnectConfirmFunction();
 		}
 		else if (ch == 'n' or ch == 'N')
 			set_input_state (INPUTSTATE_NORMAL);
@@ -611,13 +672,13 @@
 
 	if (ch >= 0x20 and ch <= 0x7E)
 	{
-		mutable_current_input().insert (g_cursor++, char (ch));
-		g_needInputRender = true;
+		mutable_current_input().insert (CursorPosition++, char (ch));
+		NeedInputRender = true;
 	}
 	else switch (ch)
 	{
 	case 'Q' - 'A' + 1: // ^Q
-		switch (g_inputState)
+		switch (CurrentInputState)
 		{
 		case INPUTSTATE_CONFIRM_DISCONNECTION:
 			break;
@@ -649,26 +710,21 @@
 		}
 		break;
 
-	case '\e':
-		if (g_inputState == INPUTSTATE_PASSWORD)
-			set_input_state (INPUTSTATE_ADDRESS);
-		else if (g_inputState == INPUTSTATE_ADDRESS)
-			set_input_state (INPUTSTATE_NORMAL);
-		break;
-
 	case KEY_LEFT:
-		if (g_cursor > 0)
+	case 'B' - 'A' + 1: // readline ^B
+		if (CursorPosition > 0)
 		{
-			g_cursor--;
-			g_needInputRender = true;
+			CursorPosition--;
+			NeedInputRender = true;
 		}
 		break;
 
 	case KEY_RIGHT:
-		if (g_cursor < current_input().length())
+	case 'F' - 'A' + 1: // readline ^F
+		if (CursorPosition < current_input().length())
 		{
-			g_cursor++;
-			g_needInputRender = true;
+			CursorPosition++;
+			NeedInputRender = true;
 		}
 		break;
 
@@ -678,63 +734,92 @@
 		break;
 
 	case KEY_HOME:
-		if (g_cursor != 0)
+	case 'A' - 'A' + 1: // readline ^A
+		if (CursorPosition != 0)
 		{
-			g_cursor = 0;
-			g_needInputRender = true;
+			CursorPosition = 0;
+			NeedInputRender = true;
 		}
 		break;
 
 	case KEY_END:
-		if (g_cursor != current_input().length())
+	case 'E' - 'A' + 1: // readline ^E
+		if (CursorPosition != current_input().length())
 		{
-			g_cursor = current_input().length();
-			g_needInputRender = true;
+			CursorPosition = current_input().length();
+			NeedInputRender = true;
 		}
 		break;
 
 	case KEY_BACKSPACE:
-		if (g_cursor > 0)
+		if (CursorPosition > 0)
 		{
-			mutable_current_input().remove_at (--g_cursor);
-			g_needInputRender = true;
+			mutable_current_input().remove_at (--CursorPosition);
+			NeedInputRender = true;
 		}
 		break;
 
 	case KEY_DC:
-		if (g_cursor < current_input().length())
+	case 'D' - 'A' + 1: // readline ^D
+		if (CursorPosition < current_input().length())
 		{
-			mutable_current_input().remove_at (g_cursor);
-			g_needInputRender = true;
+			mutable_current_input().remove_at (CursorPosition);
+			NeedInputRender = true;
 		}
 		break;
 
 	case KEY_PPAGE:
-		g_outputScroll += min (g_pageSize, LINES / 2);
-		g_needOutputRender = true;
+		OutputScroll += min (g_pageSize, LINES / 2);
+		NeedOutputRender = true;
 		break;
 
 	case KEY_NPAGE:
-		g_outputScroll -= min (g_pageSize, LINES / 2);
-		g_needOutputRender = true;
+		OutputScroll -= min (g_pageSize, LINES / 2);
+		NeedOutputRender = true;
+		break;
+
+	case 'U' - 'A' + 1: // readline ^U - delete from start to cursor
+		if (CursorPosition > 0)
+		{
+			yank (0, CursorPosition);
+			CursorPosition = 0;
+		}
+		break;
+
+	case 'K' - 'A' + 1: // readline ^K - delete from cursor to end
+		yank (CursorPosition, mutable_current_input().length());
+		break;
+
+	case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current
+		yank (interface_find_previous_word(), CursorPosition);
+		break;
+
+	case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text
+		if (not PasteBuffer.is_empty())
+		{
+			mutable_current_input().insert (CursorPosition, PasteBuffer);
+			CursorPosition += PasteBuffer.length();
+			NeedInputRender = true;
+		}
 		break;
 
 	case '\t':
 		{
 			int space = current_input().find (" ");
 
-			if (g_inputState == INPUTSTATE_NORMAL
-				and g_cursor > 0
-				and (space == -1 or space >= g_cursor))
+			if (CurrentInputState == INPUTSTATE_NORMAL
+				and InputCursor > 0
+				and (space == -1 or space >= InputCursor))
 			{
-				RCONSession::get_session()->request_tab_complete (current_input().mid (0, g_cursor));
+				String start = current_input().mid (0, InputCursor);
+				RCONSession::get_session()->request_tab_complete (start);
 			}
 		}
 		break;
 
 	case '\n':
 	case KEY_ENTER:
-		switch (g_inputState)
+		switch (CurrentInputState)
 		{
 		case INPUTSTATE_CONFIRM_DISCONNECTION:
 			break; // handled above
@@ -742,26 +827,27 @@
 		case INPUTSTATE_ADDRESS:
 			try
 			{
-				g_address = IPAddress::from_string (current_input());
+				CurrentAddress = IPAddress::from_string (current_input());
 			}
 			catch (std::exception& e)
 			{
-				print (e.what());
+				print ("%1\n", e.what());
 				return;
 			}
 
-			if (g_address.port == 0)
-				g_address.port = 10666;
+			if (CurrentAddress.port == 0)
+				CurrentAddress.port = 10666;
 
 			set_input_state (INPUTSTATE_PASSWORD);
 			break;
 
 		case INPUTSTATE_PASSWORD:
-			if (g_inputState == INPUTSTATE_PASSWORD and not current_input().is_empty())
+			if (CurrentInputState == INPUTSTATE_PASSWORD and not current_input().is_empty())
 			{
-				RCONSession* session = RCONSession::new_session();
+				RCONSession* session = RCONSession::get_session();
+				session->disconnect();
 				session->set_password (current_input());
-				session->connect (g_address);
+				session->connect (CurrentAddress);
 				set_input_state (INPUTSTATE_NORMAL);
 			}
 			break;
@@ -769,17 +855,56 @@
 		case INPUTSTATE_NORMAL:
 			if (RCONSession::get_session()->send_command (current_input()))
 			{
-				g_input.insert (0, "");
-				g_needInputRender = true;
+				InputHistory.insert (0, "");
+				NeedInputRender = true;
 			}
 			break;
 		}
 		break;
 
 	case 'N' - 'A' + 1: // ^N
-		if (g_inputState == INPUTSTATE_NORMAL)
+		if (CurrentInputState == INPUTSTATE_NORMAL)
 			safe_disconnect ([]() {set_input_state (INPUTSTATE_ADDRESS);});
 		break;
+
+	case '\e': // Escape
+		// We may have an alt key coming
+		ch = ::getch();
+
+		if (ch != ERR)
+		{
+			switch (ch)
+			{
+			case 'b':
+			case 'B':
+				// readline alt-b - move one word to the left
+				CursorPosition = interface_find_previous_word();
+				NeedInputRender = true;
+				break;
+
+			case 'f':
+			case 'F':
+				// readline alt-f - move one word to the right
+				CursorPosition = interface_find_next_word();
+				NeedInputRender = true;
+				break;
+
+			case 'd':
+			case 'D':
+				// readline alt-d - delete from here till next word boundary
+				yank (CursorPosition, interface_find_next_word());
+				break;
+			}
+		}
+		else
+		{
+			// No alt-key, handle pure escape
+			if (CurrentInputState == INPUTSTATE_PASSWORD)
+				set_input_state (INPUTSTATE_ADDRESS);
+			else if (CurrentInputState == INPUTSTATE_ADDRESS)
+				set_input_state (INPUTSTATE_NORMAL);
+		}
+		break;
 	}
 
 	render();
@@ -790,15 +915,16 @@
 FUNCTION
 Interface::render() -> void
 {
-	if (g_needStatusBarRender) interface_render_statusbar();
-	if (g_needInputRender) interface_render_input();
-	if (g_needOutputRender) interface_render_output();
+	if (NeedStatusBarRender) interface_render_statusbar();
+	if (NeedInputRender) interface_render_input();
+	if (NeedOutputRender) interface_render_output();
+	if (NeedNicklistRender) interface_render_nicklist();
 
-	if (g_needRefresh)
+	if (NeedRefresh)
 	{
 		interface_position_cursor();
 		refresh();
-		g_needRefresh = false;
+		NeedRefresh = false;
 	}
 }
 
@@ -814,15 +940,26 @@
 	{
 		if (ch == '\n')
 		{
-			g_output[g_output.size() - 1].finalize();
-			g_output << RendererLine();
+			OutputLines.last().finalize();
+			OutputLines << ColoredLine();
 			continue;
 		}
 
-		g_output[g_output.size() - 1].add_char (ch);
+		if (OutputLines.last().length() == 0)
+		{
+			time_t now;
+			time (&now);
+			char timestamp[32];
+			strftime (timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime (&now));
+
+			for (char* cp = timestamp; *cp != '\0'; ++cp)
+				OutputLines.last().add_char (*cp);
+		}
+
+		OutputLines.last().add_char (ch);
 	}
 
-	g_needOutputRender = true;
+	NeedOutputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -832,114 +969,30 @@
 {
 	try
 	{
-		g_address = IPAddress::from_string (address);
+		CurrentAddress = IPAddress::from_string (address);
 	}
 	catch (std::exception& e)
 	{
-		print (e.what());
+		print ("%1\n", e.what());
 		return;
 	}
 
-	if (g_address.port == 0)
-		g_address.port = 10666;
-
-	RCONSession* session = RCONSession::new_session();
-	session->set_password (password);
-	session->connect (g_address);
-}
+	if (CurrentAddress.port == 0)
+		CurrentAddress.port = 10666;
 
-// -------------------------------------------------------------------------------------------------
-//
-METHOD
-RendererLine::finalize() -> void
-{
-	if (m_activeColor != DEFAULT)
-		this->set_color (m_activeColor, false);
-
-	if (m_boldActive)
-		m_data << RLINE_OFF_BOLD;
-
-	m_final = true;
+	RCONSession* session = RCONSession::get_session();
+	session->disconnect();
+	session->set_password (password);
+	session->connect (CurrentAddress);
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-RendererLine::add_char (char ch) -> void
+FUNCTION
+Interface::set_player_names (const StringList& names) -> void
 {
-	if (m_final)
-		return; // Don't touch finalized lines.
-
-	if (ch == '\x1C' and m_colorCodeStage == 0)
-	{
-		m_colorCodeStage = 1;
-		return;
-	}
-
-	if (m_colorCodeStage == 1)
-	{
-		if (m_activeColor != DEFAULT)
-			this->set_color (m_activeColor, false);
-
-		if (m_boldActive)
-			m_data << RLINE_OFF_BOLD;
-
-		if (ch >= 'a' and ch <= 'v' and ch != 'l')
-		{
-			auto colorInfo = g_colorCodes[ch - 'a'];
-			m_activeColor = colorInfo.color;
-			m_boldActive = colorInfo.bold;
-			assert (m_activeColor < 8);
-			this->set_color (m_activeColor, true);
-
-			if (m_boldActive)
-				m_data << RLINE_ON_BOLD;
-		}
-
-		m_colorCodeStage = 0;
-		return;
-	}
-
-	if (isprint (ch))
-	{
-		m_string += ch;
-		m_data << int (ch);
-		++m_length;
-	}
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-METHOD
-RendererLine::set_color (Color a, bool on) -> void
-{
-	switch (a)
-	{
-	case BLACK:   m_data << (on ? RLINE_ON_BLACK   : RLINE_OFF_BLACK); break;
-	case RED:     m_data << (on ? RLINE_ON_RED     : RLINE_OFF_RED); break;
-	case GREEN:   m_data << (on ? RLINE_ON_GREEN   : RLINE_OFF_GREEN); break;
-	case YELLOW:  m_data << (on ? RLINE_ON_YELLOW  : RLINE_OFF_YELLOW); break;
-	case BLUE:    m_data << (on ? RLINE_ON_BLUE    : RLINE_OFF_BLUE); break;
-	case MAGENTA: m_data << (on ? RLINE_ON_MAGENTA : RLINE_OFF_MAGENTA); break;
-	case CYAN:    m_data << (on ? RLINE_ON_CYAN    : RLINE_OFF_CYAN); break;
-	case WHITE:   m_data << (on ? RLINE_ON_WHITE   : RLINE_OFF_WHITE); break;
-	case NUM_COLORS:
-	case DEFAULT: assert (false); break;
-	}
-}
-
-// -------------------------------------------------------------------------------------------------
-// How many rows does this line take up?
-//
-METHOD
-RendererLine::rows (int cols) const -> int
-{
-	int rows = length() / cols;
-
-	if (length() % cols != 0)
-		rows++;
-
-	return max (rows, 1);
+	PlayerNames = names;
+	NeedNicklistRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -955,7 +1008,7 @@
 			complete += ' ';
 
 		input.replace (0, part.length(), complete);
-		g_cursor = complete.length();
-		g_needInputRender = true;
+		InputCursor = complete.length();
+		NeedInputRender = true;
 	}
 }
--- a/sources/interface.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/interface.h	Fri May 15 18:36:22 2015 +0300
@@ -40,6 +40,7 @@
 	FUNCTION set_title (const String& message) -> void;
 	FUNCTION update_statusbar() -> void;
 	FUNCTION connect (String address, String password) -> void;
+	FUNCTION set_player_names (const StringList& names) -> void;
 	FUNCTION need_refresh() -> void;
 	FUNCTION tab_complete (const String& part, String complete) -> void;
 };
--- a/sources/list.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/list.h	Fri May 15 18:36:22 2015 +0300
@@ -49,79 +49,309 @@
 	using ConstReverseIterator = typename C::const_reverse_iterator;
 	using Self = Container<T, C>;
 
-	Container();
-	Container (int numvalues);
-	Container (const C& a);
-	Container (std::initializer_list<T>&& a);
+	Container(){}
+
+	Container (int numvalues) :
+		m_container (numvalues) {}
+
+	Container (const C& other) :
+		m_container (other) {}
+
+	Container (std::initializer_list<T>&& a) :
+		m_container (a) {}
+
+	T& append (const T& value)
+	{
+		m_container.push_back (value);
+		return m_container[m_container.size() - 1];
+	}
+
+	Iterator begin()
+	{
+		return m_container.begin();
+	}
+
+	ConstIterator begin() const
+	{
+		return m_container.cbegin();
+	}
+
+	void clear()
+	{
+		m_container.clear();
+	}
+
+	bool contains (const T& a) const
+	{
+		return std::find (m_container.cbegin(), m_container.cend(), a) != m_container.end();
+	}
+
+	ConstReverseIterator crbegin() const
+	{
+		return m_container.crbegin();
+	}
+
+	ConstReverseIterator crend() const
+	{
+		return m_container.crbegin();
+	}
+
+	const C& container() const
+	{
+		return m_container;
+	}
+
+	Iterator end()
+	{
+		return m_container.end();
+	}
+
+	ConstIterator end() const
+	{
+		return m_container.cend();
+	}
+
+	Iterator find (const T& needle)
+	{
+		auto it = std::find (m_container.begin(), m_container.end(), needle);
+
+		if (it == m_container.end())
+			return end();
+
+		return it;
+	}
+
+	ConstIterator find (const T& needle) const
+	{
+		auto it = std::find (m_container.cbegin(), m_container.cend(), needle);
+
+		if (it == m_container.cend())
+			return end();
+
+		return it;
+	}
+
+	Iterator find (Function<bool (T const&)> func)
+	{
+		for (Iterator it = begin(); it != end(); ++it)
+		{
+			if (func (*it))
+				return it;
+		}
+
+		return end();
+	}
+
+	ConstIterator find (Function<bool (T const&)> func) const
+	{
+		for (ConstIterator it = begin(); it != end(); ++it)
+		{
+			if (func (*it))
+				return it;
+		}
+
+		return end();
+	}
+
+	T& first()
+	{
+		return *begin();
+	}
+
+	const T& first() const
+	{
+		return *begin();
+	}
+
+	void insert (int pos, const T& value)
+	{
+		m_container.insert (m_container.begin() + pos, value);
+	}
+
+	bool is_empty() const
+	{
+		return size() == 0;
+	}
+
+	T& last()
+	{
+		return *(end() - 1);
+	}
 
-	auto append (const T& value) -> T&;
-	auto begin() -> Iterator;
-	auto begin() const -> ConstIterator;
-	auto clear() -> void;
-	auto contains (const T& a) const -> bool;
-	auto crbegin() const -> ConstReverseIterator;
-	auto crend() const -> ConstReverseIterator;
-	auto container() const -> const C&;
-	auto end() -> Iterator;
-	auto end() const -> ConstIterator;
-	auto find (const T& needle) -> Iterator;
-	auto find (const T& needle) const -> ConstIterator;
-	auto find (Function<bool (T const&)> func) -> Iterator;
-	auto find (Function<bool (T const&)> func) const -> ConstIterator;
-	auto first() const -> const T&;
-	auto insert (int pos, const T& value) -> void;
-	auto is_empty() const -> bool;
-	auto last() const -> const T&;
-	auto merge (const Self& other) -> void;
-	auto pop (T& val) -> bool;
-	auto prepend (const T& value) -> T&;
-	auto rbegin() -> ReverseIterator;
-	auto remove_at (int pos) -> void;
-	auto remove_duplicates() -> void;
-	auto remove_one (const T& it) -> void;
-	auto rend() -> ReverseIterator;
-	auto resize (int size) -> void;
-	auto reverse() const -> Self;
-	auto size() const -> int;
-	auto sort() -> void;
-	auto splice (int a, int b) const -> Self;
-	auto splice (const Range<int>& a) const -> Self;
+	const T& last() const
+	{
+		return *(end() - 1);
+	}
+
+	void merge (const Self& other)
+	{
+		int oldsize = size();
+		resize (size() + other.size());
+		std::copy (other.begin(), other.end(), begin() + oldsize);
+	}
+
+	bool pop (T& val)
+	{
+		if (is_empty())
+			return false;
+
+		val = m_container[size() - 1];
+		m_container.erase (m_container.end() - 1);
+		return true;
+	}
+
+	T& prepend (const T& value)
+	{
+		m_container.push_front (value);
+		return m_container[0];
+	}
+
+	ReverseIterator rbegin()
+	{
+		return m_container.rbegin();
+	}
+
+	void remove_at (int pos)
+	{
+		assert (pos < size());
+		m_container.erase (m_container.begin() + pos);
+	}
+
+	void remove_duplicates()
+	{
+		sort();
+		resize (std::distance (begin(), std::unique (begin(), end())));
+	}
+
+	void remove_one (const T& valueToRemove)
+	{
+		auto it = std::find (m_container.begin(), m_container.end(), valueToRemove);
+
+		if (it != m_container.end())
+			m_container.erase (it);
+	}
+
+	ReverseIterator rend()
+	{
+		return m_container.rend();
+	}
+
+	void resize (int size)
+	{
+		m_container.resize (size);
+	}
 
-	auto operator<< (const T& value) -> Self&;
-	auto operator<< (const Self& vals) -> Self&;
-	auto operator[] (int n) -> T&;
-	auto operator[] (int n) const -> const T&;
-	auto operator[] (Range<int> const& n) const -> Self;
-	auto operator+ (const Self& other) const -> Self;
+	Self reverse() const
+	{
+		Self rev;
+		std::copy (rbegin(), rend(), rev.begin());
+		return rev;
+	}
+
+	int size() const
+	{
+		return m_container.size();
+	}
+
+	void sort()
+	{
+		std::sort (begin(), end());
+	}
+
+	Self splice (int a, int b) const
+	{
+		if (a < 0 or b >= size() or b < a)
+			return Self();
+
+		Self result;
+
+		for (int i = a; i <= b; ++i)
+			result << operator[] (i);
+
+		return result;
+	}
+
+	Self splice (const Range<int>& a) const
+	{
+		return splice (a.min(), a.max());
+	}
+
+	Self& operator<< (const T& value)
+	{
+		append (value);
+		return *this;
+	}
+
+	Self& operator<< (const Self& vals)
+	{
+		merge (vals);
+		return *this;
+	}
+
+	T& operator[] (int n)
+	{
+		assert (n < size());
+		return m_container[n];
+	}
+
+	const T& operator[] (int n) const
+	{
+		assert (n < size());
+		return m_container[n];
+	}
+
+	Self operator[] (Range<int> const& n) const
+	{
+		return splice (n);
+	}
+
+	Self operator+ (const Self& other) const
+	{
+		Self out (*this);
+		out.merge (other);
+		return out;
+	}
 
 protected:
 	C m_container;
 };
 
+// -------------------------------------------------------------------------------------------------
+//
 template<typename T, typename C>
-Container<T, C>& operator>> (const T& value, Container<T, C>& haystack);
+Container<T, C>& operator>> (const T& value, Container<T, C>& haystack)
+{
+	haystack.prepend (value);
+	return haystack;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
 
 template<typename T>
 using List = Container<T, std::deque<T>>;
 
+// -------------------------------------------------------------------------------------------------
+//
+
 template<typename T>
 class Vector : public Container<T, std::vector<T>>
 {
 public:
 	using Super = Container<T, std::vector<T>>;
+	using typename Super::Container;
 
-	Vector() {}
-	Vector (int numvalues) : Super (numvalues) {}
-	Vector (const Vector<T>& a) : Super (a) {}
-	Vector (std::initializer_list<T>&& a) : Super (a) {}
-	Vector (T* data, size_t length) : Super (std::vector<T> (data, data + length)) {}
+	Vector(){}
 
-	auto data() -> T*
+	Vector (T* data, size_t length) :
+		Super (std::vector<T> (data, data + length)) {}
+
+	T* data()
 	{
 		return Super::m_container.data();
 	}
 
-	auto data() const -> const T*
+	const T* data() const
 	{
 		return Super::m_container.data();
 	}
@@ -131,395 +361,3 @@
 		return data();
 	}
 };
-
-//
-// -------------------------------------------------------------------------------------------------
-//
-// IMPLEMENTATIONS
-//
-
-template<typename T, typename C>
-Container<T, C>::Container() {}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-Container<T, C>::Container (const C& other) :
-	m_container (other) {}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-Container<T, C>::Container (std::initializer_list<T> && a) :
-	m_container (a) {}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-Container<T, C>::Container (int numvalues) :
-	m_container (numvalues) {}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::begin() -> Iterator
-{
-	return m_container.begin();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::begin() const -> ConstIterator
-{
-	return m_container.cbegin();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::end() -> Iterator
-{
-	return m_container.end();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::end() const -> ConstIterator
-{
-	return m_container.cend();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::rbegin() -> ReverseIterator
-{
-	return m_container.rbegin();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::crbegin() const -> ConstReverseIterator
-{
-	return m_container.crbegin();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::rend() -> ReverseIterator
-{
-	return m_container.rend();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::crend() const -> ConstReverseIterator
-{
-	return m_container.crend();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::remove_at (int pos) -> void
-{
-	assert (pos < size());
-	m_container.erase (m_container.begin() + pos);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::prepend (const T& value) -> T&
-{
-	m_container.push_front (value);
-	return m_container[0];
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::append (const T& value) -> T&
-{
-	m_container.push_back (value);
-	return m_container[m_container.size() - 1];
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::merge (const Self& other) -> void
-{
-	int oldsize = size();
-	resize (size() + other.size());
-	std::copy (other.begin(), other.end(), begin() + oldsize);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::pop (T& val) -> bool
-{
-	if (is_empty())
-		return false;
-
-	val = m_container[size() - 1];
-	m_container.erase (m_container.end() - 1);
-	return true;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::operator<< (const T& value) -> Self&
-{
-	append (value);
-	return *this;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::operator<< (const Self& vals) -> Self&
-{
-	merge (vals);
-	return *this;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::reverse() const -> Self
-{
-	Self rev;
-	std::copy (rbegin(), rend(), rev.begin());
-	return rev;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::clear() -> void
-{
-	m_container.clear();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::insert (int pos, const T& value) -> void
-{
-	m_container.insert (m_container.begin() + pos, value);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::remove_duplicates() -> void
-{
-	sort();
-	resize (std::distance (begin(), std::unique (begin(), end())));
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::size() const -> int
-{
-	return m_container.size();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::operator[] (int n) -> T&
-{
-	assert (n < size());
-	return m_container[n];
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::operator[] (int n) const -> const T&
-{
-	assert (n < size());
-	return m_container[n];
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::operator[] (const Range<int>& n) const -> Self
-{
-	return splice (n);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::resize (int size) -> void
-{
-	m_container.resize (size);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::sort() -> void
-{
-	std::sort (begin(), end());
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::find (const T& needle) -> Iterator
-{
-	auto it = std::find (m_container.begin(), m_container.end(), needle);
-
-	if (it == m_container.end())
-		return end();
-
-	return it;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::find (const T& needle) const -> ConstIterator
-{
-	auto it = std::find (m_container.cbegin(), m_container.cend(), needle);
-
-	if (it == m_container.cend())
-		return end();
-
-	return it;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::find (Function<bool (T const&)> func) -> Iterator
-{
-	for (Iterator it = begin(); it != end(); ++it)
-	{
-		if (func (*it))
-			return it;
-	}
-
-	return end();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::find (Function<bool (T const&)> func) const -> ConstIterator
-{
-	for (ConstIterator it = begin(); it != end(); ++it)
-	{
-		if (func (*it))
-			return it;
-	}
-
-	return end();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::remove_one (const T& a) -> void
-{
-	auto it = std::find (m_container.begin(), m_container.end(), a);
-
-	if (it != m_container.end())
-		m_container.erase (it);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::is_empty() const -> bool
-{
-	return size() == 0;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::splice (int a, int b) const -> Self
-{
-	if (a < 0 or b >= size() or b < a)
-		return Self();
-
-	Self result;
-
-	for (int i = a; i <= b; ++i)
-		result << operator[] (i);
-
-	return result;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::splice (const Range<int>& a) const -> Self
-{
-	return splice (a.min(), a.max());
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::container() const -> const C&
-{
-	return m_container;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::first() const -> const T&
-{
-	return *m_container.cbegin();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::last() const -> const T&
-{
-	return *(m_container.cend() - 1);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::contains (const T& a) const -> bool
-{
-	return std::find (m_container.cbegin(), m_container.cend(), a) != m_container.end();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto Container<T, C>::operator+ (const Self& other) const -> Self
-{
-	Self out (*this);
-	out.merge (other);
-	return out;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-auto operator>> (const T& value, Container<T, C>& haystack) -> Container<T, C>&
-{
-	haystack.prepend (value);
-	return haystack;
-}
--- a/sources/main.cpp	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/main.cpp	Fri May 15 18:36:22 2015 +0300
@@ -35,8 +35,6 @@
 #include "huffman/huffman.h"
 #include "interface.h"
 
-static bool g_shouldExit = false;
-
 // -------------------------------------------------------------------------------------------------
 //
 FUNCTION
@@ -51,7 +49,6 @@
 	}
 
 	Interface::initialize();
-	RCONSession::new_session();
 
 	if (argc == 3)
 		Interface::connect (argv[1], argv[2]);
@@ -60,9 +57,6 @@
 	{
 		for (;;)
 		{
-			if (g_shouldExit)
-				break;
-
 			fd_set fdset;
 			int highest = 0;
 			timeval timeout;
@@ -70,11 +64,9 @@
 			timeout.tv_usec = 250000; // 0.25 seconds
 			FD_ZERO (&fdset);
 			FD_SET (0, &fdset);
-			RCONSession* session = RCONSession::get_session();
 
-			if (session)
 			{
-				int fd = session->socket()->file_descriptor();
+				int fd = RCONSession::get_session()->socket()->file_descriptor();
 				highest = max (highest, fd);
 				FD_SET (fd, &fdset);
 			}
@@ -85,24 +77,12 @@
 				// stdin is ready, what's incoming?
 				Interface::handle_input();
 
-			if (session)
-				session->tick();
-
+			RCONSession::get_session()->tick();
 			Interface::render();
 		}
 	}
 	catch (const Exitception&) {}
 
-	if (RCONSession::get_session())
-		RCONSession::get_session()->disconnect();
-
+	RCONSession::get_session()->disconnect();
 	return EXIT_SUCCESS;
 }
-
-// -------------------------------------------------------------------------------------------------
-//
-FUNCTION
-request_exit() -> void
-{
-	g_shouldExit = true;
-}
--- a/sources/mystring.cpp	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/mystring.cpp	Fri May 15 18:36:22 2015 +0300
@@ -36,16 +36,14 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::compare (const String& other) const -> int
+int String::compare (const String& other) const
 {
 	return m_string.compare (other.std_string());
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::trim (int n) -> void
+void String::trim (int n)
 {
 	if (n > 0)
 		m_string = mid (0, length() - n).std_string();
@@ -55,8 +53,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::strip (const List<char>& unwanted) -> String
+String String::strip (const List<char>& unwanted)
 {
 	String copy (m_string);
 
@@ -71,8 +68,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::to_uppercase() const -> String
+String String::to_uppercase() const
 {
 	String newstr (m_string);
 
@@ -87,8 +83,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::to_lowercase() const -> String
+String String::to_lowercase() const
 {
 	String newstr (m_string);
 
@@ -103,8 +98,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::split (char del) const -> StringList
+StringList String::split (char del) const
 {
 	String delimstr;
 	delimstr += del;
@@ -113,8 +107,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::split (const String& del) const -> StringList
+StringList String::split (const String& del) const
 {
 	StringList res;
 	int a = 0;
@@ -140,8 +133,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::replace (const char* a, const char* b) -> void
+void String::replace (const char* a, const char* b)
 {
 	long pos;
 
@@ -151,8 +143,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::count (char needle) const -> int
+int String::count (char needle) const
 {
 	int needles = 0;
 
@@ -165,8 +156,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::mid (long a, long b) const -> String
+String String::mid (long a, long b) const
 {
 	if (b == -1 or b > length())
 		b = length();
@@ -190,8 +180,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::word_position (int n) const -> int
+int String::word_position (int n) const
 {
 	int count = 0;
 
@@ -208,8 +197,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::find (const char* c, int a) const -> int
+int String::find (const char* c, int a) const
 {
 	int pos = m_string.find (c, a);
 
@@ -221,8 +209,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::find_last (const char* c, int a) const -> int
+int String::find_last (const char* c, int a) const
 {
 	if (a == -1 or a >= length())
 		a = length() - 1;
@@ -236,8 +223,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::to_int (bool* ok, int base) const -> long
+long String::to_int (bool* ok, int base) const
 {
 	errno = 0;
 	char* endptr;
@@ -251,8 +237,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::to_float (bool* ok) const -> float
+float String::to_float (bool* ok) const
 {
 	errno = 0;
 	char* endptr;
@@ -266,8 +251,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::to_double (bool* ok) const -> double
+double String::to_double (bool* ok) const
 {
 	errno = 0;
 	char* endptr;
@@ -281,8 +265,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::operator+ (const String& data) const -> String
+String String::operator+ (const String& data) const
 {
 	String newString = *this;
 	newString.append (data);
@@ -291,8 +274,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::operator+ (const char* data) const -> String
+String String::operator+ (const char* data) const
 {
 	String newstr = *this;
 	newstr.append (data);
@@ -301,39 +283,16 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::is_numeric() const -> bool
+bool String::is_numeric() const
 {
-	bool gotDot = false;
-
-	for (const char & c : m_string)
-	{
-		// Allow leading hyphen for negatives
-		if (&c == &m_string[0] and c == '-')
-			continue;
-
-		// Check for decimal point
-		if (!gotDot and c == '.')
-		{
-			gotDot = true;
-			continue;
-		}
-
-		if (c >= '0' and c <= '9')
-			continue; // Digit
-
-		// If the above cases didn't catch this character, it was
-		// illegal and this is therefore not a number.
-		return false;
-	}
-
-	return true;
+	char* endptr;
+	strtol (chars(), &endptr, 10);
+	return not (endptr && *endptr);
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::ends_with (const String& other) -> bool
+bool String::ends_with (const String& other)
 {
 	if (length() < other.length())
 		return false;
@@ -344,8 +303,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::starts_with (const String& other) -> bool
+bool String::starts_with (const String& other)
 {
 	if (length() < other.length())
 		return false;
@@ -355,16 +313,18 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::sprintf (const char* fmtstr, ...) -> void
+void String::sprintf (const char* fmtstr, ...)
 {
-	char* buf;
+	char* buf = nullptr;
 	int bufsize = 256;
 	va_list va;
 	va_start (va, fmtstr);
 
 	do
+	{
+		delete[] buf;
 		buf = new char[bufsize];
+	}
 	while (vsnprintf (buf, bufsize, fmtstr, va) >= bufsize);
 
 	va_end (va);
@@ -374,8 +334,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-StringList::join (const String& delim) -> String
+String StringList::join (const String& delim)
 {
 	String result;
 
@@ -392,8 +351,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::mask_against (const String& pattern) const -> bool
+bool String::mask_against (const String& pattern) const
 {
 	// Elevate to uppercase for case-insensitive matching
 	String pattern_upper = pattern.to_uppercase();
@@ -441,8 +399,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::from_number (int a) -> String
+String String::from_number (short int a)
 {
 	char buf[32];
 	::sprintf (buf, "%d", a);
@@ -451,8 +408,16 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::from_number (long a) -> String
+String String::from_number (int a)
+{
+	char buf[32];
+	::sprintf (buf, "%d", a);
+	return String (buf);
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+String String::from_number (long int a)
 {
 	char buf[32];
 	::sprintf (buf, "%ld", a);
@@ -461,8 +426,25 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::from_number (unsigned long a) -> String
+String String::from_number (unsigned short int a)
+{
+	char buf[32];
+	::sprintf (buf, "%u", a);
+	return String (buf);
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+String String::from_number (unsigned int a)
+{
+	char buf[32];
+	::sprintf (buf, "%u", a);
+	return String (buf);
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+String String::from_number (unsigned long int a)
 {
 	char buf[32];
 	::sprintf (buf, "%lu", a);
@@ -471,8 +453,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::from_number (double a) -> String
+String String::from_number (double a)
 {
 	char buf[64];
 	::sprintf (buf, "%f", a);
@@ -481,8 +462,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::md5() const -> String
+String String::md5() const
 {
 	char checksum[33];
 	CalculateMD5 (reinterpret_cast<const unsigned char*> (chars()), length(), checksum);
@@ -492,8 +472,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-METHOD
-String::normalize (int (*filter)(int)) -> void
+void String::normalize (int (*filter)(int))
 {
 	int a = 0;
 	int b = length() - 1;
--- a/sources/mystring.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/mystring.h	Fri May 15 18:36:22 2015 +0300
@@ -39,7 +39,7 @@
 class StringList;
 
 // -------------------------------------------------------------------------------------------------
-
+//
 class String
 {
 public:
@@ -57,397 +57,114 @@
 	String (const Vector<char>& data) :
 		m_string (data.data(), data.size()) {}
 
-	inline METHOD append (const char* data) -> void;
-	inline METHOD append (char data) -> void;
-	inline METHOD append (const String& data) -> void;
-	inline METHOD begin() -> std::string::iterator;
-	inline METHOD begin() const -> std::string::const_iterator;
-	inline METHOD clear() -> void;
-	       METHOD compare (const String& other) const -> int;
-	       METHOD count (char needle) const -> int;
-	inline METHOD chars() const -> const char*;
-	inline METHOD end() -> std::string::iterator;
-	inline METHOD end() const -> std::string::const_iterator;
-	       METHOD ends_with (const String& other) -> bool;
-	       METHOD find (const char* c, int a = 0) const -> int;
-	       METHOD to_lowercase() const -> String;
-	inline METHOD index_difference (int a, int b) -> int;
-	inline METHOD insert (int pos, char c) -> void;
-	inline METHOD is_empty() const -> bool;
-	       METHOD is_numeric() const -> bool;
-	       METHOD find_last (const char* c, int a = -1) const -> int;
-	inline METHOD length() const -> int;
-	       METHOD mask_against (const String& pattern) const -> bool;
-	       METHOD md5() const -> String;
-	       METHOD mid (long a, long b = -1) const -> String;
-	inline METHOD modify_index (int& a) -> void;
-	       METHOD normalize (int (*filter)(int) = &std::isspace) -> void;
-	inline METHOD prepend (String a) -> void;
-	inline METHOD remove (int pos, int len) -> void;
-	inline METHOD remove_at (int pos) -> void;
-	inline METHOD remove_from_end (int len) -> void;
-	inline METHOD remove_from_start (int len) -> void;
-	       METHOD replace (const char* a, const char* b) -> void;
-	inline METHOD replace (int pos, int n, const String& a) -> void;
-	inline METHOD shrink_to_fit() -> void;
-	       METHOD split (const String& del) const -> StringList;
-	       METHOD split (char del) const -> StringList;
-	       METHOD sprintf (const char* fmtstr, ...) -> void;
-	       METHOD starts_with (const String& other) -> bool;
-	inline METHOD std_string() const -> const std::string&;
-	inline METHOD strip (char unwanted) -> String;
-	       METHOD strip (const List<char>& unwanted) -> String;
-	       METHOD to_double (bool* ok = nullptr) const -> double;
-	       METHOD to_float (bool* ok = nullptr) const -> float;
-	       METHOD to_int (bool* ok = nullptr, int base = 10) const -> long;
-	       METHOD trim (int n) -> void;
-	       METHOD to_uppercase() const -> String;
-	       METHOD word_position (int n) const -> int;
+	using Iterator = std::string::iterator;
+	using ConstIterator = std::string::const_iterator;
+
+	ConstIterator begin() const { return m_string.cbegin(); }
+	int compare (const String &other) const;
+	int count (char needle) const;
+	const char* chars() const { return m_string.c_str(); }
+	ConstIterator end() const { return m_string.end(); }
+	int find (const char*c, int a = 0) const;
+	bool is_empty() const { return m_string[0] == '\0'; }
+	bool is_numeric() const;
+	int find_last (const char*c, int a) const;
+	int length() const { return m_string.length(); }
+	bool mask_against (const String &pattern) const;
+	String md5() const;
+	String mid (long a, long b) const;
+	StringList split (const String &del) const;
+	StringList split (char del) const;
+	const std::string& std_string() const { return m_string; }
+	double to_double (bool* ok = nullptr) const;
+	float to_float (bool* ok = nullptr) const;
+	long to_int (bool* ok = nullptr, int base = 10) const;
+	String to_lowercase() const;
+	String to_uppercase() const;
+	int word_position (int n) const;
 
-	static METHOD from_number (int a) -> String;
-	static METHOD from_number (long a) -> String;
-	static METHOD from_number (unsigned long a) -> String;
-	static METHOD from_number (double a) -> String;
+	void append (const char* data) { m_string.append (data); }
+	void append (char data) { m_string.push_back (data); }
+	void append (const String& data) { m_string.append (data.chars()); }
+	Iterator begin() { return m_string.begin(); }
+	void clear() { m_string.clear(); }
+	Iterator end() { return m_string.end(); }
+	bool ends_with (const String &other);
+	int index_difference (int a, int b) { modify_index (a); modify_index (b); return b - a; }
+	void insert (int pos, char c) { m_string.insert (m_string.begin() + pos, c); }
+	void insert (int pos, const char*c) { m_string.insert (pos, c); }
+	void modify_index (int &a) { if (a < 0) { a = length() - a; } }
+	void normalize (int (*filter)(int) = &std::isspace);
+	void prepend (String a) { m_string = (a + m_string).std_string(); }
+	void remove (int pos, int len) { m_string.replace (pos, len, ""); }
+	void remove_at (int pos) { m_string.erase (m_string.begin() + pos); }
+	void remove_from_end (int len) { remove (length() - len, len); }
+	void remove_from_start (int len) { remove (0, len); }
+	void replace (const char* a, const char* b);
+	void replace (int pos, int n, const String &a) { m_string.replace (pos, n, a.chars()); }
+	void shrink_to_fit() { m_string.shrink_to_fit(); }
+	void sprintf (const char* fmtstr, ...);
+	bool starts_with (const String &other);
+	String strip (char unwanted) { return strip ({unwanted}); }
+	String strip (const List<char> &unwanted);
+	void trim (int n);
 
-	       METHOD operator+ (const String& data) const -> String;
-	       METHOD operator+ (const char* data) const -> String;
-	inline METHOD operator+ (int num) const -> String;
-	inline METHOD operator+= (const String& data) -> String&;
-	inline METHOD operator+= (const char* data) -> String&;
-	inline METHOD operator+= (int num) -> String&;
-	inline METHOD operator+= (char data) -> String&;
-	inline METHOD operator== (const String& other) const -> bool;
-	inline METHOD operator== (const char* other) const -> bool;
-	inline METHOD operator!= (const String& other) const -> bool;
-	inline METHOD operator!= (const char* other) const -> bool;
-	inline METHOD operator> (const String& other) const -> bool;
-	inline METHOD operator< (const String& other) const -> bool;
-	inline operator const char*() const;
-	inline operator const std::string&() const;
+	static String from_number (short int a);
+	static String from_number (int a);
+	static String from_number (long int a);
+	static String from_number (unsigned short int a);
+	static String from_number (unsigned int a);
+	static String from_number (unsigned long int a);
+	static String from_number (double a);
+
+	String operator+ (const String& data) const;
+	String operator+ (const char* data) const;
+	String operator+ (int num) const { return *this + String::from_number (num); }
+	String& operator+= (const String& data) { append (data); return *this; }
+	String& operator+= (const char* data) { append (data); return *this; }
+	String& operator+= (int num) { return operator+= (String::from_number (num)); }
+	String& operator+= (char data) { append (data); return *this; }
+	bool operator== (const String& other) const { return std_string() == other.std_string(); }
+	bool operator== (const char* other) const { return m_string == other; }
+	bool operator!= (const String& other) const { return std_string() != other.std_string(); }
+	bool operator!= (const char* other) const { return m_string != other; }
+	bool operator> (const String& other) const { return std_string() > other.std_string(); }
+	bool operator< (const String& other) const { return std_string() < other.std_string(); }
+	bool operator>= (const String& other) const { return std_string() >= other.std_string(); }
+	bool operator<= (const String& other) const { return std_string() <= other.std_string(); }
+	operator const char*() const { return chars(); }
+	operator const std::string&() const { return std_string(); }
 
 private:
 	std::string m_string;
 };
 
 // -------------------------------------------------------------------------------------------------
-
+//
 class StringList : public List<String>
 {
 public:
-	template<typename... Args>
-	StringList (Args... args) :
-		List<String> (args...) {}
+	using Super = List<String>;
+	using Super::Super;
+
+	StringList() {}
 
-	METHOD join (const String& delim) -> String;
+	StringList (const Super& other) :
+		Super (other) {}
+
+	String join (const String& delim);
 };
 
 // -------------------------------------------------------------------------------------------------
-
-inline FUNCTION operator== (const char* a, const String& b) -> bool;
-inline FUNCTION operator+ (const char* a, const String& b) -> String;
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::is_empty() const -> bool
-{
-	return m_string[0] == '\0';
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::append (const char* data) -> void
-{
-	m_string.append (data);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::append (char data) -> void
-{
-	m_string.push_back (data);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::append (const String& data) -> void
-{
-	m_string.append (data.chars());
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::begin() -> std::string::iterator
-{
-	return m_string.begin();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::begin() const -> std::string::const_iterator
-{
-	return m_string.cbegin();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline const char* String::chars() const
-{
-	return m_string.c_str();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::end() -> std::string::iterator
-{
-	return m_string.end();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::end() const -> std::string::const_iterator
-{
-	return m_string.end();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::clear() -> void
-{
-	m_string.clear();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::remove_at (int pos) -> void
-{
-	m_string.erase (m_string.begin() + pos);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::insert (int pos, char c) -> void
-{
-	m_string.insert (m_string.begin() + pos, c);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::length() const -> int
-{
-	return m_string.length();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::remove (int pos, int len) -> void
-{
-	m_string.replace (pos, len, "");
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::remove_from_start (int len) -> void
-{
-	remove (0, len);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::remove_from_end (int len) -> void
-{
-	remove (length() - len, len);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::replace (int pos, int n, const String& a) -> void
-{
-	m_string.replace (pos, n, a.chars());
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::shrink_to_fit() -> void
-{
-	m_string.shrink_to_fit();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline const std::string& String::std_string() const
-{
-	return m_string;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::strip (char unwanted) -> String
-{
-	return strip ({unwanted});
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator+ (int num) const -> String
-{
-	return *this + String::from_number (num);
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator+= (const String& data) -> String&
-{
-	append (data);
-	return *this;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator+= (const char* data) -> String&
-{
-	append (data);
-	return *this;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator+= (int num) -> String&
-{
-	return operator+= (String::from_number (num));
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::prepend (String a) -> void
-{
-	m_string = (a + m_string).std_string();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator+= (char data) -> String&
-{
-	append (data);
-	return *this;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator== (const String& other) const -> bool
-{
-	return std_string() == other.std_string();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator== (const char* other) const -> bool
-{
-	return operator== (String (other));
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator!= (const String& other) const -> bool
-{
-	return std_string() != other.std_string();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator!= (const char* other) const -> bool
-{
-	return operator!= (String (other));
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator> (const String& other) const -> bool
-{
-	return std_string() > other.std_string();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::operator< (const String& other) const -> bool
-{
-	return std_string() < other.std_string();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline String::operator const char*() const
-{
-	return chars();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline String::operator const std::string&() const
-{
-	return std_string();
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::modify_index (int& a) -> void
-{
-	if (a < 0)
-		a = length() - a;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline METHOD
-String::index_difference (int a, int b) -> int
-{
-	modify_index (a);
-	modify_index (b);
-	return b - a;
-}
-
-// -------------------------------------------------------------------------------------------------
-
-inline FUNCTION
-operator== (const char* a, const String& b) -> bool
+//
+inline bool operator== (const char* a, const String& b)
 {
 	return b == a;
 }
 
 // -------------------------------------------------------------------------------------------------
-
-inline FUNCTION
-operator+ (const char* a, const String& b) -> String
+//
+inline String operator+ (const char* a, const String& b)
 {
 	return String (a) + b;
 }
--- a/sources/network/bytestream.cpp	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/network/bytestream.cpp	Fri May 15 18:36:22 2015 +0300
@@ -68,7 +68,7 @@
 //
 Bytestream::~Bytestream()
 {
-	delete m_data;
+	delete[] m_data;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -178,8 +178,10 @@
 METHOD
 Bytestream::read_float() -> float
 {
-	int value = read_long();
-	return reinterpret_cast<float&> (value);
+	float value;
+	int intvalue = read_long();
+	memcpy (&value, &intvalue, sizeof intvalue);
+	return value;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -292,7 +294,9 @@
 Bytestream::write_float (float val) -> void
 {
 	// I know this is probably dangerous but this is what Zandronum does so yeah
-	write_long (reinterpret_cast<int&> (val));
+	int intvalue;
+	memcpy (&intvalue, &val, sizeof val);
+	write_long (intvalue);
 }
 
 // -------------------------------------------------------------------------------------------------
--- a/sources/network/ipaddress.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/network/ipaddress.h	Fri May 15 18:36:22 2015 +0300
@@ -38,7 +38,7 @@
 
 struct IPAddress
 {
-	class StringParseError
+	class StringParseError : public std::exception
 	{
 		String m_message;
 
--- a/sources/network/rconsession.cpp	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/network/rconsession.cpp	Fri May 15 18:36:22 2015 +0300
@@ -1,8 +1,6 @@
 #include "rconsession.h"
 #include "../interface.h"
 
-RCONSession* g_rconSession = nullptr;
-
 // -------------------------------------------------------------------------------------------------
 //
 RCONSession::RCONSession() :
@@ -10,42 +8,17 @@
 	m_lastPing (0),
 	m_numAdmins (0)
 {
-	if (g_rconSession != NULL)
+	if (not m_socket.set_blocking (false))
 	{
-		g_rconSession->disconnect();
-		delete g_rconSession;
+		print_to (stderr, "unable to set socket as non-blocking: %s\n",
+			m_socket.error_string().chars());
+		exit (EXIT_FAILURE);
 	}
-
-	g_rconSession = this;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-STATIC METHOD
-RCONSession::new_session() -> RCONSession*
-{
-	RCONSession* session = new RCONSession;
-
-	if (not session->socket()->set_blocking (false))
-	{
-		print ("unable to set socket as non-blocking: %s\n",
-			session->socket()->error_string().chars());
-		delete session;
-		return nullptr;
-	}
-
-	return session;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-RCONSession::~RCONSession()
-{
-	disconnect();
-
-	if (g_rconSession == this)
-		g_rconSession = nullptr;
-}
+RCONSession::~RCONSession() {}
 
 // -------------------------------------------------------------------------------------------------
 //
@@ -55,6 +28,7 @@
 	m_address = address;
 	m_state = RCON_CONNECTING;
 	Interface::update_statusbar();
+	send_hello();
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -134,12 +108,12 @@
 			switch (ServerResponse (header))
 			{
 			case SVRC_OLDPROTOCOL:
-				print ("Your RCON client is using outdated protocol.\n");
+				print_error ("Your RCON client is using outdated protocol.\n");
 				m_state = RCON_DISCONNECTED;
 				break;
 
 			case SVRC_BANNED:
-				print ("You have been banned from the server.\n");
+				print_error ("You have been banned from the server.\n");
 				m_state = RCON_DISCONNECTED;
 				break;
 
@@ -150,7 +124,7 @@
 				break;
 
 			case SVRC_INVALIDPASSWORD:
-				print ("Password incorrect.\n");
+				print_error ("Login failed.\n");
 				m_state = RCON_DISCONNECTED;
 				break;
 
@@ -223,20 +197,25 @@
 	}
 	catch (std::exception& e)
 	{
-		print ("error while reading packet: %1\n", e.what());
+		print_warning ("Couldn't process packet: %1\n", e.what());
 	}
 }
 
 METHOD
 RCONSession::process_server_updates (Bytestream& packet) -> void
 {
-	switch (RCONUpdateType (packet.read_byte()))
+	int header = packet.read_byte();
+
+	switch (RCONUpdateType (header))
 	{
 	case SVRCU_PLAYERDATA:
 		{
-			Vector<String> players;
+			StringList players;
+
 			for (int i = packet.read_byte(); i > 0; --i)
-				players << packet.read_string();
+				players.append (packet.read_string());
+
+			Interface::set_player_names (players);
 		}
 		break;
 
@@ -249,6 +228,10 @@
 		m_level = packet.read_string();
 		Interface::update_statusbar();
 		break;
+
+	default:
+		print_warning ("Unknown server update type: %d\n", header);
+		break;
 	}
 }
 
@@ -309,10 +292,8 @@
 STATIC METHOD
 RCONSession::get_session() -> RCONSession*
 {
-	if (g_rconSession == nullptr)
-		new_session();
-
-	return g_rconSession;
+	static RCONSession session;
+	return &session;
 }
 
 // -------------------------------------------------------------------------------------------------
--- a/sources/network/rconsession.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/network/rconsession.h	Fri May 15 18:36:22 2015 +0300
@@ -112,7 +112,6 @@
 	METHOD is_active() const -> bool;
 	METHOD request_tab_complete (const String& part) -> void;
 
-	static METHOD new_session() -> RCONSession*;
 	static METHOD get_session() -> RCONSession*;
 
 private:
--- a/sources/network/udpsocket.cpp	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/network/udpsocket.cpp	Fri May 15 18:36:22 2015 +0300
@@ -34,6 +34,7 @@
 #include <netinet/in.h>
 #include <string.h>
 #include <fcntl.h>
+#include <unistd.h>
 #include "udpsocket.h"
 #include "../huffman/huffman.h"
 
@@ -46,7 +47,10 @@
 
 // -----------------------------------------------------------------------------
 //
-UDPSocket::~UDPSocket() {}
+UDPSocket::~UDPSocket()
+{
+	close (m_socket);
+}
 
 // -------------------------------------------------------------------------------------------------
 //
--- a/sources/range.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/range.h	Fri May 15 18:36:22 2015 +0300
@@ -48,161 +48,102 @@
 	{
 		T value;
 		T step;
-		inline METHOD operator*() -> T&;
-		inline METHOD operator== (const Iterator& other) const -> bool;
-		inline METHOD operator!= (const Iterator& other) const -> bool;
-		inline METHOD operator++() -> Iterator&;
+
+		Iterator (T value, T step) :
+			value (value),
+			step (step) {}
+
+		T& operator*()
+		{
+			return value;
+		}
+
+		bool operator== (const Iterator& other) const
+		{
+			return value == other.value;
+		}
+
+		bool operator!= (const Iterator& other) const
+		{
+			return value != other.value;
+		}
+
+		Iterator& operator++()
+		{
+			value += step; return *this;
+		}
 	};
 
-	Range (const T& a, const T& b, const T& step = 1);
-	Range();
-
-	METHOD begin() const -> Iterator;
-	METHOD end() const -> Iterator;
-	METHOD min() const -> T;
-	METHOD max() const -> T;
-	METHOD check_bounds() -> void;
-	METHOD contains (const T& c) const -> bool;
-	METHOD contains_exclusively (const T& c) const -> bool;
-	METHOD overlaps (Range<T> const& other) const -> bool;
-	METHOD operator== (Range<T> const& other) const -> bool;
-	METHOD operator!= (Range<T> const& other) const -> bool;
-};
+	Range (T a, T b, T step = 1) :
+		m_a (a),
+		m_b (b),
+		m_step (step)
+	{
+		check_bounds();
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T>
-Range<T>::Range (const T& a, const T& b, const T& step) :
-	m_a (a),
-	m_b (b),
-	m_step (step)
-{
-	check_bounds();
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T>
-Range<T>::Range() :
-	m_a (T()),
-	m_b (T()) {}
+	Range() :
+		m_a (T()),
+		m_b (T()) {}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> inline METHOD
-Range<T>::Iterator::operator*() -> T&
-{
-	return value;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> inline METHOD
-Range<T>::Iterator::operator== (const Iterator& other) const -> bool
-{
-	return value == other.value;
-}
+	Iterator begin() const
+	{
+		Iterator it;
+		it.value = min();
+		it.step = m_step;
+		return it;
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> inline METHOD
-Range<T>::Iterator::operator!= (const Iterator& other) const -> bool
-{
-	return value != other.value;
-}
+	Iterator end() const
+	{
+		Iterator it;
+		it.value = max() + 1;
+		it.step = m_step;
+		return it;
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> inline METHOD
-Range<T>::Iterator::operator++() -> Iterator&
-{
-	value += step;
-	return *this;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::check_bounds() -> void
-{
-	if (m_b < m_a)
-		std::swap (m_a, m_b);
-}
+	T min() const
+	{
+		return m_a;
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::contains (const T& c) const -> bool
-{
-	return (c >= m_a) and (c <= m_b);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::contains_exclusively (const T& c) const -> bool
-{
-	return (c > m_a) and (c < m_b);
-}
+	T max() const
+	{
+		return m_b;
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::overlaps (Range<T> const& other) const -> bool
-{
-	return contains (other.m_a) or contains (other.m_b);
-}
+	void check_bounds()
+	{
+		if (m_b < m_a)
+			std::swap (m_a, m_b);
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::operator== (Range<T> const& other) const -> bool
-{
-	return m_a == other.m_a and m_b == other.m_b;
-}
+	bool contains (T c) const
+	{
+		return c >= m_a
+		   and c <= m_b;
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::operator!= (Range<T> const& other) const -> bool
-{
-	return not operator== (other);
-}
+	bool contains_exclusively (T c) const
+	{
+		return c > m_a
+		   and c < m_b;
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::min() const -> T
-{
-	return m_a;
-}
+	bool overlaps (Range<T> const& other) const
+	{
+		return contains (other.m_a)
+			or contains (other.m_b);
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::max() const -> T
-{
-	return m_b;
-}
+	bool operator== (Range<T> const& other) const
+	{
+		return m_a == other.m_a
+		   and m_b == other.m_b;
+	}
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::begin() const -> Iterator
-{
-	Iterator it;
-	it.value = min();
-	it.step = m_step;
-	return it;
-}
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T> METHOD
-Range<T>::end() const -> Iterator
-{
-	Iterator it;
-	it.value = max() + 1;
-	it.step = m_step;
-	return it;
-}
+	bool operator!= (Range<T> const& other) const
+	{
+		return not operator== (other);
+	}
+};
\ No newline at end of file
--- a/sources/version.cpp	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/version.cpp	Fri May 15 18:36:22 2015 +0300
@@ -40,9 +40,11 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-FUNCTION
-full_version_string() -> const char*
+const char* full_version_string()
 {
+#ifdef IS_RELEASE
+	return VERSION_STRING;
+#else
 	static char buffer[128] = {0};
 
 	if (buffer[0] == '\0')
@@ -54,12 +56,12 @@
 	}
 
 	return buffer;
+#endif
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-FUNCTION
-changeset_date_string() -> const char*
+const char* changeset_date_string()
 {
 	return HG_DATE_STRING;
 }
--- a/sources/version.h	Mon Dec 15 20:19:18 2014 +0200
+++ b/sources/version.h	Fri May 15 18:36:22 2015 +0300
@@ -33,9 +33,11 @@
 
 #define APPNAME "zfc9000"
 #define VERSION_MAJOR 1
-#define VERSION_MINOR 0
+#define VERSION_MINOR 1
 #define VERSION_PATCH 0
 
+// #define IS_RELEASE
+
 // -------------------------------------------------------------------------------------------------
 // Version string
 
@@ -53,14 +55,13 @@
 // -------------------------------------------------------------------------------------------------
 // Returns the bare version string (1.2.3)
 
-inline FUNCTION
-version_string() -> const char*
+inline const char* version_string()
 {
 	return VERSION_STRING;
 }
 
 // Returns full version string, with hash (1.2.3-abcd456)
-FUNCTION full_version_string() -> const char*;
+const char* full_version_string();
 
 // Returns changeset date string
-FUNCTION changeset_date_string() -> const char*;
+const char* changeset_date_string();
\ No newline at end of file

mercurial