diff --git a/game/engines/default/engine/Particles.lua b/game/engines/default/engine/Particles.lua
index 47fd3cca345e7f9c9d88c691e3069c11caea147c..d1aa83ab0e5a09d351de021426e86cee4b5477f3 100644
--- a/game/engines/default/engine/Particles.lua
+++ b/game/engines/default/engine/Particles.lua
@@ -26,10 +26,11 @@ module(..., package.seeall, class.make)
 local __particles_gl = {}
 
 --- Make a particle emitter
-function _M:init(def, radius, args)
+function _M:init(def, radius, args, shader)
 	self.args = args
 	self.def = def
 	self.radius = radius or 1
+	self.shader = shader
 
 	self:loaded()
 end
@@ -72,7 +73,18 @@ function _M:loaded()
 	args = args.."tile_w="..engine.Map.tile_w.."\ntile_h="..engine.Map.tile_h
 
 	self.update = fct
-	self.ps = core.particles.newEmitter("/data/gfx/particles/"..self.def..".lua", args, self.zoom, config.settings.particles_density or 100, gl)
+
+	local sha = nil
+	if self.shader then
+		if not self._shader then
+			local Shader = require 'engine.Shader'
+			self._shader = Shader.new(self.shader.type, self.shader)
+		end
+
+		sha = self._shader.shad
+	end
+
+	self.ps = core.particles.newEmitter("/data/gfx/particles/"..self.def..".lua", args, self.zoom, config.settings.particles_density or 100, gl, sha)
 end
 
 function _M:updateZoom()
diff --git a/game/engines/default/engine/Shader.lua b/game/engines/default/engine/Shader.lua
index d25c24923047d75e041139e2629058b9ef7ff02d..fa62ad9499cccf2365b5fcf63d6231d7059d835b 100644
--- a/game/engines/default/engine/Shader.lua
+++ b/game/engines/default/engine/Shader.lua
@@ -27,6 +27,8 @@ _M.verts = {}
 _M.frags = {}
 _M.progs = {}
 
+loadNoDelay = true
+
 --- Make a shader
 function _M:init(name, args)
 	self.args = args or {}
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 2b60589ca5b80a5595facdd5f9022c12d52ba1b2..504099b9fb6be79e3b0c233fc5844f761dcd43ec 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -1974,6 +1974,13 @@ function _M:onTakeHit(value, src)
 end
 
 function _M:takeHit(value, src, death_note)
+	for eid, p in pairs(self.tmp) do
+		local e = self.tempeffect_def[eid]
+		if e.damage_feedback then
+			e.damage_feedback(self, p, src, value)
+		end
+	end
+
 	local dead, val = engine.interface.ActorLife.takeHit(self, value, src, death_note)
 
 	if dead and src and src.attr and src:attr("overkill") and src.project and not src.turn_procs.overkill then
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 21ff7ce9cfe417a70d4585c0c1053fa965672f41..b0417e50b5010bfb31fce1ee32893c6c27fe8c9f 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -1213,8 +1213,15 @@ function _M:setupCommands()
 			end end
 		end end,
 		[{"_g","ctrl"}] = function() if config.settings.cheat then
-			self:registerDialog(require("mod.dialogs.DownloadCharball").new())
+			local ps = next(game.player.__particles)
+
+			local a = math.rad(rng.float(0, 360))
+			local r = rng.float(0.2, 0.4)
+			ps._shader:setUniform("impact", {math.cos(a) * r, math.sin(a) * r})
+			ps._shader:setUniform("impact_tick", core.game.getTime())
+
 do return end
+			self:registerDialog(require("mod.dialogs.DownloadCharball").new())
 			local f, err = loadfile("/data/general/events/snowstorm.lua")
 			print(f, err)
 			setfenv(f, setmetatable({level=self.level, zone=self.zone}, {__index=_G}))
