diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua
index 2627559a5f0a8289c7472ca86a531bf875c2505b..22755386b2dea85ef964830f328464ea8b221ce1 100644
--- a/builtin/game/chatcommands.lua
+++ b/builtin/game/chatcommands.lua
@@ -102,7 +102,7 @@ core.register_chatcommand("help", {
 	description = "Get help for commands or list privileges",
 	func = function(name, param)
 		local function format_help_line(cmd, def)
-			local msg = core.colorize("00ffff", "/"..cmd)
+			local msg = core.colorize("#00ffff", "/"..cmd)
 			if def.params and def.params ~= "" then
 				msg = msg .. " " .. def.params
 			end
diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua
index 8d5c80216ce173975b6c87c525b515d4ee1abc14..918315656e5f6cb6e3368c4560daea5a814ad3a5 100644
--- a/builtin/game/misc.lua
+++ b/builtin/game/misc.lua
@@ -198,19 +198,34 @@ function core.http_add_fetch(httpenv)
 	return httpenv
 end
 
-function core.get_color_escape_sequence(color)
-	--if string.len(color) == 3 then
-	--	local r = string.sub(color, 1, 1)
-	--	local g = string.sub(color, 2, 2)
-	--	local b = string.sub(color, 3, 3)
-	--	color = r ..  r .. g .. g .. b .. b
-	--end
-
-	--assert(#color == 6, "Color must be six characters in length.")
-	--return "\v" .. color
-	return "\v(color;" .. color .. ")"
-end
+if minetest.setting_getbool("disable_escape_sequences") then
+
+	function core.get_color_escape_sequence(color)
+		return ""
+	end
+
+	function core.get_background_escape_sequence(color)
+		return ""
+	end
+
+	function core.colorize(color, message)
+		return message
+	end
+
+else
+
+	local ESCAPE_CHAR = string.char(0x1b)
+	function core.get_color_escape_sequence(color)
+		return ESCAPE_CHAR .. "(c@" .. color .. ")"
+	end
+
+	function core.get_background_escape_sequence(color)
+		return ESCAPE_CHAR .. "(b@" .. color .. ")"
+	end
+
+	function core.colorize(color, message)
+		return core.get_color_escape_sequence(color) .. message .. core.get_color_escape_sequence("#ffffff")
+	end
 
-function core.colorize(color, message)
-	return core.get_color_escape_sequence(color) .. message .. core.get_color_escape_sequence("ffffff")
 end
+
diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt
index 93fb8e95273abfabc93c3bc9e0715d026ae861c0..538a04f33f7c659046a01b1af76ab62f845fb255 100644
--- a/builtin/settingtypes.txt
+++ b/builtin/settingtypes.txt
@@ -615,6 +615,11 @@ server_announce (Announce server) bool false
 #    If you want to announce your ipv6 address, use  serverlist_url = v6.servers.minetest.net.
 serverlist_url (Serverlist URL) string servers.minetest.net
 
+#    Disable escape sequences, e.g. chat coloring.
+#    Use this if you want to run a server with pre-0.4.14 clients and you want to disable
+#    the escape sequences generated by mods.
+disable_escape_sequences (Disable escape sequences) bool false
+
 [*Network]
 
 #    Network port to listen (UDP).
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 82a0acbeec06da8e65f6660600eb76e1608e43c9..aa0d7e45d609eaff65f43ff82620efb89725fde7 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -1701,6 +1701,24 @@ numerical form, the raw integer value of an ARGB8 quad:
 or string form, a ColorString (defined above):
     `colorspec = "green"`
 
+Escape sequences
+----------------
+Most text can contain escape sequences, that can for example color the text.
+There are a few exceptions: tab headers, dropdowns and vertical labels can't.
+The following functions provide escape sequences:
+* `core.get_color_escape_sequence(color)`:
+    * `color` is a ColorString
+    * The escape sequence sets the text color to `color`
+* `core.colorize(color, message)`:
+    * Equivalent to:
+      `core.get_color_escape_sequence(color) ..
+       message ..
+       core.get_color_escape_sequence("#ffffff")`
+* `color.get_background_escape_sequence(color)`
+    * `color` is a ColorString
+    * The escape sequence sets the background of the whole text element to
+      `color`. Only defined for item descriptions and tooltips.
+
 Spatial Vectors
 ---------------
 * `vector.new(a[, b, c])`: returns a vector:
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ea1564eebc2c39d4fcaba7f02a74b6fdd732a5ed..f02812415351df2372e4206c62f29fb12a853b5f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -376,6 +376,7 @@ add_subdirectory(network)
 add_subdirectory(script)
 add_subdirectory(unittest)
 add_subdirectory(util)
+add_subdirectory(irrlicht_changes)
 
 set(common_SRCS
 	ban.cpp
@@ -493,6 +494,7 @@ set(client_SRCS
 	${common_SRCS}
 	${sound_SRCS}
 	${client_network_SRCS}
+	${client_irrlicht_changes_SRCS}
 	camera.cpp
 	client.cpp
 	clientmap.cpp
diff --git a/src/cguittfont/CGUITTFont.cpp b/src/cguittfont/CGUITTFont.cpp
index 2342eb7485f21b93d40514fc7b5ad14a13f5a29f..c2d37c6c0dda0b3f61666d65810f52326e1bdfab 100644
--- a/src/cguittfont/CGUITTFont.cpp
+++ b/src/cguittfont/CGUITTFont.cpp
@@ -1,6 +1,7 @@
 /*
    CGUITTFont FreeType class for Irrlicht
    Copyright (c) 2009-2010 John Norman
+   Copyright (c) 2016 Nathanaël Courant
 
    This software is provided 'as-is', without any express or implied
    warranty. In no event will the authors be held liable for any
@@ -545,6 +546,13 @@ void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hintin
 
 void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
 {
+	draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, vcenter, clip);
+}
+
+void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
+{
+	std::vector<video::SColor> colors = text.getColors();
+
 	if (!Driver)
 		return;
 
@@ -572,7 +580,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position
 	}
 
 	// Convert to a unicode string.
-	core::ustring utext(text);
+	core::ustring utext = text.getString();
 
 	// Set up our render map.
 	core::map<u32, CGUITTGlyphPage*> Render_Map;
@@ -581,6 +589,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position
 	u32 n;
 	uchar32_t previousChar = 0;
 	core::ustring::const_iterator iter(utext);
+	std::vector<video::SColor> applied_colors;
 	while (!iter.atEnd())
 	{
 		uchar32_t currentChar = *iter;
@@ -590,7 +599,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position
 		if (currentChar == L'\r') // Mac or Windows breaks
 		{
 			lineBreak = true;
-			if (*(iter + 1) == (uchar32_t)'\n')	// Windows line breaks.
+			if (*(iter + 1) == (uchar32_t)'\n') 	// Windows line breaks.
 				currentChar = *(++iter);
 		}
 		else if (currentChar == (uchar32_t)'\n') // Unix breaks
@@ -627,6 +636,9 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position
 			page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
 			page->render_source_rects.push_back(glyph.source_rect);
 			Render_Map.set(glyph.glyph_page, page);
+			u32 current_color = iter.getPos();
+			if (current_color < colors.size())
+				applied_colors.push_back(colors[current_color]);
 		}
 		offset.X += getWidthFromCharacter(currentChar);
 
@@ -645,8 +657,6 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position
 
 		CGUITTGlyphPage* page = n->getValue();
 
-		if (!use_transparency) color.color |= 0xff000000;
-
 		if (shadow_offset) {
 			for (size_t i = 0; i < page->render_positions.size(); ++i)
 				page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
@@ -654,7 +664,17 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position
 			for (size_t i = 0; i < page->render_positions.size(); ++i)
 				page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
 		}
-		Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true);
+		for (size_t i = 0; i < page->render_positions.size(); ++i) {
+			irr::video::SColor col;
+			if (!applied_colors.empty()) {
+				col = applied_colors[i < applied_colors.size() ? i : 0];
+			} else {
+				col = irr::video::SColor(255, 255, 255, 255);
+			}
+			if (!use_transparency)
+				col.color |= 0xff000000;
+			Driver->draw2DImage(page->texture, page->render_positions[i], page->render_source_rects[i], clip, col, true);
+		}
 	}
 }
 
diff --git a/src/cguittfont/CGUITTFont.h b/src/cguittfont/CGUITTFont.h
index e24d8f18b27499f17c95a323120781bc9252748f..0aa540c5cfce7bf8b1ec5f69965fdccae7ef5cde 100644
--- a/src/cguittfont/CGUITTFont.h
+++ b/src/cguittfont/CGUITTFont.h
@@ -1,6 +1,7 @@
 /*
    CGUITTFont FreeType class for Irrlicht
    Copyright (c) 2009-2010 John Norman
+   Copyright (c) 2016 Nathanaël Courant
 
    This software is provided 'as-is', without any express or implied
    warranty. In no event will the authors be held liable for any
@@ -33,6 +34,8 @@
 
 #include <irrlicht.h>
 #include <ft2build.h>
+#include <vector>
+#include "util/enriched_string.h"
 #include FT_FREETYPE_H
 
 namespace irr
@@ -258,6 +261,10 @@ namespace gui
 			virtual void draw(const core::stringw& text, const core::rect<s32>& position,
 				video::SColor color, bool hcenter=false, bool vcenter=false,
 				const core::rect<s32>* clip=0);
+			
+			virtual void draw(const EnrichedString& text, const core::rect<s32>& position,
+				video::SColor color, bool hcenter=false, bool vcenter=false,
+				const core::rect<s32>* clip=0);
 
 			//! Returns the dimension of a character produced by this font.
 			virtual core::dimension2d<u32> getCharDimension(const wchar_t ch) const;
diff --git a/src/chat.cpp b/src/chat.cpp
index 958389df517eebb20625f8fc611bfe5a107be6ad..46555b3dcc8f9da920efa2c44cade7e7ca971d6a 100644
--- a/src/chat.cpp
+++ b/src/chat.cpp
@@ -267,28 +267,26 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
 		next_frags.push_back(temp_frag);
 	}
 
-	std::wstring name_sanitized = removeEscapes(line.name);
+	std::wstring name_sanitized = line.name.c_str();
 
 	// Choose an indentation level
 	if (line.name.empty()) {
 		// Server messages
 		hanging_indentation = 0;
-	}
-	else if (name_sanitized.size() + 3 <= cols/2) {
+	} else if (name_sanitized.size() + 3 <= cols/2) {
 		// Names shorter than about half the console width
 		hanging_indentation = line.name.size() + 3;
-	}
-	else {
+	} else {
 		// Very long names
 		hanging_indentation = 2;
 	}
-	ColoredString line_text(line.text);
+	//EnrichedString line_text(line.text);
 
 	next_line.first = true;
 	bool text_processing = false;
 
 	// Produce fragments and layout them into lines
-	while (!next_frags.empty() || in_pos < line_text.size())
+	while (!next_frags.empty() || in_pos < line.text.size())
 	{
 		// Layout fragments into lines
 		while (!next_frags.empty())
@@ -326,9 +324,9 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
 		}
 
 		// Produce fragment
-		if (in_pos < line_text.size())
+		if (in_pos < line.text.size())
 		{
-			u32 remaining_in_input = line_text.size() - in_pos;
+			u32 remaining_in_input = line.text.size() - in_pos;
 			u32 remaining_in_output = cols - out_column;
 
 			// Determine a fragment length <= the minimum of
@@ -338,14 +336,14 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
 			while (frag_length < remaining_in_input &&
 					frag_length < remaining_in_output)
 			{
-				if (isspace(line_text[in_pos + frag_length]))
+				if (isspace(line.text.getString()[in_pos + frag_length]))
 					space_pos = frag_length;
 				++frag_length;
 			}
 			if (space_pos != 0 && frag_length < remaining_in_input)
 				frag_length = space_pos + 1;
 
-			temp_frag.text = line_text.substr(in_pos, frag_length);
+			temp_frag.text = line.text.substr(in_pos, frag_length);
 			temp_frag.column = 0;
 			//temp_frag.bold = 0;
 			next_frags.push_back(temp_frag);
@@ -729,19 +727,22 @@ ChatBuffer& ChatBackend::getRecentBuffer()
 	return m_recent_buffer;
 }
 
-std::wstring ChatBackend::getRecentChat()
+EnrichedString ChatBackend::getRecentChat()
 {
-	std::wostringstream stream;
+	EnrichedString result;
 	for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i)
 	{
 		const ChatLine& line = m_recent_buffer.getLine(i);
 		if (i != 0)
-			stream << L"\n";
-		if (!line.name.empty())
-			stream << L"<" << line.name << L"> ";
-		stream << line.text;
+			result += L"\n";
+		if (!line.name.empty()) {
+			result += L"<";
+			result += line.name;
+			result += L"> ";
+		}
+		result += line.text;
 	}
-	return stream.str();
+	return result;
 }
 
 ChatPrompt& ChatBackend::getPrompt()
diff --git a/src/chat.h b/src/chat.h
index 661cafc82e09c777db1d53003d1fea2a416c4f4a..11061fd39e1c0e5619ec9d4b59c2961a7fe1260a 100644
--- a/src/chat.h
+++ b/src/chat.h
@@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <list>
 
 #include "irrlichttypes.h"
-#include "util/coloredstring.h"
+#include "util/enriched_string.h"
 
 // Chat console related classes
 
@@ -34,9 +34,9 @@ struct ChatLine
 	// age in seconds
 	f32 age;
 	// name of sending player, or empty if sent by server
-	std::wstring name;
+	EnrichedString name;
 	// message text
-	ColoredString text;
+	EnrichedString text;
 
 	ChatLine(std::wstring a_name, std::wstring a_text):
 		age(0.0),
@@ -44,12 +44,19 @@ struct ChatLine
 		text(a_text)
 	{
 	}
+
+	ChatLine(EnrichedString a_name, EnrichedString a_text):
+		age(0.0),
+		name(a_name),
+		text(a_text)
+	{
+	}
 };
 
 struct ChatFormattedFragment
 {
 	// text string
-	std::wstring text;
+	EnrichedString text;
 	// starting column
 	u32 column;
 	// formatting
@@ -262,7 +269,7 @@ class ChatBackend
 	// Get the recent messages buffer
 	ChatBuffer& getRecentBuffer();
 	// Concatenate all recent messages
-	std::wstring getRecentChat();
+	EnrichedString getRecentChat();
 	// Get the console prompt
 	ChatPrompt& getPrompt();
 
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index bcf114760e3be842579e2cef63017bcb8a37e005..a1ec37fe39cee399c1b8bc6da41591376fbe6375 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -1,6 +1,5 @@
 set(client_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
 	PARENT_SCOPE
 )
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index e8b652c17ad52f7b945fabf73cd15c41e29fa7fc..632ec0df93fbf51cbdf59ae73459cefbdb966d2b 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -202,6 +202,8 @@ void set_default_settings(Settings *settings)
 	settings->setDefault("server_name", "");
 	settings->setDefault("server_description", "");
 
+	settings->setDefault("disable_escape_sequences", "false");
+
 #if USE_FREETYPE
 	settings->setDefault("freetype", "true");
 	settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "liberationsans.ttf"));
diff --git a/src/game.cpp b/src/game.cpp
index 71a04aef57c1fa5bf2c3b66108ae3e9ed1eff5ea..def202fe558711cb6c19fa273dca64ca4038ad20 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "log.h"
 #include "filesys.h"
 #include "gettext.h"
-#include "client/guiChatConsole.h"
+#include "guiChatConsole.h"
 #include "guiFormSpecMenu.h"
 #include "guiKeyChangeMenu.h"
 #include "guiPasswordChange.h"
@@ -55,14 +55,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "tool.h"
 #include "util/directiontables.h"
 #include "util/pointedthing.h"
+#include "irrlicht_changes/static_text.h"
 #include "version.h"
 #include "minimap.h"
 #include "mapblock_mesh.h"
 
-#if USE_FREETYPE
-	#include "util/statictext.h"
-#endif
-
 #include "sound.h"
 
 #if USE_SOUND
@@ -541,7 +538,7 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, FontEngine *fe,
 		std::ostringstream os(std::ios_base::binary);
 		g_profiler->printPage(os, show_profiler, show_profiler_max);
 		std::wstring text = utf8_to_wide(os.str());
-		guitext_profiler->setText(text.c_str());
+		setStaticText(guitext_profiler, text.c_str());
 		guitext_profiler->setVisible(true);
 
 		s32 w = fe->getTextWidth(text.c_str());
@@ -1244,7 +1241,11 @@ static void updateChat(Client &client, f32 dtime, bool show_debug,
 
 	// Get new messages from error log buffer
 	while (!chat_log_error_buf.empty()) {
-		chat_backend.addMessage(L"", utf8_to_wide(chat_log_error_buf.get()));
+		std::wstring error_message = utf8_to_wide(chat_log_error_buf.get());
+		if (!g_settings->getBool("disable_escape_sequences")) {
+			error_message = L"\x1b(c@red)" + error_message + L"\x1b(c@white)";
+		}
+		chat_backend.addMessage(L"", error_message);
 	}
 
 	// Get new messages from client
@@ -1259,10 +1260,10 @@ static void updateChat(Client &client, f32 dtime, bool show_debug,
 
 	// Display all messages in a static text element
 	unsigned int recent_chat_count = chat_backend.getRecentBuffer().getLineCount();
-	std::wstring recent_chat       = chat_backend.getRecentChat();
+	EnrichedString recent_chat     = chat_backend.getRecentChat();
 	unsigned int line_height       = g_fontengine->getLineHeight();
 
-	guitext_chat->setText(recent_chat.c_str());
+	setStaticText(guitext_chat, recent_chat);
 
 	// Update gui element size and position
 	s32 chat_y = 5 + line_height;
@@ -1271,7 +1272,7 @@ static void updateChat(Client &client, f32 dtime, bool show_debug,
 		chat_y += line_height;
 
 	// first pass to calculate height of text to be set
-	s32 width = std::min(g_fontengine->getTextWidth(recent_chat) + 10,
+	s32 width = std::min(g_fontengine->getTextWidth(recent_chat.c_str()) + 10,
 			     porting::getWindowSize().X - 20);
 	core::rect<s32> rect(10, chat_y, width, chat_y + porting::getWindowSize().Y);
 	guitext_chat->setRelativePosition(rect);
@@ -2218,45 +2219,39 @@ bool Game::createClient(const std::string &playername,
 bool Game::initGui()
 {
 	// First line of debug text
-	guitext = guienv->addStaticText(
+	guitext = addStaticText(guienv,
 			utf8_to_wide(PROJECT_NAME_C).c_str(),
 			core::rect<s32>(0, 0, 0, 0),
 			false, false, guiroot);
 
 	// Second line of debug text
-	guitext2 = guienv->addStaticText(
+	guitext2 = addStaticText(guienv,
 			L"",
 			core::rect<s32>(0, 0, 0, 0),
 			false, false, guiroot);
 
 	// At the middle of the screen
 	// Object infos are shown in this
-	guitext_info = guienv->addStaticText(
+	guitext_info = addStaticText(guienv,
 			L"",
 			core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + v2s32(100, 200),
 			false, true, guiroot);
 
 	// Status text (displays info when showing and hiding GUI stuff, etc.)
-	guitext_status = guienv->addStaticText(
+	guitext_status = addStaticText(guienv,
 			L"<Status>",
 			core::rect<s32>(0, 0, 0, 0),
 			false, false, guiroot);
 	guitext_status->setVisible(false);
 
-#if USE_FREETYPE
-	// Colored chat support when using FreeType
-	guitext_chat = new gui::StaticText(L"", false, guienv, guiroot, -1, core::rect<s32>(0, 0, 0, 0), false);
-	guitext_chat->setWordWrap(true);
-	guitext_chat->drop();
-#else
-	// Standard chat when FreeType is disabled
 	// Chat text
-	guitext_chat = guienv->addStaticText(
+	guitext_chat = addStaticText(
+			guienv,
 			L"",
 			core::rect<s32>(0, 0, 0, 0),
 			//false, false); // Disable word wrap as of now
 			false, true, guiroot);
-#endif
+
 	// Remove stale "recent" chat messages from previous connections
 	chat_backend->clearRecentChat();
 
@@ -2270,7 +2265,7 @@ bool Game::initGui()
 	}
 
 	// Profiler text (size is updated when text is updated)
-	guitext_profiler = guienv->addStaticText(
+	guitext_profiler = addStaticText(guienv,
 			L"<Profiler>",
 			core::rect<s32>(0, 0, 0, 0),
 			false, false, guiroot);
@@ -4308,12 +4303,12 @@ void Game::updateGui(float *statustext_time, const RunStats &stats,
 		   << ", v_range = " << draw_control->wanted_range
 		   << std::setprecision(3)
 		   << ", RTT = " << client->getRTT();
-		guitext->setText(utf8_to_wide(os.str()).c_str());
+		setStaticText(guitext, utf8_to_wide(os.str()).c_str());
 		guitext->setVisible(true);
 	} else if (flags.show_hud || flags.show_chat) {
 		std::ostringstream os(std::ios_base::binary);
 		os << PROJECT_NAME_C " " << g_version_hash;
-		guitext->setText(utf8_to_wide(os.str()).c_str());
+		setStaticText(guitext, utf8_to_wide(os.str()).c_str());
 		guitext->setVisible(true);
 	} else {
 		guitext->setVisible(false);
@@ -4350,7 +4345,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats,
 			}
 		}
 
-		guitext2->setText(utf8_to_wide(os.str()).c_str());
+		setStaticText(guitext2, utf8_to_wide(os.str()).c_str());
 		guitext2->setVisible(true);
 
 		core::rect<s32> rect(
@@ -4362,7 +4357,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats,
 		guitext2->setVisible(false);
 	}
 
-	guitext_info->setText(infotext.c_str());
+	setStaticText(guitext_info, infotext.c_str());
 	guitext_info->setVisible(flags.show_hud && g_menumgr.menuCount() == 0);
 
 	float statustext_time_max = 1.5;
@@ -4376,7 +4371,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats,
 		}
 	}
 
-	guitext_status->setText(statustext.c_str());
+	setStaticText(guitext_status, statustext.c_str());
 	guitext_status->setVisible(!statustext.empty());
 
 	if (!statustext.empty()) {
diff --git a/src/client/guiChatConsole.cpp b/src/guiChatConsole.cpp
similarity index 99%
rename from src/client/guiChatConsole.cpp
rename to src/guiChatConsole.cpp
index d8837556af7d2f4868773359439bd02910d0f612..bb58d13050a0a8ba8c969cf8381ac3741ba0574f 100644
--- a/src/client/guiChatConsole.cpp
+++ b/src/guiChatConsole.cpp
@@ -346,9 +346,9 @@ void GUIChatConsole::drawText()
 			// Draw colored text if FreeType is enabled
 				irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(m_font);
 				tmp->draw(
-					fragment.text.c_str(),
+					fragment.text,
 					destrect,
-					fragment.text.getColors(),
+					video::SColor(255, 255, 255, 255),
 					false,
 					false,
 					&AbsoluteClippingRect);
diff --git a/src/client/guiChatConsole.h b/src/guiChatConsole.h
similarity index 100%
rename from src/client/guiChatConsole.h
rename to src/guiChatConsole.h
diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp
index ba286a91c56926fafc22e6a44cefa01b0b5f6367..96de7a4f7f6cce8b73d97624704cc42c3f5a79a1 100644
--- a/src/guiEngine.cpp
+++ b/src/guiEngine.cpp
@@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "log.h"
 #include "fontengine.h"
 #include "guiscalingfilter.h"
+#include "irrlicht_changes/static_text.h"
 
 #ifdef __ANDROID__
 #include "client/tile.h"
@@ -172,15 +173,16 @@ GUIEngine::GUIEngine(	irr::IrrlichtDevice* dev,
 		m_sound_manager = &dummySoundManager;
 
 	//create topleft header
-	std::wstring t = utf8_to_wide(std::string(PROJECT_NAME_C " ") +
+	m_toplefttext = utf8_to_wide(std::string(PROJECT_NAME_C " ") +
 			g_version_hash);
 
-	core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(t), g_fontengine->getTextHeight());
+	core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
+		g_fontengine->getTextHeight());
 	rect += v2s32(4, 0);
 
 	m_irr_toplefttext =
-		m_device->getGUIEnvironment()->addStaticText(t.c_str(),
-		rect,false,true,0,-1);
+		addStaticText(m_device->getGUIEnvironment(), m_toplefttext,
+			rect, false, true, 0, -1);
 
 	//create formspecsource
 	m_formspecgui = new FormspecFormSource("");
@@ -578,7 +580,7 @@ void GUIEngine::setTopleftText(std::string append)
 		toset += utf8_to_wide(append);
 	}
 
-	m_irr_toplefttext->setText(toset.c_str());
+	m_toplefttext = toset;
 
 	updateTopLeftTextSize();
 }
@@ -586,15 +588,14 @@ void GUIEngine::setTopleftText(std::string append)
 /******************************************************************************/
 void GUIEngine::updateTopLeftTextSize()
 {
-	std::wstring text = m_irr_toplefttext->getText();
-
-	core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(text), g_fontengine->getTextHeight());
-		rect += v2s32(4, 0);
+	core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
+		g_fontengine->getTextHeight());
+	rect += v2s32(4, 0);
 
 	m_irr_toplefttext->remove();
 	m_irr_toplefttext =
-		m_device->getGUIEnvironment()->addStaticText(text.c_str(),
-		rect,false,true,0,-1);
+		addStaticText(m_device->getGUIEnvironment(), m_toplefttext,
+			rect, false, true, 0, -1);
 }
 
 /******************************************************************************/
diff --git a/src/guiEngine.h b/src/guiEngine.h
index d527f722243dc735832a682bf1f3e0f71fc31950..fa98a21e4fa7cfc89b516ee9eb648db54da6624f 100644
--- a/src/guiEngine.h
+++ b/src/guiEngine.h
@@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "guiFormSpecMenu.h"
 #include "sound.h"
 #include "client/tile.h"
+#include "util/enriched_string.h"
 
 /******************************************************************************/
 /* Typedefs and macros                                                        */
@@ -275,6 +276,8 @@ class GUIEngine {
 
 	/** pointer to gui element shown at topleft corner */
 	irr::gui::IGUIStaticText*	m_irr_toplefttext;
+	/** and text that is in it */
+	EnrichedString m_toplefttext;
 
 	/** initialize cloud subsystem */
 	void cloudInit();
diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp
index 99b1153c121593a6f3a159d6e0045788d64d54d5..cf01dc38c8f8d45b25948aa4156cf10bd7bfedcd 100644
--- a/src/guiFormSpecMenu.cpp
+++ b/src/guiFormSpecMenu.cpp
@@ -50,6 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/hex.h"
 #include "util/numeric.h"
 #include "util/string.h" // for parseColorString()
+#include "irrlicht_changes/static_text.h"
 #include "guiscalingfilter.h"
 
 #if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
@@ -249,37 +250,6 @@ std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string &
 	return NULL;
 }
 
-static std::vector<std::string> split(const std::string &s, char delim)
-{
-	std::vector<std::string> tokens;
-
-	std::string current = "";
-	bool last_was_escape = false;
-	for (unsigned int i = 0; i < s.size(); i++) {
-		char si = s.c_str()[i];
-		if (last_was_escape) {
-			current += '\\';
-			current += si;
-			last_was_escape = false;
-		} else {
-			if (si == delim) {
-				tokens.push_back(current);
-				current = "";
-				last_was_escape = false;
-			} else if (si == '\\') {
-				last_was_escape = true;
-			} else {
-				current += si;
-				last_was_escape = false;
-			}
-		}
-	}
-	//push last element
-	tokens.push_back(current);
-
-	return tokens;
-}
-
 void GUIFormSpecMenu::parseSize(parserData* data,std::string element)
 {
 	std::vector<std::string> parts = split(element,',');
@@ -966,7 +936,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element)
 			int font_height = g_fontengine->getTextHeight();
 			rect.UpperLeftCorner.Y -= font_height;
 			rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
-			Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
+			addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
 		}
 
 		e->setPasswordBox(true,L'*');
@@ -1021,7 +991,7 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data,
 	if (name == "")
 	{
 		// spec field id to 0, this stops submit searching for a value that isn't there
-		Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
+		addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid);
 	}
 	else
 	{
@@ -1056,7 +1026,7 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data,
 			int font_height = g_fontengine->getTextHeight();
 			rect.UpperLeftCorner.Y -= font_height;
 			rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
-			Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
+			addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
 		}
 	}
 
