diff --git a/game/engines/default/engine/Map.lua b/game/engines/default/engine/Map.lua
index e2debc27f6ba612cb395630329e4e586f4a43ae7..e90daeb47c0d2aa5a48d3dbb102e2d5d67a72a41 100644
--- a/game/engines/default/engine/Map.lua
+++ b/game/engines/default/engine/Map.lua
@@ -1310,6 +1310,19 @@ function _M:processEffects(update_shape_only)
 	end
 end
 
+function _M:removeEffect(e)
+	if e.particles then
+		for j, ps in ipairs(e.particles) do self:removeParticleEmitter(ps) end
+	end
+	if e.overlay then
+		self.z_effects[e.overlay.zdepth][e] = nil
+	end
+	for i, ee in ipairs(self.effects) do if ee == e then
+		table.remove(self.effects, i)
+		break
+	end end
+end
+
 --- Returns the first effect matching the given damage type, if any
 function _M:hasEffectType(x, y, type)
 	for i, e in ipairs(self.effects) do
diff --git a/game/engines/default/engine/interface/GameTargeting.lua b/game/engines/default/engine/interface/GameTargeting.lua
index 32694d59a803ff2f7f666557376fea59f8417322..e2899db82ee83209fa7c07ce0ca68f701ef60307 100644
--- a/game/engines/default/engine/interface/GameTargeting.lua
+++ b/game/engines/default/engine/interface/GameTargeting.lua
@@ -186,7 +186,9 @@ function _M:targetMode(v, msg, co, typ)
 			end
 			if do_scan then
 				local filter = nil
-				if not (type(typ) == "table" and typ.no_first_target_filter) then
+				if typ.custom_scan_filter then
+					filter = typ.custom_scan_filter
+				elseif not (type(typ) == "table" and typ.no_first_target_filter) then
 					if type(typ) == "table" and typ.first_target and typ.first_target == "friend" then
 						filter = function(a) return self.player:reactionToward(a) >= 0 end
 					else
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 635111afbfd959c8391075ea137c3b9c019e686e..d5f84af155bf31fe17d89894ad7a02ab47617ac8 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -2968,8 +2968,10 @@ function _M:onTakeHit(value, src, death_note)
 	end
 
 	local cb = {value=value}
-	if self:fireTalentCheck("callbackOnHit", cb, src, death_note) then
-		value = cb.value
+	if self:fireTalentCheck("callbackOnHit", cb, src, death_note) then value = cb.value end
+	if self.summoner then
+		cb = {value=value}
+		if self.summoner:fireTalentCheck("callbackOnSummonHit", cb, self, src, death_note) then value = cb.value end
 	end
 
 	local hd = {"Actor:takeHit", value=value, src=src, death_note=death_note}
