From b59d0632f51dae928255401dd0089d923ad3b147 Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Sun, 24 Oct 2010 21:41:42 +0000
Subject: [PATCH] Breath attacks now have their own particles effects

git-svn-id: http://svn.net-core.org/repos/t-engine4@1617 51575b47-30f0-44d4-a5cc-537603b46e54
---
 game/engines/default/engine/utils.lua         |  11 +++
 game/modules/tome/class/Game.lua              |   2 +-
 game/modules/tome/data/gfx/particle_cloud.png | Bin 0 -> 4578 bytes
 .../tome/data/gfx/particles/breath_acid.lua   |  76 +++++++++++++++++
 .../tome/data/gfx/particles/breath_cold.lua   |  76 +++++++++++++++++
 .../tome/data/gfx/particles/breath_earth.lua  |  76 +++++++++++++++++
 .../tome/data/gfx/particles/breath_fire.lua   |  80 ++++++++++++------
 .../data/gfx/particles/breath_fire_navier.lua |  50 +++++++++++
 .../data/gfx/particles/breath_lightning.lua   |  76 +++++++++++++++++
 .../tome/data/gfx/particles/breath_slime.lua  |  76 +++++++++++++++++
 .../tome/data/talents/gifts/cold-drake.lua    |   3 +-
 .../tome/data/talents/gifts/fire-drake.lua    |   3 +-
 .../tome/data/talents/gifts/sand-drake.lua    |   3 +-
 .../tome/data/talents/gifts/storm-drake.lua   |   3 +-
 .../data/talents/gifts/summon-distance.lua    |   9 +-
 .../modules/tome/data/talents/spells/fire.lua |   5 +-
 .../data/talents/spells/stone-alchemy.lua     |   2 +-
 17 files changed, 513 insertions(+), 38 deletions(-)
 create mode 100644 game/modules/tome/data/gfx/particle_cloud.png
 create mode 100644 game/modules/tome/data/gfx/particles/breath_acid.lua
 create mode 100644 game/modules/tome/data/gfx/particles/breath_cold.lua
 create mode 100644 game/modules/tome/data/gfx/particles/breath_earth.lua
 create mode 100644 game/modules/tome/data/gfx/particles/breath_fire_navier.lua
 create mode 100644 game/modules/tome/data/gfx/particles/breath_lightning.lua
 create mode 100644 game/modules/tome/data/gfx/particles/breath_slime.lua

diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index f040011f84..a19b4eaac0 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -696,6 +696,17 @@ setmetatable(tstring, {
 })
 
 
+dir_to_angle = {
+	[1] = 225,
+	[2] = 270,
+	[3] = 315,
+	[4] = 180,
+	[5] = 0,
+	[6] = 0,
+	[7] = 135,
+	[8] = 90,
+	[9] = 45,
+}
 dir_to_coord = {
 	[1] = {-1, 1},
 	[2] = { 0, 1},
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index e37c3a4452..f3ca821080 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -658,7 +658,7 @@ function _M:setupCommands()
 				a.faction = "enemies"
 				self.zone:addEntity(self.level, a, "actor", self.player.x+1, self.player.y)
 --]]
-				game.level.map:particleEmitter(self.player.x, self.player.y, 1, "breath_fire")
+				game.level.map:particleEmitter(self.player.x, self.player.y, 1, "breath_cold", {radius=6, tx=10, ty=2})
 			end
 		end,
 	}
