diff --git a/game/engines/default/engine/UserChat.lua b/game/engines/default/engine/UserChat.lua
index 4f56dad1e3e1c194e4dadc55308a19d051221ae3..00c7fefe10ebaacf0441e4c7c0f8f61e70433966 100644
--- a/game/engines/default/engine/UserChat.lua
+++ b/game/engines/default/engine/UserChat.lua
@@ -79,6 +79,15 @@ function _M:event(e)
 		if type(game) == "table" and game.logChat and self.cur_channel == e.channel then
 			game.logChat("#YELLOW#<%s> %s", e.name, e.msg)
 		end
+	elseif e.se == "Whisper" then
+		e.msg = e.msg:removeColorCodes()
+
+		self.channels[self.cur_channel] = self.channels[self.cur_channel] or {users={}, log={}}
+		self:addMessage(self.cur_channel, e.login, e.name, "#GOLD#<whisper>#LAST#"..e.msg)
+
+		if type(game) == "table" and game.logChat then
+			game.logChat("#GOLD#<Whisper from %s> %s", e.name, e.msg)
+		end
 	elseif e.se == "Achievement" then
 		e.msg = e.msg:removeColorCodes()
 
@@ -159,26 +168,53 @@ function _M:talk(msg)
 	core.profile.pushOrder(string.format("o='ChatTalk' channel=%q msg=%q", self.cur_channel, msg))
 end
 
+function _M:whisper(to, msg)
+	if not profile.auth then return end
+	if not to or not msg or msg == "" then return end
+	msg = msg:removeColorCodes()
+	core.profile.pushOrder(string.format("o='ChatWhisper' target=%q msg=%q", to, msg))
+end
+
 function _M:achievement(name)
 	if not profile.auth then return end
 	core.profile.pushOrder(string.format("o='ChatAchievement' channel=%q msg=%q", self.cur_channel, name))
 end
 
 --- Request a line to send
--- TODO: make it better than a simple dialog
 function _M:talkBox()
 	if not profile.auth then return end
-	local GetText = require "engine.dialogs.GetText"
-	local d = GetText.new("Talk", self.cur_channel, 0, 250, function(text)
-		self:talk(text)
-	end)
-	d.key:addBind("EXIT", function() game:unregisterDialog(d) end)
-	d.key:addCommand("_ESCAPE", function() game:unregisterDialog(d) end)
+	local Talkbox = require "engine.dialogs.Talkbox"
+	local d = Talkbox.new(self)
 	game:registerDialog(d)
 
 	self:updateChanList()
 end
 
+--- Sets the current talk target, channel or whisper
+function _M:setCurrentTarget(channel, name)
+	if channel and not self.channels[name] then return end
+	self.cur_target = {channel and "channel" or "whisper", name}
+	if channel then self:selectChannel(name) end
+end
+
+--- Gets the current talk target, channel or whisper
+function _M:getCurrentTarget()
+	if not self.cur_target then return "channel", self.cur_channel end
+	return self.cur_target[1], self.cur_target[2]
+end
+
+function _M:findChannel(name)
+	for cname, data in pairs(self.channels) do
+		if cname:lower() == name:lower() then return cname end
+	end
+end
+
+function _M:findUser(name)
+	for login, data in pairs(self.channels[self.cur_channel].users) do
+		if data.name:lower() == name:lower() then return data.name end
+	end
+end
+
 function _M:updateChanList(force)
 	local time = os.time()
 	if force or not self.last_chan_update or self.last_chan_update + 60 < time then
