From 9db1f3f31337559d99e8d37a6a54a5065f8d76db Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Tue, 14 Jun 2011 22:42:01 +0000
Subject: [PATCH] Big UI update, the game log and chat logs are now displayed
 side by side in transparency over the game map, hotkeys are "flattened" over
 more width, making room for more map displayed at once

git-svn-id: http://svn.net-core.org/repos/t-engine4@3653 51575b47-30f0-44d4-a5cc-537603b46e54
---
 .../engines/default/data/keybinds/actions.lua |   2 +-
 .../engines/default/engine/HotkeysDisplay.lua |  21 +-
 game/engines/default/engine/LogDisplay.lua    |  48 ++-
 game/engines/default/engine/Map.lua           |   2 +
 game/engines/default/engine/UserChat.lua      |  86 +++--
 .../engine/interface/GameTargeting.lua        |   2 +
 .../default/engine/interface/PlayerMouse.lua  |   1 +
 game/modules/tome/class/Game.lua              | 307 +++++++++---------
 game/modules/tome/class/Player.lua            |   2 +-
 game/modules/tome/dialogs/ShowChatLog.lua     | 160 +++++++++
 10 files changed, 437 insertions(+), 194 deletions(-)
 create mode 100644 game/modules/tome/dialogs/ShowChatLog.lua