@@ -1117,7 +1087,7 @@ void GUIFormSpecMenu::parseTextArea(parserData* data,
 	if (name == "")
 	{
 		// spec field id to 0, this stops submit searching for a value that isn't there
-		Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
+		addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid);
 	}
 	else
 	{
@@ -1161,7 +1131,7 @@ void GUIFormSpecMenu::parseTextArea(parserData* data,
 			int font_height = g_fontengine->getTextHeight();
 			rect.UpperLeftCorner.Y -= font_height;
 			rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
-			Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
+			addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
 		}
 	}
 	m_fields.push_back(spec);
@@ -1230,7 +1200,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data,std::string element)
 				258+m_fields.size()
 			);
 			gui::IGUIStaticText *e =
-				Environment->addStaticText(spec.flabel.c_str(),
+				addStaticText(Environment, spec.flabel.c_str(),
 					rect, false, false, this, spec.fid);
 			e->setTextAlignment(gui::EGUIA_UPPERLEFT,
 						gui::EGUIA_CENTER);
@@ -1284,7 +1254,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element)
 			258+m_fields.size()
 		);
 		gui::IGUIStaticText *t =
-				Environment->addStaticText(spec.flabel.c_str(), rect, false, false, this, spec.fid);
+				addStaticText(Environment, spec.flabel.c_str(), rect, false, false, this, spec.fid);
 		t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
 		m_fields.push_back(spec);
 		return;