@@ -5912,6 +5914,7 @@ local sustainCallbackCheck = {
 	callbackOnTeleport = "talents_on_teleport",
 	callbackOnDealDamage = "talents_on_deal_damage",
 	callbackOnHit = "talents_on_hit",
+	callbackOnSummonHit = "talents_on_summon_hit",
 	callbackOnAct = "talents_on_act",
 	callbackOnActBase = "talents_on_act_base",
 	callbackOnActEnd = "talents_on_act_end",
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index 02e662572abd7df341ab5b4352feb4473e9734d8..91791c504a6bce4cf9033d7a6f94229bcb29793b 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -4298,3 +4298,13 @@ newDamageType{
 		return (realdam1 or 0) + (realdam2 or 0)
 	end,
 }
+
+newDamageType{
+	name = _t"putrescent liquefaction", type = "PUTRESCENT_LIQUEFACTION", text_color = "#OLIVE_DRAB#",
+	projector = function(src, x, y, type, dam, state)
+		state = initState(state)
+		useImplicitCrit(src, state)
+		local realdam = DamageType:get(DamageType.FROSTDUSK).projector(src, x, y, DamageType.FROSTDUSK, dam, state)
+		return realdam or 0
+	end,
+}
diff --git a/game/modules/tome/data/gfx/particles/pustulent_fulmination.lua b/game/modules/tome/data/gfx/particles/pustulent_fulmination.lua
new file mode 100644
index 0000000000000000000000000000000000000000..c67e318100eab1fb65936bf24518af46459693f1
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/pustulent_fulmination.lua
@@ -0,0 +1,111 @@
+-- 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
+
+--------------------------------------------------------------------------------------
+-- Advanced shaders
+--------------------------------------------------------------------------------------
+if core.shader.active(4) then
+use_shader = {type="fireflash"}
+base_size = 64
+
+local nb = 0
+
+local dir = 0
+if proj_x and src_x then
+	dir = math.deg(math.atan2(proj_y-src_y, proj_x-src_x))
+end
+
+return {
+	system_rotation = dir, system_rotationv = 0,
+	generator = function()
+	return {
+		life = 42,
+		--size = 30, sizev = 2.1*64*radius/16, sizea = 0,
+		size = 3.5*64*radius, sizev = 0, sizea = 0,
+
+		x = 0, xv = 0, xa = 0,
+		y = 0, yv = 0, ya = 0,
+		dir = 0, dirv = dirv, dira = 0,
+		vel = 0, velv = 0, vela = 0,
+
+		r = 1, rv = 0, ra = 0,
+		g = 1, gv = 0, ga = 0,
+		b = 1, bv = 0, ba = 0,
+		a = 1, av = 0, aa = 0,
+	}
+end, },
+function(self)
+	if nb < 1 then
+		self.ps:emit(1)
+	end
+	nb = nb + 1
+end,
+1, "particles_images/putrescent_pustules_fireflash"
+
+
+--------------------------------------------------------------------------------------
+-- Default
+--------------------------------------------------------------------------------------
+else
+local nb = 0
+return { generator = function()
+	local radius = radius
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(0, 360)
+	local a = math.rad(ad)
+	local r = rng.float(sradius - 5, sradius)
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local bx = math.floor(x / engine.Map.tile_w)
+	local by = math.floor(y / engine.Map.tile_h)
+	local static = rng.percent(40)
+
+	return {
+		trail = 1,
+		life = 24,
+		size = 3, sizev = static and 0.05 or 0.15, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = static and a + math.rad(90 - rng.range(10, 20)) or a, dirv = 0, dira = 0,
+		vel = static and -2 or 0.5 * (-1-nb) * radius / 2.7, velv = 0, vela = static and -0.01 or rng.float(-0.3, -0.2) * 0.3,
+
+		r = rng.range(0, 10)/255,      rv = 0, ra = 0,
+		g = rng.range(200, 255)/255,   gv = 0, ga = 0,
+		b = rng.range(120, 170)/255,   bv = 0, ba = 0,
+		a = rng.range(25, 220)/255,    av = static and -0.034 or 0, aa = 0.005,
+	}
+end, },
+function(self)
+	if nb < 5 then
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+	end
+end,
+5*radius*266
+
+end
diff --git a/game/modules/tome/data/gfx/particles_images/putrescent_pustules_fireflash.png b/game/modules/tome/data/gfx/particles_images/putrescent_pustules_fireflash.png
new file mode 100644
index 0000000000000000000000000000000000000000..fd213ef149d9f4d5d0740eb45c921a8d12042dd9
Binary files /dev/null and b/game/modules/tome/data/gfx/particles_images/putrescent_pustules_fireflash.png differ
diff --git a/game/modules/tome/data/talents/spells/master-of-bones.lua b/game/modules/tome/data/talents/spells/master-of-bones.lua
index 6240e0598307e524495b900a02e691479580a6b7..06d43e797ca2cafa429d0cdcb6aa74454cffc2da 100644
--- a/game/modules/tome/data/talents/spells/master-of-bones.lua
+++ b/game/modules/tome/data/talents/spells/master-of-bones.lua
@@ -178,7 +178,7 @@ newTalent{
 	radius = function(self, t) return self:getTalentRadius(self:getTalentFromId(self.T_NECROTIC_AURA)) end,
 	target = function(self, t) return {type="cone", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t} end,
 	getNb = function(self, t, ignore)
-		return math.max(1, math.floor(self:combatTalentScale(t, 1, 3, "log")))
+		return math.max(1, math.floor(self:combatTalentScale(t, 1, 2, "log")))
 	end,
 	getMax = function(self, t, ignore)
 		local max = math.max(1, math.floor(self:combatTalentScale(t, 1, 7)))
@@ -211,13 +211,13 @@ newTalent{
 			local pos = rng.tableRemove(possible_spots)
 			if pos then
 				if use_ressource then self:incSoul(-1) end
-				necroSetupSummon(self, self:getTalentLevel(t) >= 3 and t.minions_list.a_skel_warrior or t.minions_list.skel_warrior, pos.x, pos.y, lev, true)
+				necroSetupSummon(self, self:getTalentLevel(t) >= 3 and t.minions_list.a_skel_warrior or t.minions_list.skel_warrior, pos.x, pos.y, lev, nil, true)
 				self.__call_crypt_count = (self.__call_crypt_count or 0) + 1
 				if self.__call_crypt_count == 3 then
 					self.__call_crypt_count = 0
 					if self:getTalentLevel(t) >= 5 then
 						local pos = rng.tableRemove(possible_spots)
-						if pos then necroSetupSummon(self, rng.percent(50) and t.minions_list.skel_mage or t.minions_list.skel_m_archer, pos.x, pos.y, lev, true) end
+						if pos then necroSetupSummon(self, rng.percent(50) and t.minions_list.skel_mage or t.minions_list.skel_m_archer, pos.x, pos.y, lev, nil, true) end
 					end
 				end
 			end
@@ -228,10 +228,12 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Call upon the battlefields of all to collect bones and fuse them with souls, combining them to create skeletal minions to do your bidding.
+		return ([[Call upon the battle fields of old to collect bones and fuse them with souls, combining them to create skeletal minions to do your bidding.
 		Up to %d skeleton warriors of level %d are summoned. Up to %d skeletons can be controlled at once.
 		At level 3 the summons become armoured skeletons warriors.
 		At level 5 every 3 summoned warriors a free skeleton mage or skeleton archer is also created (without costing a soul).
+
+		#GREY##{italic}#Skeleton minions come in fewer numbers than ghoul minions but are generaly more durable.#{normal}#
 		]]):tformat(t:_getNb(self), math.max(1, self.level + t:_getLevel(self)), t:_getMax(self, true))
 	end,
 }
@@ -251,12 +253,17 @@ newTalent{
 		local block = function(_, lx, ly)
 			return game.level.map:checkAllEntities(lx, ly, "block_move")
 		end
-		return {type="wall", range=self:getTalentRange(t), halflength=halflength, talent=t, halfmax_spots=halflength+1, block_radius=block} 
+		return {
+			type="wall", range=self:getTalentRange(t), halflength=halflength, talent=t, halfmax_spots=halflength+1, block_radius=block,
+			nowarning=true, first_target="friend", custom_scan_filter=function(target)
+				return target.summoner == self and target.necrotic_minion and target.skeleton_minion
+			end,
+		}
 	end,
 	getChance = function(self, t) return math.floor(self:combatTalentScale(t, 20, 50)) end,
 	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	getLength = function(self, t) return 1 + math.floor(self:combatTalentScale(t, 3, 7)/2)*2 end,
-	getDamage = function(self, t) return self:combatTalentMindDamage(t, 3, 15) end,
+	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 3, 15) end,
 	on_pre_use = function(self, t) return necroArmyStats(self).nb_skeleton > 0 end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
@@ -491,7 +498,7 @@ newTalent{
 		elseif self:getTalentLevel(t) >= 3 then def = t.minions_list.h_bone_giant
 		end
 
-		necroSetupSummon(self, def, pos.x, pos.y, lev, true)
+		necroSetupSummon(self, def, pos.x, pos.y, lev, nil, true)
 
 		game:playSoundNear(self, "talents/spell_generic2")
 		return true
diff --git a/game/modules/tome/data/talents/spells/spells.lua b/game/modules/tome/data/talents/spells/spells.lua
index ec357ea0ea6822af970c831fdb1c0a50848f20bd..c72f8fde1908989e7c4e2be86f5fd47fa46c9d4a 100644
--- a/game/modules/tome/data/talents/spells/spells.lua
+++ b/game/modules/tome/data/talents/spells/spells.lua
@@ -145,13 +145,14 @@ function necroArmyStats(self)
 	return stats
 end
 
-function necroSetupSummon(self, def, x, y, level, no_control)
+function necroSetupSummon(self, def, x, y, level, turns, no_control)
 	local m = require("mod.class.NPC").new(def)
 	m.necrotic_minion = true
 	m.creation_turn = game.turn
 	m.faction = self.faction
 	m.summoner = self
 	m.summoner_gain_exp = true
+	if turns then m.summon_time = turns end
 	m.exp_worth = 0
 	m.life_regen = 0
 	m.unused_stats = 0
@@ -241,10 +242,19 @@ function necroSetupSummon(self, def, x, y, level, no_control)
 	game.zone:addEntity(game.level, m, "actor", x, y)
 	game.level.map:particleEmitter(x, y, 1, "summon")
 
-	-- m.on_die = function(self, killer)
-	-- 	if self.on_die_necrotic_minion then self:on_die_necrotic_minion(killer) end
-	-- 	local src = self.summoner
-	-- end
+	if m.ghoul_minion and self:knowTalent(self.T_PUTRESCENT_LIQUEFACTION) then
+		m.on_die = function(self, killer)
+			if not self.x or not game.level then return end
+			local src = self:resolveSource()
+			for i, e in ipairs(game.level.map.effects) do
+				if e.src == src and e.damtype == engine.DamageType.PUTRESCENT_LIQUEFACTION and e.grids[self.x] and e.grids[self.x][self.y] and src:isTalentActive(src.T_PUTRESCENT_LIQUEFACTION) then
+					local p = src:isTalentActive(src.T_PUTRESCENT_LIQUEFACTION)
+					p.dur = p.dur + src:callTalent(src.T_PUTRESCENT_LIQUEFACTION, "getIncrease")
+					game.logSeen(self, "#GREY#%s dissolves into the cloud of gore.", self:getName():capitalize())
+				end
+			end
+		end
+	end
 
 	-- Summons never flee
 	m.ai_tactic = m.ai_tactic or {}
@@ -294,6 +304,7 @@ load("/data/talents/spells/stone-alchemy.lua")
 load("/data/talents/spells/golem.lua")
 
 load("/data/talents/spells/master-of-bones.lua")
+load("/data/talents/spells/master-of-flesh.lua")
 load("/data/talents/spells/master-necromancer.lua")
 load("/data/talents/spells/animus.lua")
 load("/data/talents/spells/necrosis.lua")
diff --git a/game/modules/tome/data/timed_effects/magical.lua b/game/modules/tome/data/timed_effects/magical.lua
index df343a9293e0245592acbf0a2cb8b7798d2a9ab3..5667a24f5eb8abf88f44094e989ecc3c14e41d05 100644
--- a/game/modules/tome/data/timed_effects/magical.lua
+++ b/game/modules/tome/data/timed_effects/magical.lua
@@ -4780,3 +4780,48 @@ newEffect{
 		if eff.src and eff.powerful then eff.src:resolveSource():incSoul(1) end
 	end,
 }
+
+newEffect{
+	name = "CORPSE_EXPLOSION", image = "talents/corpse_explosion.png",
+	desc = _t"Corpse Explosion",
+	long_desc = function(self, eff) return ("When a ghoul is hit or dies, it explodes, doing %0.2f frostdusk damage."):tformat(eff.damage) end,
+	type = "magical",
+	subtype = { necrotic=true, ghoul=true },
+	status = "beneficial",
+	parameters = {damage=10, disease=5, radius=1},
+	callbackPriorities={callbackOnSummonDeath = 100, callbackOnSummonHit = 100}, -- trigger after most others
+	callbackOnSummonDeath = function(self, eff, minion, killer, death_note)
+		local ed = self:getEffectFromId(self.EFF_CORPSE_EXPLOSION)
+		ed.registerHit(self, eff, minion)
+	end,
+	callbackOnSummonHit = function(self, eff, cb, minion, src, death_note)
+		if cb.value <= 0 then return end
+		local ed = self:getEffectFromId(self.EFF_CORPSE_EXPLOSION)
+		ed.registerHit(self, eff, minion)
+	end,
+	callbackOnActBase = function(self, eff)
+		if #eff.turn_list == 0 then return end
+		table.sort(eff.turn_list, function(a, b) return a.creation_turn < b.creation_turn end)
+		local m = eff.turn_list[1]
+		if m.x then
+			if not m.dead then m:die(self) end
+			game.logSeen(m, "#GREY#%s explodes in a blast of gore!", m:getName():capitalize())
+			game.level.map:particleEmitter(m.x, m.y, eff.radius, "pustulent_fulmination", {radius=eff.radius})
+			game:playSoundNear(m, "talents/slime")
+			m:project({type="ball", radius=eff.radius, friendlyfire=false}, m.x, m.y, DamageType.FROSTDUSK, self:spellCrit(eff.damage))
+			local diseases = {{self.EFF_WEAKNESS_DISEASE, "str"}, {self.EFF_ROTTING_DISEASE, "con"}, {self.EFF_DECREPITUDE_DISEASE, "dex"}}
+			m:projectApply({type="ball", radius=eff.radius, friendlyfire=false}, m.x, m.y, Map.ACTOR, function(target)
+				local disease = rng.table(diseases)
+				game.log("============== %s", disease[1])
+				target:setEffect(disease[1], 6, {src=self, dam=eff.damage / 6, [disease[2]]=eff.disease, apply_power=self:combatSpellpower()})
+			end)
+		end
+		eff.turn_list = {}
+	end,
+	registerHit = function(self, eff, minion)
+		eff.turn_list[#eff.turn_list+1] = minion
+	end,
+	activate = function(self, eff)
+		eff.turn_list = {}
+	end,
+}