diff --git a/game/engines/default/data/keybinds/actions.lua b/game/engines/default/data/keybinds/actions.lua
index 64eb679ee91a6b32e61cf8a9c284dccda9098773..9eee906db4fad34497a8392b599d365885b0781d 100644
--- a/game/engines/default/data/keybinds/actions.lua
+++ b/game/engines/default/data/keybinds/actions.lua
@@ -45,7 +45,7 @@ defineAction{
 }
 
 defineAction{
-	default = { "uni:R" },
+	default = { "uni:r", "uni:R" },
 	type = "REST",
 	group = "actions",
 	name = "Rest for a while",
diff --git a/game/engines/default/engine/HotkeysDisplay.lua b/game/engines/default/engine/HotkeysDisplay.lua
index d5f0c251070ca8c558f5209eb376b6c39eb683d5..93b967469140e198497b3e5301eb0fd2193fb01c 100644
--- a/game/engines/default/engine/HotkeysDisplay.lua
+++ b/game/engines/default/engine/HotkeysDisplay.lua
@@ -152,7 +152,8 @@ function _M:onMouse(button, mx, my, click, on_over)
 					local text = ""
 					if a.hotkey[i] and a.hotkey[i][1] == "talent" then
 						local t = self.actor:getTalentFromId(a.hotkey[i][2])
-						text = "#GOLD#"..t.name.."#LAST#\n"..self.actor:getTalentFullDescription(t)
+						text = tstring{{"color","GOLD"}, {"font", "bold"}, t.name, {"font", "normal"}, {"color", "LAST"}, true}
+						text:merge(self.actor:getTalentFullDescription(t))
 					elseif a.hotkey[i] and a.hotkey[i][1] == "inventory" then
 						local o = a:findInAllInventories(a.hotkey[i][2])
 						text = o:getDesc()
diff --git a/game/engines/default/engine/Object.lua b/game/engines/default/engine/Object.lua
index a876b42e8bec9abcbcf1daff7f22ad4218f587c4..858798a2d815ccab751d4ff01228e78a33ac2dbf 100644
--- a/game/engines/default/engine/Object.lua
+++ b/game/engines/default/engine/Object.lua
@@ -175,22 +175,22 @@ function _M:getRequirementDesc(who)
 	local req = rawget(self, "require")
 	if not req then return nil end
 
-	local str = "Requires:\n"
+	local str = tstring{"Requires:", true}
 
 	if req.stat then
 		for s, v in pairs(req.stat) do
-			local c = (who:getStat(s) >= v) and "#00ff00#" or "#ff0000#"
-			str = str .. ("- %s%s %d\n"):format(c, who.stats_def[s].name, v)
+			local c = (who:getStat(s) >= v) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+			str:add("- ", ("%s %d\n"):format(who.stats_def[s].name, v), true)
 		end
 	end
 	if req.level then
-		local c = (who.level >= req.level) and "#00ff00#" or "#ff0000#"
-		str = str .. ("- %sLevel %d\n"):format(c, req.level)
+		local c = (who.level >= req.level) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+		str:add("- ", ("Level %d\n"):format(req.level), true)
 	end
 	if req.talent then
 		for _, tid in ipairs(req.talent) do
-			local c = who:knowTalent(tid) and "#00ff00#" or "#ff0000#"
-			str = str .. ("- %sTalent %s\n"):format(c, who:getTalentFromId(tid).name)
+			local c = who:knowTalent(tid) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+			str:add("- ", ("Talent %s\n"):format(who:getTalentFromId(tid).name), true)
 		end
 	end
 	return str
diff --git a/game/engines/default/engine/dialogs/ShowEquipInven.lua b/game/engines/default/engine/dialogs/ShowEquipInven.lua
index 64f8374b6cede48e22ac4e69f920f4ba398b3dc9..feb0451dd1f6fd38f25deda53e5e695e63f61e91 100644
--- a/game/engines/default/engine/dialogs/ShowEquipInven.lua
+++ b/game/engines/default/engine/dialogs/ShowEquipInven.lua
@@ -164,7 +164,6 @@ function _M:generateList()
 				if not self.filter or self.filter(o) then
 					local char = self:makeKeyChar(i)
 					list[#list+1] = { id=#list+1, char=char, name=o:getName{do_color=true}, object=o, inven=inven_id, item=item, cat=o.subtype, encumberance=o.encumber, desc=o:getDesc() }
-					self.max_h = math.max(self.max_h, #o:getDesc():splitLines(self.iw - 10, self.font))
 					chars[char] = #list
 					i = i + 1
 				end
@@ -183,7 +182,6 @@ function _M:generateList()
 		if not self.filter or self.filter(o) then
 			local char = self:makeKeyChar(i)
 			list[#list+1] = { id=#list+1, char=char, name=o:getName{do_color=true}, object=o, inven=self.actor.INVEN_INVEN, item=item, cat=o.subtype, encumberance=o.encumber, desc=o:getDesc() }
-			self.max_h = math.max(self.max_h, #o:getDesc():splitLines(self.iw - 10, self.font))
 			chars[char] = #list
 			i = i + 1
 		end
@@ -204,7 +202,7 @@ function _M:maxH()
 	local i = 1
 	for item, o in ipairs(self.actor:getInven("INVEN")) do
 		if not self.filter or self.filter(o) then
-			self.max_h = math.max(self.max_h, #o:getDesc():splitLines(self.iw - 10, self.font))
+			self.max_h = math.max(self.max_h, o:getDesc():splitLines(self.iw - 10, self.font):countLines())
 		end
 	end
 end
diff --git a/game/engines/default/engine/dialogs/ShowStore.lua b/game/engines/default/engine/dialogs/ShowStore.lua
index ff6839f628d2b329ca5934b109042d6829da7d60..6e47b98ff118b87f4aa300efd16c8b6af7325834 100644
--- a/game/engines/default/engine/dialogs/ShowStore.lua
+++ b/game/engines/default/engine/dialogs/ShowStore.lua
@@ -108,12 +108,12 @@ function _M:maxH()
 	self.max_h = 0
 	for item, o in ipairs(self.store_inven) do
 		if not self.store_filter or self.store_filter(o) then
-			self.max_h = math.max(self.max_h, #o:getDesc():splitLines(self.iw - 10, self.font))
+			self.max_h = math.max(self.max_h, o:getDesc():splitLines(self.iw - 10, self.font):countLines())
 		end
 	end
 	for item, o in ipairs(self.actor_inven) do
 		if not self.actor_filter or self.actor_filter(o) then
-			self.max_h = math.max(self.max_h, #o:getDesc():splitLines(self.iw - 10, self.font))
+			self.max_h = math.max(self.max_h, o:getDesc():splitLines(self.iw - 10, self.font):countLines())
 		end
 	end
 end
diff --git a/game/engines/default/engine/interface/ActorTalents.lua b/game/engines/default/engine/interface/ActorTalents.lua
index d9e76f965ec10a4676d6a69a31a6341576f74381..fe3fef566555c5e940e9772b1a877f8c7dc2120b 100644
--- a/game/engines/default/engine/interface/ActorTalents.lua
+++ b/game/engines/default/engine/interface/ActorTalents.lua
@@ -325,44 +325,44 @@ function _M:getTalentReqDesc(t_id, levmod)
 
 	local tlev = self:getTalentLevelRaw(t_id) + (levmod or 0)
 
-	local str = ""
+	local str = tstring{}
 
 	if not t.type_no_req then
-		str = str .. (self:knowTalentType(t.type[1]) and "#00ff00#" or "#ff0000#") .. "- Talent category known\n"
+		str:add((self:knowTalentType(t.type[1]) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}), "- Talent category known", true)
 	end
 
 	if t.type[2] and t.type[2] > 1 then
 		local known = self:numberKnownTalent(t.type[1], t.id)
-		local c = (known >= t.type[2] - 1) and "#00ff00#" or "#ff0000#"
-		str = str .. ("- %sTalents of the same category: %d\n"):format(c, t.type[2] - 1)
+		local c = (known >= t.type[2] - 1) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+		str:add("- ", c, ("Talents of the same category: %d"):format(t.type[2] - 1), true)
 	end
 
 	-- Obviously this requires the ActorStats interface
 	if req.stat then
 		for s, v in pairs(req.stat) do
 			v = util.getval(v, tlev)
-			local c = (self:getStat(s) >= v) and "#00ff00#" or "#ff0000#"
-			str = str .. ("- %s%s %d\n"):format(c, self.stats_def[s].name, v)
+			local c = (self:getStat(s) >= v) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+			str:add("- ", c, ("%s %d"):format(self.stats_def[s].name, v), true)
 		end
 	end
 	if req.level then
 		local v = util.getval(req.level, tlev)
-		local c = (self.level >= v) and "#00ff00#" or "#ff0000#"
-		str = str .. ("- %sLevel %d\n"):format(c, v)
+		local c = (self.level >= v) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+		str:add("- ", c, ("Level %d\n"):format(v), true)
 	end
 	if req.talent then
 		for _, tid in ipairs(req.talent) do
 			if type(tid) == "table" then
 				if type(tid[2]) == "boolean" and tid[2] == false then
-					local c = (not self:knowTalent(tid[1])) and "#00ff00#" or "#ff0000#"
-					str = str .. ("- %sTalent %s (not known)\n"):format(c, self:getTalentFromId(tid[1]).name)
+					local c = (not self:knowTalent(tid[1])) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+					str:add("- ", c, ("Talent %s (not known)\n"):format(c, self:getTalentFromId(tid[1]).name), true)
 				else
-					local c = (self:getTalentLevelRaw(tid[1]) >= tid[2]) and "#00ff00#" or "#ff0000#"
-					str = str .. ("- %sTalent %s (%d)\n"):format(c, self:getTalentFromId(tid[1]).name, tid[2])
+					local c = (self:getTalentLevelRaw(tid[1]) >= tid[2]) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+					str:add("- ", c, ("Talent %s (%d)\n"):format(c, self:getTalentFromId(tid[1]).name, tid[2]), true)
 				end
 			else
-				local c = self:knowTalent(tid) and "#00ff00#" or "#ff0000#"
-				str = str .. ("- %sTalent %s\n"):format(c, self:getTalentFromId(tid).name)
+				local c = self:knowTalent(tid) and {"color", 0x00,0xff,0x00} or {"color", 0xff,0x00,0x00}
+				str:add("- ", c, ("Talent %s\n"):format(c, self:getTalentFromId(tid).name), true)
 			end
 		end
 	end
@@ -373,7 +373,7 @@ end
 --- Return the full description of a talent
 -- You may overload it to add more data (like power usage, ...)
 function _M:getTalentFullDescription(t)
-	return t.info(self, t)
+	return tstring{t.info(self, t), true}
 end
 
 --- Do we know this talent type
diff --git a/game/engines/default/engine/ui/Textzone.lua b/game/engines/default/engine/ui/Textzone.lua
index 166e65c9a954171feb27e600b4941bcfd87da546..8f40019d28aa2269caf9748063b07e73b00715fa 100644
--- a/game/engines/default/engine/ui/Textzone.lua
+++ b/game/engines/default/engine/ui/Textzone.lua
@@ -25,7 +25,7 @@ local Focusable = require "engine.ui.Focusable"
 module(..., package.seeall, class.inherit(Base, Focusable))
 
 function _M:init(t)
-	self.text = assert(t.text, "no textzone text")
+	self.text = tostring(assert(t.text, "no textzone text"))
 	if t.auto_width then t.width = 1 end
 	self.w = assert(t.width, "no list width")
 	if t.auto_height then t.height = 1 end
diff --git a/game/engines/default/engine/ui/TextzoneList.lua b/game/engines/default/engine/ui/TextzoneList.lua
index 7a95358a135564af12aa1cb9f1d3fefe3b7317bc..afdcae660d2c15f2bb565b695b06360d3b0f8ee5 100644
--- a/game/engines/default/engine/ui/TextzoneList.lua
+++ b/game/engines/default/engine/ui/TextzoneList.lua
@@ -73,31 +73,49 @@ function _M:generate()
 end
 
 function _M:createItem(item, text)
-	local list = text:splitLines(self.w, self.font)
-	local scroll = 1
-	local max = #list
-	local max_display = math.floor(self.h / self.fh)
-
-	-- Draw the list items
-	local gen = {}
-	local r, g, b = 255, 255, 255
-	local s = core.display.newSurface(self.fw, self.fh)
-	for i, l in ipairs(list) do
-		s:erase()
-		r, g, b = s:drawColorStringBlended(self.font, l, 0, 0, r, g, b, true)
-		if self.no_color_bleed then r, g, b = 255, 255, 255 end
-
-		local dat = {}
-		dat._tex, dat._tex_w, dat._tex_h = s:glTexture()
-		gen[#gen+1] = dat
-	end
+	-- Handle normal text
+	if type(text) == "string" then
+		local list = text:splitLines(self.w, self.font)
+		local scroll = 1
+		local max = #list
+		local max_display = math.floor(self.h / self.fh)
+
+		-- Draw the list items
+		local gen = {}
+		local r, g, b = 255, 255, 255
+		local s = core.display.newSurface(self.fw, self.fh)
+		for i, l in ipairs(list) do
+			s:erase()
+			r, g, b = s:drawColorStringBlended(self.font, l, 0, 0, r, g, b, true)
+			if self.no_color_bleed then r, g, b = 255, 255, 255 end
+
+			local dat = {}
+			dat._tex, dat._tex_w, dat._tex_h = s:glTexture()
+			gen[#gen+1] = dat
+		end
 
-	self.items[item] = {
-		list = gen,
-		scroll = scroll,
-		max = max,
-		max_display = max_display,
-	}
+		self.items[item] = {
+			list = gen,
+			scroll = scroll,
+			max = max,
+			max_display = max_display,
+		}
+	-- Handle "pre formated" text, as a table
+	else
+		-- Draw the list items
+		local gen = tstring.makeLineTextures(text, self.fw, self.font)
+
+		local scroll = 1
+		local max = #gen
+		local max_display = math.floor(self.h / self.fh)
+
+		self.items[item] = {
+			list = gen,
+			scroll = scroll,
+			max = max,
+			max_display = max_display,
+		}
+	end
 end
 
 function _M:switchItem(item, create_if_needed)
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index 852978ea328c7eadc8dbca8bc0e7bc3eba2bbe3c..4dd4dfc811020ff16ac11c3864cc7f5af387e567 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -187,7 +187,10 @@ function string.splitLine(str, max_width, font)
 	local space_w = font:size(" ")
 	local lines = {}
 	local cur_line, cur_size = "", 0
-	for _, v in ipairs(str:split(lpeg.S"\n ")) do
+	local v
+	local ls = str:split(lpeg.S"\n ")
+	for i = 1, #ls do
+		local v = ls[i]
 		local shortv = v:lpegSub("#" * (Puid + Pcolorcodefull + Pcolorname + Pfontstyle) * "#", "")
 		local w, h = font:size(shortv)
 
@@ -206,7 +209,10 @@ end
 
 function string.splitLines(str, max_width, font)
 	local lines = {}
-	for _, v in ipairs(str:split(lpeg.S"\n")) do
+	local ls = str:split(lpeg.S"\n")
+	local v
+	for i = 1, #ls do
+		v = ls[i]
 		local ls = v:splitLine(max_width, font)
 		if #ls > 0 then
 			for i, l in ipairs(ls) do
@@ -228,6 +234,7 @@ function string.split(str, char, keep_separator)
 	return lpeg.match(p, str)
 end
 
+
 local hex_to_dec = {
 	["0"] = 0,
 	["1"] = 1,
@@ -396,12 +403,27 @@ getmetatable(tmps).__index.drawColorStringBlendedCentered = function(s, font, st
 	s:drawColorStringBlended(font, str, x, y, r, g, b, alpha_from_texture, limit_w)
 end
 
+local font_cache = {}
+local oldNewFont = core.display.newFont
+core.display.newFont = function(font, size)
+	if font_cache[font] and font_cache[font][size] then print("Using cached font", font, size) return font_cache[font][size] end
+	font_cache[font] = font_cache[font] or {}
+	font_cache[font][size] = oldNewFont(font, size)
+	return font_cache[font][size]
+end
+
 local tmps = core.display.newFont("/data/font/Vera.ttf", 12)
+local word_size_cache = {}
 local fontoldsize = getmetatable(tmps).__index.size
 getmetatable(tmps).__index.size = function(font, str)
 	local list = str:split("#" * (Puid + Pcolorcodefull + Pcolorname + Pfontstyle) * "#", true)
 	local mw, mh = 0, 0
-	for i, v in ipairs(list) do
+	local fstyle = font:getStyle()
+	word_size_cache[font] = word_size_cache[font] or {}
+	word_size_cache[font][fstyle] = word_size_cache[font][fstyle] or {}
+	local v
+	for i = 1, #list do
+		v = list[i]
 		local nr, ng, nb = lpeg.match("#" * lpeg.C(Pcolorcode) * lpeg.C(Pcolorcode) * lpeg.C(Pcolorcode) * "#", v)
 		local col = lpeg.match("#" * lpeg.C(Pcolorname) * "#", v)
 		local uid, mo = lpeg.match("#" * Puid_cap * "#", v)
@@ -414,8 +436,16 @@ getmetatable(tmps).__index.size = function(font, str)
 			-- Ignore
 		elseif fontstyle then
 			font:setStyle(fontstyle)
+			fstyle = fontstyle
+			word_size_cache[font][fstyle] = word_size_cache[font][fstyle] or {}
 		else
-			local w, h = fontoldsize(font, v)
+			local w, h
+			if word_size_cache[font][fstyle][v] then
+				w, h = word_size_cache[font][fstyle][v][1], word_size_cache[font][fstyle][v][2]
+			else
+				w, h = fontoldsize(font, v)
+				word_size_cache[font][fstyle][v] = {w, h}
+			end
 			if h > mh then mh = h end
 			mw = mw + w
 		end
@@ -423,6 +453,143 @@ getmetatable(tmps).__index.size = function(font, str)
 	return mw, mh
 end
 
+tstring = {}
+
+function tstring:add(...)
+	local v = {...}
+	for i = 1, #v do
+		self[#self+1] = v[i]
+	end
+end
+
+function tstring:merge(v)
+	for i = 1, #v do
+		self[#self+1] = v[i]
+	end
+end
+
+function tstring:countLines()
+	local nb = 1
+	local v
+	for i = 1, #self do
+		v = self[i]
+		if type(v) == "boolean" then nb = nb + 1 end
+	end
+	return nb
+end
+
+--- Tablestrings degrade "peacefully" into normal formated strings
+function tstring:toString()
+	local ret = {}
+	local v
+	for i = 1, #self do
+		v = self[i]
+		if type(v) == "boolean" then ret[#ret+1] = "\n"
+		elseif type(v) == "string" then ret[#ret+1] = v
+		elseif type(v) == "table" then
+			if v[1] == "color" and v[2] == "LAST" then ret[#ret+1] = "#LAST#"
+			elseif v[1] == "color" and not v[3] then ret[#ret+1] = "#"..v[2].."#"
+			elseif v[1] == "color" then ret[#ret+1] = ("#%02x%02x%02x#"):format(v[2], v[3], v[4]):upper()
+			elseif v[1] == "font" then ret[#ret+1] = "#{"..v[2].."}#"
+			end
+		end
+	end
+	return table.concat(ret)
+end
+
+function tstring:splitLines(max_width, font)
+	local space_w = font:size(" ")
+	local ret = tstring{}
+	local cur_size =0
+	local v
+	for i = 1, #self do
+		v = self[i]
+		if type(v) == "string" then
+			local ls = v:split(lpeg.S"\n ", true)
+			for i = 1, #ls do
+				local vv = ls[i]
+				if vv == "\n" then
+					ret[#ret+1] = true
+					cur_size = 0
+				else
+					local w, h = fontoldsize(font, vv)
+					if cur_size + w < max_width then
+						cur_size = cur_size + w
+						ret[#ret+1] = vv
+					else
+						ret[#ret+1] = true
+						ret[#ret+1] = vv
+						cur_size = w
+					end
+				end
+			end
+		elseif type(v) == "table" and v[1] == "font" then
+			font:setStyle(v[2])
+			ret[#ret+1] = v
+		elseif type(v) == "boolean" then
+			cur_size = 0
+			ret[#ret+1] = v
+		else
+			ret[#ret+1] = v
+		end
+	end
+	return ret
+end
+
+function tstring:makeLineTextures(max_width, font)
+	local list = self:splitLines(max_width, font)
+	local h = font:lineSkip()
+	local s = core.display.newSurface(max_width, h)
+	s:erase(0, 0, 0, 0)
+	local texs = {}
+	local w = 0
+	local r, g, b = 255, 255, 255
+	local oldr, oldg, oldb = 255, 255, 255
+	local v
+	for i = 1, #list do
+		v = list[i]
+		if type(v) == "string" then
+			s:drawStringBlended(font, v, w, 0, r, g, b, true)
+			w = w + fontoldsize(font, v)
+		elseif type(v) == "boolean" then
+			w = 0
+			local dat = {}
+			dat._tex, dat._tex_w, dat._tex_h = s:glTexture()
+			texs[#texs+1] = dat
+			s:erase(0, 0, 0, 0)
+		else
+			if v[1] == "color" and v[2] == "LAST" then
+				r, g, b = oldr, oldg, oldb
+			elseif v[1] == "color" and not v[3] then
+				oldr, oldg, oldb = r, g, b
+				r, g, b = unpack(colors.simple(colors[v[2]] or {255,255,255}))
+			elseif v[1] == "color" then
+				oldr, oldg, oldb = r, g, b
+				r, g, b = v[2], v[3], v[4]
+			elseif v[1] == "font" then
+				font:setStyle(v[2])
+			end
+		end
+	end
+
+	-- Last line
+	local dat = {}
+	dat._tex, dat._tex_w, dat._tex_h = s:glTexture()
+	texs[#texs+1] = dat
+
+	return texs
+end
+
+setmetatable(tstring, {
+	__call = function(self, t)
+		setmetatable(t, getmetatable(self))
+		return t
+	end,
+	__index = tstring,
+	__tostring = tstring.toString,
+})
+
+
 dir_to_coord = {
 	[1] = {-1, 1},
 	[2] = { 0, 1},
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 750816be7718decf1cc5feb10344b58ae71f03a0..e72bc4710e27cde13b7ca3b7c0797e7d9b81a7d4 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -1350,35 +1350,35 @@ function _M:getTalentFullDescription(t, addlevel)
 	local old = self.talents[t.id]
 	self.talents[t.id] = (self.talents[t.id] or 0) + (addlevel or 0)
 
-	local d = {}
+	local d = tstring{}
 
-	d[#d+1] = ("#6fff83#Effective talent level: #00FF00#%.1f"):format(self:getTalentLevel(t))
-	if t.mode == "passive" then d[#d+1] = "#6fff83#Use mode: #00FF00#Passive"
-	elseif t.mode == "sustained" then d[#d+1] = "#6fff83#Use mode: #00FF00#Sustained"
-	else d[#d+1] = "#6fff83#Use mode: #00FF00#Activated"
+	d:add({"color",0x6f,0xff,0x83}, "Effective talent level: ", {"color",0x00,0xFF,0x00}, ("%.1f"):format(self:getTalentLevel(t)), true)
+	if t.mode == "passive" then d:add({"color",0x6f,0xff,0x83}, "Use mode: ", {"color",0x00,0xFF,0x00}, "Passive", true)
+	elseif t.mode == "sustained" then d:add({"color",0x6f,0xff,0x83}, "Use mode: ", {"color",0x00,0xFF,0x00}, "Sustained", true)
+	else d:add({"color",0x6f,0xff,0x83}, "Use mode: ", {"color",0x00,0xFF,0x00}, "Activated", true)
 	end
 
-	if t.mana or t.sustain_mana then d[#d+1] = "#6fff83#Mana cost: #7fffd4#"..(t.sustain_mana or t.mana * (100 + self.fatigue) / 100) end
-	if t.stamina or t.sustain_stamina then d[#d+1] = "#6fff83#Stamina cost: #ffcc80#"..(t.sustain_stamina or t.stamina * (100 + self.fatigue) / 100) end
-	if t.equilibrium or t.sustain_equilibrium then d[#d+1] = "#6fff83#Equilibrium cost: #00ff74#"..(t.equilibrium or t.sustain_equilibrium) end
-	if t.vim or t.sustain_vim then d[#d+1] = "#6fff83#Vim cost: #888888#"..(t.sustain_vim or t.vim) end
-	if t.positive or t.sustain_positive then d[#d+1] = "#6fff83#Positive energy cost: #GOLD#"..(t.sustain_positive or t.positive * (100 + self.fatigue) / 100) end
-	if t.negative or t.sustain_negative then d[#d+1] = "#6fff83#Negative energy cost: #GREY#"..(t.sustain_negative or t.negative * (100 + self.fatigue) / 100) end
-	if t.hate or t.sustain_hate then d[#d+1] = "#6fff83#Hate cost: #GREY#"..(t.hate or t.sustain_hate) end
-	if self:getTalentRange(t) > 1 then d[#d+1] = "#6fff83#Range: #FFFFFF#"..self:getTalentRange(t)
-	else d[#d+1] = "#6fff83#Range: #FFFFFF#melee/personal"
+	if t.mana or t.sustain_mana then d:add({"color",0x6f,0xff,0x83}, "Mana cost: ", {"color",0x7f,0xff,0xd4}, ""..(t.sustain_mana or t.mana * (100 + self.fatigue) / 100), true) end
+	if t.stamina or t.sustain_stamina then d:add({"color",0x6f,0xff,0x83}, "Stamina cost: ", {"color",0xff,0xcc,0x80}, ""..(t.sustain_stamina or t.stamina * (100 + self.fatigue) / 100), true) end
+	if t.equilibrium or t.sustain_equilibrium then d:add({"color",0x6f,0xff,0x83}, "Equilibrium cost: ", {"color",0x00,0xff,0x74}, ""..(t.equilibrium or t.sustain_equilibrium), true) end
+	if t.vim or t.sustain_vim then d:add({"color",0x6f,0xff,0x83}, "Vim cost: ", {"color",0x88,0x88,0x88}, ""..(t.sustain_vim or t.vim), true) end
+	if t.positive or t.sustain_positive then d:add({"color",0x6f,0xff,0x83}, "Positive energy cost: #GOLD#"..(t.sustain_positive or t.positive * (100 + self.fatigue) / 100), true) end
+	if t.negative or t.sustain_negative then d:add({"color",0x6f,0xff,0x83}, "Negative energy cost: #GREY#"..(t.sustain_negative or t.negative * (100 + self.fatigue) / 100), true) end
+	if t.hate or t.sustain_hate then d:add({"color",0x6f,0xff,0x83}, "Hate cost: #GREY#"..(t.hate or t.sustain_hate), true) end
+	if self:getTalentRange(t) > 1 then d:add({"color",0x6f,0xff,0x83}, "Range: ", {"color",0xFF,0xFF,0xFF}, ""..self:getTalentRange(t), true)
+	else d:add({"color",0x6f,0xff,0x83}, "Range: ", {"color",0xFF,0xFF,0xFF}, "melee/personal", true)
 	end
-	if t.cooldown then d[#d+1] = "#6fff83#Cooldown: #FFFFFF#"..util.getval(t.cooldown, self, t) end
+	if t.cooldown then d:add({"color",0x6f,0xff,0x83}, "Cooldown: ", {"color",0xFF,0xFF,0xFF}, ""..util.getval(t.cooldown, self, t), true) end
 	local speed = self:getTalentProjectileSpeed(t)
-	if speed then d[#d+1] = "#6fff83#Travel Speed: #FFFFFF#"..(speed * 100).."% of base"
-	else d[#d+1] = "#6fff83#Travel Speed: #FFFFFF#instantaneous"
+	if speed then d:add({"color",0x6f,0xff,0x83}, "Travel Speed: ", {"color",0xFF,0xFF,0xFF}, ""..(speed * 100).."% of base", true)
+	else d:add({"color",0x6f,0xff,0x83}, "Travel Speed: ", {"color",0xFF,0xFF,0xFF}, "instantaneous", true)
 	end
 
-	local ret = table.concat(d, "\n").."\n#6fff83#Description: #FFFFFF#"..t.info(self, t)
+	d:add({"color",0x6f,0xff,0x83}, "Description: ", {"color",0xFF,0xFF,0xFF}, t.info(self, t), true)
 
 	self.talents[t.id] = old
 
-	return ret
+	return d
 end
 
 --- Starts a talent cooldown; overloaded from the default to handle talent cooldown reduction
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 79c7ab9fbd63ac92cafc90f67eb2b6611b5fa33e..a265b7059992b1b66a00a8709495590b75e59a0a 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -861,7 +861,7 @@ function _M:setupMouse(reset)
 	end, {button=true})
 	-- Use hotkeys with mouse
 	self.mouse:registerZone(self.hotkeys_display.display_x, self.hotkeys_display.display_y, self.w, self.h, function(button, mx, my, xrel, yrel, bx, by, event)
-		self.hotkeys_display:onMouse(button, mx, my, event == "button", function(text) self.tooltip:displayAtMap(nil, nil, self.w, self.h, text) end)
+		self.hotkeys_display:onMouse(button, mx, my, event == "button", function(text) self.tooltip:displayAtMap(nil, nil, self.w, self.h, tostring(text)) end)
 	end)
 	-- Use icons
 	self.mouse:registerZone(self.icons.display_x, self.icons.display_y, self.icons.w, self.icons.h, function(button, mx, my, xrel, yrel, bx, by)
diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua
index 17355f703371547ed0acc8fbbdf1e76a1175132a..87c92c1fdd7e920c591980aa8550576b4c0a8ec8 100644
--- a/game/modules/tome/class/Object.lua
+++ b/game/modules/tome/class/Object.lua
@@ -93,7 +93,7 @@ end
 
 --- Returns a tooltip for the object
 function _M:tooltip()
-	return self:getDesc{do_color=true}
+	return tostring(self:getDesc{do_color=true})
 end
 
 --- Describes an attribute, to expand object name
@@ -188,9 +188,9 @@ end
 
 --- Gets the full textual desc of the object without the name and requirements
 function _M:getTextualDesc()
-	local desc = {}
+	local desc = tstring{}
 
-	desc[#desc+1] = ("Type: %s / %s"):format(self.type, self.subtype)
+	desc:add(("Type: %s / %s"):format(self.type, self.subtype), true)
 
 	-- Stop here if unided
 	if not self:isIdentified() then return desc end
@@ -200,29 +200,29 @@ function _M:getTextualDesc()
 		for stat, i in pairs(self.combat.dammod or {}) do
 			dm[#dm+1] = ("+%d%% %s"):format(i * 100, Stats.stats_def[stat].name)
 		end
-		desc[#desc+1] = ("%d Power [Range %0.2f] (%s), %d Attack, %d Armor Penetration, Crit %d%%"):format(self.combat.dam or 0, self.combat.damrange or 1.1, table.concat(dm, ','), self.combat.atk or 0, self.combat.apr or 0, self.combat.physcrit or 0)
-		desc[#desc+1] = "Damage type: "..DamageType:get(self.combat.damtype or DamageType.PHYSICAL).name
-		if self.combat.range then desc[#desc+1] = "Firing range: "..self.combat.range end
-		desc[#desc+1] = ""
+		desc:add(("%d Power [Range %0.2f] (%s), %d Attack, %d Armor Penetration, Crit %d%%"):format(self.combat.dam or 0, self.combat.damrange or 1.1, table.concat(dm, ','), self.combat.atk or 0, self.combat.apr or 0, self.combat.physcrit or 0), true)
+		desc:add("Damage type: "..DamageType:get(self.combat.damtype or DamageType.PHYSICAL).name, true)
+		if self.combat.range then desc:add("Firing range: "..self.combat.range, true) end
+		desc:add(true)
 
 		if self.combat.talent_on_hit then
 			for tid, data in pairs(self.combat.talent_on_hit) do
-				desc[#desc+1] = ("Talent on hit(melee): %d%% chance %s (level %d)."):format(data.chance, self:getTalentFromId(tid).name, data.level)
+				desc:add(("Talent on hit(melee): %d%% chance %s (level %d)."):format(data.chance, self:getTalentFromId(tid).name, data.level), true)
 			end
 		end
 	end
 
 	local desc_wielder = function(w)
-	if w.combat_atk or w.combat_dam or w.combat_apr then desc[#desc+1] = ("Attack %d, Armor Penetration %d, Physical Crit %d%%, Physical power %d"):format(w.combat_atk or 0, w.combat_apr or 0, w.combat_physcrit or 0, w.combat_dam or 0) end
-	if w.combat_armor or w.combat_def or w.combat_def_ranged then desc[#desc+1] = ("Armor %d, Defense %d, Ranged Defense %d"):format(w.combat_armor or 0, w.combat_def or 0, w.combat_def_ranged or 0) end
-	if w.fatigue then desc[#desc+1] = ("Fatigue %d%%"):format(w.fatigue) end
+	if w.combat_atk or w.combat_dam or w.combat_apr then desc:add(("Attack %d, Armor Penetration %d, Physical Crit %d%%, Physical power %d"):format(w.combat_atk or 0, w.combat_apr or 0, w.combat_physcrit or 0, w.combat_dam or 0), true) end
+	if w.combat_armor or w.combat_def or w.combat_def_ranged then desc:add(("Armor %d, Defense %d, Ranged Defense %d"):format(w.combat_armor or 0, w.combat_def or 0, w.combat_def_ranged or 0), true) end
+	if w.fatigue then desc:add(("Fatigue %d%%"):format(w.fatigue), true) end
 
 	if w.inc_stats then
 		local dm = {}
 		for stat, i in pairs(w.inc_stats) do
 			dm[#dm+1] = ("%d %s"):format(i, Stats.stats_def[stat].name)
 		end
-		desc[#desc+1] = ("Increases stats: %s."):format(table.concat(dm, ','))
+		desc:add(("Increases stats: %s."):format(table.concat(dm, ',')), true)
 	end
 
 	if w.melee_project then
@@ -230,7 +230,7 @@ function _M:getTextualDesc()
 		for typ, dam in pairs(w.melee_project) do
 			rs[#rs+1] = ("%d %s"):format(dam, DamageType.dam_def[typ].name)
 		end
-		desc[#desc+1] = ("Damage on hit(melee): %s."):format(table.concat(rs, ','))
+		desc:add(("Damage on hit(melee): %s."):format(table.concat(rs, ',')), true)
 	end
 
 	if w.ranged_project then
@@ -238,7 +238,7 @@ function _M:getTextualDesc()
 		for typ, dam in pairs(w.ranged_project) do
 			rs[#rs+1] = ("%d %s"):format(dam, DamageType.dam_def[typ].name)
 		end
-		desc[#desc+1] = ("Damage on hit(ranged): %s."):format(table.concat(rs, ','))
+		desc:add(("Damage on hit(ranged): %s."):format(table.concat(rs, ',')), true)
 	end
 
 	if w.on_melee_hit then
@@ -246,7 +246,7 @@ function _M:getTextualDesc()
 		for typ, dam in pairs(w.on_melee_hit) do
 			rs[#rs+1] = ("%d %s"):format(dam, DamageType.dam_def[typ].name)
 		end
-		desc[#desc+1] = ("Damage when hit: %s."):format(table.concat(rs, ','))
+		desc:add(("Damage when hit: %s."):format(table.concat(rs, ',')), true)
 	end
 
 	if w.resists then
@@ -254,7 +254,7 @@ function _M:getTextualDesc()
 		for res, i in pairs(w.resists) do
 			rs[#rs+1] = ("%d%% %s"):format(i, res == "all" and "all" or DamageType.dam_def[res].name)
 		end
-		desc[#desc+1] = ("Increases resistances: %s."):format(table.concat(rs, ','))
+		desc:add(("Increases resistances: %s."):format(table.concat(rs, ',')), true)
 	end
 
 	if w.inc_damage then
@@ -262,7 +262,7 @@ function _M:getTextualDesc()
 		for res, i in pairs(w.inc_damage) do
 			rs[#rs+1] = ("%d%% %s"):format(i, res == "all" and "all" or DamageType.dam_def[res].name)
 		end
-		desc[#desc+1] = ("Increases damage type: %s."):format(table.concat(rs, ','))
+		desc:add(("Increases damage type: %s."):format(table.concat(rs, ',')), true)
 	end
 
 	if w.esp then
@@ -279,7 +279,7 @@ function _M:getTextualDesc()
 				end
 			end
 		end
-		desc[#desc+1] = ("Grants telepathy: %s."):format(table.concat(rs, ','))
+		desc:add(("Grants telepathy: %s."):format(table.concat(rs, ',')), true)
 	end
 
 	if w.talents_types_mastery then
@@ -290,7 +290,7 @@ function _M:getTextualDesc()
 			local name = cat:capitalize().." / "..tt.name:capitalize()
 			tms[#tms+1] = ("%0.2f %s"):format(i, name)
 		end
-		desc[#desc+1] = ("Increases talent masteries: %s."):format(table.concat(tms, ','))
+		desc:add(("Increases talent masteries: %s."):format(table.concat(tms, ',')), true)
 	end
 
 	if w.talent_cd_reduction then
@@ -298,7 +298,7 @@ function _M:getTextualDesc()
 		for tid, cd in pairs(w.talent_cd_reduction) do
 			tcds[#tcds+1] = ("%s (%d)"):format(Talents.talents_def[tid].name, cd)
 		end
-		desc[#desc+1] = ("Reduces talent cooldowns: %s."):format(table.concat(tcds, ','))
+		desc:add(("Reduces talent cooldowns: %s."):format(table.concat(tcds, ',')), true)
 	end
 
 	if w.can_breath then
@@ -306,111 +306,114 @@ function _M:getTextualDesc()
 		for what, _ in pairs(w.can_breath) do
 			ts[#ts+1] = what
 		end
-		desc[#desc+1] = ("Allows you to breathe in: %s."):format(table.concat(ts, ','))
+		desc:add(("Allows you to breathe in: %s."):format(table.concat(ts, ',')), true)
 	end
 
-	if w.combat_critical_power then desc[#desc+1] = ("Increases critical damage modifier: +%d%%."):format(w.combat_critical_power) end
+	if w.combat_critical_power then desc:add(("Increases critical damage modifier: +%d%%."):format(w.combat_critical_power), true) end
 
-	if w.disarm_bonus then desc[#desc+1] = ("Increases trap disarming bonus: %d."):format(w.disarm_bonus) end
-	if w.inc_stealth then desc[#desc+1] = ("Increases stealth bonus: %d."):format(w.inc_stealth) end
-	if w.max_encumber then desc[#desc+1] = ("Increases maximum encumberance: %d."):format(w.max_encumber) end
+	if w.disarm_bonus then desc:add(("Increases trap disarming bonus: %d."):format(w.disarm_bonus), true) end
+	if w.inc_stealth then desc:add(("Increases stealth bonus: %d."):format(w.inc_stealth), true) end
+	if w.max_encumber then desc:add(("Increases maximum encumberance: %d."):format(w.max_encumber), true) end
 
-	if w.combat_physresist then desc[#desc+1] = ("Increases physical save: %s."):format(w.combat_physresist) end
-	if w.combat_spellresist then desc[#desc+1] = ("Increases spell save: %s."):format(w.combat_spellresist) end
-	if w.combat_mentalresist then desc[#desc+1] = ("Increases mental save: %s."):format(w.combat_mentalresist) end
+	if w.combat_physresist then desc:add(("Increases physical save: %s."):format(w.combat_physresist), true) end
+	if w.combat_spellresist then desc:add(("Increases spell save: %s."):format(w.combat_spellresist), true) end
+	if w.combat_mentalresist then desc:add(("Increases mental save: %s."):format(w.combat_mentalresist), true) end
 
-	if w.blind_immune then desc[#desc+1] = ("Increases blindness immunity: %d%%."):format(w.blind_immune * 100) end
-	if w.poison_immune then desc[#desc+1] = ("Increases poison immunity: %d%%."):format(w.poison_immune * 100) end
-	if w.cut_immune then desc[#desc+1] = ("Increases cut immunity: %d%%."):format(w.cut_immune * 100) end
-	if w.silence_immune then desc[#desc+1] = ("Increases silence immunity: %d%%."):format(w.silence_immune * 100) end
-	if w.disarm_immune then desc[#desc+1] = ("Increases disarm immunity: %d%%."):format(w.disarm_immune * 100) end
-	if w.confusion_immune then desc[#desc+1] = ("Increases confusion immunity: %d%%."):format(w.confusion_immune * 100) end
-	if w.pin_immune then desc[#desc+1] = ("Increases pinning immunity: %d%%."):format(w.pin_immune * 100) end
-	if w.stun_immune then desc[#desc+1] = ("Increases stun immunity: %d%%."):format(w.stun_immune * 100) end
-	if w.fear_immune then desc[#desc+1] = ("Increases fear immunity: %d%%."):format(w.fear_immune * 100) end
-	if w.knockback_immune then desc[#desc+1] = ("Increases knockback immunity: %d%%."):format(w.knockback_immune * 100) end
-	if w.instakill_immune then desc[#desc+1] = ("Increases instant-death immunity: %d%%."):format(w.instakill_immune * 100) end
+	if w.blind_immune then desc:add(("Increases blindness immunity: %d%%."):format(w.blind_immune * 100), true) end
+	if w.poison_immune then desc:add(("Increases poison immunity: %d%%."):format(w.poison_immune * 100), true) end
+	if w.cut_immune then desc:add(("Increases cut immunity: %d%%."):format(w.cut_immune * 100), true) end
+	if w.silence_immune then desc:add(("Increases silence immunity: %d%%."):format(w.silence_immune * 100), true) end
+	if w.disarm_immune then desc:add(("Increases disarm immunity: %d%%."):format(w.disarm_immune * 100), true) end
+	if w.confusion_immune then desc:add(("Increases confusion immunity: %d%%."):format(w.confusion_immune * 100), true) end
+	if w.pin_immune then desc:add(("Increases pinning immunity: %d%%."):format(w.pin_immune * 100), true) end
+	if w.stun_immune then desc:add(("Increases stun immunity: %d%%."):format(w.stun_immune * 100), true) end
+	if w.fear_immune then desc:add(("Increases fear immunity: %d%%."):format(w.fear_immune * 100), true) end
+	if w.knockback_immune then desc:add(("Increases knockback immunity: %d%%."):format(w.knockback_immune * 100), true) end
+	if w.instakill_immune then desc:add(("Increases instant-death immunity: %d%%."):format(w.instakill_immune * 100), true) end
 
-	if w.life_regen then desc[#desc+1] = ("Regenerates %0.2f hitpoints each turn."):format(w.life_regen) end
-	if w.stamina_regen then desc[#desc+1] = ("Regenerates %0.2f stamina each turn."):format(w.stamina_regen) end
-	if w.mana_regen then desc[#desc+1] = ("Regenerates %0.2f mana each turn."):format(w.mana_regen) end
+	if w.life_regen then desc:add(("Regenerates %0.2f hitpoints each turn."):format(w.life_regen), true) end
+	if w.stamina_regen then desc:add(("Regenerates %0.2f stamina each turn."):format(w.stamina_regen), true) end
+	if w.mana_regen then desc:add(("Regenerates %0.2f mana each turn."):format(w.mana_regen), true) end
 
-	if w.stamina_regen_on_hit then desc[#desc+1] = ("Regenerates %0.2f stamina when hit."):format(w.stamina_regen_on_hit) end
-	if w.mana_regen_on_hit then desc[#desc+1] = ("Regenerates %0.2f mana when hit."):format(w.mana_regen_on_hit) end
-	if w.equilibrium_regen_on_hit then desc[#desc+1] = ("Regenerates %0.2f equilibrium when hit."):format(w.equilibrium_regen_on_hit) end
+	if w.stamina_regen_on_hit then desc:add(("Regenerates %0.2f stamina when hit."):format(w.stamina_regen_on_hit), true) end
+	if w.mana_regen_on_hit then desc:add(("Regenerates %0.2f mana when hit."):format(w.mana_regen_on_hit), true) end
+	if w.equilibrium_regen_on_hit then desc:add(("Regenerates %0.2f equilibrium when hit."):format(w.equilibrium_regen_on_hit), true) end
 
-	if w.max_life then desc[#desc+1] = ("Maximum life %d"):format(w.max_life) end
-	if w.max_mana then desc[#desc+1] = ("Maximum mana %d"):format(w.max_mana) end
-	if w.max_stamina then desc[#desc+1] = ("Maximum stamina %d"):format(w.max_stamina) end
+	if w.max_life then desc:add(("Maximum life %d"):format(w.max_life), true) end
+	if w.max_mana then desc:add(("Maximum mana %d"):format(w.max_mana), true) end
+	if w.max_stamina then desc:add(("Maximum stamina %d"):format(w.max_stamina), true) end
 
-	if w.combat_spellpower or w.combat_spellcrit then desc[#desc+1] = ("Spellpower %d, Spell Crit %d%%"):format(w.combat_spellpower or 0, w.combat_spellcrit or 0) end
+	if w.combat_spellpower or w.combat_spellcrit then desc:add(("Spellpower %d, Spell Crit %d%%"):format(w.combat_spellpower or 0, w.combat_spellcrit or 0), true) end
 
-	if w.lite then desc[#desc+1] = ("Light radius %d"):format(w.lite) end
-	if w.infravision then desc[#desc+1] = ("Infravision radius %d"):format(w.infravision) end
-	if w.heightened_senses then desc[#desc+1] = ("Heightened senses radius %d"):format(w.heightened_senses) end
+	if w.lite then desc:add(("Light radius %d"):format(w.lite), true) end
+	if w.infravision then desc:add(("Infravision radius %d"):format(w.infravision), true) end
+	if w.heightened_senses then desc:add(("Heightened senses radius %d"):format(w.heightened_senses), true) end
 
-	if w.see_invisible then desc[#desc+1] = ("See invisible: %d"):format(w.see_invisible) end
-	if w.invisible then desc[#desc+1] = ("Invisibility: %d"):format(w.invisible) end
+	if w.see_invisible then desc:add(("See invisible: %d"):format(w.see_invisible), true) end
+	if w.invisible then desc:add(("Invisibility: %d"):format(w.invisible), true) end
 
-	if w.movement_speed then desc[#desc+1] = ("Movement speed: %d%%"):format(w.movement_speed * 100) end
+	if w.movement_speed then desc:add(("Movement speed: %d%%"):format(w.movement_speed * 100), true) end
 
 	end
 
 	if self.wielder then
-		desc[#desc+1] = "#YELLOW#When wielded/worn:#LAST#"
+		desc:add({"color","YELLOW"}, "When wielded/worn:", {"color", "LAST"}, true)
 		desc_wielder(self.wielder)
 	end
 
 	if self.carrier then
-		desc[#desc+1] = "#YELLOW#When carried:#LAST#"
+		desc:add({"color","YELLOW"}, "When carried:", {"color", "LAST"}, true)
 		desc_wielder(self.carrier)
 	end
 
 	if self.imbue_powers then
-		desc[#desc+1] = "#YELLOW#When used to imbue an object:#LAST#"
+		desc:add({"color","YELLOW"}, "When used to imbue an object:", {"color", "LAST"}, true)
 		desc_wielder(self.imbue_powers)
 	end
 
 	if self.alchemist_bomb then
 		local a = self.alchemist_bomb
-		desc[#desc+1] = "#YELLOW#When used as an alchemist bomb:#LAST#"
-		if a.power then desc[#desc+1] = ("Bomb damage +%d%%"):format(a.power) end
-		if a.range then desc[#desc+1] = ("Bomb thrown range +%d"):format(a.range) end
-		if a.mana then desc[#desc+1] = ("Mana regain %d"):format(a.mana) end
-		if a.daze then desc[#desc+1] = ("%d%% chance to daze for %d turns"):format(a.daze.chance, a.daze.dur) end
-		if a.stun then desc[#desc+1] = ("%d%% chance to stun for %d turns"):format(a.stun.chance, a.stun.dur) end
-		if a.splash then desc[#desc+1] = ("Additional %d %s damage"):format(a.splash.dam, DamageType:get(DamageType[a.splash.type]).name) end
-		if a.leech then desc[#desc+1] = ("Life regen %d%% of max life"):format(a.leech) end
+		desc:add({"color","YELLOW"}, "When used as an alchemist bomb:", {"color", "LAST"}, true)
+		if a.power then desc:add(("Bomb damage +%d%%"):format(a.power), true) end
+		if a.range then desc:add(("Bomb thrown range +%d"):format(a.range), true) end
+		if a.mana then desc:add(("Mana regain %d"):format(a.mana), true) end
+		if a.daze then desc:add(("%d%% chance to daze for %d turns"):format(a.daze.chance, a.daze.dur), true) end
+		if a.stun then desc:add(("%d%% chance to stun for %d turns"):format(a.stun.chance, a.stun.dur), true) end
+		if a.splash then desc:add(("Additional %d %s damage"):format(a.splash.dam, DamageType:get(DamageType[a.splash.type]).name), true) end
+		if a.leech then desc:add(("Life regen %d%% of max life"):format(a.leech), true) end
 	end
 
 	local use_desc = self:getUseDesc()
-	if use_desc then desc[#desc+1] = use_desc end
+	if use_desc then desc:add(use_desc) end
 
 	return desc
 end
 
 --- Gets the full desc of the object
 function _M:getDesc(name_param)
-	local _, c = self:getDisplayColor()
-	local desc
+	local c, _ = self:getDisplayColor()
+	local desc = tstring{}
 	if not self:isIdentified() then
-		desc = { c..self:getName(name_param).."#FFFFFF#" }
+		desc:add({"color", unpack(c)}, self:getName(name_param), {"color", "WHITE"})
 	else
-		desc = { c..self:getName(name_param).."#FFFFFF#", self.desc }
+		desc:add({"color", unpack(c)}, self:getName(name_param), {"color", "WHITE"}, true)
+		desc:merge(self.desc)
 	end
 
 	local reqs = self:getRequirementDesc(game.player)
 	if reqs then
-		desc[#desc+1] = reqs
+		desc:add(true)
+		desc:merge(reqs)
 	end
 
 	if self.encumber then
-		desc[#desc+1] = ("#67AD00#%0.2f Encumbrance.#LAST#"):format(self.encumber)
+		desc:add({"color",0x67,0xAD,0x00}, ("%0.2f Encumbrance."):format(self.encumber), {"color", "LAST"})
 	end
 
-	local textdesc = table.concat(self:getTextualDesc(), "\n")
+	desc:add(true, true)
+	desc:merge(self:getTextualDesc())
 
-	return table.concat(desc, "\n").."\n"..textdesc
+	return desc
 end
 
 local type_sort = {
diff --git a/game/modules/tome/class/Store.lua b/game/modules/tome/class/Store.lua
index b374b6fe38e161928df9d7adadff935175ed4459..49b8a36da808b2e46fcff860490af3c21eb3e228 100644
--- a/game/modules/tome/class/Store.lua
+++ b/game/modules/tome/class/Store.lua
@@ -101,12 +101,12 @@ end
 -- @return a string (possibly multiline) describing the object
 function _M:descObject(who, what, o)
 	if what == "buy" then
-		local desc = ("Buy for: %0.2f gold (You have %0.2f gold)\n\n"):format(o:getPrice() * self.sell_percent / 100, who.money)
-		desc = desc .. o:getDesc()
+		local desc = tstring({"font", "bold"}, {"color", "GOLD"}, ("Buy for: %0.2f gold (You have %0.2f gold)"):format(o:getPrice() * self.sell_percent / 100, who.money), {"font", "normal"}, {"color", "LAST"}, true, true)
+		desc:merge(o:getDesc())
 		return desc
 	else
-		local desc = ("Sell for: %0.2f gold (You have %0.2f gold)\n\n"):format(o:getPrice() * self.buy_percent / 100, who.money)
-		desc = desc .. o:getDesc()
+		local desc = tstring({"font", "bold"}, {"color", "GOLD"}, ("Sell for: %0.2f gold (You have %0.2f gold)"):format(o:getPrice() * self.buy_percent / 100, who.money), {"font", "normal"}, {"color", "LAST"}, true, true)
+		desc:merge(o:getDesc())
 		return desc
 	end
 end
diff --git a/game/modules/tome/data/chats/escort-quest.lua b/game/modules/tome/data/chats/escort-quest.lua
index d42d61d8ab59002a42d4250813ae6830385e6edd..cfb5bfb18974fad4dee39103619d6ed9006789af 100644
--- a/game/modules/tome/data/chats/escort-quest.lua
+++ b/game/modules/tome/data/chats/escort-quest.lua
@@ -163,7 +163,7 @@ local function generate_rewards()
 						action=doit,
 						on_select=function(npc, player)
 							game.tooltip_x, game.tooltip_y = 1, 1
-							game.tooltip:displayAtMap(nil, nil, game.w, game.h, "#GOLD#"..t.name.."#LAST#\n"..player:getTalentFullDescription(t, 1))
+							game.tooltip:displayAtMap(nil, nil, game.w, game.h, "#GOLD#"..t.name.."#LAST#\n"..tostring(player:getTalentFullDescription(t, 1)))
 						end,
 					}
 			end
diff --git a/game/modules/tome/data/zones/vor-armoury/zone.lua b/game/modules/tome/data/zones/vor-armoury/zone.lua
index f1f62eca4781578a9bcd28e47d193defbc4e8501..2f33a4a5c573a1b6c4136990bce8014d3dfeb362 100644
--- a/game/modules/tome/data/zones/vor-armoury/zone.lua
+++ b/game/modules/tome/data/zones/vor-armoury/zone.lua
@@ -22,7 +22,7 @@ return {
 	level_range = {35, 50},
 	level_scheme = "player",
 	max_level = 2,
-	decay = {300, 800},
+--	decay = {300, 800},
 	actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
 	width = 30, height = 30,
 	persistant = "zone",
diff --git a/game/modules/tome/dialogs/CharacterSheet.lua b/game/modules/tome/dialogs/CharacterSheet.lua
index 78e267dd85459c02bf7ce848627080cc42a021b8..c18d106ed4d6db2852d495a849af69510e2e7a7a 100644
--- a/game/modules/tome/dialogs/CharacterSheet.lua
+++ b/game/modules/tome/dialogs/CharacterSheet.lua
@@ -467,7 +467,7 @@ function _M:dump()
 				if not self.filter or self.filter(o) then
 					local char = string.char(string.byte('a') + index)
 					nl(("%s) %s"):format(char, o:getName{force_id=true}))
-					nl(("   %s"):format(table.concat(o:getTextualDesc(), "\n    ")))
+					nl(("   %s"):format(tostring(o:getTextualDesc())))
 					if o.droppedBy then
 						nl(("   Dropped by %s"):format(o.droppedBy))
 					end
@@ -498,7 +498,7 @@ function _M:dump()
 		if not self.filter or self.filter(o) then
 			local char = string.char(string.byte('a') + item - 1)
 			nl(("%s) %s"):format(char, o:getName{force_id=true}))
-			nl(("   %s"):format(table.concat(o:getTextualDesc(), "\n    ")))
+			nl(("   %s"):format(tostring(o:getTextualDesc())))
 			if o.droppedBy then
 				nl(("   Dropped by %s"):format(o.droppedBy))
 			end
diff --git a/game/modules/tome/dialogs/LevelupTalentsDialog.lua b/game/modules/tome/dialogs/LevelupTalentsDialog.lua
index 8724d9f61a52173bcb18fa1fdcc6ebf945a37e75..1ca417e4500480425586949842bd513ce042ecd1 100644
--- a/game/modules/tome/dialogs/LevelupTalentsDialog.lua
+++ b/game/modules/tome/dialogs/LevelupTalentsDialog.lua
@@ -146,6 +146,7 @@ function _M:generateList()
 			local tshown = (self.actor.__hidden_talent_types[tt.type] == nil and ttknown) or (self.actor.__hidden_talent_types[tt.type] ~= nil and not self.actor.__hidden_talent_types[tt.type])
 			local node = {
 				name=function(item) return "#{bold}#"..cat:capitalize().." / "..tt.name:capitalize() ..(" (mastery %.02f)"):format(self.actor:getTalentTypeMastery(tt.type)).."#{normal}#" end,
+				rawname=function(item) return cat:capitalize().." / "..tt.name:capitalize() ..(" (mastery %.02f)"):format(self.actor:getTalentTypeMastery(tt.type)) end,
 				type=tt.type,
 				color=function(item) return self.actor:knowTalentType(item.type) and {0,200,0} or {175,175,175} end,
 				shown = tshown,
@@ -166,6 +167,7 @@ function _M:generateList()
 					list[#list+1] = {
 						__id=t.id,
 						name=t.name.." ("..typename..")",
+						rawname=t.name.." ("..typename..")",
 						talent=t.id,
 						_type=tt.type,
 						color=function(item) return self.actor:knowTalentType(item._type) and {255,255,255} or {175,175,175} end,
@@ -217,45 +219,49 @@ end
 
 function _M:onDrawItem(item)
 	if not item then return end
+	local text = tstring{}
 
-	local text = {}
-
-	text[#text+1] = util.getval(item.name, item)
-	text[#text+1] = ""
+	text:add({"color", "GOLD"}, {"font", "bold"}, util.getval(item.rawname, item), {"color", "LAST"}, {"font", "normal"})
+	text:add(true, true)
 
 	if item.type then
-		text[#text+1] = "#00FFFF#Talent Category"
-		text[#text+1] = "#00FFFF#A talent category allows you to learn talents of this category. You gain a talent category point at level 10, 20 and 30. You may also find trainers or artifacts that allow you to learn more.\nA talent category point can be used either to learn a new category or increase the mastery of a known one.\n"
+		text:add({"color",0x00,0xFF,0xFF}, "Talent Category", true)
+		text:add({"color",0x00,0xFF,0xFF}, "A talent category allows you to learn talents of this category. You gain a talent category point at level 10, 20 and 30. You may also find trainers or artifacts that allow you to learn more.\nA talent category point can be used either to learn a new category or increase the mastery of a known one.", true, true, {"color", "WHITE"})
+		text:add(self.actor:getTalentTypeFrom(item.type).description)
 	else
 		local t = self.actor:getTalentFromId(item.talent)
 
 		local what
 		if t.generic then
 			what = "generic talent"
-			text[#text+1] = "#00FFFF#Generic Talent"
-			text[#text+1] = "#00FFFF#A generic talent allows you to perform various utility actions and improve your character. It represents talents anybody can learn (should they find a trainer for it). You gain one point every levels (except every 5th level). You may also find trainers or artifacts that allow you to learn more.\n"
+			text:add({"color",0x00,0xFF,0xFF}, "Generic Talent", true)
+			text:add({"color",0x00,0xFF,0xFF}, "A generic talent allows you to perform various utility actions and improve your character. It represents talents anybody can learn (should they find a trainer for it). You gain one point every levels (except every 5th level). You may also find trainers or artifacts that allow you to learn more.", true, true, {"color", "WHITE"})
 		else
 			what = "class talent"
-			text[#text+1] = "#00FFFF#Class talent"
-			text[#text+1] = "#00FFFF#A class talent allows you to perform new combat moves, cast spells, and improve your character. It represents the core function of your class. You gain one point every level and two every 5th level. You may also find trainers or artifacts that allow you to learn more.\n"
+			text:add({"color",0x00,0xFF,0xFF}, "Class talent", true)
+			text:add({"color",0x00,0xFF,0xFF}, "A class talent allows you to perform new combat moves, cast spells, and improve your character. It represents the core function of your class. You gain one point every level and two every 5th level. You may also find trainers or artifacts that allow you to learn more.", true, true, {"color", "WHITE"})
 		end
 
 		if self.actor:getTalentLevelRaw(t.id) > 0 then
 			local req = self.actor:getTalentReqDesc(item.talent, 0)
-			req = "Current "..what.." level: "..self.actor:getTalentLevelRaw(t.id).."\n"..req
-			text[#text+1] = req
-			text[#text+1] = self.actor:getTalentFullDescription(t)
+			text:add{"color","WHITE"}
+			text:add("Current "..what.." level: "..(self.actor:getTalentLevelRaw(t.id)))
+			text:add(true)
+			text:merge(req)
+			text:merge(self.actor:getTalentFullDescription(t))
 		end
 
 		if self.actor:getTalentLevelRaw(t.id) < t.points then
 			local req2 = self.actor:getTalentReqDesc(item.talent, 1)
-			req2 = "Next "..what.." level: "..(self.actor:getTalentLevelRaw(t.id)+1).."\n"..req2
-			text[#text+1] = req2
-			text[#text+1] = self.actor:getTalentFullDescription(t, 1)
+			text:add("Next "..what.." level: "..(self.actor:getTalentLevelRaw(t.id)+1))
+			text:add(true)
+			text:merge(req2)
+			text:merge(self.actor:getTalentFullDescription(t, 1))
 		end
 	end
 
-	self.c_desc:createItem(item, table.concat(text, "\n"))
+	self.c_desc:createItem(item, text)
+
 end
 
 function _M:select(item)
diff --git a/src/core_lua.c b/src/core_lua.c
index 7f65235621fe59c6f4a88ac68a5439613e9ab5da..544c30507dcc319d2d6b2491f237a684c34e9c6b 100644
--- a/src/core_lua.c
+++ b/src/core_lua.c
@@ -530,6 +530,19 @@ static int sdl_font_lineskip(lua_State *L)
 	return 1;
 }
 
+static int sdl_font_style_get(lua_State *L)
+{
+	TTF_Font **f = (TTF_Font**)auxiliar_checkclass(L, "sdl{font}", 1);
+	int style = TTF_GetFontStyle(*f);
+
+	if (style & TTF_STYLE_BOLD) lua_pushstring(L, "bold");
+	else if (style & TTF_STYLE_ITALIC) lua_pushstring(L, "italic");
+	else if (style & TTF_STYLE_UNDERLINE) lua_pushstring(L, "underline");
+	else lua_pushstring(L, "normal");
+
+	return 1;
+}
+
 static int sdl_font_style(lua_State *L)
 {
 	TTF_Font **f = (TTF_Font**)auxiliar_checkclass(L, "sdl{font}", 1);
@@ -1508,6 +1521,7 @@ static const struct luaL_reg sdl_font_reg[] =
 	{"height", sdl_font_height},
 	{"lineSkip", sdl_font_lineskip},
 	{"setStyle", sdl_font_style},
+	{"getStyle", sdl_font_style_get},
 	{NULL, NULL},
 };