@@ -1910,7 +1880,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 	{
 		assert(m_tooltip_element == NULL);
 		// Note: parent != this so that the tooltip isn't clipped by the menu rectangle
-		m_tooltip_element = Environment->addStaticText(L"",core::rect<s32>(0,0,110,18));
+		m_tooltip_element = addStaticText(Environment, L"",core::rect<s32>(0,0,110,18));
 		m_tooltip_element->enableOverrideColor(true);
 		m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
 		m_tooltip_element->setDrawBackground(true);
@@ -2255,7 +2225,6 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase,
 			std::wstring tooltip_text = L"";
 			if (hovering && !m_selected_item) {
 				tooltip_text = utf8_to_wide(item.getDefinition(m_gamedef->idef()).description);
-				tooltip_text = unescape_enriched(tooltip_text);
 			}
 			if (tooltip_text != L"") {
 				std::vector<std::wstring> tt_rows = str_split(tooltip_text, L'\n');
@@ -2263,7 +2232,7 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase,
 				m_tooltip_element->setOverrideColor(m_default_tooltip_color);
 				m_tooltip_element->setVisible(true);
 				this->bringToFront(m_tooltip_element);
-				m_tooltip_element->setText(tooltip_text.c_str());
+				setStaticText(m_tooltip_element, tooltip_text.c_str());
 				s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
 #if IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2
 				s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5;
@@ -2535,8 +2504,10 @@ void GUIFormSpecMenu::drawMenu()
 					iter != m_fields.end(); ++iter) {
 				if (iter->fid == id && m_tooltips[iter->fname].tooltip != L"") {
 					if (m_old_tooltip != m_tooltips[iter->fname].tooltip) {
+						m_tooltip_element->setBackgroundColor(m_tooltips[iter->fname].bgcolor);
+						m_tooltip_element->setOverrideColor(m_tooltips[iter->fname].color);
 						m_old_tooltip = m_tooltips[iter->fname].tooltip;
-						m_tooltip_element->setText(m_tooltips[iter->fname].tooltip.c_str());
+						setStaticText(m_tooltip_element, m_tooltips[iter->fname].tooltip.c_str());
 						std::vector<std::wstring> tt_rows = str_split(m_tooltips[iter->fname].tooltip, L'\n');
 						s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
 						s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5;
@@ -2558,8 +2529,6 @@ void GUIFormSpecMenu::drawMenu()
 						core::position2d<s32>(tooltip_x, tooltip_y),
 						core::dimension2d<s32>(tooltip_width, tooltip_height)));
 					}
-					m_tooltip_element->setBackgroundColor(m_tooltips[iter->fname].bgcolor);
-					m_tooltip_element->setOverrideColor(m_tooltips[iter->fname].color);
 					m_tooltip_element->setVisible(true);
 					this->bringToFront(m_tooltip_element);
 					break;
@@ -2568,6 +2537,8 @@ void GUIFormSpecMenu::drawMenu()
 		}
 	}
 
