diff --git a/game/engines/default/engine/Zone.lua b/game/engines/default/engine/Zone.lua
index 7d9141c34bca1b8b0eea6110148b828c4a2b3160..f2ff872bf200bd26a007c497bbae16b90e9795f0 100644
--- a/game/engines/default/engine/Zone.lua
+++ b/game/engines/default/engine/Zone.lua
@@ -1074,6 +1074,7 @@ function _M:newLevel(level_data, lev, old_lev, game)
 	if level_data.post_process_map then
 		level_data.post_process_map(level, self)
 		if level.force_recreate then
+			forceprint("[Zone:newLevel] post_process_map "..generator.__CLASSNAME.." forced recreation: ",level.force_recreate)
 			level:removed()
 			return self:newLevel(level_data, lev, old_lev, game)
 		end
@@ -1125,6 +1126,7 @@ function _M:newLevel(level_data, lev, old_lev, game)
 	if level_data.post_process then
 		level_data.post_process(level, self)
 		if level.force_recreate then
+			forceprint("[Zone:newLevel] post_process "..generator.__CLASSNAME.." forced recreation: ",level.force_recreate)
 			level:removed()
 			return self:newLevel(level_data, lev, old_lev, game)
 		end
@@ -1135,7 +1137,7 @@ function _M:newLevel(level_data, lev, old_lev, game)
 	if not level_data.no_level_connectivity then
 		print("[LEVEL GENERATION] checking entrance to exit A*", ux, uy, "to", dx, dy)
 		if ux and uy and dx and dy and (ux ~= dx or uy ~= dy) and not a:calc(ux, uy, dx, dy) then
-			forceprint("Level unconnected, no way from entrance", ux, uy, "to exit", dx, dy)
+			forceprint("[Zone:newLevel] Level unconnected, no way from entrance", ux, uy, "to exit", dx, dy)
 			level:removed()
 			return self:newLevel(level_data, lev, old_lev, game)
 		end
@@ -1152,7 +1154,7 @@ function _M:newLevel(level_data, lev, old_lev, game)
 
 			print("[LEVEL GENERATION] checking A*", spot.x, spot.y, "to", cx, cy)
 			if spot.x and spot.y and cx and cy and (spot.x ~= cx or spot.y ~= cy) and not a:calc(spot.x, spot.y, cx, cy) then
-				forceprint("Level unconnected, no way from spot", spot.type, spot.subtyp, "at", spot.x, spot.y, "to", cx, cy, spot.check_connectivity)
+				forceprint("[Zone:newLevel] Level unconnected, no way from spot", spot.type, spot.subtyp, "at", spot.x, spot.y, "to", cx, cy, spot.check_connectivity)
 				level:removed()
 				return self:newLevel(level_data, lev, old_lev, game)
 			end
diff --git a/game/engines/default/engine/generator/map/MapScript.lua b/game/engines/default/engine/generator/map/MapScript.lua
index 40fb301100009b0ccbdd7d2229261414897ad4c0..b2f6e98355d1353b5df7e7d0128a7e41db6c24ed 100644
--- a/game/engines/default/engine/generator/map/MapScript.lua
+++ b/game/engines/default/engine/generator/map/MapScript.lua
@@ -19,15 +19,14 @@
 
 require "engine.class"
 local Map = require "engine.Map"
-local BaseGenerator = require "engine.Generator"
 local RoomsLoader = require "engine.generator.map.RoomsLoader"
-require "engine.Generator"
+local Generator = require "engine.Generator"
 
 --- @classmod engine.generator.map.MapScript
-module(..., package.seeall, class.inherit(engine.Generator, RoomsLoader))
+module(..., package.seeall, class.inherit(Generator, RoomsLoader))
 
 function _M:init(zone, map, level, data)
-	engine.Generator.init(self, zone, map, level)
+	Generator.init(self, zone, map, level)
 	self.data = data
 	self.grid_list = zone.grid_list
 	self.spots = {}
@@ -35,10 +34,19 @@ function _M:init(zone, map, level, data)
 	self.post_gen = {}
 	self.maps_positions = {}
 	self.maps_registers = {}
+	self.self_tiles = {}
 
 	RoomsLoader.init(self, data)
 end
 
+function _M:resolve(c, list, force)
+	if force then return Generator.resolve(self, c, list, force) end
+	if self.self_tiles[c] then
+		return Generator.resolve(self, self.self_tiles[c].grid or '.', list, true)
+	else
+		return Generator.resolve(self, c, list, force)
+	end
+end
 
 --- Resolve a filename, from /data/ /data-addon/ or subfodler of zonedir
 function _M:getFile(file, folder)
@@ -71,24 +79,27 @@ function _M:loadFile(mapscript, lev, old_lev, args)
 		lev = lev,
 		old_lev = old_lev,
 		loadMapScript = function(name, args) return self:loadFile(name, lev, old_lev, args) end,
+		merge_order = {'.', '_', 'r', '+', '#', 'O', ';', '=', 'T'},
 	}
 	for f in fs.iterate("/engine/tilemaps/", function(f) return f:find("%.lua$") end) do
 		local n = f:sub(1, -5)
 		local nf = "engine.tilemaps."..n
 		nenv[n] = require(nf)
 	end
+	nenv.tm = nenv.Tilemap.new(self.mapsize)
 	setfenv(f, setmetatable(nenv, {__index=_G}))
 	return f()
 end
 
 function _M:custom(lev, old_lev)