diff --git a/game/engines/default/engine/dialogs/Talkbox.lua b/game/engines/default/engine/dialogs/Talkbox.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f102dbe8e20ea9ff19c025934e8ac41ff24d7837
--- /dev/null
+++ b/game/engines/default/engine/dialogs/Talkbox.lua
@@ -0,0 +1,104 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010, 2011 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 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 General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Module = require "engine.Module"
+local Dialog = require "engine.ui.Dialog"
+local Button = require "engine.ui.Button"
+local Textbox = require "engine.ui.Textbox"
+
+module(..., package.seeall, class.inherit(Dialog))
+
+function _M:init(chat)
+	self.chat = chat
+	self.min = 2
+	self.max = 300
+	self.absolute = absolute
+
+	Dialog.init(self, self:getTitle(), 320, 110)
+
+	local c_box = Textbox.new{title="Say: ", text="", chars=30, max_len=max,
+		fct=function(text) self:okclick() end,
+		on_change = function(text) self:checkTarget(text) end,
+	}
+	self.c_box = c_box
+	local ok = require("engine.ui.Button").new{text="Accept", fct=function() self:okclick() end}
+	local cancel = require("engine.ui.Button").new{text="Cancel", fct=function() self:cancelclick() end}
+
+	self:loadUI{
+		{left=0, top=0, padding_h=10, ui=c_box},
+		{left=0, bottom=0, ui=ok},
+		{right=0, bottom=0, ui=cancel},
+	}
+	self:setFocus(c_box)
+	self:setupUI(true, true)
+
+	self.key:addBinds{
+		EXIT = function() game:unregisterDialog(self) end,
+	}
+	self.key:addCommand("_ESCAPE", function() game:unregisterDialog(self) end)
+--	self.key:addCommand("_TAB", function()
+--		self:checkTarget(self.c_box.text)
+--	end)
+end
+
+function _M:getTitle()
+	local type, name = self.chat:getCurrentTarget()
+	if type == "channel" then
+		return "Talk on channel: "..name
+	elseif type == "whisper" then
+		return "Whisper to: "..name
+	end
+	return "????"
+end
+
+function _M:checkTarget(text)
+	if text:sub(text:len()) == ":" then
+		local name = text:sub(1, text:len() - 1)
+		local channel = self.chat:findChannel(name)
+		local uname = self.chat:findUser(name)
+		if uname then
+			self.chat:setCurrentTarget(false, uname or name)
+			self:updateTitle(self:getTitle())
+			self.c_box:setText("")
+		elseif channel then
+			self.chat:setCurrentTarget(true, channel)
+			self:updateTitle(self:getTitle())
+			self.c_box:setText("")
+		end
+	end
+end
+
+function _M:okclick()
+	local text = self.c_box.text
+	if text:len() >= self.min and text:len() <= self.max then
+		game:unregisterDialog(self)
+
+		local type, name = self.chat:getCurrentTarget()
+		if type == "channel" then
+			self.chat:talk(text)
+		elseif type == "whisper" then
+			self.chat:whisper(name, text)
+		end
+	end
+end
+
+function _M:cancelclick()
+	self.key:triggerVirtual("EXIT")
+end
diff --git a/game/modules/tome/class/interface/PlayerLore.lua b/game/modules/tome/class/interface/PlayerLore.lua
index 98f16c2a522b85535980d7dc3e722957622f89ba..de902a10526379476b0846cdaa39b979373d09ff 100644
--- a/game/modules/tome/class/interface/PlayerLore.lua
+++ b/game/modules/tome/class/interface/PlayerLore.lua
@@ -80,7 +80,7 @@ function _M:learnLore(lore, nopopup)
 		game.logPlayer(self, "Lore found: #0080FF#%s", l.name)
 		if not nopopup then
 			LorePopup.new(l, game.w * 0.6, 0.8)
-			game.logPlayer(self, "#ANTIQUE_WHITE#%s", util.getval(l.lore))
+--			game.logPlayer(self, "#ANTIQUE_WHITE#%s", util.getval(l.lore))
 			game.logPlayer(self, "You can read all your collected lore in the game menu, by pressing Escape.")
 		end
 		learnt = true
diff --git a/game/profile-thread/Client.lua b/game/profile-thread/Client.lua
index 43ec1fd33bb2b9ab4c80dff8f216c4c061643a55..ed2d011cf4f1517721599fde09f6cafbb12ca269 100644
--- a/game/profile-thread/Client.lua
+++ b/game/profile-thread/Client.lua
@@ -320,6 +320,11 @@ function _M:orderChatTalk(o)
 	self:read("200")
 end
 
+function _M:orderChatWhisper(o)
+	self:command("WHIS", o.target..":=:"..o.msg)
+	self:read("200")
+end
+
 function _M:orderChatAchievement(o)
 	self:command("ACHV", o.channel, o.msg)
 	self:read("200")
diff --git a/game/profile-thread/UserChat.lua b/game/profile-thread/UserChat.lua
index 6133ed5110cd3456ac95e8bc090a7507e869a61c..319b4f89f68e0833a3ecb8cbb12181975996261c 100644
--- a/game/profile-thread/UserChat.lua
+++ b/game/profile-thread/UserChat.lua
@@ -32,6 +32,9 @@ function _M:event(e)
 	if e.e == "ChatTalk" then
 		cprofile.pushEvent(string.format("e='Chat' se='Talk' channel=%q login=%q name=%q msg=%q", e.channel, e.login, e.name, e.msg))
 		print("[USERCHAT] channel talk", e.login, e.channel, e.msg)
+	elseif e.e == "ChatWhisper" then
+		cprofile.pushEvent(string.format("e='Chat' se='Whisper' login=%q name=%q msg=%q", e.login, e.name, e.msg))
+		print("[USERCHAT] whisper", e.login, e.msg)
 	elseif e.e == "ChatAchievement" then
 		cprofile.pushEvent(string.format("e='Chat' se='Achievement' channel=%q login=%q name=%q msg=%q", e.channel, e.login, e.name, e.msg))
 		print("[USERCHAT] channel achievement", e.login, e.channel, e.msg)