+	m_tooltip_element->draw();
+
 	/*
 		Draw dragged item stack
 	*/
diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h
index ef230c81c4153d94ec0d51a4a304815e5877b6b3..4122b1f565fbac9801ae4f5acdf0526ade9aa7d1 100644
--- a/src/guiFormSpecMenu.h
+++ b/src/guiFormSpecMenu.h
@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "guiTable.h"
 #include "network/networkprotocol.h"
 #include "util/string.h"
+#include "util/enriched_string.h"
 
 class IGameDef;
 class InventoryManager;
@@ -202,7 +203,8 @@ class GUIFormSpecMenu : public GUIModalMenu
 			fname(name),
 			fid(id)
 		{
-			flabel = unescape_enriched(label);
+			//flabel = unescape_enriched(label);
+			flabel = label;
 			fdefault = unescape_enriched(default_text);
 			send = false;
 			ftype = f_Unknown;
@@ -239,7 +241,8 @@ class GUIFormSpecMenu : public GUIModalMenu
 			bgcolor(a_bgcolor),
 			color(a_color)
 		{
-			tooltip = unescape_enriched(utf8_to_wide(a_tooltip));
+			//tooltip = unescape_enriched(utf8_to_wide(a_tooltip));
+			tooltip = utf8_to_wide(a_tooltip);
 		}
 		std::wstring tooltip;
 		irr::video::SColor bgcolor;
@@ -256,7 +259,8 @@ class GUIFormSpecMenu : public GUIModalMenu
 			rect(a_rect),
 			parent_button(NULL)
 		{
-			text = unescape_enriched(a_text);
+			//text = unescape_enriched(a_text);
+			text = a_text;
 		}
 		StaticTextSpec(const std::wstring &a_text,
 				const core::rect<s32> &a_rect,
@@ -264,7 +268,8 @@ class GUIFormSpecMenu : public GUIModalMenu
 			rect(a_rect),
 			parent_button(a_parent_button)
 		{
-			text = unescape_enriched(a_text);
+			//text = unescape_enriched(a_text);
+			text = a_text;
 		}
 		std::wstring text;
 		core::rect<s32> rect;
diff --git a/src/irrlicht_changes/CMakeLists.txt b/src/irrlicht_changes/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3a265c99d0f8728d026cd95479ad3526607b1f77
--- /dev/null
+++ b/src/irrlicht_changes/CMakeLists.txt
@@ -0,0 +1,7 @@
+if (BUILD_CLIENT)
+	set(client_irrlicht_changes_SRCS
+		${CMAKE_CURRENT_SOURCE_DIR}/static_text.cpp
+		PARENT_SCOPE
+	)
+endif()
+
diff --git a/src/util/statictext.cpp b/src/irrlicht_changes/static_text.cpp
similarity index 83%
rename from src/util/statictext.cpp
rename to src/irrlicht_changes/static_text.cpp
index b534b560ee260bacc9abeb0cee29dd3593936719..703287eb310cf8d8854215f47ff7aa97c98203f1 100644
--- a/src/util/statictext.cpp
+++ b/src/irrlicht_changes/static_text.cpp
@@ -1,12 +1,12 @@
 // Copyright (C) 2002-2012 Nikolaus Gebhardt
+// Copyright (C) 2016 Nathanaël Courant:
+//   Modified the functions to use EnrichedText instead of string.
 // This file is part of the "Irrlicht Engine".
 // For conditions of distribution and use, see copyright notice in irrlicht.h
 
-#include "statictext.h"
+#include "static_text.h"
 #ifdef _IRR_COMPILE_WITH_GUI_
 
-//Only compile this if freetype is enabled.
-
 #include <vector>
 #include <string>
 #include <iostream>
@@ -17,15 +17,21 @@
 #include <rect.h>
 #include <SColor.h>
 
-#include "cguittfont/xCGUITTFont.h"
+#if USE_FREETYPE
+	#include "cguittfont/xCGUITTFont.h"
+#endif
+
 #include "util/string.h"
 
 namespace irr
 {
+
+#if USE_FREETYPE
+
 namespace gui
 {
 //! constructor
-StaticText::StaticText(const wchar_t* text, bool border,
+StaticText::StaticText(const EnrichedString &text, bool border,
 			IGUIEnvironment* environment, IGUIElement* parent,
 			s32 id, const core::rect<s32>& rectangle,
 			bool background)
@@ -40,7 +46,8 @@ StaticText::StaticText(const wchar_t* text, bool border,
 	setDebugName("StaticText");
 	#endif
 
-	Text = text;
+	Text = text.c_str();
+	cText = text;
 	if (environment && environment->getSkin())
 	{
 		BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE);
@@ -55,7 +62,6 @@ StaticText::~StaticText()
 		OverrideFont->drop();
 }
 
-
 //! draws the element and its children
 void StaticText::draw()
 {
@@ -88,7 +94,7 @@ void StaticText::draw()
 	}
 
 	// draw the text
-	if (Text.size())
+	if (cText.size())
 	{
 		IGUIFont* font = getActiveFont();
 
@@ -105,10 +111,11 @@ void StaticText::draw()
 				if (HAlign == EGUIA_LOWERRIGHT)
 				{
 					frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
-						font->getDimension(Text.c_str()).Width;
+						font->getDimension(cText.c_str()).Width;
 				}
 
-				font->draw(Text.c_str(), frameRect,
+				irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
+				tmp->draw(cText, frameRect,
 					OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
 					HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL));
 			}
@@ -138,16 +145,17 @@ void StaticText::draw()
 							font->getDimension(BrokenText[i].c_str()).Width;
 					}
 
-					std::vector<irr::video::SColor> colors;
-					std::wstring str;
+					//std::vector<irr::video::SColor> colors;
+					//std::wstring str;
+					EnrichedString str = BrokenText[i];
 
-					str = colorizeText(BrokenText[i].c_str(), colors, previous_color);
-					if (!colors.empty())
-						previous_color = colors[colors.size() - 1];
+					//str = colorizeText(BrokenText[i].c_str(), colors, previous_color);
+					//if (!colors.empty())
+					//	previous_color = colors[colors.size() - 1];
 
 					irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
-					tmp->draw(str.c_str(), r,
-						colors,
+					tmp->draw(str, r,
+						previous_color, // FIXME
 						HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL));
 
 					r.LowerRightCorner.Y += height;
@@ -340,17 +348,17 @@ void StaticText::breakText()
 
 	LastBreakFont = font;
 
-	core::stringw line;
-	core::stringw word;
-	core::stringw whitespace;
-	s32 size = Text.size();
+	EnrichedString line;
+	EnrichedString word;
+	EnrichedString whitespace;
+	s32 size = cText.size();
 	s32 length = 0;
 	s32 elWidth = RelativeRect.getWidth();
 	if (Border)
 		elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X);
 	wchar_t c;
 
-	std::vector<irr::video::SColor> colors;
+	//std::vector<irr::video::SColor> colors;
 
 	// We have to deal with right-to-left and left-to-right differently
 	// However, most parts of the following code is the same, it's just
@@ -360,17 +368,17 @@ void StaticText::breakText()
 		// regular (left-to-right)
 		for (s32 i=0; i<size; ++i)
 		{
-			c = Text[i];
+			c = cText.getString()[i];
 			bool lineBreak = false;
 
 			if (c == L'\r') // Mac or Windows breaks
 			{
 				lineBreak = true;
-				if (Text[i+1] == L'\n') // Windows breaks
-				{
-					Text.erase(i+1);
-					--size;
-				}
+				//if (Text[i+1] == L'\n') // Windows breaks
+				//{
+				//	Text.erase(i+1);
+				//	--size;
+				//}
 				c = '\0';
 			}
 			else if (c == L'\n') // Unix breaks
@@ -383,7 +391,8 @@ void StaticText::breakText()
 			if ( !isWhitespace )
 			{
 				// part of a word
-				word += c;
+				//word += c;
+				word.addChar(cText, i);
 			}
 
 			if ( isWhitespace || i == (size-1))
@@ -393,20 +402,21 @@ void StaticText::breakText()
 					// here comes the next whitespace, look if
 					// we must break the last word to the next line.
 					const s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
-					const std::wstring sanitized = removeEscapes(word.c_str());
-					const s32 wordlgth = font->getDimension(sanitized.c_str()).Width;
+					//const std::wstring sanitized = removeEscapes(word.c_str());
+					const s32 wordlgth = font->getDimension(word.c_str()).Width;
 
 					if (wordlgth > elWidth)
 					{
 						// This word is too long to fit in the available space, look for
 						// the Unicode Soft HYphen (SHY / 00AD) character for a place to
 						// break the word at
-						int where = word.findFirst( wchar_t(0x00AD) );
+						int where = core::stringw(word.c_str()).findFirst( wchar_t(0x00AD) );
 						if (where != -1)
 						{
-							core::stringw first  = word.subString(0, where);
-							core::stringw second = word.subString(where, word.size() - where);
-							BrokenText.push_back(line + first + L"-");
+							EnrichedString first = word.substr(0, where);
+							EnrichedString second = word.substr(where, word.size() - where);
+							first.addCharNoColor(L'-');
+							BrokenText.push_back(line + first);
 							const s32 secondLength = font->getDimension(second.c_str()).Width;
 
 							length = secondLength;
@@ -437,13 +447,13 @@ void StaticText::breakText()
 						length += whitelgth + wordlgth;
 					}
 
-					word = L"";
-					whitespace = L"";
+					word.clear();
+					whitespace.clear();
 				}
 
-				if ( isWhitespace )
+				if ( isWhitespace && c != 0)
 				{
-					whitespace += c;
+					whitespace.addChar(cText, i);
 				}
 
 				// compute line break
@@ -452,9 +462,9 @@ void StaticText::breakText()
 					line += whitespace;
 					line += word;
 					BrokenText.push_back(line);
-					line = L"";
-					word = L"";
-					whitespace = L"";
+					line.clear();
+					word.clear();
+					whitespace.clear();
 					length = 0;
 				}
 			}
@@ -469,17 +479,17 @@ void StaticText::breakText()
 		// right-to-left
 		for (s32 i=size; i>=0; --i)
 		{
-			c = Text[i];
+			c = cText.getString()[i];
 			bool lineBreak = false;
 
 			if (c == L'\r') // Mac or Windows breaks
 			{
 				lineBreak = true;
-				if ((i>0) && Text[i-1] == L'\n') // Windows breaks
-				{
-					Text.erase(i-1);
-					--size;
-				}
+				//if ((i>0) && Text[i-1] == L'\n') // Windows breaks
+				//{
+				//	Text.erase(i-1);
+				//	--size;
+				//}
 				c = '\0';
 			}
 			else if (c == L'\n') // Unix breaks
@@ -512,12 +522,13 @@ void StaticText::breakText()
 						length += whitelgth + wordlgth;
 					}
 
-					word = L"";
-					whitespace = L"";
+					word.clear();
+					whitespace.clear();
 				}
 
 				if (c != 0)
-					whitespace = core::stringw(&c, 1) + whitespace;
+				//	whitespace = core::stringw(&c, 1) + whitespace;
+				whitespace = cText.substr(i, 1) + whitespace;
 
 				// compute line break
 				if (lineBreak)
@@ -525,16 +536,17 @@ void StaticText::breakText()
 					line = whitespace + line;
 					line = word + line;
 					BrokenText.push_back(line);
-					line = L"";
-					word = L"";
-					whitespace = L"";
+					line.clear();
+					word.clear();
+					whitespace.clear();
 					length = 0;
 				}
 			}
 			else
 			{
 				// yippee this is a word..
-				word = core::stringw(&c, 1) + word;
+				//word = core::stringw(&c, 1) + word;
+				word = cText.substr(i, 1) + word;
 			}
 		}
 
