diff --git a/game/engines/default/engine/Game.lua b/game/engines/default/engine/Game.lua
index 18b0bc25780e67fc90d111c67d59bfd89a626a4a..b74966e81735b0be8dfd51a162950d8b0a058810 100644
--- a/game/engines/default/engine/Game.lua
+++ b/game/engines/default/engine/Game.lua
@@ -54,6 +54,7 @@ function _M:loaded()
 	self.mouse:setCurrent()
 
 	self.__threads = self.__threads or {}
+	self.__coroutines = self.__coroutines or {}
 end
 
 --- Defines the default fields to be saved by the savefile code
@@ -113,6 +114,25 @@ end
 
 --- This is the "main game loop", do something here
 function _M:tick()
+	local stop = {}
+	local id, co = next(self.__coroutines)
+	while id do
+		local ok, err = coroutine.resume(co)
+		if not ok then
+			print(debug.traceback(co))
+			print("[COROUTINE] error", err)
+		end
+		if coroutine.status(co) == "dead" then
+			stop[#stop+1] = id
+		end
+		id, co = next(self.__coroutines, id)
+	end
+	if #stop > 0 then
+		for i = 1, #stop do
+			self.__coroutines[stop[i]] = nil
+			print("[COROUTINE] dead", stop[i])
+		end
+	end
 end
 
 --- Called when a zone leaves a level
@@ -213,6 +233,35 @@ end
 function _M:saveGame()
 end
 
+--- Add a coroutine to the pool
+-- Coroutines registered will be run each game tick
+function _M:registerCoroutine(id, co)
+	print("[COROUTINE] registering", id, co)
+	self.__coroutines[id] = co
+end
+
+--- Get the coroutine corresponding to the id
+function _M:getCoroutine(id)
+	return self.__coroutines[id]
+end
+
+--- Ask a registered coroutine to cancel
+-- The coroutine must accept a "cancel" action
+function _M:cancelCoroutine(id)
+	local co = self.__coroutines[id]
+	if not co then return end
+	local ok, err = coroutine.resume(co, "cancel")
+	if not ok then
+		print(debug.traceback(co))
+		print("[COROUTINE] error", err)
+	end
+	if coroutine.status(co) == "dead" then
+		self.__coroutines[id] = nil
+	else
+		error("Told coroutine "..id.." to cancel, but it is not dead!")
+	end
+end
+
 --- Save a thread into the thread pool
 -- Threads will be auto join'ed when the module exits or when it can<br/>
 -- ALL THREADS registered *MUST* return true when they exit
diff --git a/game/engines/default/engine/GameEnergyBased.lua b/game/engines/default/engine/GameEnergyBased.lua
index ce6d2622e4073f74c27d97cc3ef5cc46b139e52c..00ff9ec67d2d67dd029f71ac5fef8b5c41c635f5 100644
--- a/game/engines/default/engine/GameEnergyBased.lua
+++ b/game/engines/default/engine/GameEnergyBased.lua
@@ -63,7 +63,7 @@ function _M:tick()
 		for i = 1, #arr do
 			e = arr[i]
 			if e and e.act and e.energy then
---				print("<ENERGY", e.name, e.uid, "::", e.energy.value, game.paused, "::", e.player)
+--				print("<ENERGY", e.name, e.uid, "::", e.energy.value, self.paused, "::", e.player)
 				if e.energy.value < self.energy_to_act then
 					e.energy.value = (e.energy.value or 0) + self.energy_per_tick * (e.energy.mod or 1)
 				end
@@ -71,7 +71,7 @@ function _M:tick()
 					e.energy.used = false
 					e:act(self)
 				end
---				print(">ENERGY", e.name, e.uid, "::", e.energy.value, game.paused, "::", e.player)
+--				print(">ENERGY", e.name, e.uid, "::", e.energy.value, self.paused, "::", e.player)
 			end
 		end
 	end
diff --git a/game/engines/default/engine/GameTurnBased.lua b/game/engines/default/engine/GameTurnBased.lua
index 27c30e187e5404e49a974d709eb191aa99bd3afd..8fecc3b36e8c4b029d9216d254254f541a502c23 100644
--- a/game/engines/default/engine/GameTurnBased.lua
+++ b/game/engines/default/engine/GameTurnBased.lua
@@ -37,7 +37,7 @@ end
 function _M:tick()
 	if self.paused then
 		-- Auto unpause if the player has no energy to act
-		if game:getPlayer() and not game:getPlayer():enoughEnergy() then game.paused = false end
+		if self:getPlayer() and not self:getPlayer():enoughEnergy() then self.paused = false end
 
 		-- If we are paused do not get energy, but still process frames if needed
 		engine.Game.tick(self)
diff --git a/game/engines/default/engine/Savefile.lua b/game/engines/default/engine/Savefile.lua
index bcb5e956cb336dca3360644a7c0102857a22c59d..1ec4b3ecc9ce24aeabf13806d9f1cab8b1e71493 100644
--- a/game/engines/default/engine/Savefile.lua
+++ b/game/engines/default/engine/Savefile.lua
@@ -92,25 +92,36 @@ function _M:saveObject(obj, zip)
 		local tbl = table.remove(self.process)
 		self.tables[tbl] = self:getFileName(tbl)
 		zip:add(self:getFileName(tbl), tbl:save())
+		-- If run from a coroutine, we pause every object
+		if coroutine.running() then
+			local coret = coroutine.yield()
+			if coret and type(coret) == "string" and coret == "cancel" then
+				print("[SAVE] abording")
+				break
+			end
+		end
 	end
 	return self.tables[obj]
 end
 
 --- Save the given world
-function _M:saveWorld(world)
+function _M:saveWorld(world, no_dialog)
 	collectgarbage("collect")
 
 	fs.mkdir(self.save_dir)
 
-	local popup = Dialog:simplePopup("Saving world", "Please wait while saving the world...")
-	popup.__showup = nil
+	local popup
+	if not no_dialog then
+		popup = Dialog:simplePopup("Saving world", "Please wait while saving the world...")
+		popup.__showup = nil
+	end
 	core.display.forceRedraw()
 
 	local zip = fs.zipOpen(self.save_dir.."world.teaw")
 	self:saveObject(world, zip)
 	zip:close()
 
-	game:unregisterDialog(popup)
+	if not no_dialog then game:unregisterDialog(popup) end
 end
 
 --- Save the given birth descriptors, used for quick start
@@ -142,13 +153,16 @@ function _M:loadQuickBirth()
 end
 
 --- Save the given game
-function _M:saveGame(game)
+function _M:saveGame(game, no_dialog)
 	collectgarbage("collect")
 
 	fs.mkdir(self.save_dir)
 
-	local popup = Dialog:simplePopup("Saving game", "Please wait while saving the game...")
-	popup.__showup = nil
+	local popup
+	if not no_dialog then
+		popup = Dialog:simplePopup("Saving game", "Please wait while saving the game...")
+		popup.__showup = nil
+	end
 	core.display.forceRedraw()
 
 	local zip = fs.zipOpen(self.save_dir.."game.teag")
@@ -162,37 +176,43 @@ function _M:saveGame(game)
 	f:write(("description = %q\n"):format(desc.description))
 	f:close()
 
-	game:unregisterDialog(popup)
+	if not no_dialog then game:unregisterDialog(popup) end
 end
 
 --- Save a zone
-function _M:saveZone(zone)
+function _M:saveZone(zone, no_dialog)
 	fs.mkdir(self.save_dir)
 
-	local popup = Dialog:simplePopup("Saving zone", "Please wait while saving the zone...")
-	popup.__showup = nil
+	local popup
+	if not no_dialog then
+		popup = Dialog:simplePopup("Saving zone", "Please wait while saving the zone...")
+		popup.__showup = nil
+	end
 	core.display.forceRedraw()
 
 	local zip = fs.zipOpen(self.save_dir..("zone-%s.teaz"):format(zone.short_name))
 	self:saveObject(zone, zip)
 	zip:close()
 
-	game:unregisterDialog(popup)
+	if not no_dialog then game:unregisterDialog(popup) end
 end
 
 --- Save a level
-function _M:saveLevel(level)
+function _M:saveLevel(level, no_dialog)
 	fs.mkdir(self.save_dir)
 
-	local popup = Dialog:simplePopup("Saving level", "Please wait while saving the level...")
-	popup.__showup = nil
+	local popup
+	if not no_dialog then
+		popup = Dialog:simplePopup("Saving level", "Please wait while saving the level...")
+		popup.__showup = nil
+	end
 	core.display.forceRedraw()
 
 	local zip = fs.zipOpen(self.save_dir..("level-%s-%d.teal"):format(level.data.short_name, level.level))
 	self:saveObject(level, zip)
 	zip:close()
 
-	game:unregisterDialog(popup)
+	if not no_dialog then game:unregisterDialog(popup) end
 end
 
 local function resolveSelf(o, base, allow_object)
diff --git a/game/engines/default/engine/class.lua b/game/engines/default/engine/class.lua
index 50a696abe74c6bb83fbd9c3982294581c4aeaca5..91a73719a0260fe02adac161f8e325e4d5c295a4 100644
--- a/game/engines/default/engine/class.lua
+++ b/game/engines/default/engine/class.lua
@@ -108,6 +108,34 @@ function _M:clone(t)
 	return n
 end
 
+
+local function clonerecursfull(clonetable, d)
+	local n = {}
+	clonetable[d] = n
+
+	for k, e in pairs(d) do
+		local nk, ne = k, e
+
+		if clonetable[k] then nk = clonetable[k]
+		elseif type(k) == "table" then nk = clonerecursfull(clonetable, k)
+		end
+
+		if clonetable[e] then ne = clonetable[e]
+		elseif type(e) == "table" then ne = clonerecursfull(clonetable, e)
+		end
+		n[nk] = ne
+	end
+	setmetatable(n, getmetatable(d))
+	return n
+end
+
+--- Clones the object, all subobjects without cloning twice a subobject
+function _M:cloneFull()
+	local clonetable = {}
+	local n = clonerecursfull(clonetable, self)
+	return n
+end
+
 --- Replaces the object with an other, by copying (not deeply)
 function _M:replaceWith(t)
 	-- Delete fields
diff --git a/game/engines/default/engine/interface/ActorProject.lua b/game/engines/default/engine/interface/ActorProject.lua
index 49997ad03146bf78c671ba288c1a9fca1480f673..f9543ffe316a337403180e08ab51ac50170a402a 100644
--- a/game/engines/default/engine/interface/ActorProject.lua
+++ b/game/engines/default/engine/interface/ActorProject.lua
@@ -74,14 +74,14 @@ function _M:project(t, x, y, damtype, dam, particles)
 	-- Ok if we are at the end reset lx and ly for the next code
 	if not lx and not ly then lx, ly = x, y end
 
-	if typ.ball then
+	if typ.ball and typ.ball > 0 then
 		core.fov.calc_circle(lx, ly, typ.ball, function(_, px, py)
 			-- Deal damage: ball
 			addGrid(px, py)
 			if not typ.no_restrict and game.level.map:checkEntity(px, py, Map.TERRAIN, "block_move") then return true end
 		end, function()end, nil)
 		addGrid(lx, ly)
-	elseif typ.cone then
+	elseif typ.cone and typ.cone > 0 then
 		core.fov.calc_beam(lx, ly, typ.cone, initial_dir, typ.cone_angle, function(_, px, py)
 			-- Deal damage: cone
 			addGrid(px, py)
@@ -247,14 +247,14 @@ function _M:projectDoStop(typ, tg, damtype, dam, particles, lx, ly, tmp)
 		grids[x][y] = true
 	end
 
-	if typ.ball then
+	if typ.ball and typ.ball > 0 then
 		core.fov.calc_circle(lx, ly, typ.ball, function(_, px, py)
 			-- Deal damage: ball
 			addGrid(px, py)
 			if not typ.no_restrict and game.level.map:checkEntity(px, py, Map.TERRAIN, "block_move") then return true end
 		end, function()end, nil)
 		addGrid(lx, ly)
-	elseif typ.cone then
+	elseif typ.cone and typ.cone > 0 then
 		local initial_dir = lx and util.getDir(lx, ly, x, y) or 5
 		core.fov.calc_beam(lx, ly, typ.cone, initial_dir, typ.cone_angle, function(_, px, py)
 			-- Deal damage: cone
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index c41ae4112d2337c319d88be44bd66af1d7e232ed..8cf98ad33f8cb0a3f2acea2e8ec6c99b9c63e29e 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -376,7 +376,7 @@ function _M:tick()
 		-- (since display is on a set FPS while tick() ticks as much as possible
 		-- engine.GameEnergyBased.tick(self)
 	end
-	if game.paused then return true end
+	if self.paused and not self.saving then return true end
 end
 
 --- Called every game turns
diff --git a/game/modules/tome/class/PlayerDisplay.lua b/game/modules/tome/class/PlayerDisplay.lua
index ab1283c070cceaa6273ce280ff1a685bfd1875a6..fad6545c5ebfe85d843e1e0b69272b1b91c32620 100644
--- a/game/modules/tome/class/PlayerDisplay.lua
+++ b/game/modules/tome/class/PlayerDisplay.lua
@@ -109,6 +109,8 @@ function _M:display()
 		self.surface:drawColorStringBlended(self.font, ("#904010#Vim:     #ffffff#%d/%d"):format(player:getVim(), player.max_vim), 0, h, 255, 255, 255) h = h + self.font_h
 	end
 
+	if game.saving then h = h + self.font_h self.surface:drawColorStringBlended(self.font, "#YELLOW#Saving...", 0, h, 255, 255, 255) h = h + self.font_h end
+
 	h = h + self.font_h
 	for tid, act in pairs(player.sustain_talents) do
 		if act then self.surface:drawColorStringBlended(self.font, ("#LIGHT_GREEN#%s"):format(player:getTalentFromId(tid).name), 0, h, 255, 255, 255) h = h + self.font_h end
diff --git a/game/modules/tome/class/World.lua b/game/modules/tome/class/World.lua
index ea2cc0b54e06247c32dad1ac9eb4628afc84cafe..05cf3d30e42c81ab5d558d0bd50338d8a24dc62a 100644
--- a/game/modules/tome/class/World.lua
+++ b/game/modules/tome/class/World.lua
@@ -33,9 +33,9 @@ function _M:run()
 end
 
 --- Requests the world to save
-function _M:saveWorld()
+function _M:saveWorld(no_dialog)
 	local save = Savefile.new("")
-	save:saveWorld(self)
+	save:saveWorld(self, no_dialog)
 	save:close()
 	game.log("Saved world.")
 end
diff --git a/game/modules/tome/data/talents/corruptions/plague.lua b/game/modules/tome/data/talents/corruptions/plague.lua
new file mode 100644
index 0000000000000000000000000000000000000000..4584d65dc782d83385acf798af9623592d0f35a3
--- /dev/null
+++ b/game/modules/tome/data/talents/corruptions/plague.lua
@@ -0,0 +1,219 @@
+-- ToME - Tales of Middle-Earth
+-- Copyright (C) 2009, 2010 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
+
+newTalent{
+	name = "Virulent Disease",
+	type = {"corruption/plague", 1},
+	require = corrs_req1,
+	points = 5,
+	vim = 8,
+	cooldown = 3,
+	range = function(self, t) return 5 + math.floor(self:getTalentLevel(t) * 1.3) end,
+	action = function(self, t)
+		local tg = {type="bolt", range=self:getTalentRange(t)}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+
+		local diseases = {{self.EFF_WEAKNESS_DISEASE, "str"}, {self.EFF_ROTTING_DISEASE,"con"}, {self.EFF_DECREPITUDE_DISEASE,"dex"}}
+		local disease = rng.table(diseases)
+
+		-- Try to rot !
+		self:project(tg, x, y, function(px, py)
+			local target = game.level.map(px, py, engine.Map.ACTOR)
+			if not target then return end
+			if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 12 - self:getTalentLevel(t)) and target:canBe("disease") then
+				target:setEffect(disease[1], 6, {src=self, dam=self:combatTalentSpellDamage(t, 5, 45), [disease[2]]=self:combatTalentSpellDamage(t, 5, 25)})
+			else
+				game.logSeen(target, "%s resists the disease!", target.name:capitalize())
+			end
+			game.level.map:particleEmitter(px, py, 1, "slime")
+		end)
+		game:playSoundNear(self, "talents/slime")
+
+		return true
+	end,
+	info = function(self, t)
+		return ([[Fires a bolt of pure filth, diseasing your target with a random disease doing %0.2f blight damage per turns for 6 turns and reducing one of its physical stats (strength, constitution, dexterity) by %d.
+		The effect will increase with your Magic stat.]]):
+		format(self:combatTalentSpellDamage(t, 5, 45), self:combatTalentSpellDamage(t, 5, 25))
+	end,
+}
+
+newTalent{
+	name = "Cyst Burst",
+	type = {"corruption/plague", 2},
+	require = corrs_req2,
+	points = 5,
+	vim = 18,
+	cooldown = 9,
+	range = 15,
+	action = function(self, t)
+		local tg = {type="bolt", range=self:getTalentRange(t)}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+
+		local dam = self:combatTalentSpellDamage(t, 15, 85)
+		local diseases = {}
+
+		-- Try to rot !
+		local source = nil
+		self:project(tg, x, y, function(px, py)
+			local target = game.level.map(px, py, engine.Map.ACTOR)
+			if not target then return end
+
+			for eff_id, p in pairs(target.tmp) do
+				local e = target.tempeffect_def[eff_id]
+				if e.type == "disease" then
+					diseases[#diseases+1] = {id=eff_id, params=p}
+				end
+			end
+
+			if #diseases > 0 then
+				DamageType:get(DamageType.BLIGHT).projector(self, px, py, DamageType.BLIGHT, dam * #diseases)
+				game.level.map:particleEmitter(px, py, 1, "slime")
+			end
+			source = target
+		end)
+
+		if #diseases > 0 then
+			self:project({type="ball", radius=1, range=self:getTalentRange(t)}, x, y, function(px, py)
+				local target = game.level.map(px, py, engine.Map.ACTOR)
+				if not target or target == source or target == self then return end
+
+				local disease = rng.table(diseases)
+				target:setEffect(disease.id, 6, {src=self, dam=disease.params.dam, str=disease.params.str, dex=disease.params.dex, con=disease.params.con})
+				game.level.map:particleEmitter(px, py, 1, "slime")
+			end)
+		end
+		game:playSoundNear(self, "talents/slime")
+
+		return true
+	end,
+	info = function(self, t)
+		return ([[Make your target's diseases burst, doing %0.2f blight damage for each diseases it is infected with.
+		This will also spread the diseases to any nearby foes in a radius of 1.
+		The damage will increase with your Magic stat.]]):
+		format(self:combatTalentSpellDamage(t, 15, 85))
+	end,
+}
+
+newTalent{
+	name = "Catalepsy",
+	type = {"corruption/plague", 3},
+	require = corrs_req3,
+	points = 5,
+	vim = 35,
+	cooldown = 15,
+	range = 10,
+	action = function(self, t)
+		local tg = {type="ball", range=self:getTalentRange(t), radius=2}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+
+		local dur = math.floor(2 + self:getTalentLevel(t) / 2)
+
+		local source = nil
+		self:project(tg, x, y, function(px, py)
+			local target = game.level.map(px, py, engine.Map.ACTOR)
+			if not target then return end
+
+			-- List all diseas
+			local diseases = {}
+			for eff_id, p in pairs(target.tmp) do
+				local e = target.tempeffect_def[eff_id]
+				if e.type == "disease" then
+					diseases[#diseases+1] = {id=eff_id, params=p}
+				end
+			end
+			-- Make them EXPLODE !!!
+			for i, d in ipairs(diseases) do
+				target:removeEffect(d.id)
+				DamageType:get(DamageType.BLIGHT).projector(self, px, py, DamageType.BLIGHT, d.params.dam * d.params.dur)
+			end
+
+			if #diseases >  0 and target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 8) and target:canBe("stun") then
+				target:setEffect(target.EFF_STUNNED, dur, {})
+			else
+				game.logSeen(target, "%s resists the stun!", target.name:capitalize())
+			end
+			game.level.map:particleEmitter(px, py, 1, "slime")
+		end)
+		game:playSoundNear(self, "talents/slime")
+
+		return true
+	end,
+	info = function(self, t)
+		return ([[All your foes infected with a disease enter a catalepsy, stunning them for %d turns and dealing all the diseases remaining damage instantly.]]):
+		format(math.floor(2 + self:getTalentLevel(t) / 2))
+	end,
+}
+
+newTalent{
+	name = "Epidemic",
+	type = {"corruption/plague", 4},
+	require = corrs_req4,
+	points = 5,
+	vim = 20,
+	cooldown = 13,
+	range = 10,
+	do_spread = function(self, t, carrier)
+		-- List all diseas
+		local diseases = {}
+		for eff_id, p in pairs(carrier.tmp) do
+			local e = carrier.tempeffect_def[eff_id]
+			if e.type == "disease" then
+				diseases[#diseases+1] = {id=eff_id, params=p}
+			end
+		end
+
+		self:project({type="ball", radius=2}, carrier.x, carrier.y, function(px, py)
+			local target = game.level.map(px, py, engine.Map.ACTOR)
+			if not target or target == carrier or target == self then return end
+
+			local disease = rng.table(diseases)
+			target:setEffect(disease.id, 6, {src=self, dam=disease.params.dam, str=disease.params.str, dex=disease.params.dex, con=disease.params.con})
+			game.level.map:particleEmitter(px, py, 1, "slime")
+		end)
+	end,
+	action = function(self, t)
+		local tg = {type="bolt", range=self:getTalentRange(t)}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+
+		-- Try to rot !
+		self:project(tg, x, y, function(px, py)
+			local target = game.level.map(px, py, engine.Map.ACTOR)
+			if not target then return end
+			if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 12 - self:getTalentLevel(t)) and target:canBe("disease") then
+				target:setEffect(self.EFF_EPIDEMIC, 6, {src=self, dam=self:combatTalentSpellDamage(t, 15, 50)})
+			else
+				game.logSeen(target, "%s resists the disease!", target.name:capitalize())
+			end
+			game.level.map:particleEmitter(px, py, 1, "slime")
+		end)
+		game:playSoundNear(self, "talents/slime")
+
+		return true
+	end,
+	info = function(self, t)
+		return ([[Infects the target with a very contagious disease doing %0.2f damage per turn for 6 turns.
+		If any blight damage from non-diseases hit the target the epidemic will activate and spread diseases to nearby targets.]]):
+		format(self:combatTalentSpellDamage(t, 15, 50))
+	end,
+}