-	for f in fs.iterate("/engine/tilemaps/", function(f) return f:find("%.lua$") end) do
-		local n = f:sub(1, -5)
-		local nf = "engine.tilemaps."..n
-		package.loaded[nf] = nil
+	if config.settings.cheat then
+		for f in fs.iterate("/engine/tilemaps/", function(f) return f:find("%.lua$") end) do
+			local n = f:sub(1, -5)
+			local nf = "engine.tilemaps."..n
+			package.loaded[nf] = nil
+		end
 	end
 
-
 	local ret = nil
 	if self.data.mapscript then
 		local mapscript = self.data.mapscript
@@ -127,19 +138,96 @@ function _M:generate(lev, old_lev)
 	if not self.exit_pos then self.exit_pos = data:point(1, 1) end
 
 	data = data:getResult(true)
-	for i = 0, self.map.w - 1 do
-		for j = 0, self.map.h - 1 do
-			if data[j+1][i+1] ~= "⍓" and data[j+1][i+1] ~= "⎕" then
-				self.map(i, j, Map.TERRAIN, self:resolve(data[j+1][i+1] or '#'))
+
+	-- First do the map itself
+	for i = 0, self.map.w - 1 do for j = 0, self.map.h - 1 do
+		if data[j+1][i+1] ~= "⍓" and data[j+1][i+1] ~= "⎕" then
+			self.map(i, j, Map.TERRAIN, self:resolve(data[j+1][i+1] or '#'))
+		end
+
+		if self.status_all then
+			local s = table.clone(self.status_all)
+			if s.lite then self.level.map.lites(i, j, true) s.lite = nil end
+			if s.remember then self.level.map.remembers(i, j, true) s.remember = nil end
+			if s.special then self.map.room_map[i][j].special = s.special s.special = nil end
+			if s.room_map then for k, v in pairs(s.room_map) do self.map.room_map[i][j][k] = v end s.room_map = nil end
+			if pairs(s) then for k, v in pairs(s) do self.level.map.attrs(i, j, k, v) end end
+		end
+	end end
+
+	-- Now add the various entities
+	for i = 0, self.map.w - 1 do for j = 0, self.map.h - 1 do local def = self.self_tiles[data[j+1][i+1]] if def then
+		local actor = def.actor
+		local trap = def.trap
+		local object = def.object
+		local trigger = def.trigger
+		local status = def.status
+		local define_spot = def.define_spot
+
+		if trigger then
+			local t, mod
+			if type(trigger) == "string" then t = self.zone:makeEntityByName(self.level, "trigger", trigger)
+			elseif type(trigger) == "table" and trigger.random_filter then mod = trigger.entity_mod t = self.zone:makeEntity(self.level, "terrain", trigger.random_filter, nil, true)
+			else t = self.zone:finishEntity(self.level, "terrain", trigger)
 			end
+			if t then if mod then t = mod(t) end self:roomMapAddEntity(i, j, "trigger", t) end
+		end
+
+		if object then
+			local o, mod
+			if type(object) == "string" then o = self.zone:makeEntityByName(self.level, "object", object)
+			elseif type(object) == "table" and object.random_filter then mod = object.entity_mod o = self.zone:makeEntity(self.level, "object", object.random_filter, nil, true)
+			else o = self.zone:finishEntity(self.level, "object", object)
+			end
+			if o then if mod then o = mod(o) end self:roomMapAddEntity(i, j, "object", o, elists) end --takes care of uniques
+		end
+
+		if trap then
+			local t, mod
+			if type(trap) == "string" then t = self.zone:makeEntityByName(self.level, "trap", trap)
+			elseif type(trap) == "table" and trap.random_filter then mod = trap.entity_mod t = self.zone:makeEntity(self.level, "trap", trap.random_filter, nil, true)
+			else t = self.zone:finishEntity(self.level, "trap", trap)
+			end
+			if t then if mod then t = mod(t) end self:roomMapAddEntity(i, j, "trap", t, elists) end
 		end
-	end
 