@@ -548,7 +560,17 @@ void StaticText::breakText()
 //! Sets the new caption of this element.
 void StaticText::setText(const wchar_t* text)
 {
-	IGUIElement::setText(text);
+	setText(EnrichedString(text));
+}
+
+//! Sets the new caption of this element.
+void StaticText::setText(const EnrichedString &text)
+{
+	IGUIElement::setText(text.c_str());
+	cText = text;
+	if (text.hasBackground()) {
+		setBackgroundColor(text.getBackground());
+	}
 	breakText();
 }
 
@@ -598,7 +620,7 @@ s32 StaticText::getTextWidth() const
 	}
 	else
 	{
-		return font->getDimension(Text.c_str()).Width;
+		return font->getDimension(cText.c_str()).Width;
 	}
 }
 
@@ -648,6 +670,9 @@ void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWr
 }
 
 } // end namespace gui
+
+#endif // USE_FREETYPE
+
 } // end namespace irr
 
 
diff --git a/src/util/statictext.h b/src/irrlicht_changes/static_text.h
similarity index 54%
rename from src/util/statictext.h
rename to src/irrlicht_changes/static_text.h
index 8d2f879e7c87319c0f37596cc90df94cdfc970a5..408a127845674c07eec8663596a595cf8a7d1a78 100644
--- a/src/util/statictext.h
+++ b/src/irrlicht_changes/static_text.h
@@ -1,4 +1,6 @@
 // Copyright (C) 2002-2012 Nikolaus Gebhardt