diff --git a/game/engines/default/data/keybinds/actions.lua b/game/engines/default/data/keybinds/actions.lua
index 5f43ef94ce..bba236c11d 100644
--- a/game/engines/default/data/keybinds/actions.lua
+++ b/game/engines/default/data/keybinds/actions.lua
@@ -94,7 +94,7 @@ defineAction{
 }
 
 defineAction{
-	default = { "uni:C" },
+	default = { "uni:c", "uni:C" },
 	type = "SHOW_CHARACTER_SHEET",
 	group = "actions",
 	name = "Show character sheet",
diff --git a/game/engines/default/engine/HotkeysDisplay.lua b/game/engines/default/engine/HotkeysDisplay.lua
index 4aa713b32f..5e0b247940 100644
--- a/game/engines/default/engine/HotkeysDisplay.lua
+++ b/game/engines/default/engine/HotkeysDisplay.lua
@@ -50,10 +50,6 @@ function _M:resize(x, y, w, h)
 	self.texture, self.texture_w, self.texture_h = self.surface:glTexture()
 	if self.actor then self.actor.changed = true end
 
-	local cw, ch = self.font:size(" ")
-	self.font_w = cw
-	self.max_char_w = math.min(127, math.floor(w / self.font_w))
-
 	if self.bg_image then
 		local fill = core.display.loadImage(self.bg_image)
 		local fw, fh = fill:getSize()
@@ -73,14 +69,17 @@ function _M:display()
 	local a = self.actor
 	if not a or not a.changed then return self.surface end
 
+	local page = a.hotkey_page
+	if page == 1 and core.key.modState("ctrl") then page = 2
+	elseif page == 1 and core.key.modState("shift") then page = 3 end
+
 	local hks = {}
 	for i = 1, 12 do
-		local j = i + (12 * (a.hotkey_page - 1))
-		local ks = game.key:formatKeyString(game.key:findBoundKeys("HOTKEY_"..page_to_hotkey[a.hotkey_page]..i))
+		local j = i + (12 * (page - 1))
 		if a.hotkey[j] and a.hotkey[j][1] == "talent" then
-			hks[#hks+1] = {a.hotkey[j][2], j, "talent", ks}
+			hks[#hks+1] = {a.hotkey[j][2], i, "talent"}
 		elseif a.hotkey[j] and a.hotkey[j][1] == "inventory" then
-			hks[#hks+1] = {a.hotkey[j][2], j, "inventory", ks}
+			hks[#hks+1] = {a.hotkey[j][2], i, "inventory"}
 		end
 	end
 
@@ -122,14 +121,14 @@ function _M:display()
 			end
 		end
 
-		txt = ("%1d/%2d) %-"..(self.max_char_w-4-24).."s Key: %s"):format(a.hotkey_page, i - (a.hotkey_page-1) * 12, txt, ts[4])
+		txt = ("%1d/%2d) %s"):format(page, i, txt)
 		local w, h, gen
 		if self.cache[txt] then
 			gen = self.cache[txt]
 			w, h = gen.fw, gen.fh
 		else
 			w, h = self.font:size(txt)
-			gen = self.font:draw(txt, self.w, color[1], color[2], color[3], true)[1]
+			gen = self.font:draw(txt, self.w / 3, color[1], color[2], color[3], true)[1]
 			gen.fw, gen.fh = w, h
 		end
 		gen.x, gen.y = x, y
@@ -138,7 +137,7 @@ function _M:display()
 		self.clics[i] = {x,y,w+4,h+4}
 
 		if y + self.font_h * 2 > self.h then
-			x = x + self.w / 2
+			x = x + self.w / 3
 			y = 0
 		else
 			y = y + self.font_h
diff --git a/game/engines/default/engine/LogDisplay.lua b/game/engines/default/engine/LogDisplay.lua
index 6b6e3b50bc..83c31877a0 100644
--- a/game/engines/default/engine/LogDisplay.lua
+++ b/game/engines/default/engine/LogDisplay.lua
@@ -50,6 +50,10 @@ function _M:enableShadow(v)
 	self.shadow = v
 end
 
+function _M:enableFading(v)
+	self.fading = v
+end
+
 --- Resize the display area
 function _M:resize(x, y, w, h)
 	self.display_x, self.display_y = math.floor(x), math.floor(y)
@@ -72,9 +76,22 @@ function _M:resize(x, y, w, h)
 	self.scrollbar = Slider.new{size=self.h - 20, max=1, inverse=true}
 end
 
+--- Returns the full log
+function _M:getLog()
+	local log = {}
+	for i = 1, #self.log do log[#log+1] = self.log[i].str end
+	return log
+end
+
+function _M:getLogLast(channel)
+	if not self.log[1] then return 0 end
+	return self.log[1].timestamp
+end
+
 --- Make a dialog popup with the full log
 function _M:showLogDialog(title, shadow)
-	local d = require("engine.dialogs.ShowLog").new(title or "Message Log", shadow, self)
+	local log = self:getLog()
+	local d = require_first("mod.dialogs.ShowLog", "engine.dialogs.ShowLog").new(title or "Message Log", shadow, {log=log})
 	game:registerDialog(d)
 end
 
@@ -86,7 +103,7 @@ function _M:call(str, ...)
 	str = str:format(...)
 	print("[LOG]", str)
 	local tstr = str:toString()
-	table.insert(self.log, 1, tstr)
+	table.insert(self.log, 1, {str=tstr, timestamp = core.game.getTime()})
 	while #self.log > self.max_log do
 		local old = table.remove(self.log)
 		self.cache[old] = nil
@@ -109,7 +126,7 @@ function _M:getLines(number)
 	if from > #self.log then from = #self.log end
 	local lines = { }
 	for i = from, 1, -1 do
-		lines[#lines+1] = tostring(self.log[i])
+		lines[#lines+1] = tostring(self.log[i].str)
 	end
 	return lines
 end
@@ -125,7 +142,7 @@ function _M:display()
 	local old_style = self.font:getStyle()
 	for z = 1 + self.scroll, #self.log do
 		local stop = false
-		local tstr = self.log[z]
+		local tstr = self.log[z].str
 		local gen
 		if self.cache[tstr] then
 			gen = self.cache[tstr]
@@ -134,7 +151,7 @@ function _M:display()
 			self.cache[tstr] = gen
 		end
 		for i = #gen, 1, -1 do
-			self.dlist[#self.dlist+1] = gen[i]
+			self.dlist[#self.dlist+1] = {item=gen[i], date=self.log[z].timestamp}
 			h = h + self.fh
 			if h > self.h - self.fh then stop=true break end
 		end
@@ -146,16 +163,29 @@ end
 
 function _M:toScreen()
 	self:display()
+
 	if self.bg_texture then self.bg_texture:toScreenFull(self.display_x, self.display_y, self.w, self.h, self.bg_texture_w, self.bg_texture_h) end
+
+	local now = core.game.getTime()
+
 	local h = self.display_y + self.h -  self.fh
 	for i = 1, #self.dlist do
-		local item = self.dlist[i]
-		if self.shadow then item._tex:toScreenFull(self.display_x+2, h+2, self.fw, self.fh, item._tex_w, item._tex_h, 0,0,0, self.shadow) end
-		item._tex:toScreenFull(self.display_x, h, self.fw, self.fh, item._tex_w, item._tex_h)
+		local item = self.dlist[i].item
+
+		local fade = 1
+		if self.fading then
+			fade = now - self.dlist[i].date
+			if fade < self.fading * 1000 then fade = 1
+			elseif fade < self.fading * 2000 then fade = (self.fading * 2000 - fade) / (self.fading * 1000)
+			else fade = 0 end
+		end
+
+		if self.shadow then item._tex:toScreenFull(self.display_x+2, h+2, self.fw, self.fh, item._tex_w, item._tex_h, 0,0,0, self.shadow * fade) end
+		item._tex:toScreenFull(self.display_x, h, self.fw, self.fh, item._tex_w, item._tex_h, 1, 1, 1, fade)
 		h = h - self.fh
 	end
 
-	if true then
+	if not self.fading then
 		self.scrollbar.pos = self.scroll
 		self.scrollbar.max = self.max - self.max_display + 1
 		self.scrollbar:display(self.display_x + self.w - self.scrollbar.w, self.display_y)
diff --git a/game/engines/default/engine/Map.lua b/game/engines/default/engine/Map.lua
index 7d74de5e6f..6c5cea8443 100644
--- a/game/engines/default/engine/Map.lua
+++ b/game/engines/default/engine/Map.lua
@@ -182,6 +182,7 @@ function _M:save()
 		_map = true,
 		_fovcache = true,
 		surface = true,
+		finished = true,
 	})
 end
 
@@ -287,6 +288,7 @@ end
 
 --- Recreate the internal map using new dimensions
 function _M:recreate()
+	if not self.finished then return end
 	self:makeCMap()
 	self.changed = true
 
diff --git a/game/engines/default/engine/UserChat.lua b/game/engines/default/engine/UserChat.lua
index f16d257729..e2251515dc 100644
--- a/game/engines/default/engine/UserChat.lua
+++ b/game/engines/default/engine/UserChat.lua
@@ -34,6 +34,7 @@ function _M:init()
 	self.cur_channel = "global"
 	self.channels = {}
 	self.max = 500
+	self.do_display_chans = true
 end
 
 --- Hook up in the current running game
@@ -63,7 +64,7 @@ end
 
 function _M:addMessage(channel, login, name, msg, extra_data, no_change)
 	local log = self.channels[channel].log
-	table.insert(log, 1, {login=login, name=name, msg=msg, extra_data=extra_data})
+	table.insert(log, 1, {login=login, name=name, msg=msg, extra_data=extra_data, timestamp=core.game.getTime()})
 	while #log > self.max do table.remove(log) end
 	self.changed = true
 	if not no_change and channel ~= self.cur_channel then self.channels[channel].changed = true self.channels_changed = true end
@@ -254,15 +255,32 @@ end
 -- UI Section
 ----------------------------------------------------------------
 
---- Make a dialog popup with the full log
-function _M:showLogDialog(title, shadow)
+--- Returns the full log
+function _M:getLog(channel)
+	channel = channel or self.cur_channel
 	local log = {}
-	if self.channels[self.cur_channel] then
-		for _, i in ipairs(self.channels[self.cur_channel].log) do
+	if self.channels[channel] then
+		for _, i in ipairs(self.channels[channel].log) do
 			log[#log+1] = ("<%s> %s"):format(i.name, i.msg)
 		end
 	end
-	local d = require("engine.dialogs.ShowLog").new(title or "Chat Log", shadow, {log=log})
+	return log
+end
+
+function _M:getLogLast(channel)
+	channel = channel or self.cur_channel
+	if self.channels[channel] then
+		if not self.channels[channel].log[1] then return 0 end
+		return self.channels[channel].log[1].timestamp
+	else
+		return 0
+	end
+end
+
+--- Make a dialog popup with the full log
+function _M:showLogDialog(title, shadow)
+	local log = self:getLog(self.cur_channel)
+	local d = require_first("mod.dialogs.ShowLog", "engine.dialogs.ShowLog").new(title or "Chat Log", shadow, {log=log})
 	game:registerDialog(d)
 end
 
@@ -342,6 +360,14 @@ function _M:enableShadow(v)
 	self.shadow = v
 end
 
+function _M:enableFading(v)
+	self.fading = v
+end
+
+function _M:enableDisplayChans(v)
+	self.do_display_chans = v
+end
+
 function _M:onMouse(fct)
 	self.on_mouse = fct
 end
@@ -386,9 +412,9 @@ function _M:display()
 		for i = #gen, 1, -1 do
 			gen[i].login = log[z].login
 			gen[i].extra_data = log[z].extra_data
-			self.dlist[#self.dlist+1] = gen[i]
+			self.dlist[#self.dlist+1] = {item=gen[i], date=log[z].timestamp}
 			h = h + self.fh
-			if h > self.h - self.fh - self.fh then stop=true break end
+			if h > self.h - self.fh - (self.do_display_chans and self.fh or 0) then stop=true break end
 		end
 		if stop then break end
 	end
@@ -400,30 +426,42 @@ function _M:toScreen()
 	self:display()
 	if self.bg_texture then self.bg_texture:toScreenFull(self.display_x, self.display_y, self.w, self.h, self.bg_texture_w, self.bg_texture_h) end
 	local h = self.display_y + self.h -  self.fh
+	local now = core.game.getTime()
 	for i = 1, #self.dlist do
-		local item = self.dlist[i]
+		local item = self.dlist[i].item
+
+		local fade = 1
+		if self.fading then
+			fade = now - self.dlist[i].date
+			if fade < self.fading * 1000 then fade = 1
+			elseif fade < self.fading * 2000 then fade = (self.fading * 2000 - fade) / (self.fading * 1000)
+			else fade = 0 end
+		end
+
 		item.dh = h
-		if self.shadow then item._tex:toScreenFull(self.display_x+2, h+2, item.w, item.h, item._tex_w, item._tex_h, 0,0,0, self.shadow) end
-		item._tex:toScreenFull(self.display_x, h, item.w, item.h, item._tex_w, item._tex_h)
+		if self.shadow then item._tex:toScreenFull(self.display_x+2, h+2, item.w, item.h, item._tex_w, item._tex_h, 0,0,0, self.shadow * fade) end
+		item._tex:toScreenFull(self.display_x, h, item.w, item.h, item._tex_w, item._tex_h, 1, 1, 1, fade)
 		h = h - self.fh
 	end
 
-	local w = 0
-	for i = 1, #self.display_chans do
-		local item = self.display_chans[i]
-		local f = item.sel and self.frame_sel or self.frame
-		f.w = item.w
-
-		Base:drawFrame(f, self.display_x + w, self.display_y)
-		if self.channels[item.name].changed then
-			local glow = (1+math.sin(core.game.getTime() / 500)) / 2 * 100 + 120
-			Base:drawFrame(f, self.display_x + w, self.display_y, 139/255, 210/255, 77/255, glow / 255)
+	if self.do_display_chans then
+		local w = 0
+		for i = 1, #self.display_chans do
+			local item = self.display_chans[i]
+			local f = item.sel and self.frame_sel or self.frame
+			f.w = item.w
+
+			Base:drawFrame(f, self.display_x + w, self.display_y)
+			if self.channels[item.name].changed then
+				local glow = (1+math.sin(core.game.getTime() / 500)) / 2 * 100 + 120
+				Base:drawFrame(f, self.display_x + w, self.display_y, 139/255, 210/255, 77/255, glow / 255)
+			end
+			item._tex:toScreenFull(self.display_x + w, self.display_y, item.w, item.h, item._tex_w, item._tex_h)
+			w = w + item.w + 4
 		end
-		item._tex:toScreenFull(self.display_x + w, self.display_y, item.w, item.h, item._tex_w, item._tex_h)
-		w = w + item.w + 4
 	end
 
-	if true then
+	if not self.fading then
 		self.scrollbar.pos = self.scroll
 		self.scrollbar.max = self.max - self.max_display + 1
 		self.scrollbar:display(self.display_x + self.w - self.scrollbar.w, self.display_y)
diff --git a/game/engines/default/engine/interface/GameTargeting.lua b/game/engines/default/engine/interface/GameTargeting.lua
index b9661a75ae..8748664d1d 100644
--- a/game/engines/default/engine/interface/GameTargeting.lua
+++ b/game/engines/default/engine/interface/GameTargeting.lua
@@ -195,6 +195,8 @@ end
 --- Handle mouse event for targeting
 -- @return true if the event was handled
 function _M:targetMouse(button, mx, my, xrel, yrel, event)
+	if not self.level then return end
+
 	-- Move tooltip
 	self.tooltip_x, self.tooltip_y = mx, my
 	local tmx, tmy = self.level.map:getMouseTile(mx, my)
diff --git a/game/engines/default/engine/interface/PlayerMouse.lua b/game/engines/default/engine/interface/PlayerMouse.lua
index 75d906890f..426519d9ab 100644
--- a/game/engines/default/engine/interface/PlayerMouse.lua
+++ b/game/engines/default/engine/interface/PlayerMouse.lua
@@ -120,6 +120,7 @@ end
 -- @param key the Key object to which to pass the event if not treated, this should be your game default key handler probably
 -- @param allow_move true if this will allow player movement (you should use it to check that you are not in targetting mode)
 function _M:mouseHandleDefault(key, allow_move, button, mx, my, xrel, yrel, event)
+	if not game.level then return end
 	local tmx, tmy = game.level.map:getMouseTile(mx, my)
 
 	-- Move
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 5e232ccf6e..f517351535 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -81,7 +81,7 @@ function _M:init()
 end
 
 function _M:run()
-	local size, size_mono, font, font_mono
+	local size, size_mono, font, font_mono, font_mono_h, font_h
 	local flysize = ({normal=14, small=12, big=16})[config.settings.tome.fonts.size]
 	if config.settings.tome.fonts.type == "fantasy" then
 		size = ({normal=16, small=14, big=18})[config.settings.tome.fonts.size]
@@ -94,16 +94,27 @@ function _M:run()
 		font = "/data/font/Vera.ttf"
 		font_mono = "/data/font/VeraMono.ttf"
 	end
+	local f = core.display.newFont(font, size)
+	font_h = f:lineSkip()
+	f = core.display.newFont(font_mono, size_mono)
+	font_mono_h = f:lineSkip()
 
 	self.delayed_log_damage = {}
 	self.calendar = Calendar.new("/data/calendar_allied.lua", "Today is the %s %s of the %s year of the Age of Ascendancy of Maj'Eyal.\nThe time is %02d:%02d.", 122, 167, 11)
 
-	self.player_display = PlayerDisplay.new(0, 230, 200, self.h * 0.8 - 230, {30,30,0}, font_mono, size_mono)
-	self.flash = LogFlasher.new(0, 0, self.w, 20, nil, font, size, {255,255,255}, {0,0,0})
-	self.logdisplay = LogDisplay.new(0, self.h * 0.8 + 7, self.w * 0.5 - 46, self.h * 0.2 - 7, nil, font, size, {255,255,255}, "/data/gfx/ui/message-log.png")
+	self.player_display = PlayerDisplay.new(0, 200, 200, self.h - 200, {30,30,0}, font_mono, size_mono)
+--	self.flash = LogFlasher.new(0, 0, self.w, 20, nil, font, size, {255,255,255}, {0,0,0})
+	self.map_h_stop = self.h - font_mono_h * 4
+	self.logdisplay = LogDisplay.new(216, self.map_h_stop - font_h * 5 -16, (self.w - 216) / 2, font_h * 5, nil, font, size, nil, nil)
 	self.logdisplay:enableShadow(0.6)
-	profile.chat:resize(0, self.h * 0.8 + 7, self.w * 0.5 - 46, self.h * 0.2 - 7, font, size, {255,255,255}, "/data/gfx/ui/message-log.png")
-	self.hotkeys_display = HotkeysDisplay.new(nil, self.w * 0.5 + 46, self.h * 0.8 + 7, self.w * 0.5 - 46, self.h * 0.2 - 7, "/data/gfx/ui/talents-list.png", font_mono, size_mono)
+	self.logdisplay:enableFading(2)
+
+	profile.chat:resize(216 + (self.w - 216) / 2, self.map_h_stop - font_h * 5 -16, (self.w - 216) / 2, font_h * 5, font, size, nil, nil)
+	profile.chat:enableShadow(0.6)
+	profile.chat:enableFading(6)
+	profile.chat:enableDisplayChans(false)
+
+	self.hotkeys_display = HotkeysDisplay.new(nil, 216, self.h - font_mono_h * 4, self.w - 216, font_mono_h * 4, "/data/gfx/ui/talents-list.png", font_mono, size_mono)
 	self.hotkeys_display:enableShadow(0.6)
 	self.npcs_display = ActorsSeenDisplay.new(nil, self.w * 0.5 + 46, self.h * 0.8 + 7, self.w * 0.5 - 46, self.h * 0.2 - 7, "/data/gfx/ui/talents-list.png", font_mono, size_mono)
 	self.tooltip = Tooltip.new(font_mono, size, {255,255,255}, {30,30,30,230})
@@ -116,34 +127,22 @@ function _M:run()
 	self.nicer_tiles = NicerTiles.new()
 	self:createSeparators()
 
-	self.log = function(style, ...) if type(style) == "number" then self.logdisplay(...) self.flash(style, ...) else self.logdisplay(style, ...) self.flash(self.flash.NEUTRAL, style, ...) end end
+	self.log = function(style, ...) if type(style) == "number" then self.logdisplay(...) else self.logdisplay(style, ...) end end
 	self.logChat = function(style, ...)
-		if not config.settings.tome.chat_log then return end
+		if true or not config.settings.tome.chat_log then return end
 		if type(style) == "number" then
 		local old = self.logdisplay.changed
-		self.logdisplay(...) self.flash(style, ...) else self.logdisplay(style, ...) self.flash(self.flash.NEUTRAL, style, ...) end
+		self.logdisplay(...) else self.logdisplay(style, ...) end
 		if self.show_userchat then self.logdisplay.changed = old end
 	end
 	self.logSeen = function(e, style, ...) if e and e.x and e.y and self.level.map.seens(e.x, e.y) then self.log(style, ...) end end
 	self.logPlayer = function(e, style, ...) if e == self.player or e == self.party then self.log(style, ...) end end
 
-	self.log(self.flash.GOOD, "Welcome to #00FF00#Tales of Maj'Eyal!")
+--	self.log(self.flash.GOOD, "Welcome to #00FF00#Tales of Maj'Eyal!")
 
 	-- List of stuff to do on tick end
 	self.on_tick_end = {}
 
-	-- Setup inputs
-	self:setupCommands()
-	self:setupMouse()
-
-	-- Starting from here we create a new game
-	if not self.player then self:newGame() end
-
-	self.hotkeys_display.actor = self.player
-	self.npcs_display.actor = self.player
-
-	self:initTargeting()
-
 	-- Ok everything is good to go, activate the game in the engine!
 	self:setCurrent()
 
@@ -153,9 +152,21 @@ function _M:run()
 	-- Start time
 	self.real_starttime = os.time()
 
-	if self.level then self:setupDisplayMode(false, "postinit") end
+	self:setupDisplayMode(false, "postinit")
 	if self.level and self.level.data.day_night then self.state:dayNightCycle() end
 	if self.level and self.player then self.calendar = Calendar.new("/data/calendar_"..(self.player.calendar or "allied")..".lua", "Today is the %s %s of the %s year of the Age of Ascendancy of Maj'Eyal.\nThe time is %02d:%02d.", 122, 167, 11) end
+
+	-- Setup inputs
+	self:setupCommands()
+	self:setupMouse()
+
+	-- Starting from here we create a new game
+	if not self.player then self:newGame() end
+
+	self:initTargeting()
+
+	self.hotkeys_display.actor = self.player
+	self.npcs_display.actor = self.player
 end
 
 --- Checks if the current character is "tainted" by cheating
@@ -315,6 +326,7 @@ function _M:loaded()
 	Zone.post_filter = function(...) return self.state:entityFilterPost(...) end
 	Map:setViewerActor(self.player)
 	self:setupDisplayMode(false, "init")
+	self:setupDisplayMode(false, "postinit")
 	if self.player then self.player.changed = true end
 	self.key = engine.KeyBind.new()
 
@@ -334,6 +346,14 @@ function _M:setupDisplayMode(reboot, mode)
 			util.showMainMenu(false, nil, nil, self.__mod_info.short_name, self.save_name, false)
 		end
 
+		local do_bg = true
+
+		Map:resetTiles()
+	end
+
+	if not mode or mode == "postinit" then
+		local gfx = config.settings.tome.gfx
+
 		-- Show a count for stacked objects
 		Map.object_stack_count = true
 
@@ -345,20 +365,14 @@ function _M:setupDisplayMode(reboot, mode)
 		print("[DISPLAY MODE] Tileset: "..gfx.tiles)
 		print("[DISPLAY MODE] Size: "..gfx.size)
 
-		local do_bg = true
-
 		if gfx.size == "64x64" then
-			Map:setViewPort(216, 36, self.w - 216, math.floor(self.h * 0.80) - 36, 64, 64, nil, 44, do_bg)
-			Map:resetTiles()
+			Map:setViewPort(216, 0, self.w - 216, (self.map_h_stop or 80) - 16, 64, 64, nil, 44, do_bg)
 		elseif gfx.size == "48x48" then
-			Map:setViewPort(216, 36, self.w - 216, math.floor(self.h * 0.80) - 36, 48, 48, nil, 36, do_bg)
-			Map:resetTiles()
+			Map:setViewPort(216, 0, self.w - 216, (self.map_h_stop or 80) - 16, 48, 48, nil, 36, do_bg)
 		elseif gfx.size == "32x32" then
-			Map:setViewPort(216, 36, self.w - 216, math.floor(self.h * 0.80) - 36, 32, 32, nil, 22, do_bg)
-			Map:resetTiles()
+			Map:setViewPort(216, 0, self.w - 216, (self.map_h_stop or 80) - 16, 32, 32, nil, 22, do_bg)
 		elseif gfx.size == "16x16" then
-			Map:setViewPort(216, 36, self.w - 216, math.floor(self.h * 0.80) - 36, 16, 16, "/data/font/FSEX300.ttf", 16, do_bg)
-			Map:resetTiles()
+			Map:setViewPort(216, 0, self.w - 216, (self.map_h_stop or 80) - 16, 16, 16, "/data/font/FSEX300.ttf", 16, do_bg)
 		end
 
 		Map.tiles.use_images = true
@@ -367,19 +381,19 @@ function _M:setupDisplayMode(reboot, mode)
 		if gfx.tiles == "shockbolt" then Map.tiles.nicer_tiles = true end
 		if gfx.tiles == "oldrpg" then Map.tiles.nicer_tiles = true end
 
-		-- Create the framebuffer
-		self.fbo = core.display.newFBO(Map.viewport.width, Map.viewport.height)
-		if self.fbo then self.fbo_shader = Shader.new("main_fbo") if not self.fbo_shader.shad then self.fbo = nil self.fbo_shader = nil end end
-		if self.player then self.player:updateMainShader() end
-	end
-
-	if not mode or mode == "postinit" then
 		if self.level then
-			self.level.map:recreate()
+			if self.level.map.finished then
+				self.level.map:recreate()
+				self.level.map:moveViewSurround(self.player.x, self.player.y, 8, 8)
+			end
 			self:initTargeting()
-			self.level.map:moveViewSurround(self.player.x, self.player.y, 8, 8)
 		end
 		self:setupMiniMap()
+
+		-- Create the framebuffer
+		self.fbo = core.display.newFBO(Map.viewport.width, Map.viewport.height)
+		if self.fbo then self.fbo_shader = Shader.new("main_fbo") if not self.fbo_shader.shad then self.fbo = nil self.fbo_shader = nil end end
+		if self.player then self.player:updateMainShader() end
 	end
 end
 
@@ -389,7 +403,7 @@ end
 
 
 function _M:setupMiniMap()
-	if self.level and self.level.map then self.level.map._map:setupMiniMapGridSize(4) end
+	if self.level and self.level.map and self.level.map.finished then self.level.map._map:setupMiniMapGridSize(4) end
 end
 
 function _M:save()
@@ -736,13 +750,13 @@ end
 function _M:displayDelayedLogDamage()
 	for src, tgts in pairs(self.delayed_log_damage) do
 		for target, dams in pairs(tgts) do
-			local flash = game.flash.NEUTRAL
-			if target == game.player then flash = game.flash.BAD end
-			if src == game.player then flash = game.flash.GOOD end
+--			local flash = game.flash.NEUTRAL
+--			if target == game.player then flash = game.flash.BAD end
+--			if src == game.player then flash = game.flash.GOOD end
 			if #dams.descs > 1 then
-				game.logSeen(target, flash, "%s hits %s for %s damage (total %0.2f).", src.name:capitalize(), target.name, table.concat(dams.descs, ", "), dams.total)
+				game.logSeen(target, "%s hits %s for %s damage (total %0.2f).", src.name:capitalize(), target.name, table.concat(dams.descs, ", "), dams.total)
 			else
-				game.logSeen(target, flash, "%s hits %s for %s damage.", src.name:capitalize(), target.name, table.concat(dams.descs, ", "))
+				game.logSeen(target, "%s hits %s for %s damage.", src.name:capitalize(), target.name, table.concat(dams.descs, ", "))
 			end
 
 			local rsrc = src.resolveSource and src:resolveSource() or src
@@ -852,9 +866,9 @@ function _M:display(nb_keyframes)
 		map:displayEmotes(nb_keyframe or 1)
 
 		-- Minimap display
-		self.minimap_bg:toScreen(0, 35, 200, 200)
+		self.minimap_bg:toScreen(0, 0, 200, 200)
 		self.minimap_scroll_x, self.minimap_scroll_y = util.bound(self.player.x - 25, 0, map.w - 50), util.bound(self.player.y - 25, 0, map.h - 50)
-		map:minimapDisplay(0, 35, self.minimap_scroll_x, self.minimap_scroll_y, 50, 50, 1)
+		map:minimapDisplay(0, 0, self.minimap_scroll_x, self.minimap_scroll_y, 50, 50, 1)
 
 		-- Mouse gestures
 		self.gestures:update()
@@ -862,10 +876,12 @@ function _M:display(nb_keyframes)
 	end
 
 	-- We display the player's interface
-	self.flash:toScreen(nb_keyframe)
-	if self.show_userchat then profile.chat:toScreen()
-	else self.logdisplay:toScreen()
-	end
+--	self.flash:toScreen(nb_keyframe)
+--	if self.show_userchat then profile.chat:toScreen()
+--	else self.logdisplay:toScreen()
+--	end
+	profile.chat:toScreen()
+	self.logdisplay:toScreen()
 
 	self.player_display:toScreen(nb_keyframes)
 	if self.show_npc_list then
@@ -901,7 +917,7 @@ function _M:onUnregisterDialog(d)
 	-- Clean up tooltip
 	self.tooltip_x, self.tooltip_y = nil, nil
 	self.tooltip2_x, self.tooltip2_y = nil, nil
-	if self.player then self.player:updateMainShader() end
+	if self.player then self.player:updateMainShader() self.player.changed = true end
 end
 
 function _M:setupCommands()
@@ -955,7 +971,12 @@ function _M:setupCommands()
 
 	self.key.any_key = function(sym)
 		-- Control resets the tooltip
-		if sym == self.key._LCTRL or sym == self.key._RCTRL then self.tooltip.old_tmx = nil end
+		if sym == self.key._LCTRL or sym == self.key._RCTRL then
+			self.player.changed = true
+			self.tooltip.old_tmx = nil
+		elseif sym == self.key._LSHIFT or sym == self.key._RSHIFT then
+			self.player.changed = true
+		end
 	end
 	self.key:addBinds
 	{
@@ -1129,11 +1150,7 @@ function _M:setupCommands()
 		end,
 
 		SHOW_MESSAGE_LOG = function()
-			if not self.show_userchat then
-				self.logdisplay:showLogDialog(nil, 0.6)
-			else
-				profile.chat:showLogDialog(nil, 0.6)
-			end
+			self:registerDialog(require("mod.dialogs.ShowChatLog").new("Message Log", 0.6, self.logdisplay, profile.chat))
 		end,
 
 		-- Show time
@@ -1193,8 +1210,8 @@ function _M:setupCommands()
 		end,
 
 		LOOK_AROUND = function()
-			self.flash:empty(true)
-			self.flash(self.flash.GOOD, "Looking around... (direction keys to select interesting things, shift+direction keys to move freely)")
+--			self.flash:empty(true)
+			self.log("Looking around... (direction keys to select interesting things, shift+direction keys to move freely)")
 			local co = coroutine.create(function() self.player:getTarget{type="hit", no_restrict=true, range=2000} end)
 			local ok, err = coroutine.resume(co)
 			if not ok and err then print(debug.traceback(co)) error(err) end
@@ -1244,6 +1261,7 @@ function _M:setupMouse(reset)
 		self.player:mouseHandleDefault(self.key, self.key == self.normal_key, button, mx, my, xrel, yrel, event)
 	end)
 	-- Scroll message log
+--[[
 	self.mouse:registerZone(self.logdisplay.display_x, self.logdisplay.display_y, self.w, self.h, function(button, mx, my, xrel, yrel, bx, by, event)
 		if self.show_userchat then
 			profile.chat.mouse:delegate(button, mx, my, xrel, yrel, bx, by, event)
@@ -1253,6 +1271,7 @@ function _M:setupMouse(reset)
 			if button == "left" then self.logdisplay:showLogDialog(nil, 0.6) end
 		end
 	end)
+]]
 	-- 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)
 		if event == "button" and button == "left" and self.zone and self.zone.wilderness then return end
@@ -1277,11 +1296,11 @@ function _M:setupMouse(reset)
 		if button == "left" then self:clickIcon(bx, by) end
 	end)
 	-- Tooltip over the player pane
-	self.mouse:registerZone(self.player_display.display_x, self.player_display.display_y, self.player_display.w, self.player_display.h, function(button, mx, my, xrel, yrel, bx, by, event)
+	self.mouse:registerZone(self.player_display.display_x, self.player_display.display_y, self.player_display.w, self.player_display.h - self.icons.h, function(button, mx, my, xrel, yrel, bx, by, event)
 		self.player_display.mouse:delegate(button, mx, my, xrel, yrel, bx, by, event)
 	end)
 	-- Move using the minimap
-	self.mouse:registerZone(0, 35, 200, 200, function(button, mx, my, xrel, yrel, bx, by, event)
+	self.mouse:registerZone(0, 0, 200, 200, function(button, mx, my, xrel, yrel, bx, by, event)
 		if button == "left" and not xrel and not yrel and event == "button" then
 			local tmx, tmy = math.floor(bx / 4), math.floor(by / 4)
 			self.player:mouseMove(tmx + self.minimap_scroll_x, tmy + self.minimap_scroll_y)
@@ -1505,6 +1524,7 @@ local _sep_vert = {core.display.loadImage("/data/gfx/ui/separator-vert.png")} _s
 local _sep_top = {core.display.loadImage("/data/gfx/ui/separator-top.png")} _sep_top.tex = {_sep_top[1]:glTexture()}
 local _sep_bottom = {core.display.loadImage("/data/gfx/ui/separator-bottom.png")} _sep_bottom.tex = {_sep_bottom[1]:glTexture()}
 local _sep_bottoml = {core.display.loadImage("/data/gfx/ui/separator-bottom_line_end.png")} _sep_bottoml.tex = {_sep_bottoml[1]:glTexture()}
+local _sep_left = {core.display.loadImage("/data/gfx/ui/separator-left.png")} _sep_left.tex = {_sep_left[1]:glTexture()}
 local _sep_leftl = {core.display.loadImage("/data/gfx/ui/separator-left_line_end.png")} _sep_leftl.tex = {_sep_leftl[1]:glTexture()}
 local _sep_rightl = {core.display.loadImage("/data/gfx/ui/separator-right_line_end.png")} _sep_rightl.tex = {_sep_rightl[1]:glTexture()}
 
@@ -1521,116 +1541,107 @@ function _M:displayUI()
 	local middle = self.w * 0.5
 	local bottom = self.h * 0.8
 	local bottom_h = self.h * 0.2
-	local icon_x = middle - (_talents_icon_w*2) / 2
-	local icon_x2 = middle + (_talents_icon_w*2) / 2
-	local mid_min = icon_x - (_sep_vert[2])
-	local mid_max = icon_x2
+	local icon_x = 0
+	local icon_y = self.h - (_talents_icon_h * 1)
+	local glow = (1+math.sin(core.game.getTime() / 500)) / 2 * 100 + 77
 
 	-- Icons
-	local x, y = icon_x, bottom + _sep_horiz[3] / 2
-	_talents_icon:toScreenFull(x + _talents_icon_w, y, _talents_icon_w, _talents_icon_h, _talents_icon_w, _talents_icon_h)
-	if not self.show_npc_list then _sel_icon:toScreenFull(x + _talents_icon_w, y, _sel_icon_w, _sel_icon_h, _sel_icon_w, _sel_icon_h) end
-	y = y + _talents_icon_h
-	_actors_icon:toScreenFull(x + _talents_icon_w, y, _actors_icon_w, _actors_icon_h, _actors_icon_w, _actors_icon_h)
-	if self.show_npc_list then _sel_icon:toScreenFull(x + _talents_icon_w, y, _sel_icon_w, _sel_icon_h, _sel_icon_w, _sel_icon_h) end
-	y = y + _talents_icon_h
-
-	local glow = (1+math.sin(core.game.getTime() / 500)) / 2 * 100 + 77
-	local x, y = icon_x, bottom + _sep_horiz[3] / 2
-	if self.logdisplay.changed then core.display.drawQuad(x, y, _sel_icon_w, _sel_icon_h, 139, 210, 77, glow) end
+	local x, y = icon_x, icon_y
+	_talents_icon:toScreenFull(x, y, _talents_icon_w, _talents_icon_h, _talents_icon_w, _talents_icon_h)
+	if not self.show_npc_list then _sel_icon:toScreenFull(x, y, _sel_icon_w, _sel_icon_h, _sel_icon_w, _sel_icon_h) end
+	x = x + _talents_icon_w
+	_actors_icon:toScreenFull(x, y, _actors_icon_w, _actors_icon_h, _actors_icon_w, _actors_icon_h)
+	if self.show_npc_list then _sel_icon:toScreenFull(x, y, _sel_icon_w, _sel_icon_h, _sel_icon_w, _sel_icon_h) end
+	x = x + _talents_icon_w
+
+--	if self.logdisplay.changed then core.display.drawQuad(x, y, _sel_icon_w, _sel_icon_h, 139, 210, 77, glow) end
+--	_log_icon:toScreenFull(x, y, _log_icon_w, _log_icon_h, _log_icon_w, _log_icon_h)
+--	if not self.show_userchat then _sel_icon:toScreenFull(x, y, _sel_icon_w, _sel_icon_h, _sel_icon_w, _sel_icon_h) end
+--	x = x + _talents_icon_w
+
+--	if profile.chat.changed then core.display.drawQuad(x, y, _sel_icon_w, _sel_icon_h, 139, 210, 77, glow) end
+--	_chat_icon:toScreenFull(x, y, _chat_icon_w, _chat_icon_h, _chat_icon_w, _chat_icon_h)
+--	if self.show_userchat then _sel_icon:toScreenFull(x, y, _sel_icon_w, _sel_icon_h, _sel_icon_w, _sel_icon_h) end
+
+--	x = 0
+--	y = y + _chat_icon_h
+--	x = x + _talents_icon_w
+
+	_inventory_icon:toScreenFull(x, y, _inventory_icon_w, _inventory_icon_h, _inventory_icon_w, _inventory_icon_h)
+	x = x + _talents_icon_w
+	_charsheet_icon:toScreenFull(x, y, _charsheet_icon_w, _charsheet_icon_h, _charsheet_icon_w, _charsheet_icon_h)
+	x = x + _talents_icon_w
+	_main_menu_icon:toScreenFull(x, y, _main_menu_icon_w, _main_menu_icon_h, _main_menu_icon_w, _main_menu_icon_h)
+	x = x + _talents_icon_w
 	_log_icon:toScreenFull(x, y, _log_icon_w, _log_icon_h, _log_icon_w, _log_icon_h)
-	if not self.show_userchat then _sel_icon:toScreenFull(x, y, _sel_icon_w, _sel_icon_h, _sel_icon_w, _sel_icon_h) end
-	y = y + _log_icon_h
-	if profile.chat.changed then core.display.drawQuad(x, y, _sel_icon_w, _sel_icon_h, 139, 210, 77, glow) end
-	_chat_icon:toScreenFull(x, y, _chat_icon_w, _chat_icon_h, _chat_icon_w, _chat_icon_h)
-	if self.show_userchat then _sel_icon:toScreenFull(x, y, _sel_icon_w, _sel_icon_h, _sel_icon_w, _sel_icon_h) end
-	y = y + _chat_icon_h
-
-	_inventory_icon:toScreenFull(x + _talents_icon_w/2, y, _inventory_icon_w, _inventory_icon_h, _inventory_icon_w, _inventory_icon_h) y = y + _inventory_icon_h
-	_charsheet_icon:toScreenFull(x + _talents_icon_w/2, y, _charsheet_icon_w, _charsheet_icon_h, _charsheet_icon_w, _charsheet_icon_h) y = y + _charsheet_icon_h
-	_main_menu_icon:toScreenFull(x + _talents_icon_w/2, y, _main_menu_icon_w, _main_menu_icon_h, _main_menu_icon_w, _main_menu_icon_h) y = y + _main_menu_icon_h
 
 	-- Separators
-	_sep_horiz.tex[1]:toScreenFull(0, 20, self.w, _sep_horiz[3], _sep_horiz.tex[2], _sep_horiz.tex[3])
-	_sep_horiz.tex[1]:toScreenFull(0, bottom - _sep_horiz[3] / 2, self.w, _sep_horiz[3], _sep_horiz.tex[2], _sep_horiz.tex[3])
+--	_sep_horiz.tex[1]:toScreenFull(0, 20, self.w, _sep_horiz[3], _sep_horiz.tex[2], _sep_horiz.tex[3])
+	_sep_horiz.tex[1]:toScreenFull(216, self.map_h_stop - _sep_horiz[3], self.w - 216, _sep_horiz[3], _sep_horiz.tex[2], _sep_horiz.tex[3])
 
-	_sep_vert.tex[1]:toScreenFull(mid_min, bottom, _sep_vert[2], bottom_h, _sep_vert.tex[2], _sep_vert.tex[3])
-	_sep_vert.tex[1]:toScreenFull(mid_max, bottom, _sep_vert[2], bottom_h, _sep_vert.tex[2], _sep_vert.tex[3])
+--	_sep_vert.tex[1]:toScreenFull(mid_min, bottom, _sep_vert[2], bottom_h, _sep_vert.tex[2], _sep_vert.tex[3])
+--	_sep_vert.tex[1]:toScreenFull(mid_max, bottom, _sep_vert[2], bottom_h, _sep_vert.tex[2], _sep_vert.tex[3])
 
-	_sep_vert.tex[1]:toScreenFull(200, 20, _sep_vert[2], bottom - 20, _sep_vert.tex[2], _sep_vert.tex[3])
+	_sep_vert.tex[1]:toScreenFull(200, 0, _sep_vert[2], self.h, _sep_vert.tex[2], _sep_vert.tex[3])
 
 	-- Ornaments
-	_sep_top.tex[1]:toScreenFull(mid_min - (-_sep_vert[2] + _sep_top[2]) / 2, bottom - 14, _sep_top[2], _sep_top[3], _sep_top.tex[2], _sep_top.tex[3])
-	_sep_top.tex[1]:toScreenFull(mid_max - (-_sep_vert[2] + _sep_top[2]) / 2, bottom - 14, _sep_top[2], _sep_top[3], _sep_top.tex[2], _sep_top.tex[3])
-	_sep_bottoml.tex[1]:toScreenFull(mid_min - (-_sep_vert[2] + _sep_bottoml[2]) / 2, self.h - _sep_bottoml[3], _sep_bottoml[2], _sep_bottoml[3], _sep_bottoml.tex[2], _sep_bottoml.tex[3])
-	_sep_bottoml.tex[1]:toScreenFull(mid_max - (-_sep_vert[2] + _sep_bottoml[2]) / 2, self.h - _sep_bottoml[3], _sep_bottoml[2], _sep_bottoml[3], _sep_bottoml.tex[2], _sep_bottoml.tex[3])
+--	_sep_top.tex[1]:toScreenFull(mid_min - (-_sep_vert[2] + _sep_top[2]) / 2, bottom - 14, _sep_top[2], _sep_top[3], _sep_top.tex[2], _sep_top.tex[3])
+--	_sep_top.tex[1]:toScreenFull(mid_max - (-_sep_vert[2] + _sep_top[2]) / 2, bottom - 14, _sep_top[2], _sep_top[3], _sep_top.tex[2], _sep_top.tex[3])
+--	_sep_bottoml.tex[1]:toScreenFull(mid_min - (-_sep_vert[2] + _sep_bottoml[2]) / 2, self.h - _sep_bottoml[3], _sep_bottoml[2], _sep_bottoml[3], _sep_bottoml.tex[2], _sep_bottoml.tex[3])
+--	_sep_bottoml.tex[1]:toScreenFull(mid_max - (-_sep_vert[2] + _sep_bottoml[2]) / 2, self.h - _sep_bottoml[3], _sep_bottoml[2], _sep_bottoml[3], _sep_bottoml.tex[2], _sep_bottoml.tex[3])
 
-	_sep_leftl.tex[1]:toScreenFull(0, 20 - _sep_leftl[3] / 2 + 7, _sep_leftl[2], _sep_leftl[3], _sep_leftl.tex[2], _sep_leftl.tex[3])
-	_sep_leftl.tex[1]:toScreenFull(0, bottom - _sep_leftl[3] / 2, _sep_leftl[2], _sep_leftl[3], _sep_leftl.tex[2], _sep_leftl.tex[3])
+--	_sep_leftl.tex[1]:toScreenFull(0, 20 - _sep_leftl[3] / 2 + 7, _sep_leftl[2], _sep_leftl[3], _sep_leftl.tex[2], _sep_leftl.tex[3])
+	_sep_left.tex[1]:toScreenFull(200 - 7, self.map_h_stop - 7 - _sep_left[3] / 2, _sep_left[2], _sep_left[3], _sep_left.tex[2], _sep_left.tex[3])
 
-	_sep_rightl.tex[1]:toScreenFull(self.w - _sep_rightl[2], 20 - _sep_rightl[3] / 2 + 7, _sep_rightl[2], _sep_rightl[3], _sep_rightl.tex[2], _sep_rightl.tex[3])
-	_sep_rightl.tex[1]:toScreenFull(self.w - _sep_rightl[2], bottom - _sep_rightl[3] / 2, _sep_rightl[2], _sep_rightl[3], _sep_rightl.tex[2], _sep_rightl.tex[3])
+--	_sep_rightl.tex[1]:toScreenFull(self.w - _sep_rightl[2], 20 - _sep_rightl[3] / 2 + 7, _sep_rightl[2], _sep_rightl[3], _sep_rightl.tex[2], _sep_rightl.tex[3])
+	_sep_rightl.tex[1]:toScreenFull(self.w - _sep_rightl[2], self.map_h_stop - _sep_left[3] / 2, _sep_rightl[2], _sep_rightl[3], _sep_rightl.tex[2], _sep_rightl.tex[3])
 
-	_sep_top.tex[1]:toScreenFull(200 - (_sep_top[2] - _sep_vert[2]) / 2, 20 - 7, _sep_top[2], _sep_top[3], _sep_top.tex[2], _sep_top.tex[3])
-	_sep_bottom.tex[1]:toScreenFull(200 - (_sep_bottom[2] - _sep_vert[2]) / 2, bottom - 25, _sep_bottom[2], _sep_bottom[3], _sep_bottom.tex[2], _sep_bottom.tex[3])
+	_sep_top.tex[1]:toScreenFull(200 - (_sep_top[2] - _sep_vert[2]) / 2, - 7, _sep_top[2], _sep_top[3], _sep_top.tex[2], _sep_top.tex[3])
+--	_sep_bottom.tex[1]:toScreenFull(200 - (_sep_bottom[2] - _sep_vert[2]) / 2, bottom - 25, _sep_bottom[2], _sep_bottom[3], _sep_bottom.tex[2], _sep_bottom.tex[3])
 
 end
 
 function _M:createSeparators()
-	local middle = self.w * 0.5
-	local bottom = self.h * 0.8
-	local bottom_h = self.h * 0.2
+	local icon_x = 0
+	local icon_y = self.h - (_talents_icon_h * 1)
 	self.icons = {
-		display_x = middle - (_talents_icon_w*2) / 2,
-		display_y = bottom + _sep_horiz[3] / 2,
-		w = _talents_icon_w*2,
-		h = 5*_talents_icon_h
+		display_x = icon_x,
+		display_y = icon_y,
+		w = 200,
+		h = self.h - icon_y,
 	}
 end
 
 function _M:clickIcon(bx, by)
-	if by < _talents_icon_h then
-		if bx >= _talents_icon_w then
-			self.show_npc_list = false
-			self.player.changed = true
-		else
-			self.show_userchat = false
-			self.logdisplay.changed = true
-		end
-	elseif by < 2*_talents_icon_h then
-		if bx >= _talents_icon_w then
-			self.show_npc_list = true
-			self.player.changed = true
-		else
-			self.show_userchat = true
-		end
-	elseif by < 3*_talents_icon_h then
+	if bx < _talents_icon_w then
+		self.show_npc_list = false
+		self.player.changed = true
+	elseif bx < 2*_talents_icon_w then
+		self.show_npc_list = true
+		self.player.changed = true
+	elseif bx < 3*_talents_icon_w then
 		self.key:triggerVirtual("SHOW_INVENTORY")
-	elseif by < 4*_talents_icon_h then
+	elseif bx < 4*_talents_icon_w then
 		self.key:triggerVirtual("SHOW_CHARACTER_SHEET")
-	elseif by < 5*_talents_icon_h then
+	elseif bx < 5*_talents_icon_w then
 		self.key:triggerVirtual("EXIT")
+	elseif bx < 6*_talents_icon_w then
+		self.key:triggerVirtual("SHOW_MESSAGE_LOG")
 	end
 end
 
 function _M:mouseIcon(bx, by)
-	if by < _talents_icon_h then
-		if bx >= _talents_icon_w then
-			self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Display talents\nToggle with [tab]")
-		else
-			self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Display game log\nToggle with [ctrl+space]")
-		end
-	elseif by < 2*_talents_icon_h then
-		if bx >= _talents_icon_w then
-			self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Display creatures\nToggle with [tab]")
-		else
-			self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Display community chat\nToggle with [ctrl+space]")
-		end
-	elseif by < 3*_talents_icon_h then
-		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Inventory")
-	elseif by < 4*_talents_icon_h then
-		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Character Sheet")
-	elseif by < 5*_talents_icon_h then
-		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Main menu")
+	if bx < _talents_icon_w then
+		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Display talents\nToggle with #{bold}##GOLD#[tab]#LAST##{normal}#")
+	elseif bx < 2*_talents_icon_w then
+		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Display creatures\nToggle with #{bold}##GOLD#[tab]#LAST##{normal}#")
+	elseif bx < 3*_talents_icon_w then
+		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "#{bold}##GOLD#I#LAST##{normal}#nventory")
+	elseif bx < 4*_talents_icon_w then
+		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "#{bold}##GOLD#C#LAST##{normal}#haracter Sheet")
+	elseif bx < 5*_talents_icon_w then
+		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Main menu (#{bold}##GOLD#Esc#LAST##{normal}#)")
+	elseif bx < 6*_talents_icon_w then
+		self.tooltip:displayAtMap(nil, nil, self.w, self.h, "Show message/chat log (#{bold}##GOLD#ctrl+m#LAST##{normal}#)")
 	end
 end
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index fca6fca929..6899a46f0f 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -219,7 +219,7 @@ function _M:act()
 	self.old_life = self.life
 
 	-- Clean log flasher
-	game.flash:empty()
+--	game.flash:empty()
 
 	-- Resting ? Running ? Otherwise pause
 	if not self:restStep() and not self:runStep() and self.player then
diff --git a/game/modules/tome/dialogs/ShowChatLog.lua b/game/modules/tome/dialogs/ShowChatLog.lua
new file mode 100644
index 0000000000..3f5a7c2d76
--- /dev/null
+++ b/game/modules/tome/dialogs/ShowChatLog.lua
@@ -0,0 +1,160 @@
+-- ToME - Tales of Maj'Eyal
+-- 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 Dialog = require "engine.ui.Dialog"
+local Tab = require "engine.ui.Tab"
+local Mouse = require "engine.Mouse"
+local Slider = require "engine.ui.Slider"
+
+module(..., package.seeall, class.inherit(Dialog))
+
+function _M:init(title, shadow, log, chat)
+	local w = math.floor(game.w * 0.9)
+	local h = math.floor(game.h * 0.9)
+	Dialog.init(self, title, w, h)
+	if shadow then self.shadow = shadow end
+
+	self.log, self.chat = log, chat
+
+	local tabs = {}
+
+	local order = {}
+	local list = {}
+	for name, data in pairs(chat.channels) do list[#list+1] = name end
+	table.sort(list, function(a,b) if a == "global" then return 1 elseif b == "global" then return nil else return a < b end end)
+	order[#order+1] = {timestamp=log:getLogLast(), tab="__log"}
+
+	tabs[#tabs+1] = {top=0, left=0, ui = Tab.new{title="Game Log", fct=function() end, on_change=function() local i = #tabs self:switchTo(tabs[1]) end, default=true}, tab_channel="__log" }
+	for i, name in ipairs(list) do
+		local oname = name
+		local nb_users = 0
+		for _, _ in pairs(chat.channels[name].users) do nb_users = nb_users + 1 end
+		name = name:capitalize().." ("..nb_users..")"
+
+		local ii = i
+		tabs[#tabs+1] = {top=0, left=(#tabs==0) and 0 or tabs[#tabs].ui, ui = Tab.new{title=name, fct=function() end, on_change=function() local i = ii+1 self:switchTo(tabs[i]) end, default=false}, tab_channel=oname }
+		order[#order+1] = {timestamp=chat:getLogLast(oname), tab=oname}
+	end
+
+	self.start_y = tabs[1].ui.h + 5
+
+	self:loadUI(tabs)
+	self.tabs = tabs
+	self:setupUI()
+
+	self.scrollbar = Slider.new{size=self.h - 20, max=1, inverse=true}
+
+	table.sort(order, function(a,b) return a.timestamp > b.timestamp end)
+	self:switchTo(self.last_tab or "__log")
+end
+
+function _M:generate()
+	Dialog.generate(self)
+
+	-- Add UI controls
+	local tabs = self.tabs
+	self.key:addBinds{
+		MOVE_UP = function() self:setScroll(self.scroll - 1) end,
+		MOVE_DOWN = function() self:setScroll(self.scroll + 1) end,
+		ACCEPT = "EXIT",
+		EXIT = function() game:unregisterDialog(self) end,
+	}
+	self.key:addCommands{
+		_TAB = function() local sel = 1 for i=1, #tabs do if tabs[i].ui.selected then sel = i break end end self:switchTo(tabs[util.boundWrap(sel+1, 1, #tabs)]) end,
+		_HOME = function() self:setScroll(1) end,
+		_END = function() self:setScroll(self.max) end,
+		_PAGEUP = function() self:setScroll(self.scroll - self.max_display) end,
+		_PAGEDOWN = function() self:setScroll(self.scroll + self.max_display) end,
+	}
+end
+
+function _M:mouseEvent(button, x, y, xrel, yrel, bx, by, event)
+	Dialog.mouseEvent(self, button, x, y, xrel, yrel, bx, by, event)
+
+	if button == "wheelup" and event == "button" then self.key:triggerVirtual("MOVE_UP")
+	elseif button == "wheeldown" and event == "button" then self.key:triggerVirtual("MOVE_DOWN")
+	end
+end
+
+function _M:loadLog(log)
+	self.lines = {}
+	for i = #log, 1, -1 do
+		self.lines[#self.lines+1] = log[i]
+	end
+
+	self.max_h = self.ih - self.iy
+	self.max = #log
+	self.max_display = math.floor(self.max_h / self.font_h)
+
+	self.scrollbar.max = self.max - self.max_display + 1
+	self.scroll = nil
+	self:setScroll(self.max - self.max_display + 1)
+end
+
+function _M:switchTo(ui)
+	if type(ui) == "string" then for i, tab in ipairs(self.tabs) do if tab.tab_channel == ui then ui = tab end end end
+	if type(ui) == "string" then ui = self.tabs[1] end
+
+	for i, ui in ipairs(self.tabs) do ui.ui.selected = false end
+	ui.ui.selected = true
+	if ui.tab_channel == "__log" then
+		self:loadLog(self.log:getLog())
+	else
+		self:loadLog(self.chat:getLog(ui.tab_channel))
+	end
+	-- Set it on the class to persist between invocations
+	_M.last_tab = ui.tab_channel
+end
+
+function _M:setScroll(i)
+	local old = self.scroll
+	self.scroll = util.bound(i, 1, math.max(1, self.max - self.max_display + 1))
+	if self.scroll == old then return end
+
+	self.dlist = {}
+	local nb = 0
+	local old_style = self.font:getStyle()
+	for z = 1 + self.scroll, #self.lines do
+		local stop = false
+		local tstr = self.lines[z]
+		if not tstr then break end
+		local gen = self.font:draw(tstr, self.iw - 10, 255, 255, 255)
+		for i = 1, #gen do
+			self.dlist[#self.dlist+1] = gen[i]
+			nb = nb + 1
+			if nb >= self.max_display then stop = true break end
+		end
+		if stop then break end
+	end
+	self.font:setStyle(old_style)
+end
+
+function _M:innerDisplay(x, y, nb_keyframes)
+	local h = y + self.iy + self.start_y
+	for i = 1, #self.dlist do
+		local item = self.dlist[i]
+		if self.shadow then item._tex:toScreenFull(x+2, h+2, item.w, item.h, item._tex_w, item._tex_h, 0,0,0, self.shadow) end
+		item._tex:toScreenFull(x, h, item.w, item.h, item._tex_w, item._tex_h)
+		h = h + self.font_h
+	end
+
+	self.scrollbar.pos = self.scrollbar.max - self.scroll + 1
+	self.scrollbar:display(x + self.iw - self.scrollbar.w, y)
+end
-- 
GitLab