diff --git a/game/engines/default/engine/generator/map/Forest.lua b/game/engines/default/engine/generator/map/Forest.lua
index ccf16d1d75f3ae46658e5df964a72dfedc198218..320c66c36b5273692e349b55ffa08bc723d177e8 100644
--- a/game/engines/default/engine/generator/map/Forest.lua
+++ b/game/engines/default/engine/generator/map/Forest.lua
@@ -227,6 +227,26 @@ function _M:generate(lev, old_lev)
 			rooms[#rooms+1] = r
 			end_room = r
 			print("Successfully loaded the end room")
+		else 
+			self.force_recreate = true return
+		end
+	end
+
+	-- Those we are required to have
+	if #self.required_rooms > 0 then
+		for i, rroom in ipairs(self.required_rooms) do
+			local ok = false
+			if type(rroom) == "table" and rroom.chance_room then
+				if rng.percent(rroom.chance_room) then rroom = rroom[1] ok = true end
+			else ok = true
+			end
+
+			if ok then
+				local r = self:roomAlloc(rroom, #rooms+1, lev, old_lev)
+				if r then rooms[#rooms+1] = r
+				else self.force_recreate = true return end
+				nb_room = nb_room - 1
+			end
 		end
 	end
 
diff --git a/game/engines/default/engine/generator/map/RoomsLoader.lua b/game/engines/default/engine/generator/map/RoomsLoader.lua
index 6f254ac618de538eefe9897894c2fe45f9fd6cfd..a3e0a480bf749a69be8140565687ea09271e1d94 100644
--- a/game/engines/default/engine/generator/map/RoomsLoader.lua
+++ b/game/engines/default/engine/generator/map/RoomsLoader.lua
@@ -50,12 +50,232 @@ end
 
 local rooms_cache = {}
 
+function _M:tmxLoadRoom(file)
+	file = file:gsub("%.lua$", ".tmx")
+	if not fs.exists(file) then return end
+	print("Static generator using file", file)
+	local f = fs.open(file, "r")
+	local data = f:read(10485760)
+	f:close()
+
+	local t = {}
+	local g = self:getLoader(t)
+	local map = lom.parse(data)
+	local mapprops = {}
+	if map:findOne("properties") then mapprops = map:findOne("properties"):findAllAttrs("property", "name", "value") end
+	local w, h = tonumber(map.attr.width), tonumber(map.attr.height)
+	local tw, th = tonumber(map.attr.tilewidth), tonumber(map.attr.tileheight)
+	local chars = {}
+	local start_tid, end_tid = nil, nil
+	for _, tileset in ipairs(map:findAll("tileset")) do
+		if tileset:findOne("properties") then for name, value in pairs(tileset:findOne("properties"):findAllAttrs("property", "name", "value")) do
+			if name == "load_terrains" then
+				local list = self:loadLuaInEnv(g, nil, "return "..value) or {}
+				self.zone.grid_class:loadList(list, nil, self.grid_list, nil, self.grid_list.__loaded_files)
+			elseif name == "load_traps" then
+				local list = self:loadLuaInEnv(g, nil, "return "..value) or {}
+				self.zone.trap_class:loadList(list, nil, self.trap_list, nil, self.trap_list.__loaded_files)
+			elseif name == "load_objects" then
+				local list = self:loadLuaInEnv(g, nil, "return "..value) or {}
+				self.zone.object_class:loadList(list, nil, self.object_list, nil, self.object_list.__loaded_files)
+			elseif name == "load_actors" then
+				local list = self:loadLuaInEnv(g, nil, "return "..value) or {}
+				self.zone.npc_class:loadList(list, nil, self.npc_list, nil, self.npc_list.__loaded_files)
+			end
+		end end
+
+		local firstgid = tonumber(tileset.attr.firstgid)
+		for _, tile in ipairs(tileset:findAll("tile")) do
+			local tid = tonumber(tile.attr.id + firstgid)
+			local display = tile:findOne("property", "name", "display")
+			local id = tile:findOne("property", "name", "id")
+			local data_id = tile:findOne("property", "name", "data_id")
+			local custom = tile:findOne("property", "name", "custom")
+			local is_start = tile:findOne("property", "name", "start")
+			local is_end = tile:findOne("property", "name", "end") or tile:findOne("property", "name", "stop")
+			if display then
+				chars[tid] = display.attr.value
+			elseif id then
+				t[tid] = id.attr.value
+			elseif data_id then
+				t[tid] = self.data[data_id.attr.value]
+			elseif custom then
+				local ret = self:loadLuaInEnv(g, nil, "return "..custom.attr.value)
+				t[tid] = ret
+			end
+			if is_start then start_tid = tid end
+			if is_end then end_tid = tid end
+		end
+	end
+
+	local rotate = "default"
+	if mapprops.rotate then
+		rotate = self:loadLuaInEnv(g, nil, "return "..mapprops.rotate) or mapprops.rotate
+	end
+
+	if mapprops.status_all then
+		self.status_all = self:loadLuaInEnv(g, nil, "return "..mapprops.status_all) or {}
+	end
+
+	if mapprops.add_data then
+		table.merge(self.level.data, self:loadLuaInEnv(g, nil, "return "..mapprops.add_data) or {}, true)
+	end
+
+	if mapprops.lua then
+		self:loadLuaInEnv(g, nil, "return "..mapprops.lua)
+	end
+
+	local m = { w=w, h=h }
+
+	local function rotate_coords(i, j)
+		local ii, jj = i, j
+		if rotate == "flipx" then ii, jj = m.w - i + 1, j
+		elseif rotate == "flipy" then ii, jj = i, m.h - j + 1
+		elseif rotate == "90" then ii, jj = j, m.w - i + 1
+		elseif rotate == "180" then ii, jj = m.w - i + 1, m.h - j + 1
+		elseif rotate == "270" then ii, jj = m.h - j + 1, i
+		end
+		return ii, jj
+	end
+	local function populate(i, j, c, tid)
+		local ii, jj = rotate_coords(i, j)
+
+		m[ii] = m[ii] or {}
+		if type(c) == "string" then
+			m[ii][jj] = c
+		else
+			m[ii][jj] = m[ii][jj] or {}
+			table.update(m[ii][jj], c)
+		end
+		if tid == start_tid then m.startx = ii - 1 m.starty = jj - 1 m.start_rotated = true end
+		if tid == end_tid then m.endx = ii - 1 m.endy = jj - 1 m.end_rotated = true end
+	end
+
+	self.gen_map = m
+	self.tiles = t
+
+	self.map.w = m.w
+	self.map.h = m.h
+
+	for _, layer in ipairs(map:findAll("layer")) do
+		local mapdata = layer:findOne("data")
+		local layername = layer.attr.name:lower()
+		if layername == "terrain" then layername = "grid" end
+
+		if mapdata.attr.encoding == "base64" then
+			local b64 = mime.unb64(mapdata[1]:trim())
+			local data
+			if mapdata.attr.compression == "zlib" then data = zlib.decompress(b64)
+			elseif not mapdata.attr.compression then data = b64
+			else error("tmx map compression unsupported: "..mapdata.attr.compression)
+			end
+			local gid, i = nil, 1
+			local x, y = 1, 1
+			while i <= #data do
+				gid, i = struct.unpack("<I4", data, i)
+				if chars[gid] then populate(x, y, chars[id])
+				else populate(x, y, {[layername] = gid}, gid)
+				end
+				x = x + 1
+				if x > w then x = 1 y = y + 1 end
+			end
+		elseif mapdata.attr.encoding == "csv" then
+			local data = mapdata[1]:gsub("[^,0-9]", ""):split(",")
+			local x, y = 1, 1
+			for i, gid in ipairs(data) do
+				gid = tonumber(gid)
+				if chars[gid] then populate(x, y, chars[id])
+				else populate(x, y, {[layername] = gid}, gid)
+				end
+				x = x + 1
+				if x > w then x = 1 y = y + 1 end
+			end
+		elseif not mapdata.attr.encoding then
+			local data = mapdata:findAll("tile")
+			local x, y = 1, 1
+			for i, tile in ipairs(data) do
+				local gid = tonumber(tile.attr.gid)
+				if chars[gid] then populate(x, y, chars[id])
+				else populate(x, y, {[layername] = gid}, gid)
+				end
+				x = x + 1
+				if x > w then x = 1 y = y + 1 end
+			end
+		end
+	end
+
+	self.add_attrs_later = {}
+
+	for _, og in ipairs(map:findAll("objectgroup")) do
+		for _, o in ipairs(map:findAll("object")) do
+			local props = o:findOne("properties"):findAllAttrs("property", "name", "value")
+
+			if og.attr.name:find("^addSpot") then
+				local x, y, w, h = math.floor(tonumber(o.attr.x) / tw), math.floor(tonumber(o.attr.y) / th), math.floor(tonumber(o.attr.width) / tw), math.floor(tonumber(o.attr.height) / th)
+				if props.start then m.startx = x m.starty = y end
+				if props['end'] then m.endx = x m.endy = y end
+				if props.type and props.subtype then
+					for i = x, x + w do for j = y, y + h do
+						local i, j = rotate_coords(i, j)
+						g.addSpot({i, j}, props.type, props.subtype)
+					end end
+				end
+			elseif og.attr.name:find("^addZone") then
+				local x, y, w, h = math.floor(tonumber(o.attr.x) / tw), math.floor(tonumber(o.attr.y) / th), math.floor(tonumber(o.attr.width) / tw), math.floor(tonumber(o.attr.height) / th)
+				if props.type and props.subtype then
+					local i1, j2 = rotate_coords(x, y)
+					local i2, j2 = rotate_coords(x + w, y + h)
+					g.addZone({x, y, x + w, y + h}, props.type, props.subtype)
+				end
+			elseif og.attr.name:find("^attrs") then
+				local x, y, w, h = math.floor(tonumber(o.attr.x) / tw), math.floor(tonumber(o.attr.y) / th), math.floor(tonumber(o.attr.width) / tw), math.floor(tonumber(o.attr.height) / th)
+				for k, v in pairs(props) do
+					for i = x, x + w do for j = y, y + h do
+						local i, j = rotate_coords(i, j)
+						self.add_attrs_later[#self.add_attrs_later+1] = {x=i, y=j, key=k, value=self:loadLuaInEnv(g, nil, "return "..v)}
+						print("====", i, j, k)
+					end end
+				end
+			end
+		end
+	end
+
+	m.startx = m.startx or math.floor(m.w / 2)
+	m.starty = m.starty or math.floor(m.h / 2)
+	m.endx = m.endx or math.floor(m.w / 2)
+	m.endy = m.endy or math.floor(m.h / 2)
+	if rotate == "flipx" then
+		if not m.start_rotated then m.startx = m.w - m.startx - 1 end
+		if not m.end_rotated then m.endx   = m.w - m.endx - 1 end
+	elseif rotate == "flipy" then
+		if not m.start_rotated then m.starty = m.h - m.starty - 1 end
+		if not m.end_rotated then m.endy   = m.h - m.endy - 1 end
+	elseif rotate == "90" then
+		if not m.start_rotated then m.startx, m.starty = m.starty, m.w - m.startx - 1 end
+		if not m.end_rotated then m.endx,   m.endy   = m.endy,   m.w - m.endx   - 1 end
+		m.w, m.h = m.h, m.w
+	elseif rotate == "180" then
+		if not m.start_rotated then m.startx, m.starty = m.w - m.startx - 1, m.h - m.starty - 1 end
+		if not m.end_rotated then m.endx,   m.endy   = m.w - m.endx   - 1, m.h - m.endy   - 1 end
+	elseif rotate == "270" then
+		if not m.start_rotated then m.startx, m.starty = m.h - m.starty - 1, m.startx end
+		if not m.end_rotated then m.endx,   m.endy   = m.h - m.endy   - 1, m.endx end
+		m.w, m.h = m.h, m.w
+	end
+
+	print("[STATIC TMX MAP] size", m.w, m.h)
+	return true
+end
+
 function _M:loadRoom(file)
 	if rooms_cache[file] then return rooms_cache[file] end
 
 	local filename = "/data/rooms/"..file..".lua"
 	-- Found in the zone itself ?
 	if file:find("^!") then filename = self.zone:getBaseName().."/rooms/"..file:sub(2)..".lua" end
+	local tmxed = self:tmxLoadRoom(filename)
+	if tmxed then return tmxed end
+	
 	local f, err = loadfile(filename)
 	if not f and err then error(err) end
 	setfenv(f, setmetatable({
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 001d29e41c48dae16425a3dc6bc987d9efee608d..a4279352fe2c4a2bc439757b59aac502bf335543 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -1694,7 +1694,7 @@ function _M:setupCommands()
 			print("===============")
 		end end,
 		[{"_g","ctrl"}] = function() if config.settings.cheat then
-			self:changeLevel(5, "orcs+slumbering-caves")
+			self:changeLevel(1, "orcs+lost-city")
 			-- self:changeLevel(6, "orcs+palace-fumes")
 do return end
 			local o = game.zone:makeEntity(game.level, "object", {subtype="steamsaw", random_object=true}, nil, true)
diff --git a/game/modules/tome/data/gfx/dark-ui/inven_tabs/tinkers.png b/game/modules/tome/data/gfx/dark-ui/inven_tabs/tinkers.png
new file mode 100644
index 0000000000000000000000000000000000000000..92c7005e513c50ca7a40553648bb010898d101e4
Binary files /dev/null and b/game/modules/tome/data/gfx/dark-ui/inven_tabs/tinkers.png differ
diff --git a/game/modules/tome/data/gfx/metal-ui/inven_tabs/tinkers.png b/game/modules/tome/data/gfx/metal-ui/inven_tabs/tinkers.png
new file mode 100644
index 0000000000000000000000000000000000000000..af792fbb58f05240f141fc28d9e92aa577415c93
Binary files /dev/null and b/game/modules/tome/data/gfx/metal-ui/inven_tabs/tinkers.png differ
diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua
index 0bb29e9853041dea2c3e61c3c89ff29048564280..11e06b29eb6234c1614484de974c74bd005384ee 100644
--- a/game/modules/tome/load.lua
+++ b/game/modules/tome/load.lua
@@ -251,7 +251,7 @@ InventoryUI.default_tabslist = function(self)
 	tabslist[#tabslist+1] = {image="inven_tabs/jewelry.png", 	kind="jewelry",		desc="Rings and Amulets",		filter=function(o) return not o.__transmo and (o.type == "jewelry") end}
 	tabslist[#tabslist+1] = {image="inven_tabs/gems.png", 		kind="gems",		desc="Gems"		,		filter=function(o) return not o.__transmo and (o.type == "gem" or o.type == "alchemist-gem") end}
 	tabslist[#tabslist+1] = {image="inven_tabs/inscriptions.png", 	kind="inscriptions",	desc="Infusions, Runes, ...",		filter=function(o) return not o.__transmo and (o.type == "scroll") end}
-	if self.actor.can_tinker then tabslist[#tabslist+1] = {image="inven_tabs/chest.png", kind="tinker", desc="Tinkers", filter=function(o) return o.is_tinker end} end
+	if self.actor.can_tinker then tabslist[#tabslist+1] = {image="inven_tabs/tinkers.png", kind="tinker", desc="Tinkers", filter=function(o) return o.is_tinker end} end
 	tabslist[#tabslist+1] = {image="inven_tabs/misc.png", 		kind="misc",		desc="Miscellaneous",			filter="others"}
 	tabslist[#tabslist+1] = {image="inven_tabs/quests.png", 	kind="quests",		desc="Quest and plot related items",	filter=function(o) return not o.__transmo and (o.plot or o.quest) end}
 	if self.actor:attr("has_transmo") then tabslist[#tabslist+1] = {image="inven_tabs/chest.png", kind="transmo", desc="Transmogrification Chest", filter=function(o) return o.__transmo end} end