diff --git a/game/modules/tome/data/gfx/particle_cloud.png b/game/modules/tome/data/gfx/particle_cloud.png
new file mode 100644
index 0000000000000000000000000000000000000000..061ff9c7e6ba080a6a7eca53c45b03123b4c6b91
GIT binary patch
literal 4578
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
M07*qoM6N<$f}F37;{X5v

literal 0
HcmV?d00001

diff --git a/game/modules/tome/data/gfx/particles/breath_acid.lua b/game/modules/tome/data/gfx/particles/breath_acid.lua
new file mode 100644
index 0000000000..320567da9f
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/breath_acid.lua
@@ -0,0 +1,76 @@
+-- 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
+
+local nb = 12
+local dir
+local spread = spread or 55/2
+local radius = radius or 6
+
+local max = math.max(math.abs(tx), math.abs(ty))
+tx = tx / max
+ty = ty / max
+if tx >= 0.5 then tx = 1 elseif tx <= -0.5 then tx = -1 else tx = 0 end
+if ty >= 0.5 then ty = -1 elseif ty <= -0.5 then ty = 1 else ty = 0 end -- Why the hell is ty inverted ? .. but it works :/
+
+if tx == 1 and ty == 0 then dir = 0
+elseif tx ==  1 and ty == -1 then dir = 45
+elseif tx ==  0 and ty == -1 then dir = 90
+elseif tx == -1 and ty == -1 then dir = 135
+elseif tx == -1 and ty ==  0 then dir = 180
+elseif tx == -1 and ty ==  1 then dir = 225
+elseif tx ==  0 and ty ==  1 then dir = 270
+elseif tx ==  1 and ty ==  1 then dir = 315
+end
+
+return { generator = function()
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(dir - spread, dir + spread)
+	local a = math.rad(ad)
+	local r = 0
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local static = rng.percent(40)
+	local vel = sradius * ((24 - nb * 1.4) / 24) / 12
+
+	return {
+		trail = 1,
+		life = 12,
+		size = 12 - (12 - nb) * 0.7, sizev = 0, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = a, dirv = 0, dira = 0,
+		vel = rng.float(vel * 0.6, vel * 1.2), velv = 0, vela = 0,
+
+		r = rng.range(0, 0)/255,   rv = 0, ra = 0,
+		g = rng.range(80, 200)/255,   gv = 0.005, ga = 0.0005,
+		b = rng.range(0, 0)/255,      bv = 0, ba = 0,
+		a = rng.range(255, 255)/255,    av = static and -0.034 or 0, aa = 0.005,
+	}
+end, },
+function(self)
+	if nb > 0 then
+		local i = math.min(nb, 6)
+		i = (i * i) * radius
+		self.ps:emit(i)
+		nb = nb - 1
+	end
+end,
+30*radius*7*12,
+"particle_cloud"
diff --git a/game/modules/tome/data/gfx/particles/breath_cold.lua b/game/modules/tome/data/gfx/particles/breath_cold.lua
new file mode 100644
index 0000000000..62724c083e
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/breath_cold.lua
@@ -0,0 +1,76 @@
+-- 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
+
+local nb = 12
+local dir
+local spread = spread or 55/2
+local radius = radius or 6
+
+local max = math.max(math.abs(tx), math.abs(ty))
+tx = tx / max
+ty = ty / max
+if tx >= 0.5 then tx = 1 elseif tx <= -0.5 then tx = -1 else tx = 0 end
+if ty >= 0.5 then ty = -1 elseif ty <= -0.5 then ty = 1 else ty = 0 end -- Why the hell is ty inverted ? .. but it works :/
+
+if tx == 1 and ty == 0 then dir = 0
+elseif tx ==  1 and ty == -1 then dir = 45
+elseif tx ==  0 and ty == -1 then dir = 90
+elseif tx == -1 and ty == -1 then dir = 135
+elseif tx == -1 and ty ==  0 then dir = 180
+elseif tx == -1 and ty ==  1 then dir = 225
+elseif tx ==  0 and ty ==  1 then dir = 270
+elseif tx ==  1 and ty ==  1 then dir = 315
+end
+
+return { generator = function()
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(dir - spread, dir + spread)
+	local a = math.rad(ad)
+	local r = 0
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local static = rng.percent(40)
+	local vel = sradius * ((24 - nb * 1.4) / 24) / 12
+
+	return {
+		trail = 1,
+		life = 12,
+		size = 12 - (12 - nb) * 0.7, sizev = 0, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = a, dirv = 0, dira = 0,
+		vel = rng.float(vel * 0.6, vel * 1.2), velv = 0, vela = 0,
+
+		r = 0,   rv = 0, ra = 0,
+		g = rng.range(170, 210)/255,   gv = 0, ga = 0,
+		b = rng.range(200, 255)/255,   gv = 0, ga = 0,
+		a = rng.range(230, 225)/255,   av = 0, aa = 0,
+	}
+end, },
+function(self)
+	if nb > 0 then
+		local i = math.min(nb, 6)
+		i = (i * i) * radius
+		self.ps:emit(i)
+		nb = nb - 1
+	end
+end,
+30*radius*7*12,
+"particle_cloud"
diff --git a/game/modules/tome/data/gfx/particles/breath_earth.lua b/game/modules/tome/data/gfx/particles/breath_earth.lua
new file mode 100644
index 0000000000..ff020def46
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/breath_earth.lua
@@ -0,0 +1,76 @@
+-- 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
+
+local nb = 12
+local dir
+local spread = spread or 55/2
+local radius = radius or 6
+
+local max = math.max(math.abs(tx), math.abs(ty))
+tx = tx / max
+ty = ty / max
+if tx >= 0.5 then tx = 1 elseif tx <= -0.5 then tx = -1 else tx = 0 end
+if ty >= 0.5 then ty = -1 elseif ty <= -0.5 then ty = 1 else ty = 0 end -- Why the hell is ty inverted ? .. but it works :/
+
+if tx == 1 and ty == 0 then dir = 0
+elseif tx ==  1 and ty == -1 then dir = 45
+elseif tx ==  0 and ty == -1 then dir = 90
+elseif tx == -1 and ty == -1 then dir = 135
+elseif tx == -1 and ty ==  0 then dir = 180
+elseif tx == -1 and ty ==  1 then dir = 225
+elseif tx ==  0 and ty ==  1 then dir = 270
+elseif tx ==  1 and ty ==  1 then dir = 315
+end
+
+return { generator = function()
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(dir - spread, dir + spread)
+	local a = math.rad(ad)
+	local r = 0
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local static = rng.percent(40)
+	local vel = sradius * ((24 - nb * 1.4) / 24) / 12
+
+	return {
+		trail = 1,
+		life = 12,
+		size = 12 - (12 - nb) * 0.7, sizev = 0, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = a, dirv = 0, dira = 0,
+		vel = rng.float(vel * 0.6, vel * 1.2), velv = 0, vela = 0,
+
+		r = rng.range(200, 230)/255,   rv = 0, ra = 0,
+		g = rng.range(130, 160)/255,   gv = 0.005, ga = 0.0005,
+		b = rng.range(50, 70)/255,      bv = 0, ba = 0,
+		a = rng.range(255, 255)/255,    av = static and -0.034 or 0, aa = 0.005,
+	}
+end, },
+function(self)
+	if nb > 0 then
+		local i = math.min(nb, 6)
+		i = (i * i) * radius
+		self.ps:emit(i)
+		nb = nb - 1
+	end
+end,
+30*radius*7*12,
+"particle_cloud"
diff --git a/game/modules/tome/data/gfx/particles/breath_fire.lua b/game/modules/tome/data/gfx/particles/breath_fire.lua
index 10b39656b6..d688e7ceab 100644
--- a/game/modules/tome/data/gfx/particles/breath_fire.lua
+++ b/game/modules/tome/data/gfx/particles/breath_fire.lua
@@ -17,34 +17,60 @@
 -- Nicolas Casalini "DarkGod"
 -- darkgod@te4.org
 
-local nb = 0
+local nb = 12
+local dir
+local spread = spread or 55/2
+local radius = radius or 6
 
-return { gas = {w=60, h=60},
-generator = function()
-	if nb < 10 then
-		nb = nb + 1
-		return {
---[[
-			{ sx = 3, sy = 29, dx = 3, dy = -3 },
-			{ sx = 3, sy = 30, dx = 3, dy = 0 },
-			{ sx = 3, sy = 31, dx = 3, dy = 3 },
+local max = math.max(math.abs(tx), math.abs(ty))
+tx = tx / max
+ty = ty / max
+if tx >= 0.5 then tx = 1 elseif tx <= -0.5 then tx = -1 else tx = 0 end
+if ty >= 0.5 then ty = -1 elseif ty <= -0.5 then ty = 1 else ty = 0 end -- Why the hell is ty inverted ? .. but it works :/
 
-			{ sx = 4, sy = 29, dx = 3, dy = -3 },
-			{ sx = 4, sy = 30, dx = 3, dy = 0 },
-			{ sx = 4, sy = 31, dx = 3, dy = 3 },
-]]
-			{ sx = 29, sy = 29, dx = -3, dy = -3 },
-			{ sx = 31, sy = 29, dx =  3, dy = -3 },
-			{ sx = 29, sy = 31, dx = -3, dy =  3 },
-			{ sx = 31, sy = 31, dx =  3, dy =  3 },
+if tx == 1 and ty == 0 then dir = 0
+elseif tx ==  1 and ty == -1 then dir = 45
+elseif tx ==  0 and ty == -1 then dir = 90
+elseif tx == -1 and ty == -1 then dir = 135
+elseif tx == -1 and ty ==  0 then dir = 180
+elseif tx == -1 and ty ==  1 then dir = 225
+elseif tx ==  0 and ty ==  1 then dir = 270
+elseif tx ==  1 and ty ==  1 then dir = 315
+end
 
-			{ sx = 30, sy = 29, dx =  0, dy = -3 },
-			{ sx = 30, sy = 31, dx =  0, dy =  3 },
-			{ sx = 29, sy = 30, dx = -3, dy =  0 },
-			{ sx = 31, sy = 30, dx =  3, dy =  0 },
-		}
-	else return {}
-	end
+return { generator = function()
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(dir - spread, dir + spread)
+	local a = math.rad(ad)
+	local r = 0
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local static = rng.percent(40)
+	local vel = sradius * ((24 - nb * 1.4) / 24) / 12
+
+	return {
+		trail = 1,
+		life = 12,
+		size = 12 - (12 - nb) * 0.7, sizev = 0, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = a, dirv = 0, dira = 0,
+		vel = rng.float(vel * 0.6, vel * 1.2), velv = 0, vela = 0,
+
+		r = rng.range(200, 255)/255,   rv = 0, ra = 0,
+		g = rng.range(120, 170)/255,   gv = 0.005, ga = 0.0005,
+		b = rng.range(0, 10)/255,      bv = 0, ba = 0,
+		a = rng.range(25, 220)/255,    av = static and -0.034 or 0, aa = 0.005,
+	}
 end, },
-function(self)end,
-30*6
+function(self)
+	if nb > 0 then
+		local i = math.min(nb, 6)
+		i = (i * i) * radius
+		self.ps:emit(i)
+		nb = nb - 1
+	end
+end,
+30*radius*7*12,
+"particle_cloud"
diff --git a/game/modules/tome/data/gfx/particles/breath_fire_navier.lua b/game/modules/tome/data/gfx/particles/breath_fire_navier.lua
new file mode 100644
index 0000000000..10b39656b6
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/breath_fire_navier.lua
@@ -0,0 +1,50 @@
+-- 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
+
+local nb = 0
+
+return { gas = {w=60, h=60},
+generator = function()
+	if nb < 10 then
+		nb = nb + 1
+		return {
+--[[
+			{ sx = 3, sy = 29, dx = 3, dy = -3 },
+			{ sx = 3, sy = 30, dx = 3, dy = 0 },
+			{ sx = 3, sy = 31, dx = 3, dy = 3 },
+
+			{ sx = 4, sy = 29, dx = 3, dy = -3 },
+			{ sx = 4, sy = 30, dx = 3, dy = 0 },
+			{ sx = 4, sy = 31, dx = 3, dy = 3 },
+]]
+			{ sx = 29, sy = 29, dx = -3, dy = -3 },
+			{ sx = 31, sy = 29, dx =  3, dy = -3 },
+			{ sx = 29, sy = 31, dx = -3, dy =  3 },
+			{ sx = 31, sy = 31, dx =  3, dy =  3 },
+
+			{ sx = 30, sy = 29, dx =  0, dy = -3 },
+			{ sx = 30, sy = 31, dx =  0, dy =  3 },
+			{ sx = 29, sy = 30, dx = -3, dy =  0 },
+			{ sx = 31, sy = 30, dx =  3, dy =  0 },
+		}
+	else return {}
+	end
+end, },
+function(self)end,
+30*6
diff --git a/game/modules/tome/data/gfx/particles/breath_lightning.lua b/game/modules/tome/data/gfx/particles/breath_lightning.lua
new file mode 100644
index 0000000000..06e81a80f8
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/breath_lightning.lua
@@ -0,0 +1,76 @@
+-- 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
+
+local nb = 12
+local dir
+local spread = spread or 55/2
+local radius = radius or 6
+
+local max = math.max(math.abs(tx), math.abs(ty))
+tx = tx / max
+ty = ty / max
+if tx >= 0.5 then tx = 1 elseif tx <= -0.5 then tx = -1 else tx = 0 end
+if ty >= 0.5 then ty = -1 elseif ty <= -0.5 then ty = 1 else ty = 0 end -- Why the hell is ty inverted ? .. but it works :/
+
+if tx == 1 and ty == 0 then dir = 0
+elseif tx ==  1 and ty == -1 then dir = 45
+elseif tx ==  0 and ty == -1 then dir = 90
+elseif tx == -1 and ty == -1 then dir = 135
+elseif tx == -1 and ty ==  0 then dir = 180
+elseif tx == -1 and ty ==  1 then dir = 225
+elseif tx ==  0 and ty ==  1 then dir = 270
+elseif tx ==  1 and ty ==  1 then dir = 315
+end
+
+return { generator = function()
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(dir - spread, dir + spread)
+	local a = math.rad(ad)
+	local r = 0
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local static = rng.percent(40)
+	local vel = sradius * ((24 - nb * 1.4) / 24) / 12
+
+	return {
+		trail = 1,
+		life = 12,
+		size = 12 - (12 - nb) * 0.7, sizev = 0, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = a, dirv = 0, dira = 0,
+		vel = rng.float(vel * 0.6, vel * 1.2), velv = 0, vela = 0,
+
+		r = rng.range(140, 200)/255, rv = 0, ra = 0,
+		g = rng.range(180, 220)/255, gv = 0, ga = 0,
+		b = rng.range(220, 240)/255, bv = 0, ba = 0,
+		a = rng.range(230, 255)/255, av = 0, aa = 0,
+	}
+end, },
+function(self)
+	if nb > 0 then
+		local i = math.min(nb, 6)
+		i = (i * i) * radius
+		self.ps:emit(i)
+		nb = nb - 1
+	end
+end,
+30*radius*7*12,
+"particle_cloud"
diff --git a/game/modules/tome/data/gfx/particles/breath_slime.lua b/game/modules/tome/data/gfx/particles/breath_slime.lua
new file mode 100644
index 0000000000..06844e7cd7
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/breath_slime.lua
@@ -0,0 +1,76 @@
+-- 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
+
+local nb = 12
+local dir
+local spread = spread or 55/2
+local radius = radius or 6
+
+local max = math.max(math.abs(tx), math.abs(ty))
+tx = tx / max
+ty = ty / max
+if tx >= 0.5 then tx = 1 elseif tx <= -0.5 then tx = -1 else tx = 0 end
+if ty >= 0.5 then ty = -1 elseif ty <= -0.5 then ty = 1 else ty = 0 end -- Why the hell is ty inverted ? .. but it works :/
+
+if tx == 1 and ty == 0 then dir = 0
+elseif tx ==  1 and ty == -1 then dir = 45
+elseif tx ==  0 and ty == -1 then dir = 90
+elseif tx == -1 and ty == -1 then dir = 135
+elseif tx == -1 and ty ==  0 then dir = 180
+elseif tx == -1 and ty ==  1 then dir = 225
+elseif tx ==  0 and ty ==  1 then dir = 270
+elseif tx ==  1 and ty ==  1 then dir = 315
+end
+
+return { generator = function()
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(dir - spread, dir + spread)
+	local a = math.rad(ad)
+	local r = 0
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local static = rng.percent(40)
+	local vel = sradius * ((24 - nb * 1.4) / 24) / 12
+
+	return {
+		trail = 1,
+		life = 12,
+		size = 12 - (12 - nb) * 0.7, sizev = 0, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = a, dirv = 0, dira = 0,
+		vel = rng.float(vel * 0.6, vel * 1.2), velv = 0, vela = 0,
+
+		r = 0,  rv = 0, ra = 0,
+		g = rng.range(80, 200)/255,      gv = 0, ga = 0,
+		b = 0,      bv = 0, ba = 0,
+		a = rng.range(80, 220)/255,   av = 0, aa = 0,
+	}
+end, },
+function(self)
+	if nb > 0 then
+		local i = math.min(nb, 6)
+		i = (i * i) * radius
+		self.ps:emit(i)
+		nb = nb - 1
+	end
+end,
+30*radius*7*12,
+"particle_cloud"
diff --git a/game/modules/tome/data/talents/gifts/cold-drake.lua b/game/modules/tome/data/talents/gifts/cold-drake.lua
index 6ef30cf79a..f4dca7d209 100644
--- a/game/modules/tome/data/talents/gifts/cold-drake.lua
+++ b/game/modules/tome/data/talents/gifts/cold-drake.lua
@@ -141,7 +141,8 @@ newTalent{
 		local tg = {type="cone", range=0, radius=self:getTalentRange(t), friendlyfire=false, talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.ICE, 30 + self:getStr(50) * self:getTalentLevel(t), {type="freeze"})
+		self:project(tg, x, y, DamageType.ICE, 30 + self:getStr(50) * self:getTalentLevel(t))
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_cold", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/breath")
 		return true
 	end,
diff --git a/game/modules/tome/data/talents/gifts/fire-drake.lua b/game/modules/tome/data/talents/gifts/fire-drake.lua
index 52e4090ac3..7c64bd6278 100644
--- a/game/modules/tome/data/talents/gifts/fire-drake.lua
+++ b/game/modules/tome/data/talents/gifts/fire-drake.lua
@@ -126,7 +126,8 @@ newTalent{
 		local tg = {type="cone", range=0, radius=self:getTalentRange(t), friendlyfire=false, talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.FIREBURN, 30 + self:getStr(65) * self:getTalentLevel(t), {type="flame"})
+		self:project(tg, x, y, DamageType.FIREBURN, 30 + self:getStr(65) * self:getTalentLevel(t))
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_fire", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/breath")
 		return true
 	end,
diff --git a/game/modules/tome/data/talents/gifts/sand-drake.lua b/game/modules/tome/data/talents/gifts/sand-drake.lua
index 6677d77304..b7ae0d901b 100644
--- a/game/modules/tome/data/talents/gifts/sand-drake.lua
+++ b/game/modules/tome/data/talents/gifts/sand-drake.lua
@@ -116,7 +116,8 @@ newTalent{
 		local tg = {type="cone", range=0, radius=self:getTalentRange(t), friendlyfire=false, talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.SAND, {dur=2+self:getTalentLevelRaw(t), dam=10 + self:getStr() * 0.3 * self:getTalentLevel(t)}, {type="flame"})
+		self:project(tg, x, y, DamageType.SAND, {dur=2+self:getTalentLevelRaw(t), dam=10 + self:getStr() * 0.3 * self:getTalentLevel(t)})
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_earth", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/breath")
 		return true
 	end,
diff --git a/game/modules/tome/data/talents/gifts/storm-drake.lua b/game/modules/tome/data/talents/gifts/storm-drake.lua
index b372715b2d..6dfdea7947 100644
--- a/game/modules/tome/data/talents/gifts/storm-drake.lua
+++ b/game/modules/tome/data/talents/gifts/storm-drake.lua
@@ -168,7 +168,8 @@ newTalent{
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
 		local dam = 40 + self:getStr(80) * self:getTalentLevel(t)
-		self:project(tg, x, y, DamageType.LIGHTNING_DAZE, rng.avg(dam / 3, dam, 3), {type="lightning_explosion"})
+		self:project(tg, x, y, DamageType.LIGHTNING_DAZE, rng.avg(dam / 3, dam, 3))
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_lightning", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/breath")
 		return true
 	end,
diff --git a/game/modules/tome/data/talents/gifts/summon-distance.lua b/game/modules/tome/data/talents/gifts/summon-distance.lua
index 50ddbeb6ca..ecc83cf947 100644
--- a/game/modules/tome/data/talents/gifts/summon-distance.lua
+++ b/game/modules/tome/data/talents/gifts/summon-distance.lua
@@ -56,7 +56,8 @@ newTalent{
 		local tg = {type="cone", range=0, radius=self:getTalentRange(t), friendlyfire=false, talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.ACID, 30 + self:getWil(50) * self:getTalentLevel(t), {type="acid"})
+		self:project(tg, x, y, DamageType.ACID, 30 + self:getWil(50) * self:getTalentLevel(t))
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_acid", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/breath")
 		return true
 	end,
@@ -84,7 +85,8 @@ newTalent{
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
 		local dam = 30 + self:getWil(80) * self:getTalentLevel(t)
-		self:project(tg, x, y, DamageType.LIGHTNING, rng.avg(dam / 3, dam, 3), {type="lightning_explosion"})
+		self:project(tg, x, y, DamageType.LIGHTNING, rng.avg(dam / 3, dam, 3))
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_lightning", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/lightning")
 		return true
 	end,
@@ -115,7 +117,8 @@ newTalent{
 		local tg = {type="cone", range=0, radius=self:getTalentRange(t), friendlyfire=false, talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.POISON, 30 + self:getWil(70) * self:getTalentLevel(t), {type="slime"})
+		self:project(tg, x, y, DamageType.POISON, 30 + self:getWil(70) * self:getTalentLevel(t))
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_slime", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/breath")
 		return true
 	end,
diff --git a/game/modules/tome/data/talents/spells/fire.lua b/game/modules/tome/data/talents/spells/fire.lua
index 7a4f42c63f..99d32e4fc7 100644
--- a/game/modules/tome/data/talents/spells/fire.lua
+++ b/game/modules/tome/data/talents/spells/fire.lua
@@ -53,7 +53,7 @@ newTalent{
 	points = 5,
 	random_ego = "attack",
 	mana = 30,
-	cooldown = 18,
+--	cooldown = 18,
 	tactical = {
 		ATTACKAREA = 10,
 	},
@@ -63,7 +63,8 @@ newTalent{
 		local tg = {type="cone", range=0, radius=3 + self:getTalentLevelRaw(t), friendlyfire=false, talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.FLAMESHOCK, {dur=self:getTalentLevelRaw(t) + 2, dam=self:spellCrit(self:combatTalentSpellDamage(t, 10, 120))}, {type="flame"})
+		self:project(tg, x, y, DamageType.FLAMESHOCK, {dur=self:getTalentLevelRaw(t) + 2, dam=self:spellCrit(self:combatTalentSpellDamage(t, 10, 120))})
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_fire", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/fire")
 		return true
 	end,
diff --git a/game/modules/tome/data/talents/spells/stone-alchemy.lua b/game/modules/tome/data/talents/spells/stone-alchemy.lua
index 52d39ba6a3..893c8abc7d 100644
--- a/game/modules/tome/data/talents/spells/stone-alchemy.lua
+++ b/game/modules/tome/data/talents/spells/stone-alchemy.lua
@@ -68,7 +68,7 @@ newTalent{
 			self:removeObject(inven, item)
 
 			local level = o.material_level or 1
-			local gem = game.zone:makeEntity(game.level, "object", {type="gem", special=function(e) return e.material_level == level end}, nil, true)
+			local gem = game.zone:makeEntity(game.level, "object", {type="gem", special=function(e) return not e.unique and e.material_level == level end}, nil, true)
 			if gem then
 				self:addObject(self.INVEN_INVEN, gem)
 				game.logPlayer(self, "You extract: %s", gem:getName{do_color=true, do_count=true})
-- 
GitLab