+		if actor then
+			local m, mod
+			if type(actor) == "string" then m = self.zone:makeEntityByName(self.level, "actor", actor)
+			elseif type(actor) == "table" and actor.random_filter then mod = actor.entity_mod m = self.zone:makeEntity(self.level, "actor", actor.random_filter, nil, true)
+			else m = self.zone:finishEntity(self.level, "actor", actor)
+			end
+			if m then
+				if mod then m = mod(m) end
+				self:roomMapAddEntity(i, j, "actor", m, elists)
+			end
+		end
+
+		if status then
+			local s = table.clone(status)
+			if s.lite then self.level.map.lites(i, j, true) s.lite = nil end
+			if s.remember then self.level.map.remembers(i, j, true) s.remember = nil end
+			if s.special then self.map.room_map[i][j].special = s.special s.special = nil end
+			if s.room_map then for k, v in pairs(s.room_map) do self.map.room_map[i][j][k] = v end s.room_map = nil end
+			if pairs(s) then for k, v in pairs(s) do self.level.map.attrs(i, j, k, v) end end
+		end
+
+		if define_spot then
+			define_spot = table.clone(define_spot, true)
+			assert(define_spot.type, "defineTile auto spot without type field")
+			assert(define_spot.subtype, "defineTile auto spot without subtype field")
+			define_spot.x = i
+			define_spot.y = j
+			self.spots[#self.spots+1] = define_spot
+		end
+	end end end
+
+	-- Any psot gen stuff
 	for _, post in pairs(self.post_gen) do
 		post(self, lev, old_lev)
 	end
 
-	return self.entrance_pos.x - 1, self.entrance_pos.y - 1, self.exit_pos.x - 1, self.exit_pos.y - 1
+	return self.entrance_pos.x - 1, self.entrance_pos.y - 1, self.exit_pos.x - 1, self.exit_pos.y - 1, self.spots
 end
 
 function _M:setEntrance(pos)
@@ -149,16 +237,44 @@ function _M:setExit(pos)
 	self.exit_pos = pos
 end
 
-function _M:addSpot(x, y, type, subtype, data)
+function _M:setStatusAll(s)
+	self.status_all = s
+end
+
+function _M:addSpot(x, y, _type, subtype, data)
+	if type(x) == "table" then x, y, _type, subtype, data = x.x, x.y, y, _type, subtype end
 	data = data or {}
 	-- Tilemap uses 1 based indexes
 	data.x = math.floor(x) - 1
 	data.y = math.floor(y) - 1
-	data.type = type
+	data.type = _type
 	data.subtype = subtype
 	self.spots[#self.spots+1] = data
 end
 
+function _M:addZone(p1, p2, type, subtype, additional)
+	local zone = {x1=p1.x-1, y1=p1.y-1, x2=p2.x-1, y2=p2.y-1, type=type or "static", subtype=subtype or "static"}
+	table.update(zone, additional or {})
+	self.level.custom_zones = self.level.custom_zones or {}
+	self.level.custom_zones[#self.level.custom_zones+1] = zone
+end
+
+function _M:checkConnectivity(dst, src, type, subtype)
+	local data = {}
+	if type(src) == "string" then data.check_connectivity = src
+	else data.check_connectivity = {x=src.x-1, y=src.y-1} end
+	self:addSpot(dst, type or "static", subtype or "static", data)
+end
+
+function _M:defineTile(char, grid, obj, actor, trap, status, spot)
+	self.self_tiles[char] = {grid=grid, object=obj, actor=actor, trap=trap, status=status, define_spot=spot}
+end
+
+function _M:copyTile(dchar, schar, alter)
+	self.self_tiles[dchar] = table.clone(self.self_tiles[schar], true)
+	if alter then alter(self.self_tiles[dchar]) end
+end
+
 function _M:postGen(fct)
 	self.post_gen[#self.post_gen+1] = fct
 end
@@ -174,7 +290,7 @@ function _M:makeTemporaryMap(map_w, map_h, fct)
 	local new_data = table.clone(self.data, true)
 
 	-- Fake a generator call to init tmp_map.room_map
-	local ngen = BaseGenerator.new({}, tmp_map, {}, {})
+	local ngen = Generator.new({}, tmp_map, {}, {})
 
 	fct(tmp_map, new_data)
 
diff --git a/game/engines/default/engine/tilemaps/Static.lua b/game/engines/default/engine/tilemaps/Static.lua
index 1e81f8a0e4ec1fc6103fce6276391b1869265b60..d7d234f838688a7a61de384889131557bda715fb 100644
--- a/game/engines/default/engine/tilemaps/Static.lua
+++ b/game/engines/default/engine/tilemaps/Static.lua
@@ -27,7 +27,8 @@ module(..., package.seeall, class.inherit(Tilemap))
 function _M:init(file)
 	Tilemap.init(self)
 
-	self.data = self:tmxLoad(file)
+	if file:find("%.tmx$") then self.data = self:tmxLoad(file)
+	else self.data = self:mapLoad(file) end
 	self.data_h = #self.data
 	self.data_w = self.data[1] and #self.data[1] or 0
 	self.data_size = self:point(self.data_w, self.data_h)
diff --git a/game/engines/default/engine/tilemaps/Tilemap.lua b/game/engines/default/engine/tilemaps/Tilemap.lua
index 6741f4f634cc2e02c31912769f7eca28925102c6..b3df902d6bf295dbf2f885eb71b3c5a936066b1b 100644
--- a/game/engines/default/engine/tilemaps/Tilemap.lua
+++ b/game/engines/default/engine/tilemaps/Tilemap.lua
@@ -242,6 +242,29 @@ function _M:scale(sx, sy)
 	return self
 end
 
+--- Used internally to load a tilemap from a lua map file
+function _M:mapLoad(file)
+	local f, err = loadfile(file)
+	if not f then error(err) end
+	setfenv(f, {})
+	local ok, raw = pcall(f)
+	if not ok then error(raw) end
+
+	raw = raw:split('\n')
+	local h = #raw
+	local w = #raw[1]
+
+	local data = {}
+	for y = 1, h do
+		data[y] = {}
+		local row = raw[y]
+		for x = 1, w do
+			data[y][x] = row:sub(x, x) or ' '
+		end
+	end
+	return data, w, h
+end
+
 --- Used internally to load a tilemap from a tmx file
 function _M:tmxLoad(file)
 	local f = fs.open(file, "r") local data = f:read(10485760) f:close()
@@ -511,6 +534,20 @@ function _M:eliminateByFloodfill(walls)
 	end
 end
 
+function _M:getBorderGroup(group)
+	local border = {list={}}
+	for _, d in ipairs(group.list) do
+		for i = -1, 1 do for j = -1, 1 do if (i ~= 0 or j ~= 0) and self.data[d.y+j] and self.data[d.y+j][d.x+i] then
+			if not self:isInGroup(group, d.x+i, d.y+j) then
+				if not self:isInGroup(border, d.x+i, d.y+j, true) then
+					border.list[#border.list+1] = self:point{x=d.x+i, y=d.y+j}
+				end
+			end
+		end end end
+	end
+	return border
+end
+
 function _M:fillGroup(group, char)
 	-- print("[Tilemap] Filling group of", #group.list, "with", char)
 	for j = 1, #group.list do
@@ -519,17 +556,102 @@ function _M:fillGroup(group, char)
 	end
 end
 
-function _M:isInGroup(group, x, y)
-	if not group.reverse then
-		group.reverse = {}
-		for j = 1, #group.list do
-			local jn = group.list[j]
-			group.reverse[jn.x] = group.reverse[jn.x] or {}
-			group.reverse[jn.x][jn.y] = true
-		end
+function _M:computeGroupReverse(group)
+	group.reverse = {}
+	for j = 1, #group.list do
+		local jn = group.list[j]
+		group.reverse[jn.x] = group.reverse[jn.x] or {}
+		group.reverse[jn.x][jn.y] = true
+	end
+end
+
+function _M:isInGroup(group, x, y, force)
+	if not group.reverse or force then
+		self:computeGroupReverse(group)
 	end
 	return group.reverse[x] and group.reverse[x][y]
 end
+
+function _M:pickGroupSpot(group, mode, what)
+	self:computeGroupReverse(group)
+
+	local function check(x, y)
+		return group.reverse[x] and group.reverse[x][y]
+	end
+
+	local list = table.clone(group.list)
+	while #list > 0 do
+		local jn = rng.tableRemove(list)
+		if mode == "any" or not mode then
+			return self:point{x=jn.x, y=jn.y}
+		elseif mode == "inside-wall" then
+			local g8 = check(jn.x+0, jn.y-1)
+			local g2 = check(jn.x+0, jn.y+1)
+			local g4 = check(jn.x-1, jn.y+0)
+			local g6 = check(jn.x+1, jn.y+0)
+			local g7 = check(jn.x-1, jn.y-1)
+			local g3 = check(jn.x+1, jn.y+1)
+			local g1 = check(jn.x-1, jn.y+1)
+			local g9 = check(jn.x+1, jn.y-1)
+			if not what or what == "any" or what == "straight" then
+				print("==check on ", jn.x, jn.y, "::", g8,g2,g4,g6)
+				if g8 and g2 and not g4 and not g6 then
+					return self:point{x=jn.x, y=jn.y}
+				elseif g4 and g6 and not g2 and not g8 then
+					return self:point{x=jn.x, y=jn.y}
+				end
+			end
+			if what == "any" or what == "diagonal" then
+				if not g8 and not g2 and not g4 and not g6 then
+					return self:point{x=jn.x, y=jn.y}
+				elseif not g4 and not g6 and not g2 and not g8 then
+					return self:point{x=jn.x, y=jn.y}
+				end
+			end
+		elseif mode == "corder" then
+			local g8 = check(jn.x+0, jn.y-1)
+			local g2 = check(jn.x+0, jn.y+1)
+			local g4 = check(jn.x-1, jn.y+0)
+			local g6 = check(jn.x+1, jn.y+0)
+			local g7 = check(jn.x-1, jn.y-1)
+			local g3 = check(jn.x+1, jn.y+1)
+			local g1 = check(jn.x-1, jn.y+1)
+			local g9 = check(jn.x+1, jn.y-1)
+			if not what or what == "any" or what == "straight" then
+				if not g8 and g2 and not g4 and g6 and not g7 and not g3  and not g1 and not g9 then
+					return self:point{x=jn.x, y=jn.y}
+				elseif g4 and not g6 and g2 and not g8  and not g7 and not g3  and not g1 and not g9 then
+					return self:point{x=jn.x, y=jn.y}
+				elseif g4 and not g6 and not g2 and g8  and not g7 and not g3  and not g1 and not g9 then
+					return self:point{x=jn.x, y=jn.y}
+				elseif not g4 and g6 and not g2 and g8  and not g7 and not g3  and not g1 and not g9 then
+					return self:point{x=jn.x, y=jn.y}
+				end
+			end
+			if what == "any" or what == "diagonal" then
+				if not g4 and not g6 and not g2 and not g8 and g7 and not g3 and g1 and not g7 then
+					return self:point{x=jn.x, y=jn.y}
+				elseif not g4 and not g6 and not g2 and not g8  and g7 and not g3  and not g1 and g9 then
+					return self:point{x=jn.x, y=jn.y}
+				elseif not g4 and not g6 and not g2 and not g8  and not g7 and g3  and not g1 and g9 then
+					return self:point{x=jn.x, y=jn.y}
+				elseif not g4 and not g6 and not g2 and not g8  and not g7 and g3  and g1 and not g9 then
+					return self:point{x=jn.x, y=jn.y}
+				end
+			end
+		elseif mode == "has-neighbours" then
+			local nb = 0
+			for i = -1, 1 do for j = -1, 1 do if (i ~= 0 or j ~= 0) and check(jn.x+i, jn.y+j) then
+				nb = nb + 1
+			end end end
+			if nb == what then
+				return self:point{x=jn.x, y=jn.y}
+			end
+		end
+	end
+	return nil
+end
+
 --[=[
 --- Find the biggest rectangle that can fit fully in the given group
 function _M:groupInnerRectangle(group)
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 23faf39c2bb71e7ccab7712d7cba9de45ab3fda9..052a86852b3cb16e54191553e29a73246c1f4c46 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -2013,7 +2013,9 @@ function _M:setupCommands()
 			print("===============")
 		end end,
 		[{"_g","ctrl"}] = function() if config.settings.cheat then
-			local f, err = loadfile("/data-cults/general/events/tentacle-tree.lua")
+			self:changeLevel(game.level.level + 1)
+do return end
+			local f, err = loadfile("/data/general/events/rat-lich.lua")
 			print(f, err)
 			setfenv(f, setmetatable({level=self.level, zone=self.zone}, {__index=_G}))
 			print(pcall(f))
@@ -2028,9 +2030,6 @@ do return end
 			else
 				self:changeLevel(game.level.level + 1)
 			end
-do return end
-			package.loaded["mod.dialogs.Donation"] = nil
-			self:registerDialog(require("mod.dialogs.Donation").new())
 do return end
 			local m = game.zone:makeEntity(game.level, "actor", {name="elven mage"}, nil, true)
 			local x, y = util.findFreeGrid(game.player.x, game.player.y, 20, true, {[Map.ACTOR]=true})
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index feb8c8cd1be6d2ff059b058e7ca44a9d01a615d0..8476cee5a817904557a88590524343552da8e76d 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -215,7 +215,7 @@ function _M:onEnterLevelEnd(zone, level)
 	if level._player_enter_scatter then return end
 	level._player_enter_scatter = true
 
-	if level.data.generator and level.data.generator.map and level.data.generator.map.class == "engine.generator.map.Static" and not level.data.static_force_scatter then return end
+	if level.data.generator and level.data.generator.map and (level.data.generator.map.class == "engine.generator.map.MapScript" or level.data.generator.map.class == "engine.generator.map.Static") and not level.data.static_force_scatter then return end
 
 	self:project({type="ball", radius=5}, self.x, self.y, function(px, py)
 		local a = level.map(px, py, Map.ACTOR)
diff --git a/game/modules/tome/class/generator/map/VaultLevel.lua b/game/modules/tome/class/generator/map/VaultLevel.lua
index 68abc4683b65643958b3e3b5f23044c1b73b7e8d..bbfee62d29c0ef5e2896dfa530d1bc449108d9e3 100644
--- a/game/modules/tome/class/generator/map/VaultLevel.lua
+++ b/game/modules/tome/class/generator/map/VaultLevel.lua
@@ -65,7 +65,8 @@ function _M:generate(lev, old_lev)
 			end
 		end end
 		-- search for ways into the vault, always including the vault connection point
-		local possible_entrances = {{x=ret.cx, y=ret.cy}}
+		local possible_entrances_backup = {{x=ret.cx, y=ret.cy}}
+		local possible_entrances = {}
 		for i = rx, rx + room.w -1 do
 			local y1, y2 = ry, ry + room.h - 1
 			if self.map:checkEntity(i, y1, Map.TERRAIN, "is_door") then possible_entrances[#possible_entrances+1] = {x=i, y=y1, sx=i, sy=util.bound(y1-entry_path_length, 0, self.map.h-1)} end
@@ -74,28 +75,30 @@ function _M:generate(lev, old_lev)
 		for j = ry, ry + room.h - 1 do
 			local x1, x2 = rx, rx + room.w - 1
 			if self.map:checkEntity(x1, j, Map.TERRAIN, "is_door") then possible_entrances[#possible_entrances+1] = {x=x1, y=j, sx=util.bound(x1-entry_path_length, 0, self.map.w-1), sy=j} end
-			if self.map:checkEntity(x2, j, Map.TERRAIN, "is_door") then possible_entrances[#possible_entrances+1] = {x=x2, y=j, sx=util.bound(x1+entry_path_length, 0, self.map.w-1), sy=j} end
+			if self.map:checkEntity(x2, j, Map.TERRAIN, "is_door") then possible_entrances[#possible_entrances+1] = {x=x2, y=j, sx=util.bound(x2+entry_path_length, 0, self.map.w-1), sy=j} end
 		end
 		if #possible_entrances > 0 then
 			local e = rng.table(possible_entrances)
 			sx, sy = e.sx, e.sy
 			ex, ey = e.x, e.y
-			if not (sx and sy) then -- vault connection point: connect to a (nearest) non-vault grid
-				print("[VaultLevel] generating level start point from vault connection at", ex, ey)
-				-- start at center of room and pick a random direction
-				sx, sy = math.floor(rx+(room.w-1)/2), math.floor(ry+(room.h-1)/2)
-				local dir, xd, yd = util.getDir(ex, ey, sx+rng.normal(0, 1), sy+rng.normal(0, 1))
-				if dir == 5 then dir = rng.table(util.primaryDirs()) end
-				local rm
-				local steps = math.max(room.w, room.h) + 2*(room.border or 0) + 1
-				repeat
-					steps = steps - 1
-					rm = self.map.room_map[sx][sy]
-					sx, sy = util.coordAddDir(sx, sy, dir)
-					if not (rm.room or rm.border) then entry_path_length = entry_path_length - 1 end
-				until steps <= 0 or entry_path_length <= 0 or not self.map:isBound(sx, sy)
-				sx, sy = util.bound(sx, 0, self.map.w-1), util.bound(sy, 0, self.map.h-1)
-			end
+		elseif #possible_entrances_backup > 0 then
+			local e = rng.table(possible_entrances_backup)
+			ex, ey = e.x, e.y
+			-- vault connection point: connect to a (nearest) non-vault grid
+			print("[VaultLevel] generating level start point from vault connection at", ex, ey)
+			-- start at center of room and pick a random direction
+			sx, sy = math.floor(rx+(room.w-1)/2), math.floor(ry+(room.h-1)/2)
+			local dir, xd, yd = util.getDir(ex, ey, sx+rng.normal(0, 1), sy+rng.normal(0, 1))
+			if dir == 5 then dir = rng.table(util.primaryDirs()) end
+			local rm
+			local steps = math.max(room.w, room.h) + 2*(room.border or 0) + 1
+			repeat
+				steps = steps - 1
+				rm = self.map.room_map[sx][sy]
+				sx, sy = util.coordAddDir(sx, sy, dir)
+				if not (rm.room or rm.border) then entry_path_length = entry_path_length - 1 end
+			until steps <= 0 or entry_path_length <= 0 or not self.map:isBound(sx, sy)
+			sx, sy = util.bound(sx, 0, self.map.w-1), util.bound(sy, 0, self.map.h-1)
 		else
 			ex, ey = ret.cx, ret.cy
 		end
diff --git a/game/modules/tome/data/general/events/sub-vault.lua b/game/modules/tome/data/general/events/sub-vault.lua
index 0c1569c5ce78c21023bbe28e71858b33894bca54..0c76a53ddf53ab3abac11b61fe69ccfb3cf83194 100644
--- a/game/modules/tome/data/general/events/sub-vault.lua
+++ b/game/modules/tome/data/general/events/sub-vault.lua
@@ -18,7 +18,10 @@
 -- darkgod@te4.org
 
 -- Find a random spot
-local x, y = game.state:findEventGrid(level)
+local x, y
+local spot = level:pickSpotRemove{type="event-spot", subtype="subvault-place"}
+if spot then x, y = spot.x, spot.y
+else x, y = game.state:findEventGrid(level) end
 if not x then return false end
 local id = "sub-vault"..game.turn.."-"..rng.range(1,9999)
 print("[EVENT] Placing event", id, "at", x, y)
diff --git a/game/modules/tome/data/maps/zones/prides.lua b/game/modules/tome/data/maps/zones/prides.lua
index 487976fc4a3c6371a5ce19cedc393f5d35f96325..fb816fa9bbedcfc5bb9d337257777e4981b6929b 100644
--- a/game/modules/tome/data/maps/zones/prides.lua
+++ b/game/modules/tome/data/maps/zones/prides.lua
@@ -23,7 +23,7 @@ endx = 0
 endy = 32
 
 subGenerator{
-	x = 8, y = 8, w = 48, h = 48,
+	x = 7, y = 7, w = 48-2*8+2, h = 48-2*8+2,
 	generator = data.sublevel.class,
 	data = table.clone(data.sublevel),
 }
@@ -55,67 +55,51 @@ defineTile('*', generic_leveler_door, nil, nil, nil, {lever_action=2, lever_acti
 
 -- ASCII map section
 return [[
-################################################################
-################################################################
-########################..g...##################################
-########################&#g......###############################
-########################..g...##.###############################
-################################.###############################
-################################.###############################
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-###;;;#..................................................#ooo###
-##;;;##..................................................##...##
-##;;;##..................................................##...##
-;;;;;##..................................................##.....
-;;;;;#....................................................#.....
-; ;;;#....................................................#.....
-> ;;;*....................................................+O...<
-; ;;;#....................................................#.....
-;;;;;#....................................................#.....
-;;;;;##..................................................##.....
-##;;;##..................................................##...##
-##;;;##..................................................##...##
-###;;;#..................................................#ooo###
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-#######..................................................#######
-################################.###############################
-################################.###############################
-########################..g...##.###############################
-########################&#g......###############################
-########################..g...##################################
-################################################################
-################################################################]]
\ No newline at end of file
+################################################
+################################################
+################..g...##########################
+################&#g......#######################
+################..g...##.#######################
+########################.#######################
+########################.#######################
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+###;;;#..................................#ooo###
+##;;;##..................................##...##
+##;;;##..................................##...##
+;;;;;##..................................##.....
+;;;;;#....................................#.....
+; ;;;#....................................#.....
+> ;;;*....................................+O...<
+; ;;;#....................................#.....
+;;;;;#....................................#.....
+;;;;;##..................................##.....
+##;;;##..................................##...##
+##;;;##..................................##...##
+###;;;#..................................#ooo###
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+#######..................................#######
+########################.#######################
+########################.#######################
+################..g...##.#######################
+################&#g......#######################
+################..g...##########################
+################################################
+################################################]]
\ No newline at end of file
diff --git a/game/modules/tome/data/mapscripts/maps/pride-outer.lua b/game/modules/tome/data/mapscripts/maps/pride-outer.lua
new file mode 100644
index 0000000000000000000000000000000000000000..d287989b296fa9558558052511960c27bd02cadb
--- /dev/null
+++ b/game/modules/tome/data/mapscripts/maps/pride-outer.lua
@@ -0,0 +1,49 @@
+return [[
+################################################
+################################################
+################..o...##########################
+################&#o......#######################
+################..o...##.#######################
+########################.#######################
+########################.#######################
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+###---#                                  #ooo###
+##---##                                  ##...##
+##---##                                  ##...##
+-----##                                  ##.....
+-----#;                                  ;#.....
+-X---#;                                  ;#.....
+>X---*;                                  ;+O...<
+-X---#;                                  ;#.....
+-----#;                                  ;#.....
+-----##                                  ##.....
+##---##                                  ##...##
+##---##                                  ##...##
+###---#                                  #ooo###
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+#######                                  #######
+########################.#######################
+########################.#######################
+################..o...##.#######################
+################&#o......#######################
+################..o...##########################
+################################################
+################################################]]
\ No newline at end of file
diff --git a/game/modules/tome/data/zones/rak-shor-pride/zone.lua b/game/modules/tome/data/zones/rak-shor-pride/zone.lua
index e626ad70163f694b8a6f70d698bf09ba5844400f..3dcb8da51a2ab896a06c7934efd11f328872f14a 100644
--- a/game/modules/tome/data/zones/rak-shor-pride/zone.lua
+++ b/game/modules/tome/data/zones/rak-shor-pride/zone.lua
@@ -27,6 +27,7 @@ return {
 	width = 50, height = 50,
 	persistent = "zone",
 --	all_remembered = true,
+	no_level_connectivity = true,
 	all_lited = true,
 	day_night = true,
 	ambient_music = {"March.ogg", "weather/desert_base.ogg"},
diff --git a/game/modules/tome/data/zones/vor-pride/events.lua b/game/modules/tome/data/zones/vor-pride/events.lua
index a7020c3f7e073ca8650b8566b835fb66e7226674..1c3247fa3e12198f0b2848f387cb70c7a60f23b0 100644
--- a/game/modules/tome/data/zones/vor-pride/events.lua
+++ b/game/modules/tome/data/zones/vor-pride/events.lua
@@ -22,4 +22,5 @@ return { one_per_level=true,
 	{name="fell-aura", minor=true, percent=100, max_repeat=3},
 	{name="spellblaze-scar", minor=true, percent=50},
 	{name="glowing-chest", minor=true, percent=30},
+	{name="sub-vault", minor=true, percent=100, max_repeat=3},
 }
diff --git a/game/modules/tome/data/zones/vor-pride/grids.lua b/game/modules/tome/data/zones/vor-pride/grids.lua
index b27e55fcdf9db827dbb4c2a134c2bc8eaa8f951f..ed4ce8fbb6df62f5fc9361e3ebfd035130bb5b3d 100644
--- a/game/modules/tome/data/zones/vor-pride/grids.lua
+++ b/game/modules/tome/data/zones/vor-pride/grids.lua
@@ -17,6 +17,11 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
-load("/data/general/grids/basic.lua")
+load("/data/general/grids/basic.lua", function(e)
+	if e.define_as ~= "FLOOR" and e.image == "terrain/marble_floor.png" then
+		e.image = "terrain/grass_burnt1.png"
+	end
+end)
 load("/data/general/grids/forest.lua")
 load("/data/general/grids/water.lua")
+load("/data/general/grids/burntland.lua")
diff --git a/game/modules/tome/data/zones/vor-pride/mapscripts/main.lua b/game/modules/tome/data/zones/vor-pride/mapscripts/main.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f07948668b750b1e3cfb419dfb602cbde0044fd8
--- /dev/null
+++ b/game/modules/tome/data/zones/vor-pride/mapscripts/main.lua
@@ -0,0 +1,57 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009 - 2019 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
+
+self:defineTile("-", "BURNT_GROUND", nil, nil, nil, {no_teleport=true})
+self:defineTile("o", "FLOOR", nil, {entity_mod=function(e) e.make_escort = nil return e end, random_filter={type='humanoid', subtype='orc', special=function(e) return e.pride == mapdata.pride end}})
+if level.level == zone.max_level then self:defineTile(">", 'BURNT_GROUND') else self:defineTile(">", "FLAT_DOWN4", nil, nil, nil, {no_teleport=true}) end
+self:defineTile("O", "FLOOR", nil, {random_filter={type='humanoid', subtype='orc', special=function(e) return e.pride == mapdata.pride end, random_boss={nb_classes=1, loot_quality="store", loot_quantity=3, ai_move="move_complex", rank=4,}}})
+self:defineTile("X", "BURNT_GROUND", nil, {entity_mod=function(e) e.make_escort = nil return e end, random_filter={type='humanoid', subtype='orc', special=function(e) return e.pride == mapdata.pride end, random_boss={nb_classes=1, loot_quality="store", loot_quantity=1, no_loot_randart=true, ai_move="move_complex", rank=3}}}, nil, {no_teleport=true})
+self:defineTile('&', "GENERIC_LEVER", nil, nil, nil, {lever=1, lever_kind="pride-doors", lever_spot={type="lever", subtype="door", check_connectivity="entrance"}}, {type="lever", subtype="lever", check_connectivity="entrance"})
+self:defineTile('*', "GENERIC_LEVER_DOOR", nil, nil, nil, {lever_action=2, lever_action_value=0, lever_action_kind="pride-doors"}, {type="lever", subtype="door", check_connectivity="entrance"})
+
+local wfc = WaveFunctionCollapse.new{
+	mode="overlapping",
+	sample=self:getFile("!buildings.tmx", "samples"),
+	size={34, 34},
+	n=3, symmetry=8, periodic_out=false, periodic_in=false, has_foundation=false
+}
+local outer = Static.new(self:getFile("pride-outer.lua", "mapscripts/maps"))
+tm:merge(1, 1, outer, merge_order)
+tm:merge(8, 8, wfc, merge_order)
+
+-- Find rooms
+local rooms = tm:findGroupsOf{'r'}
+tm:applyOnGroups(rooms, function(room, idx)
+	tm:fillGroup(room, '.')
+	local border = tm:getBorderGroup(room)
+	local door = tm:pickGroupSpot(border, "inside-wall")
+	if door then tm:put(door, '+') end
+
+	local event = tm:pickGroupSpot(room, "any")
+	if event then self:addSpot(event, "event-spot", "subvault-place") end
+end)
+
+-- Complete the map by putting wall in all the remaining blank spaces
+tm:fillAll()
+
+-- if tm:eliminateByFloodfill{'#', 'T'} < 400 then return self:regenerate() end
+
+tm:printResult()
+
+return tm
diff --git a/game/modules/tome/data/zones/vor-pride/samples/buildings.tmx b/game/modules/tome/data/zones/vor-pride/samples/buildings.tmx
new file mode 100644
index 0000000000000000000000000000000000000000..792fc5754b56ff8783efdbd02afaaa1e2b5bb3ab
--- /dev/null
+++ b/game/modules/tome/data/zones/vor-pride/samples/buildings.tmx
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" tiledversion="1.0.3" orientation="orthogonal" renderorder="right-down" width="22" height="22" tilewidth="32" tileheight="32" nextobjectid="31">
+ <properties>
+  <property name="status_all" value="{no_teleport=true}"/>
+ </properties>
+ <tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90" columns="9">
+  <image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
+  <tile id="0">
+   <properties>
+    <property name="id" value="#"/>
+   </properties>
+  </tile>
+  <tile id="3">
+   <properties>
+    <property name="id" value="+"/>
+   </properties>
+  </tile>
+  <tile id="51">
+   <properties>
+    <property name="id" value="."/>
+   </properties>
+  </tile>
+  <tile id="54">
+   <properties>
+    <property name="id" value="_"/>
+   </properties>
+  </tile>
+  <tile id="58">
+   <properties>
+    <property name="id" value="r"/>
+   </properties>
+  </tile>
+ </tileset>
+ <tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144" columns="8">
+  <image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
+  <tile id="24">
+   <objectgroup draworder="index">
+    <properties>
+     <property name="id" value=";"/>
+    </properties>
+   </objectgroup>
+  </tile>
+  <tile id="100">
+   <objectgroup draworder="index">
+    <properties>
+     <property name="id" value="T"/>
+    </properties>
+   </objectgroup>
+  </tile>
+ </tileset>
+ <tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36" columns="6">
+  <image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
+ </tileset>
+ <layer name="Terrain" width="22" height="22">
+  <data encoding="base64" compression="zlib">
+   eJytlFEOwCAIQ9m5PJYH5GrLEj9I0w4wfPChka7WN7aZ7VMP1B6qT2uditoezrjoU3pMd9ov5oD54F3c/r0rv7i/fnQr+TrZ7+QU+xkPmM8UL1E346HLy4TH7B0UJ8qrYqn7H6kz7M0ZT9jDcs78Kt3O/VhO1cwn2Kt8l91nkv0FuirDKj9KVzFSnRmZp1vdSka3Oag1mztTb9qdOy+yNJzT
+  </data>
+ </layer>
+</map>
diff --git a/game/modules/tome/data/zones/vor-pride/zone.lua b/game/modules/tome/data/zones/vor-pride/zone.lua
index 7e84cc1cf1b3cdfa4a82f18eabeccbd50c5ca666..d4a4f4a0bb632ec3d4f0af1a57b01fa112d07c5b 100644
--- a/game/modules/tome/data/zones/vor-pride/zone.lua
+++ b/game/modules/tome/data/zones/vor-pride/zone.lua
@@ -24,44 +24,46 @@ return {
 	max_level = 3,
 	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 = 64, height = 64,
+	width = 48, height = 48,
 	persistent = "zone",
---	all_remembered = true,
+	-- all_remembered = true,
 	all_lited = true,
 	day_night = true,
 	ambient_music = "Breaking the siege.ogg",
 	min_material_level = 4,
 	max_material_level = 5,
-	no_level_connectivity = true,
 	effects = {"EFF_ZONE_AURA_VOR"},
 	generator =  {
 		map = {
-			class = "engine.generator.map.Static",
-			map = "zones/prides",
-			up = "FLAT_UP6",
-			down = "FLAT_DOWN4",
-			sublevel = {
-				class = "engine.generator.map.Town",
-				pride = "vor",
-				building_chance = 70,
-				max_building_w = 8, max_building_h = 8,
-				edge_entrances = {6,4},
-				floor = "FLOOR",
-				external_floor = "FLOOR",
-				wall = "WALL",
-				door = "DOOR",
-				up = "FLOOR",
-				down = "FLOOR",
+			class = "engine.generator.map.MapScript",
+			['<'] = "FLAT_UP6", ['>'] = "FLAT_DOWN4",
+			['.'] = "FLOOR", ['+'] = "DOOR", ['#'] = "WALL",
+			['_'] = "FLOOR", ['O'] = "WALL", 
+			[';'] = "BURNT_GROUND", ['T'] = "BURNT_TREE",
+			['='] = "DEEP_WATER",
+			door = "DOOR",
+			mapscript = "!main",
+			pride = "vor",
 
-				nb_rooms = {1,1,2,3},
-				rooms = {"lesser_vault", "greater_vault"},
-				lesser_vaults_list = {"orc-armoury", "double-t", "circle", "hostel", "orc-necromancer", "horror-chamber"},
-				lite_room_chance = 100,
-			},
+				-- class = "engine.generator.map.Town",
+				-- building_chance = 70,
+				-- max_building_w = 6, max_building_h = 6,
+				-- edge_entrances = {6,4},
+				-- floor = "FLOOR",
+				-- external_floor = "FLOOR",
+				-- wall = "WALL",
+				-- door = "DOOR",
+				-- up = "FLOOR",
+				-- down = "FLOOR",
+
+				-- nb_rooms = {1,1,2,3},
+				-- rooms = {"lesser_vault", "greater_vault"},
+				-- lesser_vaults_list = {"orc-armoury", "double-t", "circle", "hostel", "orc-necromancer", "horror-chamber"},
+				-- lite_room_chance = 100,
 		},
 		actor = {
 			class = "mod.class.generator.actor.Random",
-			nb_npc = {50, 60},
+			nb_npc = {35, 40},
 			guardian = "VOR",
 		},
 		object = {
@@ -79,12 +81,7 @@ return {
 	{
 		[1] = {
 			generator = { map = {
-				up = "FLAT_UP_WILDERNESS",
-			}, },
-		},
-		[3] = {
-			generator = { map = {
-				down = "FLAT_UP_WILDERNESS",
+				['<'] = "FLAT_UP_WILDERNESS",
 			}, },
 		},
 	},