+// Copyright (C) 2016 Nathanaël Courant
+//   Modified this class to work with EnrichedStrings too
 // This file is part of the "Irrlicht Engine".
 // For conditions of distribution and use, see copyright notice in irrlicht.h
 
@@ -11,18 +13,30 @@
 #include "IGUIStaticText.h"
 #include "irrArray.h"
 
+#include "log.h"
+
 #include <vector>
 
+#include "util/enriched_string.h"
+#include "config.h"
+#include <IGUIEnvironment.h>
+
+#if USE_FREETYPE
+
 namespace irr
 {
+
 namespace gui
 {
+
+	const EGUI_ELEMENT_TYPE EGUIET_ENRICHED_STATIC_TEXT = (EGUI_ELEMENT_TYPE)(0x1000);
+
 	class StaticText : public IGUIStaticText
 	{
 	public:
 
 		//! constructor
-		StaticText(const wchar_t* text, bool border, IGUIEnvironment* environment,
+		StaticText(const EnrichedString &text, bool border, IGUIEnvironment* environment,
 			IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
 			bool background = false);
 
@@ -121,6 +135,16 @@ namespace gui
 		//! Reads attributes of the element
 		virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
 
+		virtual bool hasType(EGUI_ELEMENT_TYPE t) const {
+			return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT);
+		};
+
+		virtual bool hasType(EGUI_ELEMENT_TYPE t) {
+			return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT);
+		};
+
+		void setText(const EnrichedString &text);
+
 	private:
 
 		//! Breaks the single text line.
@@ -139,12 +163,106 @@ namespace gui
 		gui::IGUIFont* OverrideFont;
 		gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated.
 
-		core::array< core::stringw > BrokenText;
+		EnrichedString cText;
+		core::array< EnrichedString > BrokenText;
 	};
 
+
 } // end namespace gui
+
 } // end namespace irr
 