diff --git a/game/modules/tome/data/gfx/particles/shader_shield.lua b/game/modules/tome/data/gfx/particles/shader_shield.lua
new file mode 100644
index 0000000000000000000000000000000000000000..211fbb36a5693d10c40c9540f681562fb9cc6e68
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/shader_shield.lua
@@ -0,0 +1,48 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 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
+
+base_size = 32
+
+local r = 1
+local g = 1
+local b = 1
+local a = 1
+
+return { generator = function()
+	return {
+		trail = 0,
+		life = 10,
+		size = 38, 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 = r, rv = 0, ra = 0,
+		g = g, gv = 0, ga = 0,
+		b = b, bv = 0, ba = 0,
+		a = a, av = -0.02, aa = 0.005,
+	}
+end, },
+function(self)
+	self.ps:emit(1)
+end,
+1,
+"particles_images/shield2"
diff --git a/game/modules/tome/data/gfx/particles_images/shield2.png b/game/modules/tome/data/gfx/particles_images/shield2.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa9a097833754ab205c1a9e60a749686cd40354a
Binary files /dev/null and b/game/modules/tome/data/gfx/particles_images/shield2.png differ
diff --git a/game/modules/tome/data/gfx/shaders/shield.frag b/game/modules/tome/data/gfx/shaders/shield.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1e255718ba7c618b1f7d2f325eedd8d3c523661c
--- /dev/null
+++ b/game/modules/tome/data/gfx/shaders/shield.frag
@@ -0,0 +1,49 @@
+uniform sampler2D tex;
+uniform float tick;
+uniform float aadjust;
+uniform vec3 color;
+uniform float time_factor;
+
+vec3 impact_color = vec3(1.0, 0.3, 1.0);
+uniform vec2 impact;
+uniform float impact_tick;
+
+void main(void)
+{
+	vec2 uv = vec2(0.5, 0.5) - gl_TexCoord[0].xy;
+	float l = length(uv) * 2.0;
+	float ll = l * l;
+
+	vec4 c1 = texture2D(tex, (uv * ll / 1.3 + vec2(tick / time_factor, 0.0)));
+	vec4 c2 = texture2D(tex, (uv * ll / 1.3 + vec2(0.0, tick / time_factor)));
+	vec4 c = c1 * c2;
+
+	float dist = max(min(1.0, 1.0 - l), 0.0) * 3.0;
+	c.a *= c.a * dist;
+
+	float z = l;
+	c.a *= z;
+
+	if (l > 1.0) c.a = 0.0;
+	if (l < 0.5) {
+		c.a *= ll * 4.0;
+	}
+
+	// Impact
+	float it = tick - impact_tick;
+	if (it < 400.0) {
+		float v = (400.0 - it) / 400.0;
+		float il = distance(impact / ll, (vec2(0.5) - gl_TexCoord[0].xy) / ll);
+		if (il < 0.5 * (1.0 - v)) {
+			v *= v * v;
+			float ic = (1.0 - length(uv - impact)) * v * 3.0;
+			c.rgb = mix(c.rgb, impact_color, ic);
+			aadjust *= 1.0 + v * 3.0;
+		}
+	}
+
+	c.a *= aadjust;
+
+	c.rgb *= color;
+	gl_FragColor = c;
+}
diff --git a/game/modules/tome/data/gfx/shaders/shield.lua b/game/modules/tome/data/gfx/shaders/shield.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f510bd0b3c9b1947299f1fb43666efe54213efae
--- /dev/null
+++ b/game/modules/tome/data/gfx/shaders/shield.lua
@@ -0,0 +1,32 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 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
+
+return {
+	frag = "shield",
+	vert = nil,
+	args = {
+		tex = { texture = 0 },
+		color = color or {0.4, 0.7, 1.0},
+		time_factor = time_factor or 4000,
+		aadjust = aadjust or 10,
+		impact = {0, 0},
+		impact_tick = -1000,
+	},
+	clone = false,
+}
diff --git a/game/modules/tome/data/timed_effects/magical.lua b/game/modules/tome/data/timed_effects/magical.lua
index 3a2656a7b5dd850b64f8ef12f4810ff78a9aedb5..103258d9f7da2e5168fbc42c4d1b900f77b7c858 100644
--- a/game/modules/tome/data/timed_effects/magical.lua
+++ b/game/modules/tome/data/timed_effects/magical.lua
@@ -435,6 +435,14 @@ newEffect{
 	on_aegis = function(self, eff, aegis)
 		self.damage_shield_absorb = self.damage_shield_absorb + eff.power * aegis / 100
 	end,
+	damage_feedback = function(self, eff, src, value)
+		if eff.particle and eff.particle._shader and eff.particle._shader.shad and src and src.x and src.y then
+			local r = -rng.float(0.2, 0.4)
+			local a = math.atan2(src.y - self.y, src.x - self.x)
+			eff.particle._shader:setUniform("impact", {math.cos(a) * r, math.sin(a) * r})
+			eff.particle._shader:setUniform("impact_tick", core.game.getTime())
+		end
+	end,
 	activate = function(self, eff)
 		if self:attr("shield_factor") then eff.power = eff.power * (100 + self:attr("shield_factor")) / 100 end
 		if self:attr("shield_dur") then eff.dur = eff.dur + self:attr("shield_dur") end
@@ -443,7 +451,11 @@ newEffect{
 		--- Warning there can be only one time shield active at once for an actor
 		self.damage_shield_absorb = eff.power
 		self.damage_shield_absorb_max = eff.power
-		eff.particle = self:addParticles(Particles.new("damage_shield", 1))
+		if core.shader.active() then
+			eff.particle = self:addParticles(Particles.new("shader_shield", 1, nil, {type="shield", color={0.4, 0.7, 1.0}}))
+		else
+			eff.particle = self:addParticles(Particles.new("damage_shield", 1))
+		end
 	end,
 	deactivate = function(self, eff)
 		self:removeParticles(eff.particle)
diff --git a/src/particles.c b/src/particles.c
index 58823eea9d8bc4d8fd7692008d49ceb8b839a857..d008615ae387ec2d87c9887348c01615ca2923b9 100644
--- a/src/particles.c
+++ b/src/particles.c
@@ -83,6 +83,8 @@ static int particles_new(lua_State *L)
 	// float zoom = luaL_checknumber(L, 3);
 	int density = luaL_checknumber(L, 4);
 	GLuint *texture = (GLuint*)auxiliar_checkclass(L, "gl{texture}", 5);
+	shader_type *s = NULL;
+	if (lua_isuserdata(L, 6)) s = (shader_type*)auxiliar_checkclass(L, "gl{program}", 6);
 
 	particles_type *ps = (particles_type*)lua_newuserdata(L, sizeof(particles_type));
 	auxiliar_setclass(L, "core{particles}", -1);
@@ -100,6 +102,7 @@ static int particles_new(lua_State *L)
 	ps->particles = NULL;
 	ps->init = FALSE;
 	ps->texture = *texture;
+	ps->shader = s;
 
 	thread_add(ps);
 	return 1;
@@ -182,6 +185,8 @@ static int particles_to_screen(lua_State *L)
 	glScalef(ps->zoom * zoom, ps->zoom * zoom, ps->zoom * zoom);
 	glRotatef(ps->rotate, 0, 0, 1);
 
+	if (ps->shader) useShader(ps->shader, 1, 1, 1, 1, 1, 1, 1, 1);
+
 	int remaining = ps->batch_nb;
 	while (remaining >= PARTICLES_PER_ARRAY)
 	{
@@ -190,6 +195,8 @@ static int particles_to_screen(lua_State *L)
 	}
 	if (remaining) glDrawArrays(GL_QUADS, 0, remaining);
 
+	if (ps->shader) glUseProgramObjectARB(0);
+
 	glRotatef(-ps->rotate, 0, 0, 1);
 	glPopMatrix();
 	glTranslatef(-x, -y, 0);
diff --git a/src/particles.h b/src/particles.h
index ef4a85afcfc813f88910a37342cfd123e77063ba..46dc615773abe2f6d40ccd5ddc3a8c9c74aad6d7 100644
--- a/src/particles.h
+++ b/src/particles.h
@@ -22,6 +22,7 @@
 #define _PARTICLES_H_
 
 #include "tgl.h"
+#include "useshader.h"
 
 typedef struct {
 	float size, sizev, sizea;
@@ -40,6 +41,7 @@ typedef struct {
 
 	// Read only by main
 	GLuint texture;
+	shader_type *shader;
 
 	// W by main, R by thread
 	const char *name_def;