diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 13538a888fcf1c6807165768afa35ebf12306ce0..e6f5082a6718a5db9c7b3e6ce42a8a7d46f526cc 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -2778,6 +2778,14 @@ function _M:checkEncumbrance()
 	end
 end
 
+--- Return attachement coords
+function _M:attachementSpot(kind, particle)
+	if not self.attachement_spots or not self.attachement_spots[kind] then return end
+	local x, y = 0, 0
+	if particle then x, y = -0.5, -0.5 end
+	return self.attachement_spots[kind].x + x, self.attachement_spots[kind].y + y
+end
+
 --- Update tile for races that can handle it
 function _M:updateModdableTile()
 	if not self.moddable_tile or Map.tiles.no_moddable_tiles then return end
diff --git a/game/modules/tome/data/birth/classes/mage.lua b/game/modules/tome/data/birth/classes/mage.lua
index 0a22ca867529cefb62df3fddc395c4621a4a7a03..dabe624f7f57ac26a245989ccaf88557c2bcebea 100644
--- a/game/modules/tome/data/birth/classes/mage.lua
+++ b/game/modules/tome/data/birth/classes/mage.lua
@@ -127,10 +127,11 @@ newBirthDescriptor{
 	stats = { mag=5, wil=3, cun=1, },
 	birth_example_particles = {
 		function(actor)
-			if core.shader.active(4) then actor:addParticles(Particles.new("shader_wings", 1, {infinite=1}))
+			if core.shader.active(4) then local x, y = actor:attachementSpot("back", true) actor:addParticles(Particles.new("shader_wings", 1, {x=y, y=y, infinite=1}))
 			else actor:addParticles(Particles.new("wildfire", 1))
 			end
 		end,
+		--[[
 		function(actor)
 			if core.shader.active(4) then actor:addParticles(Particles.new("shader_ring_rotating", 1, {radius=1.1}, {type="flames", hide_center=0, time_factor=1700, zoom=0.3, npow=1, color1={0.6, 0.3, 0.8, 1}, color2={0.8, 0, 0.8, 1}, xy={0,0}}))
 			else actor:addParticles(Particles.new("ultrashield", 1, {rm=180, rM=220, gm=10, gM=50, bm=190, bM=220, am=120, aM=200, radius=0.4, density=100, life=8, instop=20}))
@@ -154,6 +155,7 @@ newBirthDescriptor{
 			else actor:addParticles(Particles.new("tempest", 1))
 			end
 		end,
+		]]
 	},
 	talents_types = {
 		["spell/arcane"]={true, 0.3},
diff --git a/game/modules/tome/data/birth/classes/warrior.lua b/game/modules/tome/data/birth/classes/warrior.lua
index 0770ada3a80ca2df78c067829faa4653a66ea09c..39c280a3125881a217e0931c38d10360102bbb84 100644
--- a/game/modules/tome/data/birth/classes/warrior.lua
+++ b/game/modules/tome/data/birth/classes/warrior.lua
@@ -17,6 +17,8 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
+local Particles = require "engine.Particles"
+
 newBirthDescriptor{
 	type = "class",
 	name = "Warrior",
@@ -231,6 +233,18 @@ newBirthDescriptor{
 	unlockable_talents_types = {
 		["spell/stone"]={false, 0.1, "mage_geomancer"},
 	},
+	birth_example_particles = {
+		function(actor) if core.shader.active(4) then
+			local slow = rng.percent(50)
+			local h1x, h1y = actor:attachementSpot("hand1", true) if h1x then actor:addParticles(Particles.new("shader_shield", 1, {img="fireball", a=0.7, size_factor=0.4, x=h1x, y=h1y-0.1}, {type="flamehands", time_factor=slow and 700 or 1000})) end
+			local h2x, h2y = actor:attachementSpot("hand2", true) if h2x then actor:addParticles(Particles.new("shader_shield", 1, {img="fireball", a=0.7, size_factor=0.4, x=h2x, y=h2y-0.1}, {type="flamehands", time_factor=not slow and 700 or 1000})) end
+		end end,
+		function(actor) if core.shader.active(4) then
+			local slow = rng.percent(50)
+			local h1x, h1y = actor:attachementSpot("hand1", true) if h1x then actor:addParticles(Particles.new("shader_shield", 1, {img="lightningwings", a=0.7, size_factor=0.4, x=h1x, y=h1y-0.1}, {type="flamehands", time_factor=slow and 700 or 1000})) end
+			local h2x, h2y = actor:attachementSpot("hand2", true) if h2x then actor:addParticles(Particles.new("shader_shield", 1, {img="lightningwings", a=0.7, size_factor=0.4, x=h2x, y=h2y-0.1}, {type="flamehands", time_factor=not slow and 700 or 1000})) end
+		end end,
+	},
 	talents = {
 		[ActorTalents.T_FLAME] = 1,
 		[ActorTalents.T_ARCANE_COMBAT] = 1,
diff --git a/game/modules/tome/data/birth/races/dwarf.lua b/game/modules/tome/data/birth/races/dwarf.lua
index 1c6eb4dfe27721063c7951327b6ab80786fe1f7a..d8812976010ef1a282e2fd7f948c8c049eb62df1 100644
--- a/game/modules/tome/data/birth/races/dwarf.lua
+++ b/game/modules/tome/data/birth/races/dwarf.lua
@@ -50,6 +50,20 @@ newBirthDescriptor{
 	},
 	random_escort_possibilities = { {"tier1.1", 1, 2}, {"tier1.2", 1, 2}, {"daikara", 1, 2}, {"old-forest", 1, 4}, {"dreadfell", 1, 8}, {"reknor", 1, 2}, },
 
+	moddable_attachement_spots = { base=128,
+		female = {
+			head = {x=66, y=19},
+			back = {x=66, y=51},
+			hand1 = {x=19, y=78},
+			hand2 = {x=110, y=78},
+		},
+		male = {
+			head = {x=65, y=20},
+			back = {x=65, y=51},
+			hand1 = {x=20, y=78},
+			hand2 = {x=110, y=78},
+		},
+	},
 	cosmetic_unlock = {
 		cosmetic_race_human_redhead = {
 			{name="Redhead [donator only]", donator=true, on_actor=function(actor) if actor.moddable_tile then actor.moddable_tile_base = "base_redhead_01.png" actor.moddable_tile_ornament={male="beard_redhead_02"} end end, check=function(birth) return birth.descriptors_by_type.sex == "Male" end},
diff --git a/game/modules/tome/data/birth/races/human.lua b/game/modules/tome/data/birth/races/human.lua
index ee4e36c5b9ddbbb8d933705f7172c718fa9c6eec..20f1dded9fbc20b3d64007911e0f9436b54b1f72 100644
--- a/game/modules/tome/data/birth/races/human.lua
+++ b/game/modules/tome/data/birth/races/human.lua
@@ -56,6 +56,20 @@ newBirthDescriptor{
 	},
 	random_escort_possibilities = { {"tier1.1", 1, 2}, {"tier1.2", 1, 2}, {"daikara", 1, 2}, {"old-forest", 1, 4}, {"dreadfell", 1, 8}, {"reknor", 1, 2}, },
 
+	moddable_attachement_spots = { base=128,
+		female = {
+			head = {x=64, y=6},
+			back = {x=66, y=34},
+			hand1 = {x=38, y=66},
+			hand2 = {x=90, y=66},
+		},
+		male = {
+			head = {x=60, y=6},
+			back = {x=64, y=31},
+			hand1 = {x=31, y=66},
+			hand2 = {x=95, y=66},
+		},
+	},
 	cosmetic_unlock = {
 		cosmetic_race_human_redhead = {
 			{name="Redhead [donator only]", donator=true, on_actor=function(actor) if actor.moddable_tile then actor.moddable_tile_base = "base_redhead_01.png" end end},
diff --git a/game/modules/tome/data/gfx/particles/bolt_fire.lua b/game/modules/tome/data/gfx/particles/bolt_fire.lua
index dcaaa7de560a11c05119d4d01d680d33b940131d..dbf63c6623981e1a7701f54a496e207fe0a64a64 100644
--- a/game/modules/tome/data/gfx/particles/bolt_fire.lua
+++ b/game/modules/tome/data/gfx/particles/bolt_fire.lua
@@ -24,12 +24,15 @@ if core.shader.active(4) then
 use_shader = {type="fireball"}
 base_size = 64
 
+local basedir = math.atan2(ty or 1, tx or 0)
+local dir = math.deg(basedir)
+
 return {
-	system_rotation = rng.range(0,359), system_rotationv = 3,
+	system_rotation = 0 or dir, system_rotationv = 0,
 	generator = function()
 	return {
 		life = 1000,
-		size = 40, sizev = 0, sizea = 0,
+		size = 80, sizev = 0, sizea = 0,
 
 		x = 0, xv = 0, xa = 0,
 		y = 0, yv = 0, ya = 0,
@@ -45,7 +48,7 @@ end, },
 function(self)
 	self.ps:emit(1)
 end,
-1
+1, "particles_images/fireball"
 
 
 --------------------------------------------------------------------------------------
diff --git a/game/modules/tome/data/gfx/particles/fireflash.lua b/game/modules/tome/data/gfx/particles/fireflash.lua
index c05c585c01f41039b689fb55a65ad1e348e1b31e..c50593553d1b03120d4ebd60896a98e3beeb707b 100644
--- a/game/modules/tome/data/gfx/particles/fireflash.lua
+++ b/game/modules/tome/data/gfx/particles/fireflash.lua
@@ -21,13 +21,12 @@
 -- Advanced shaders
 --------------------------------------------------------------------------------------
 if core.shader.active(4) then
-use_shader = {type="fireball"}
+use_shader = {type="fireboom"}
 base_size = 64
 
 local nb = 0
 
 return {
-	system_rotation = rng.range(0,359), system_rotationv = 5,
 	generator = function()
 	return {
 		life = 16,
@@ -50,7 +49,7 @@ function(self)
 	end
 	nb = nb + 1
 end,
-1
+1, "particles_images/fireboom"
 
 
 --------------------------------------------------------------------------------------
diff --git a/game/modules/tome/data/gfx/particles/firetrail.lua b/game/modules/tome/data/gfx/particles/firetrail.lua
index 0bfc9ca511ce45e12589b49075eb966b2fe02b63..c9cd7578952108b0e0bc8b22396f9ab8e9296ec5 100644
--- a/game/modules/tome/data/gfx/particles/firetrail.lua
+++ b/game/modules/tome/data/gfx/particles/firetrail.lua
@@ -44,9 +44,11 @@ use_shader = {type="fireball"}
 base_size = 64
 
 local nb = 0
+local basedir = math.atan2(ty or 1, tx or 0)
+local dir = math.deg(basedir)
 
 return {
-	system_rotation = rng.range(0,359), system_rotationv = 3,
+	system_rotation = 0 or dir, system_rotationv = 0,
 	generator = function()
 	return {
 		life = 10,
@@ -69,7 +71,7 @@ function(self)
 	end
 	nb = nb + 1
 end,
-1
+1, "particles_images/fireball"
 
 
 --------------------------------------------------------------------------------------
diff --git a/game/modules/tome/data/gfx/particles/shader_shield.lua b/game/modules/tome/data/gfx/particles/shader_shield.lua
index a6ddd812934e64476253070252da417b88a38659..3573a900d415b4642106174b050b7800518e0f7d 100644
--- a/game/modules/tome/data/gfx/particles/shader_shield.lua
+++ b/game/modules/tome/data/gfx/particles/shader_shield.lua
@@ -22,7 +22,7 @@ base_size = 64
 local r = 1
 local g = 1
 local b = 1
-local a = 1
+local a = a or 1
 
 return { generator = function()
 	return {
@@ -30,8 +30,8 @@ return { generator = function()
 		life = 10,
 		size = 2*38 * (size_factor or 1), sizev = 0, sizea = 0,
 
-		x = (x or 0) * engine.Map.tile_w, xv = 0, xa = 0,
-		y = (y or 0) * engine.Map.tile_h, yv = 0, ya = 0,
+		x = (x or 0) * 64, xv = 0, xa = 0,
+		y = (y or 0) * 64, yv = 0, ya = 0,
 		dir = 0, dirv = dirv, dira = 0,
 		vel = 0, velv = 0, vela = 0,
 
diff --git a/game/modules/tome/data/gfx/particles/shader_wings.lua b/game/modules/tome/data/gfx/particles/shader_wings.lua
index cfc8ebbf023935910d35b4d487f0470a7069e1cf..4b4b442c78da8544baf08b9638f4a91efd08e4b0 100644
--- a/game/modules/tome/data/gfx/particles/shader_wings.lua
+++ b/game/modules/tome/data/gfx/particles/shader_wings.lua
@@ -33,8 +33,8 @@ return { generator = function()
 		life = life or 10,
 		size = 64 * (size_factor or 1), sizev = 0, sizea = 0,
 
-		x = x or 0, xv = 0, xa = 0,
-		y = y or -25, yv = 0, ya = 0,
+		x = (x or 0) * 32, xv = 0, xa = 0,
+		y = (y or -0.781) * 32, yv = 0, ya = 0,
 		dir = 0, dirv = dirv, dira = 0,
 		vel = 0, velv = 0, vela = 0,
 
diff --git a/game/modules/tome/data/gfx/particles_images/fireball.png b/game/modules/tome/data/gfx/particles_images/fireball.png
new file mode 100644
index 0000000000000000000000000000000000000000..2a7c146f95771b22b2c3fe0ec23a1ae8d572299c
Binary files /dev/null and b/game/modules/tome/data/gfx/particles_images/fireball.png differ
diff --git a/game/modules/tome/data/gfx/shaders/fireball.frag b/game/modules/tome/data/gfx/shaders/fireball.frag
index 7dd6071142d626694e8752b5c32b25722b922d9e..176ce1940559671b573da269a0e71360e346f8bf 100644
--- a/game/modules/tome/data/gfx/shaders/fireball.frag
+++ b/game/modules/tome/data/gfx/shaders/fireball.frag
@@ -1,244 +1,230 @@
-// Fireball
-// Awd
-// @AlexWDunn
+uniform sampler2D tex;
+uniform float tick;
+uniform float time_factor;
 
-#ifdef GL_ES
-precision highp float;
-#endif
+	
+vec4 permute( vec4 x ) {
 
-uniform float tick;
+	return mod( ( ( x * 34.0 ) + 1.0 ) * x, 289.0 );
 
-#define saturate(oo) clamp(oo, 0.0, 1.0)
-
-// Quality Settings
-#define MarchSteps 6
-
-// Scene Settings
-#define ExpPosition vec3(0.0)
-#define Radius 2.0
-#define Background vec4(0.0, 0.0, 0.0, 0.0)
-
-// Noise Settings
-#define NoiseSteps 4
-#define NoiseAmplitude 0.15
-#define NoiseFrequency 1.0
-#define Animation vec3(0.0, -3.0, 0.5)
-
-uniform float speed;
-
-// Colour Gradient
-uniform vec4 color1;
-uniform vec4 color2;
-uniform vec4 color3;
-uniform vec4 color4;
-/*
-#define color1 vec4(0.0, 0.0, 0.0, 0.0)
-#define color2 vec4(0.5, 0.2, 0.0, 0.5)
-#define color3 vec4(0.7, 0.2, 0.0, 0.7)
-#define color4 vec4(1.0, 0.8, 0.0, 1.0)
-//*/
-/*
-#define color1 vec4(0.0, 0.0, 0.0, 0.0)
-#define color2 vec4(.0, .0, 1.0, 0.3)
-#define color3 vec4(0.0, 1.03, 1.0, 0.5)
-#define color4 vec4(0.05, 0.02, 0.2, 0.8)
-*/
-
-/* Bright fel-green
-#define color1 vec4(1.0, 1.0, 1.0, 1.0)
-#define color2 vec4(1.0, 0.8, 0.2, 1.0)
-#define color3 vec4(1.0, 0.8, 0.0, 1.0)
-#define color4 vec4(0.6, 0.6, 0.1, 1.0)
-*/
-
-// Description : Array and textureless GLSL 2D/3D/4D simplex
-//               noise functions.
-//      Author : Ian McEwan, Ashima Arts.
-//  Maintainer : ijm
-//     Lastmod : 20110822 (ijm)
-//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
-//               Distributed under the MIT License. See LICENSE file.
-//               https://github.com/ashima/webgl-noise
-//
-
-vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
-vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
-vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
-vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; }
-
-float snoise(vec3 v)
-{
-	const vec2  C = vec2(1.0/6.0, 1.0/3.0);
-	const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);
+} 
+
+vec4 taylorInvSqrt( vec4 r ) {
+
+	return 1.79284291400159 - 0.85373472095314 * r;
+
+}
+
+float snoise( vec3 v ) {
+
+	const vec2 C = vec2( 1.0 / 6.0, 1.0 / 3.0 );
+	const vec4 D = vec4( 0.0, 0.5, 1.0, 2.0 );
 
 	// First corner
-	vec3 i  = floor(v + dot(v, C.yyy));
-	vec3 x0 = v - i + dot(i, C.xxx);
+
+	vec3 i  = floor( v + dot( v, C.yyy ) );
+	vec3 x0 = v - i + dot( i, C.xxx );
 
 	// Other corners
-	vec3 g = step(x0.yzx, x0.xyz);
+
+	vec3 g = step( x0.yzx, x0.xyz );
 	vec3 l = 1.0 - g;
-	vec3 i1 = min(g.xyz, l.zxy);
-	vec3 i2 = max(g.xyz, l.zxy);
-	vec3 x1 = x0 - i1 + C.xxx;
-	vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
-	vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y
+	vec3 i1 = min( g.xyz, l.zxy );
+	vec3 i2 = max( g.xyz, l.zxy );
+
+	vec3 x1 = x0 - i1 + 1.0 * C.xxx;
+	vec3 x2 = x0 - i2 + 2.0 * C.xxx;
+	vec3 x3 = x0 - 1. + 3.0 * C.xxx;
 
 	// Permutations
-	i = mod289(i);
-	vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
 
-	// Gradients: 7x7 points over a square, mapped onto an octahedron.
-	// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
-	float n_ = 0.142857142857; // 1.0/7.0
-	vec3  ns = n_ * D.wyz - D.xzx;
-	vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)
+	i = mod( i, 289.0 );
+	vec4 p = permute( permute( permute(
+		i.z + vec4( 0.0, i1.z, i2.z, 1.0 ) )
+		+ i.y + vec4( 0.0, i1.y, i2.y, 1.0 ) )
+		+ i.x + vec4( 0.0, i1.x, i2.x, 1.0 ) );
+
+	// Gradients
+	// ( N*N points uniformly over a square, mapped onto an octahedron.)
+
+	float n_ = 1.0 / 7.0; // N=7
 
-	vec4 x_ = floor(j * ns.z);
-	vec4 y_ = floor(j - 7.0 * x_);    // mod(j,N)
+	vec3 ns = n_ * D.wyz - D.xzx;
+
+	vec4 j = p - 49.0 * floor( p * ns.z *ns.z );  //  mod(p,N*N)
+
+	vec4 x_ = floor( j * ns.z );
+	vec4 y_ = floor( j - 7.0 * x_ );    // mod(j,N)
 
 	vec4 x = x_ *ns.x + ns.yyyy;
 	vec4 y = y_ *ns.x + ns.yyyy;
+	vec4 h = 1.0 - abs( x ) - abs( y );
 
-	vec4 h = 1.0 - abs(x) - abs(y);
-	vec4 b0 = vec4(x.xy, y.xy);
-	vec4 b1 = vec4(x.zw, y.zw);
+	vec4 b0 = vec4( x.xy, y.xy );
+	vec4 b1 = vec4( x.zw, y.zw );
 
-	vec4 s0 = floor(b0) * 2.0 + 1.0;
-	vec4 s1 = floor(b1) * 2.0 + 1.0;
-	vec4 sh = -step(h, vec4(0.0));
+
+	vec4 s0 = floor( b0 ) * 2.0 + 1.0;
+	vec4 s1 = floor( b1 ) * 2.0 + 1.0;
+	vec4 sh = -step( h, vec4( 0.0 ) );
 
 	vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
 	vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
 
-	vec3 p0 = vec3(a0.xy, h.x);
-	vec3 p1 = vec3(a0.zw, h.y);
-	vec3 p2 = vec3(a1.xy, h.z);
-	vec3 p3 = vec3(a1.zw, h.w);
+	vec3 p0 = vec3( a0.xy, h.x );
+	vec3 p1 = vec3( a0.zw, h.y );
+	vec3 p2 = vec3( a1.xy, h.z );
+	vec3 p3 = vec3( a1.zw, h.w );
 
-	//Normalise gradients
-	vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
+	// Normalise gradients
 
+	vec4 norm = taylorInvSqrt( vec4( dot( p0, p0 ), dot( p1, p1 ), dot( p2, p2 ), dot( p3, p3 ) ) );
 	p0 *= norm.x;
 	p1 *= norm.y;
 	p2 *= norm.z;
 	p3 *= norm.w;
 
 	// Mix final noise value
-	vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
+
+	vec4 m = max( 0.6 - vec4( dot( x0, x0 ), dot( x1, x1 ), dot( x2, x2 ), dot( x3, x3 ) ), 0.0 );
 	m = m * m;
+	return 42.0 * dot( m*m, vec4( dot( p0, x0 ), dot( p1, x1 ),
+		dot( p2, x2 ), dot( p3, x3 ) ) );
 
-	return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
-}
+}  
 
-float Turbulence(vec3 position, float minFreq, float maxFreq, float qWidth)
+vec2 snoise2(vec3 pos)
 {
-	float value = 0.0;
-	float cutoff = clamp(0.5/qWidth, 0.0, maxFreq);
-	float fade;
-	float fOut = minFreq;
-
-	for(int i=NoiseSteps ; i>=0 ; i++)
-	{
-		if(fOut >= 0.5 * cutoff) break;
-
-		fOut *= 2.0;
-		value += abs(snoise(position * fOut))/fOut;
-	}
-
-	fade = clamp(2.0 * (cutoff-fOut)/cutoff, 0.0, 1.0);
-	value += fade * abs(snoise(position * fOut))/fOut;
-
-	return 1.0-value;
+	return vec2(snoise(pos), snoise(pos + vec3(0.0, 0.0, 1.0)));
 }
 
-float SphereDist(vec3 position)
+float GetFireDelta(float currTime, vec2 pos, float freqMult, float stretchMult, float scrollSpeed, float evolutionSpeed)
 {
-	return length(position - ExpPosition) - Radius;
-}
+	//firewall
+	float delta = 0;
+//	pos.y += (1.0 - pos.y) * 0.5;
+	//pos.y += 0.5;
+	pos.y /= stretchMult;
+	pos *= freqMult;
+	pos.y += currTime * scrollSpeed;
+//	pos.y -= currTime * 3.0;
 
-vec4 Shade(float distance)
-{
-	float c1 = saturate(distance*5.0 + 0.5);
-	float c2 = saturate(distance*5.0);
-	float c3 = saturate(distance*3.4 - 0.5);
 	
-	vec4 a = mix(color1,color2, c1);
-	vec4 b = mix(a,     color3, c2);
-	return 	 mix(b,     color4, c3);
-}
-
+	delta += snoise(vec3(pos * 1.0, currTime * 1.0 * evolutionSpeed)) * 1.5;
+	delta += snoise(vec3(pos * 2.0, currTime * 2.0 * evolutionSpeed)) * 1.5;
+	delta += snoise(vec3(pos * 4.0, currTime * 4.0 * evolutionSpeed)) * 1.5;	
+	delta += snoise(vec3(pos * 8.0, currTime * 8.0 * evolutionSpeed)) * 1.5;
+	delta += snoise(vec3(pos * 16.0, currTime * 16.0 * evolutionSpeed)) * 0.5;
 
-// Draws the scene
-float RenderScene(vec3 position, out float distance)
+	return delta;
+}
+vec4 GetFireColor(float currTime, vec2 pos, float freqMult, float stretchMult, float ampMult)
 {
-	float time = tick / speed;
-	float noise = Turbulence(position * NoiseFrequency + Animation*time, 0.1, 1.5, 0.03) * NoiseAmplitude;
-	noise = saturate(abs(noise));
-	distance = SphereDist(position) - noise;
-		
-	return noise;
+	float delta = GetFireDelta(currTime, pos, freqMult, stretchMult, 3.0, 0.5);
+	delta *= min(1.0, max(0.0, 1.0 * (1.0 - pos.y)));
+	delta *= min(1.0, max(0.0, 1.0 * (0.0 + pos.y)));
+	vec2 displacedPoint = pos + vec2(0, delta * ampMult);
+	displacedPoint.y = min(0.99, displacedPoint.y);
+	displacedPoint.y = max(0.01, displacedPoint.y);
+	
+	return texture2D(tex, displacedPoint);
 }
 
-// Basic ray marching method.
-vec4 March(vec3 rayOrigin, vec3 rayStep)
+vec4 GetCheckboardColor(vec2 pos)
 {
-	vec3 position = rayOrigin;
-	
-	float distance;
-	float displacement;
-	
-	for(int step = MarchSteps; step >=0  ; --step)
+	vec4 col = vec4(0.0, 0.0, 0.0, 0.0);
+	if(pos.x > 0.0 && pos.x < 1.0 && pos.y > 0.0 && pos.y < 1.0)
 	{
-		displacement = RenderScene(position, distance);
-	
-		if(distance < 0.05) break;
-		
-		position += rayStep * distance;
+		if(mod(pos.x, 0.1) < 0.05 ^^ mod(pos.y, 0.1) < 0.05)
+			col = vec4(pos.x, pos.y, 0.0, 1.0);
+		else
+			col = vec4(0.0, 0.0, 0.0, 1.0);
 	}
-	return mix(Shade(displacement), Background, float(distance >= 0.5));
+	return col;
 }
 
-bool IntersectSphere(vec3 ro, vec3 rd, vec3 pos, float radius, out vec3 intersectPoint)
+vec4 GetFireBallColor(float currTime, vec2 pos, float freqMult, float stretchMult, float ampMult, float power, float radius1, float radius2, vec2 velocity, float paletteCoord)
 {
-	vec3 relDistance = (ro - pos);
-	
-	float b = dot(relDistance, rd);
-	float c = dot(relDistance, relDistance) - radius*radius;
-	float d = b*b - c;
+	float pi = 3.141593;
+	vec2 velocityDir = vec2(1, 0);
+	if(length(velocity) > 0)
+		velocityDir = velocity / length(velocity);
+	vec2 velocityPerp = vec2(-velocityDir.y, velocityDir.x);
 	
-	intersectPoint = ro + rd*(-b - sqrt(d));
-	
-	return d >= 0.0;
+	float ang = atan(dot(pos, velocityPerp), dot(pos, velocityDir));
+	vec4 fireballColor = vec4(0.0, 0.0, 0.0, 0.0);
+	if(length(pos) < radius1)
+	{		
+		float sinAlpha = length(pos) / radius1;
+		float alpha = 0.0;
+		alpha = asin(sinAlpha);
+		
+		vec2 sphericalProjectedCoord = vec2(0.5, 0.5) + pos * (alpha / (3.141592 / 2.0)) / length(pos);
+		//fireballColor = GetCheckboardColor(sphericalProjectedCoord);
+		float delta = GetFireDelta(currTime, sphericalProjectedCoord, freqMult * 0.1, 1.0, 0.0, 1.5) * (1.0 - pow(length(pos) / radius1, 3.0)) * 0.5;
+		
+		float verticalPos = 0.99 - delta * delta;	
+		verticalPos = min(0.99, verticalPos);
+		verticalPos = max(0.01, verticalPos);
+		
+		fireballColor = texture2D(tex, vec2(0.75, verticalPos));
+		fireballColor.a = 1.0;
+	}else
+	{
+		float dist = (length(pos) - radius1) / (radius2 - radius1);
+		
+
+		vec2 polarPos = vec2(ang, dist);
+			
+		float dstAng = (1.0 - pow(1.0 - abs(polarPos.x / pi), 4.0)) * pi;
+		if(polarPos.x < 0.0) dstAng = -dstAng;
+		
+		polarPos.x = dstAng + (polarPos.x - dstAng) * exp(-polarPos.y * 2.0);
+		polarPos.y *= 0.15 + 1.4 * pow(abs(polarPos.x) / pi, 2.0);
+		//polarPos.y *= exp(-(1.0 - pow(abs(polarPos.x) / pi, 2.0)) * 2.0);
+		
+		//polarPos.x = (1.0 - pow(1.0 - abs(polarPos.x / pi), 1.0)) * pi;
+		//polarPos.x *= 2.0 - 1.0 * exp(-(1.0 - pow(1.0 - abs(polarPos.x) / pi, 3.0)) * 5.0 * polarPos.y);
+		//polarPos.x *= 1.0 + 1.0 * exp(-abs(polarPos.x));//0.1 + 0.9 * (1.0 - pow(1.0 - abs(polarPos.x) / pi, 0.5));
+		
+		
+		vec2 planarPos = vec2((polarPos.x + pi) / (2.0 * pi), 1.0 - polarPos.y * 1.0);
+		
+		if(planarPos.y > 0.0)
+		{
+			//return GetCheckboardColor(planarPos);
+			
+			float delta =  
+				GetFireDelta(currTime, planarPos, freqMult, stretchMult, 2.5, 0.5) * (1.0 - planarPos.x)	+ 
+				GetFireDelta(currTime, vec2(planarPos.x - 1.0, planarPos.y), freqMult, stretchMult, 2.5, 0.5) * planarPos.x;
+				
+			delta *= min(1.0, max(0.0, 1.0 * (1.0 - planarPos.y)));
+			delta *= min(1.0, max(0.0, 1.0 * (0.0 + planarPos.y)));
+
+			float verticalPos = planarPos.y + delta * ampMult;	
+			verticalPos = min(0.99, verticalPos);
+			verticalPos = max(0.01, verticalPos);
+			
+			fireballColor = texture2D(tex, vec2(0.25, verticalPos));
+		}
+	}
+	return fireballColor;
 }
 
 void main(void)
 {
-	vec2 resolution = vec2(640.0, 640.0);
-	//vec2 p = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0;
-	vec2 p = gl_TexCoord[0].xy - 0.5;
+	vec2 radius = gl_TexCoord[0].xy - vec2(0.5, 0.5);
 	
-	p.x *= resolution.x/resolution.y;
-
-	float rotx = 0.00;
-	float roty = 0.0;
-	float zoom = 9.0;
-
-	// camera
-	vec3 ro = zoom * normalize(vec3(cos(roty), cos(rotx), sin(roty)));
-	vec3 ww = normalize(vec3(0.0, 0.0, 0.0) - ro);
-	vec3 uu = normalize(cross( vec3(0.0, 1.0, 0.0), ww));
-	vec3 vv = normalize(cross(ww, uu));
-	vec3 rd = normalize(p.x*uu + p.y*vv + 1.5*ww);
-
-	vec4 col = Background;
-
-	vec3 origin;
+	//on-hit wobbling effect
+	float radiusLen = length(radius);
+		
 	
-	if(IntersectSphere(ro, rd, ExpPosition, Radius + NoiseAmplitude*6.0, origin))
-	{
-		col = March(origin, rd);
-	}
+	float ballRadius = 0.1;
+	float coronaWidth = 0.05;
+	
+	vec4 c;
+	c = GetFireBallColor(tick / time_factor +  0.0 , radius, 6, 15.0, 1, 2, ballRadius, ballRadius + coronaWidth, vec2(1, 0), 0.75);
+	c.a *= gl_Color.a;
 	
-	gl_FragColor = col * gl_Color;
+	gl_FragColor = c;
 }
diff --git a/game/modules/tome/data/gfx/shaders/fireball.lua b/game/modules/tome/data/gfx/shaders/fireball.lua
index 39541c01f31ed85429b19e0ab8ba98e5fe1863af..33452d6812d4209536a33d40776fb183d4fdf212 100644
--- a/game/modules/tome/data/gfx/shaders/fireball.lua
+++ b/game/modules/tome/data/gfx/shaders/fireball.lua
@@ -21,13 +21,11 @@ return {
 	frag = "fireball",
 	vert = nil,
 	args = {
-		resolution = resolution or {128,128},
-		zoom = zoom or 1,
-		speed = 400,
-		color1 = {1.0, 1.0, 1.0, 0.85},
-		color2 = {1.0, 0.9, 0.1, 0.85},
-		color3 = {1.0, 0.4, 0.0, 0.85},
-		color4 = {0.6, 0.1, 0.0, 0.85},
+		tex = { texture = 0 },
+		color = color or {0.4, 0.7, 1.0},
+		time_factor = time_factor or 4000,
+		ellipsoidalFactor = ellipsoidalFactor or 1.0, --1 is perfect circle, >1 is ellipsoidal
+		oscillationSpeed = oscillationSpeed or 0.0, --oscillation between ellipsoidal and spherical form
 	},
 	clone = false,
 }
diff --git a/game/modules/tome/data/gfx/shaders/flamehands.frag b/game/modules/tome/data/gfx/shaders/flamehands.frag
new file mode 100644
index 0000000000000000000000000000000000000000..97cd0ce7236748bdbee1f4a977d7758902794754
--- /dev/null
+++ b/game/modules/tome/data/gfx/shaders/flamehands.frag
@@ -0,0 +1,219 @@
+uniform sampler2D tex;
+uniform float tick;
+uniform float time_factor;
+
+	
+vec4 permute( vec4 x ) {
+
+	return mod( ( ( x * 34.0 ) + 1.0 ) * x, 289.0 );
+
+} 
+
+vec4 taylorInvSqrt( vec4 r ) {
+
+	return 1.79284291400159 - 0.85373472095314 * r;
+
+}
+
+float snoise( vec3 v ) {
+
+	const vec2 C = vec2( 1.0 / 6.0, 1.0 / 3.0 );
+	const vec4 D = vec4( 0.0, 0.5, 1.0, 2.0 );
+
+	// First corner
+
+	vec3 i  = floor( v + dot( v, C.yyy ) );
+	vec3 x0 = v - i + dot( i, C.xxx );
+
+	// Other corners
+
+	vec3 g = step( x0.yzx, x0.xyz );
+	vec3 l = 1.0 - g;
+	vec3 i1 = min( g.xyz, l.zxy );
+	vec3 i2 = max( g.xyz, l.zxy );
+
+	vec3 x1 = x0 - i1 + 1.0 * C.xxx;
+	vec3 x2 = x0 - i2 + 2.0 * C.xxx;
+	vec3 x3 = x0 - 1. + 3.0 * C.xxx;
+
+	// Permutations
+
+	i = mod( i, 289.0 );
+	vec4 p = permute( permute( permute(
+		i.z + vec4( 0.0, i1.z, i2.z, 1.0 ) )
+		+ i.y + vec4( 0.0, i1.y, i2.y, 1.0 ) )
+		+ i.x + vec4( 0.0, i1.x, i2.x, 1.0 ) );
+
+	// Gradients
+	// ( N*N points uniformly over a square, mapped onto an octahedron.)
+
+	float n_ = 1.0 / 7.0; // N=7
+
+	vec3 ns = n_ * D.wyz - D.xzx;
+
+	vec4 j = p - 49.0 * floor( p * ns.z *ns.z );  //  mod(p,N*N)
+
+	vec4 x_ = floor( j * ns.z );
+	vec4 y_ = floor( j - 7.0 * x_ );    // mod(j,N)
+
+	vec4 x = x_ *ns.x + ns.yyyy;
+	vec4 y = y_ *ns.x + ns.yyyy;
+	vec4 h = 1.0 - abs( x ) - abs( y );
+
+	vec4 b0 = vec4( x.xy, y.xy );
+	vec4 b1 = vec4( x.zw, y.zw );
+
+
+	vec4 s0 = floor( b0 ) * 2.0 + 1.0;
+	vec4 s1 = floor( b1 ) * 2.0 + 1.0;
+	vec4 sh = -step( h, vec4( 0.0 ) );
+
+	vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
+	vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
+
+	vec3 p0 = vec3( a0.xy, h.x );
+	vec3 p1 = vec3( a0.zw, h.y );
+	vec3 p2 = vec3( a1.xy, h.z );
+	vec3 p3 = vec3( a1.zw, h.w );
+
+	// Normalise gradients
+
+	vec4 norm = taylorInvSqrt( vec4( dot( p0, p0 ), dot( p1, p1 ), dot( p2, p2 ), dot( p3, p3 ) ) );
+	p0 *= norm.x;
+	p1 *= norm.y;
+	p2 *= norm.z;
+	p3 *= norm.w;
+
+	// Mix final noise value
+
+	vec4 m = max( 0.6 - vec4( dot( x0, x0 ), dot( x1, x1 ), dot( x2, x2 ), dot( x3, x3 ) ), 0.0 );
+	m = m * m;
+	return 42.0 * dot( m*m, vec4( dot( p0, x0 ), dot( p1, x1 ),
+		dot( p2, x2 ), dot( p3, x3 ) ) );
+
+}  
+
+vec2 snoise2(vec3 pos)
+{
+	return vec2(snoise(pos), snoise(pos + vec3(0.0, 0.0, 1.0)));
+}
+
+float GetFireDelta(float currTime, vec2 pos, float freqMult, float stretchMult, float scrollSpeed, float evolutionSpeed)
+{
+	//firewall
+	float delta = 0;
+//	pos.y += (1.0 - pos.y) * 0.5;
+	//pos.y += 0.5;
+	pos.y /= stretchMult;
+	pos *= freqMult;
+	pos.y += currTime * scrollSpeed;
+//	pos.y -= currTime * 3.0;
+
+	
+	delta += snoise(vec3(pos * 1.0, currTime * 1.0 * evolutionSpeed)) * 1.5;
+	delta += snoise(vec3(pos * 2.0, currTime * 2.0 * evolutionSpeed)) * 1.5;
+	delta += snoise(vec3(pos * 4.0, currTime * 4.0 * evolutionSpeed)) * 1.5;	
+	delta += snoise(vec3(pos * 8.0, currTime * 8.0 * evolutionSpeed)) * 1.5;
+	delta += snoise(vec3(pos * 16.0, currTime * 16.0 * evolutionSpeed)) * 0.5;
+
+	return delta;
+}
+vec4 GetFireColor(float currTime, vec2 pos, float freqMult, float stretchMult, float ampMult)
+{
+	float delta = GetFireDelta(currTime, pos, freqMult, stretchMult, 3.0, 0.5);
+	delta *= min(1.0, max(0.0, 1.0 * (1.0 - pos.y)));
+	delta *= min(1.0, max(0.0, 1.0 * (0.0 + pos.y)));
+	vec2 displacedPoint = pos + vec2(0, delta * ampMult);
+	displacedPoint.y = min(0.99, displacedPoint.y);
+	displacedPoint.y = max(0.01, displacedPoint.y);
+	
+	return texture2D(tex, displacedPoint);
+}
+
+vec4 GetCheckboardColor(vec2 pos)
+{
+	vec4 col = vec4(0.0, 0.0, 0.0, 0.0);
+	if(pos.x > 0.0 && pos.x < 1.0 && pos.y > 0.0 && pos.y < 1.0)
+	{
+		if(mod(pos.x, 0.1) < 0.05 ^^ mod(pos.y, 0.1) < 0.05)
+			col = vec4(pos.x, pos.y, 0.0, 1.0);
+		else
+			col = vec4(0.0, 0.0, 0.0, 1.0);
+	}
+	return col;
+}
+
+vec4 GetFireCandleColor(float currTime, vec2 pos, float freqMult, float stretchMult, float ampMult, float power, float radius, float flameHeight, float paletteCoord)
+{
+	float pi = 3.141593;
+		
+	vec4 fireballColor = vec4(0.0, 0.0, 0.0, 0.0);
+	if(length(pos) < radius)
+	{		
+		float ang = atan(pos.x, pos.y);
+		
+		float sinAlpha = length(pos) / radius;
+		float alpha = 0.0;
+		alpha = asin(sinAlpha);
+		
+		vec2 sphericalProjectedCoord = vec2(0.5, 0.5) + pos * (alpha / (3.141592 / 2.0)) / length(pos);
+		//fireballColor = GetCheckboardColor(sphericalProjectedCoord);
+		float delta = GetFireDelta(currTime, sphericalProjectedCoord, freqMult * 0.3, 1.0, 0.0, 1.5) * (1.0 - pow(length(pos) / radius, 3.0)) * 0.5;
+		
+		float verticalPos = 0.99 - delta * delta;	
+		verticalPos = min(0.99, verticalPos);
+		verticalPos = max(0.01, verticalPos);
+		
+		fireballColor = texture2D(tex, vec2(0.75, verticalPos));
+		fireballColor.a = 1.0;
+	}else
+	{
+		float bottomPos = 0;
+		
+		pos.x += cos(pos.y * 20.0 + currTime * 20.0) * 0.05 * (1.0 - pow(1.0 - max(0.0, min((-radius - pos.y) * 2.0, 1.0)), 2.0));
+		if(abs(pos.x) < radius)
+		{
+			bottomPos = -sqrt(radius * radius - pos.x * pos.x);
+//			float ratio = (pos.y - bottomPos) / (bottomPos * (flameHeight / radius) - bottomPos);
+			float ratio = (pos.y - bottomPos) / (bottomPos - flameHeight - radius - bottomPos) * (1.0 + 0.5 * pow(abs(pos.x) / radius, 4.0));
+			
+			vec2 planarPos = vec2(pos.x / radius, 1.0 - ratio);
+			planarPos.x *= 1.0 + (1.0 - planarPos.y) * 2.5;
+			planarPos.x = planarPos.x / 2.0 + 0.5;
+
+			if(planarPos.x > 0.0 && planarPos.x < 1.0 && planarPos.y > 0.0 && planarPos.y < 1.0)
+			{
+				float delta = GetFireDelta(currTime, planarPos, freqMult * 0.2, 0.6, 1.0, 0.2);
+				delta *= min(1.0, max(0.0, 1.0 * (1.0 - planarPos.y)));
+				delta *= min(1.0, max(0.0, 1.0 * (0.0 + planarPos.y)));	
+				
+				float verticalPos = pow(planarPos.y, 0.7) + delta * 1.0;	
+				verticalPos = min(0.99, verticalPos);
+				verticalPos = max(0.01, verticalPos);
+				
+				//fireballColor = GetCheckboardColor(planarPos);
+				fireballColor = texture2D(tex, vec2(0.25, verticalPos));
+			}
+		}
+	}
+	return fireballColor;
+}
+
+void main(void)
+{
+	vec2 radius = gl_TexCoord[0].xy - vec2(0.5, 0.8);
+	
+	//on-hit wobbling effect
+	float radiusLen = length(radius);
+		
+	
+	float ballRadius = 0.1;
+	float flameHeight = 0.6;
+	
+	vec4 c;
+	c = GetFireCandleColor(tick / time_factor +  0.0 , radius, 2, 3.0, 1, 2, ballRadius, flameHeight, 0.75);
+	c.a *= gl_Color.a;
+	//c.a += 0.5;
+	
+	gl_FragColor = c;
+}
diff --git a/game/modules/tome/data/gfx/shaders/flamehands.lua b/game/modules/tome/data/gfx/shaders/flamehands.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ece3f346c22efaa3f807ea27cab27552331aeb52
--- /dev/null
+++ b/game/modules/tome/data/gfx/shaders/flamehands.lua
@@ -0,0 +1,28 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012, 2013 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 = "flamehands",
+	vert = nil,
+	args = {
+		tex = { texture = 0 },
+		time_factor = time_factor or 700,
+	},
+	clone = false,
+}
diff --git a/game/modules/tome/data/talents/spells/enhancement.lua b/game/modules/tome/data/talents/spells/enhancement.lua
index 85cda0cb2b5e18482f66eb59e3506adb5e807f6b..f356b447af4167eaf2465e7ece0f3e945e4394db 100644
--- a/game/modules/tome/data/talents/spells/enhancement.lua
+++ b/game/modules/tome/data/talents/spells/enhancement.lua
@@ -30,13 +30,22 @@ newTalent{
 	getFireDamageIncrease = function(self, t) return self:combatTalentSpellDamage(t, 5, 14) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/fire")
-		return {
+		local ret = {
+			particle = particle,
 			dam = self:addTemporaryValue("melee_project", {[DamageType.FIRE] = t.getFireDamage(self, t)}),
 			per = self:addTemporaryValue("inc_damage", {[DamageType.FIRE] = t.getFireDamageIncrease(self, t)}),
 			sta = self:addTemporaryValue("stamina_regen_on_hit", self:getTalentLevel(t) / 3),
 		}
+		if core.shader.active(4) then
+			local slow = rng.percent(50)
+			local h1x, h1y = self:attachementSpot("hand1", true) if h1x then ret.particle1 = self:addParticles(Particles.new("shader_shield", 1, {img="fireball", a=0.7, size_factor=0.4, x=h1x, y=h1y-0.1}, {type="flamehands", time_factor=slow and 700 or 1000})) end
+			local h2x, h2y = self:attachementSpot("hand2", true) if h2x then ret.particle2 = self:addParticles(Particles.new("shader_shield", 1, {img="fireball", a=0.7, size_factor=0.4, x=h2x, y=h2y-0.1}, {type="flamehands", time_factor=not slow and 700 or 1000})) end
+		end
+		return ret
 	end,
 	deactivate = function(self, t, p)
+		if p.particle1 then self:removeParticles(p.particle1) end
+		if p.particle2 then self:removeParticles(p.particle2) end
 		self:removeTemporaryValue("melee_project", p.dam)
 		self:removeTemporaryValue("inc_damage", p.per)
 		self:removeTemporaryValue("stamina_regen_on_hit", p.sta)
@@ -89,13 +98,21 @@ newTalent{
 	getIceDamageIncrease = function(self, t) return self:combatTalentSpellDamage(t, 5, 14) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/lightning")
-		return {
+		local ret = {
 			dam = self:addTemporaryValue("melee_project", {[DamageType.LIGHTNING_DAZE] = t.getIceDamage(self, t)}),
 			per = self:addTemporaryValue("inc_damage", {[DamageType.LIGHTNING] = t.getIceDamageIncrease(self, t)}),
 			man = self:addTemporaryValue("mana_regen_on_hit", self:getTalentLevel(t) / 3),
 		}
+		if core.shader.active(4) then
+			local slow = rng.percent(50)
+			local h1x, h1y = self:attachementSpot("hand1", true) if h1x then ret.particle1 = self:addParticles(Particles.new("shader_shield", 1, {img="lightningwings", a=0.7, size_factor=0.4, x=h1x, y=h1y-0.1}, {type="flamehands", time_factor=slow and 700 or 1000})) end
+			local h2x, h2y = self:attachementSpot("hand2", true) if h2x then ret.particle2 = self:addParticles(Particles.new("shader_shield", 1, {img="lightningwings", a=0.7, size_factor=0.4, x=h2x, y=h2y-0.1}, {type="flamehands", time_factor=not slow and 700 or 1000})) end
+		end
+		return ret
 	end,
 	deactivate = function(self, t, p)
+		if p.particle1 then self:removeParticles(p.particle1) end
+		if p.particle2 then self:removeParticles(p.particle2) end
 		self:removeTemporaryValue("melee_project", p.dam)
 		self:removeTemporaryValue("inc_damage", p.per)
 		self:removeTemporaryValue("mana_regen_on_hit", p.man)
diff --git a/game/modules/tome/data/talents/spells/wildfire.lua b/game/modules/tome/data/talents/spells/wildfire.lua
index 42a6e791d13a62f5fd51ab0e6a0c44cb1410d336..48ad5bcf0570e73fc17cf5bb439e6e9cedc02d5d 100644
--- a/game/modules/tome/data/talents/spells/wildfire.lua
+++ b/game/modules/tome/data/talents/spells/wildfire.lua
@@ -124,7 +124,8 @@ newTalent{
 
 		local particle
 		if core.shader.active(4) then
-			particle = self:addParticles(Particles.new("shader_wings", 1, {infinite=1, x=self.wings_x, y=self.wings_y}))
+			local bx, by = self:attachementSpot("back", true)
+			particle = self:addParticles(Particles.new("shader_wings", 1, {infinite=1, x=bx, y=by}))
 		else
 			particle = self:addParticles(Particles.new("wildfire", 1))
 		end
diff --git a/game/modules/tome/data/zones/town-angolwen/npcs.lua b/game/modules/tome/data/zones/town-angolwen/npcs.lua
index e156b501c43a2e005cf308a2a6f20754110c4f74..91ca2abca2795dcb308b89bab42e40fa6b6d12d7 100644
--- a/game/modules/tome/data/zones/town-angolwen/npcs.lua
+++ b/game/modules/tome/data/zones/town-angolwen/npcs.lua
@@ -94,7 +94,7 @@ newEntity{ define_as = "SUPREME_ARCHMAGE_LINANIIL",
 		[Talents.T_KEEN_SENSES]=5,
 		[Talents.T_PREMONITION]=5,
 	},
-	wings_x = 2, wings_y = -33,
+	attachement_spots = { back={x=35/64, y=1/64} },
 	resolvers.sustains_at_birth(),
 
 	can_talk = "angolwen-leader",
diff --git a/game/modules/tome/dialogs/Birther.lua b/game/modules/tome/dialogs/Birther.lua
index 50a439022a058b8aea3f3ff6694b72491ef62572..5cca8e2cc5653e80e373ec81f198a720ea148f0d 100644
--- a/game/modules/tome/dialogs/Birther.lua
+++ b/game/modules/tome/dialogs/Birther.lua
@@ -1035,6 +1035,7 @@ function _M:setTile(f, w, h, last)
 	self.actor:removeAllMOs()
 	if not f then
 		if not self.has_custom_tile then
+			local dbr = self.birth_descriptor_def.race[self.descriptors_by_type.race or "Human"]
 			local dr = self.birth_descriptor_def.subrace[self.descriptors_by_type.subrace or "Cornac"]
 			local ds = self.birth_descriptor_def.sex[self.descriptors_by_type.sex or "Female"]
 			self.actor.image = "player/"..(self.descriptors_by_type.subrace or "Cornac"):lower():gsub("[^a-z0-9_]", "_").."_"..(self.descriptors_by_type.sex or "Female"):lower():gsub("[^a-z0-9_]", "_")..".png"
@@ -1044,10 +1045,24 @@ function _M:setTile(f, w, h, last)
 			self.actor.moddable_tile = dr.copy.moddable_tile
 			self.actor.moddable_tile_base = dr.copy.moddable_tile_base
 			self.actor.moddable_tile_ornament = dr.copy.moddable_tile_ornament
+			self.actor.attachement_spots = nil
+			local moddable_attachement_spots = dr.moddable_attachement_spots or dbr.moddable_attachement_spots
+			if moddable_attachement_spots then
+				local base = moddable_attachement_spots.base
+				local b = moddable_attachement_spots.all
+				if not b then b = self.actor.female and moddable_attachement_spots.female or moddable_attachement_spots.male end
+				local t = {}
+				self.actor.attachement_spots = t
+				for kind, d in pairs(b) do
+					t[kind] = {}
+					for o, p in pairs(d) do t[kind][o] = p / base end
+				end
+			end
 		end
 	else
 		self.actor.make_tile = nil
 		self.actor.moddable_tile = nil
+		self.actor.attachement_spots = nil
 		if h > w then
 			self.actor.image = "invis.png"
 			self.actor.add_mos = {{image=f, display_h=2, display_y=-1}}