+inline irr::gui::IGUIStaticText *addStaticText(
+		irr::gui::IGUIEnvironment *guienv,
+		const EnrichedString &text,
+		const core::rect< s32 > &rectangle,
+		bool border = false,
+		bool wordWrap = true,
+		irr::gui::IGUIElement *parent = NULL,
+		s32 id = -1,
+		bool fillBackground = false)
+{
+	if (parent == NULL) {
+		// parent is NULL, so we must find one, or we need not to drop
+		// result, but then there will be a memory leak.
+		//
+		// What Irrlicht does is to use guienv as a parent, but the problem
+		// is that guienv is here only an IGUIEnvironment, while it is a
+		// CGUIEnvironment in Irrlicht, which inherits from both IGUIElement
+		// and IGUIEnvironment.
+		//
+		// A solution would be to dynamic_cast guienv to a
+		// IGUIElement*, but Irrlicht is shipped without rtti support
+		// in some distributions, causing the dymanic_cast to segfault.
+		//
+		// Thus, to find the parent, we create a dummy StaticText and ask
+		// for its parent, and then remove it.
+		irr::gui::IGUIStaticText *dummy_text =
+			guienv->addStaticText(L"", rectangle, border, wordWrap,
+			parent, id, fillBackground);
+		parent = dummy_text->getParent();
+		dummy_text->remove();
+	}
+	irr::gui::IGUIStaticText *result = new irr::gui::StaticText(
+		text, border, guienv, parent,
+		id, rectangle, fillBackground);
+
+	result->setWordWrap(wordWrap);
+	result->drop();
+	return result;
+}
+
+inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text)
+{
+	// dynamic_cast not possible due to some distributions shipped
+	// without rtti support in irrlicht
+	if (static_text->hasType(irr::gui::EGUIET_ENRICHED_STATIC_TEXT)) {
+		irr::gui::StaticText* stext = static_cast<irr::gui::StaticText*>(static_text);
+		stext->setText(text);
+	} else {
+		static_text->setText(text.c_str());
+	}
+}
+
+#else // USE_FREETYPE
+
+inline irr::gui::IGUIStaticText *addStaticText(
+		irr::gui::IGUIEnvironment *guienv,
+		const EnrichedString &text,
+		const core::rect< s32 > &rectangle,
+		bool border = false,
+		bool wordWrap = true,
+		irr::gui::IGUIElement *parent = NULL,
+		s32 id = -1,
+		bool fillBackground = false)
+{
+	return guienv->addStaticText(text.c_str(), rectangle, border, wordWrap, parent, id, fillBackground);
+}
+
+inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text)
+{
+	static_text->setText(text.c_str());
+}
+
+#endif
+
+inline irr::gui::IGUIStaticText *addStaticText(
+		irr::gui::IGUIEnvironment *guienv,
+		const wchar_t *text,
+		const core::rect< s32 > &rectangle,
+		bool border = false,
+		bool wordWrap = true,
+		irr::gui::IGUIElement *parent = NULL,
+		s32 id = -1,
+		bool fillBackground = false) {
+	return addStaticText(guienv, EnrichedString(text), rectangle, border, wordWrap, parent, id, fillBackground);
+}
+
+inline void setStaticText(irr::gui::IGUIStaticText *static_text, const wchar_t *text)
+{
+	setStaticText(static_text, EnrichedString(text));
+}
+
 #endif // _IRR_COMPILE_WITH_GUI_
 
 #endif // C_GUI_STATIC_TEXT_H_INCLUDED
diff --git a/src/terminal_chat_console.cpp b/src/terminal_chat_console.cpp
index c86a960fa49d0dacc964f4569609d8c0d101f04e..a8c4ebaef1a8926e559f06ae534567b46fe8c3f1 100644
--- a/src/terminal_chat_console.cpp
+++ b/src/terminal_chat_console.cpp
@@ -345,9 +345,11 @@ void TerminalChatConsole::step(int ch)
 		if (p.first > m_log_level)
 			continue;
 
-		m_chat_backend.addMessage(
-			utf8_to_wide(Logger::getLevelLabel(p.first)),
-			utf8_to_wide(p.second));
+		std::wstring error_message = utf8_to_wide(Logger::getLevelLabel(p.first));
+		if (!g_settings->getBool("disable_escape_sequences")) {
+			error_message = L"\x1b(c@red)" + error_message + L"\x1b(c@white)";
+		}
+		m_chat_backend.addMessage(error_message, utf8_to_wide(p.second));
 	}
 
 	// handle input
@@ -438,7 +440,7 @@ void TerminalChatConsole::draw_text()
 			continue;
 		for (u32 i = 0; i < line.fragments.size(); ++i) {
 			const ChatFormattedFragment& fragment = line.fragments[i];
-			addstr(wide_to_utf8(fragment.text).c_str());
+			addstr(wide_to_utf8(fragment.text.getString()).c_str());
 		}
 	}
 }
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
index e028a0435b0df54547c7256dcfb177c05abf9669..f571ab22ca75a2746ad8aea95bbdc36bf68dbe4d 100644
--- a/src/util/CMakeLists.txt
+++ b/src/util/CMakeLists.txt
@@ -1,17 +1,9 @@
-if(USE_FREETYPE)
-	set(UTIL_FREETYPEDEP_SRCS
-		${CMAKE_CURRENT_SOURCE_DIR}/statictext.cpp
-	)
-else()
-	set(UTIL_FREETYPEDEP_SRCS )
-endif(USE_FREETYPE)
-
 set(UTIL_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/areastore.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/coloredstring.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp
@@ -20,6 +12,5 @@ set(UTIL_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/string.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/timetaker.cpp
-	${UTIL_FREETYPEDEP_SRCS}
 	PARENT_SCOPE)
 
diff --git a/src/util/coloredstring.cpp b/src/util/coloredstring.cpp
deleted file mode 100644
index 7db586550b30efb75d1f0bf06d70d86e41d26c68..0000000000000000000000000000000000000000
--- a/src/util/coloredstring.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "coloredstring.h"
-#include "util/string.h"
-
-ColoredString::ColoredString()
-{}
-
-ColoredString::ColoredString(const std::wstring &string, const std::vector<SColor> &colors):
-	m_string(string),
-	m_colors(colors)
-{}
-
-ColoredString::ColoredString(const std::wstring &s) {
-	m_string = colorizeText(s, m_colors, SColor(255, 255, 255, 255));
-}
-
-void ColoredString::operator=(const wchar_t *str) {
-	m_string = colorizeText(str, m_colors, SColor(255, 255, 255, 255));
-}
-
-size_t ColoredString::size() const {
-	return m_string.size();
-}
-
-ColoredString ColoredString::substr(size_t pos, size_t len) const {
-	if (pos == m_string.length())
-		return ColoredString();
-	if (len == std::string::npos || pos + len > m_string.length()) {
-		return ColoredString(
-		           m_string.substr(pos, std::string::npos),
-		           std::vector<SColor>(m_colors.begin() + pos, m_colors.end())
-		       );
-	} else {
-		return ColoredString(
-		           m_string.substr(pos, len),
-		           std::vector<SColor>(m_colors.begin() + pos, m_colors.begin() + pos + len)
-		       );
-	}
-}
-
-const wchar_t *ColoredString::c_str() const {
-	return m_string.c_str();
-}
-
-const std::vector<SColor> &ColoredString::getColors() const {
-	return m_colors;
-}
-
-const std::wstring &ColoredString::getString() const {
-	return m_string;
-}
diff --git a/src/util/coloredstring.h b/src/util/coloredstring.h
deleted file mode 100644
index a6d98db30d37d7bd16ac411f963971d2a5943854..0000000000000000000000000000000000000000
--- a/src/util/coloredstring.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#ifndef COLOREDSTRING_HEADER
-#define COLOREDSTRING_HEADER
-
-#include <string>
-#include <vector>
-#include <SColor.h>
-
-using namespace irr::video;
-
-class ColoredString {
-public:
-	ColoredString();
-	ColoredString(const std::wstring &s);
-	ColoredString(const std::wstring &string, const std::vector<SColor> &colors);
-	void operator=(const wchar_t *str);
-	size_t size() const;
-	ColoredString substr(size_t pos = 0, size_t len = std::string::npos) const;
-	const wchar_t *c_str() const;
-	const std::vector<SColor> &getColors() const;
-	const std::wstring &getString() const;
-private:
-	std::wstring m_string;
-	std::vector<SColor> m_colors;
-};
-
-#endif
diff --git a/src/util/enriched_string.cpp b/src/util/enriched_string.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a7fc3a828255a5bcb1b14c2483bde7faf803b9dd
--- /dev/null
+++ b/src/util/enriched_string.cpp
@@ -0,0 +1,166 @@
+/*
+Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is>
+Copyright (C) 2016 Nore, Nathanaël Courant <nore@mesecons.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "enriched_string.h"
+#include "util/string.h"
+#include "log.h"
+using namespace irr::video;
+
+EnrichedString::EnrichedString()
+{
+	clear();
+}
+
+EnrichedString::EnrichedString(const std::wstring &string,
+		const std::vector<SColor> &colors):
+	m_string(string),
+	m_colors(colors),
+	m_has_background(false)
+{}
+
+EnrichedString::EnrichedString(const std::wstring &s, const SColor &color)
+{
+	clear();
+	addAtEnd(s, color);
+}
+
+EnrichedString::EnrichedString(const wchar_t *str, const SColor &color)
+{
+	clear();
+	addAtEnd(std::wstring(str), color);
+}
+
+void EnrichedString::operator=(const wchar_t *str)
+{
+	clear();
+	addAtEnd(std::wstring(str), SColor(255, 255, 255, 255));
+}
+
+void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color)
+{
+	SColor color(initial_color);
+	size_t i = 0;
+	while (i < s.length()) {
+		if (s[i] != L'\x1b') {
+			m_string += s[i];
+			m_colors.push_back(color);
+			++i;
+			continue;
+		}
+		++i;
+		size_t start_index = i;
+		size_t length;
+		if (i == s.length()) {
+			break;
+		}
+		if (s[i] == L'(') {
+			++i;
+			++start_index;
+			while (i < s.length() && s[i] != L')') {
+				if (s[i] == L'\\') {
+					++i;
+				}
+				++i;
+			}
+			length = i - start_index;
+			++i;
+		} else {
+			++i;
+			length = 1;
+		}
+		std::wstring escape_sequence(s, start_index, length);
+		std::vector<std::wstring> parts = split(escape_sequence, L'@');
+		if (parts[0] == L"c") {
+			if (parts.size() < 2) {
+				continue;
+			}
+			parseColorString(wide_to_utf8(parts[1]), color, true);
+		} else if (parts[0] == L"b") {
+			if (parts.size() < 2) {
+				continue;
+			}
+			parseColorString(wide_to_utf8(parts[1]), m_background, true);
+			m_has_background = true;
+		}
+		continue;
+	}
+}
+
+void EnrichedString::addChar(const EnrichedString &source, size_t i)
+{
+	m_string += source.m_string[i];
+	m_colors.push_back(source.m_colors[i]);
+}
+
+void EnrichedString::addCharNoColor(wchar_t c)
+{
+	m_string += c;
+	if (m_colors.empty()) {
+		m_colors.push_back(SColor(255, 255, 255, 255));
+	} else {
+		m_colors.push_back(m_colors[m_colors.size() - 1]);
+	}
+}
+
+EnrichedString EnrichedString::operator+(const EnrichedString &other) const
+{
+	std::vector<SColor> result;
+	result.insert(result.end(), m_colors.begin(), m_colors.end());
+	result.insert(result.end(), other.m_colors.begin(), other.m_colors.end());
+	return EnrichedString(m_string + other.m_string, result);
+}
+
+void EnrichedString::operator+=(const EnrichedString &other)
+{
+	m_string += other.m_string;
+	m_colors.insert(m_colors.end(), other.m_colors.begin(), other.m_colors.end());
+}
+
+EnrichedString EnrichedString::substr(size_t pos, size_t len) const
+{
+	if (pos == m_string.length()) {
+		return EnrichedString();
+	}
+	if (len == std::string::npos || pos + len > m_string.length()) {
+		return EnrichedString(
+		           m_string.substr(pos, std::string::npos),
+		           std::vector<SColor>(m_colors.begin() + pos, m_colors.end())
+		       );
+	} else {
+		return EnrichedString(
+		           m_string.substr(pos, len),
+		           std::vector<SColor>(m_colors.begin() + pos, m_colors.begin() + pos + len)
+		       );
+	}
+}
+
+const wchar_t *EnrichedString::c_str() const
+{
+	return m_string.c_str();
+}
+
+const std::vector<SColor> &EnrichedString::getColors() const
+{
+	return m_colors;
+}
+
+const std::wstring &EnrichedString::getString() const
+{
+	return m_string;
+}
diff --git a/src/util/enriched_string.h b/src/util/enriched_string.h
new file mode 100644
index 0000000000000000000000000000000000000000..1aca8948ae6160720b6c899d14e0a0389148a421
--- /dev/null
+++ b/src/util/enriched_string.h
@@ -0,0 +1,91 @@
+/*
+Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is>
+Copyright (C) 2016 Nore, Nathanaël Courant <nore@mesecons.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef ENRICHEDSTRING_HEADER
+#define ENRICHEDSTRING_HEADER
+
+#include <string>
+#include <vector>
+#include <SColor.h>
+
+class EnrichedString {
+public:
+	EnrichedString();
+	EnrichedString(const std::wstring &s,
+		const irr::video::SColor &color = irr::video::SColor(255, 255, 255, 255));
+	EnrichedString(const wchar_t *str,
+		const irr::video::SColor &color = irr::video::SColor(255, 255, 255, 255));
+	EnrichedString(const std::wstring &string,
+		const std::vector<irr::video::SColor> &colors);
+	void operator=(const wchar_t *str);
+	void addAtEnd(const std::wstring &s, const irr::video::SColor &color);
+
+	// Adds the character source[i] at the end.
+	// An EnrichedString should always be able to be copied
+	// to the end of an existing EnrichedString that way.
+	void addChar(const EnrichedString &source, size_t i);
+
+	// Adds a single character at the end, without specifying its
+	// color. The color used will be the one from the last character.
+	void addCharNoColor(wchar_t c);
+
+	EnrichedString substr(size_t pos = 0, size_t len = std::string::npos) const;
+	EnrichedString operator+(const EnrichedString &other) const;
+	void operator+=(const EnrichedString &other);
+	const wchar_t *c_str() const;
+	const std::vector<irr::video::SColor> &getColors() const;
+	const std::wstring &getString() const;
+	inline bool operator==(const EnrichedString &other) const
+	{
+		return (m_string == other.m_string && m_colors == other.m_colors);
+	}
+	inline bool operator!=(const EnrichedString &other) const
+	{
+		return !(*this == other);
+	}
+	inline void clear()
+	{
+		m_string.clear();
+		m_colors.clear();
+		m_has_background = false;
+	}
+	inline bool empty() const
+	{
+		return m_string.empty();
+	}
+	inline size_t size() const
+	{
+		return m_string.size();
+	}
+	inline bool hasBackground() const
+	{
+		return m_has_background;
+	}
+	inline irr::video::SColor getBackground() const
+	{
+		return m_background;
+	}
+private:
+	std::wstring m_string;
+	std::vector<irr::video::SColor> m_colors;
+	bool m_has_background;
+	irr::video::SColor m_background;
+};
+
+#endif
diff --git a/src/util/string.h b/src/util/string.h
index 40ef3e4d3700877df0f3d6bd9d98e0febe1e81f4..c77c5a6f97bbb25ce814e9a64a90d74e249c8514 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -519,6 +519,38 @@ std::basic_string<T> unescape_enriched(const std::basic_string<T> &s)
 	return output;
 }
 
+template <typename T>
+std::vector<std::basic_string<T> > split(const std::basic_string<T> &s, T delim)
+{
+	std::vector<std::basic_string<T> > tokens;
+
+	std::basic_string<T> current;
+	bool last_was_escape = false;
+	for (size_t i = 0; i < s.length(); i++) {
+		T si = s[i];
+		if (last_was_escape) {
+			current += '\\';
+			current += si;
+			last_was_escape = false;
+		} else {
+			if (si == delim) {
+				tokens.push_back(current);
+				current = std::basic_string<T>();
+				last_was_escape = false;
+			} else if (si == '\\') {
+				last_was_escape = true;
+			} else {
+				current += si;
+				last_was_escape = false;
+			}
+		}
+	}
+	//push last element
+	tokens.push_back(current);
+
+	return tokens;
+}
+
 /**
  * Checks that all characters in \p to_check are a decimal digits.
  *