diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index b44efea96690154374b634aefa96e65b15e12b78..3c5932f606b0c1dabc25a72b0256b6d0d7bbd330 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -132,6 +132,13 @@ function _M:init(t, no_default)
 	t.positive = t.positive or 0
 	t.negative = t.negative or 0
 
+	t.hate_rating = t.hate_rating or 0.2
+	t.hate_regen = t.hate_regen or -0.035
+	t.max_hate = t.max_hate or 10
+	t.absolute_max_hate = t.absolute_max_hate or 15
+	t.hate = t.hate or 10
+	t.hate_per_kill = t.hate_per_kill or 0.8
+
 	-- Equilibrium has a default very high max, as bad effects happen even before reaching it
 	t.max_equilibrium = t.max_equilibrium or 100000
 	t.equilibrium = t.equilibrium or 0
@@ -177,6 +184,10 @@ function _M:act()
 	self:cooldownTalents()
 	-- Regen resources
 	self:regenLife()
+	if self:knowTalent(self.T_UNNATURAL_BODY) then
+		local t = self:getTalentFromId(self.T_UNNATURAL_BODY)
+		t.do_regenLife(self, t)
+	end
 	self:regenResources()
 	-- Compute timed effects
 	self:timedEffects()
@@ -198,6 +209,11 @@ function _M:act()
 		local t = self:getTalentFromId(self.T_BLOOD_FRENZY)
 		t.do_turn(self, t)
 	end
+	-- this handles cursed gloom turn based effects
+	if self:isTalentActive(self.T_GLOOM) then
+	    local t = self:getTalentFromId(self.T_GLOOM)
+		t.do_gloom(self, t)
+	end
 
 	if self:attr("stunned") then self.energy.value = 0 end
 	if self:attr("stoned") then self.energy.value = 0 end
@@ -500,6 +516,12 @@ function _M:onTakeHit(value, src)
 		self:removeEffect(self.EFF_DAZED)
 	end
 
+	-- remove stalking if there is an interaction
+	if self.stalker and src and self.stalker == src then
+		self.stalker:removeEffect(self.EFF_STALKER)
+		self:removeEffect(self.EFF_STALKED)
+	end
+
 	if self:attr("invulnerable") then
 		return 0
 	end
@@ -591,6 +613,11 @@ function _M:onTakeHit(value, src)
 		end
 	end
 
+	-- Adds hate
+	if src and src.max_hate and src.max_hate > 0 then
+		src.hate = math.min(src.max_hate, src.hate + src.hate_per_kill)
+	end
+
 	-- Achievements
 	if src and src.resolveSource and src:resolveSource().player and value >= 600 then
 		world:gainAchievement("SIZE_MATTERS", src:resolveSource())
@@ -775,6 +802,10 @@ function _M:levelup()
 	self:incMaxStamina(self.stamina_rating)
 	self:incMaxPositive(self.positive_negative_rating)
 	self:incMaxNegative(self.positive_negative_rating)
+	if self.max_hate < self.absolute_max_hate then
+		local amount = math.min(self.hate_rating, self.absolute_max_hate - self.max_hate)
+		self:incMaxHate(amount)
+	end
 	-- Heal up on new level
 	self:resetToFull()
 
@@ -908,6 +939,7 @@ function _M:learnTalent(t_id, force, nb)
 	if t.type[1]:find("^corruption/") and not self:knowTalent(self.T_VIM_POOL) and t.vim or t.sustain_vim then self:learnTalent(self.T_VIM_POOL, true) end
 	if t.type[1]:find("^divine/") and (t.positive or t.sustain_positive) and not self:knowTalent(self.T_POSITIVE_POOL) then self:learnTalent(self.T_POSITIVE_POOL, true) end
 	if t.type[1]:find("^divine/") and (t.negative or t.sustain_negative) and not self:knowTalent(self.T_NEGATIVE_POOL) then self:learnTalent(self.T_NEGATIVE_POOL, true) end
+	if t.type[1]:find("^cursed/") and not self:knowTalent(self.T_HATE_POOL) then self:learnTalent(self.T_HATE_POOL, true) end
 
 	-- If we learn an archery talent, also learn to shoot
 	if t.type[1]:find("^technique/archery") and not self:knowTalent(self.T_SHOOT) then self:learnTalent(self.T_SHOOT, true) end
@@ -960,6 +992,10 @@ function _M:preUseTalent(ab, silent, fake)
 			game.logPlayer(self, "You do not have enough negative energy to activate %s.", ab.name)
 			return false
 		end
+		if ab.sustain_hate and self.max_hate < ab.sustain_hate and not self:isTalentActive(ab.id) then
+			game.logPlayer(self, "You do not have enough hate to activate %s.", ab.name)
+			return false
+		end
 	else
 		if ab.mana and self:getMana() < ab.mana * (100 + self.fatigue) / 100 then
 			game.logPlayer(self, "You do not have enough mana to cast %s.", ab.name)
@@ -981,6 +1017,10 @@ function _M:preUseTalent(ab, silent, fake)
 			game.logPlayer(self, "You do not have enough negative energy to use %s.", ab.name)
 			return false
 		end
+		if ab.hate and self:getHate() < ab.hate * (100 + self.fatigue) / 100 then
+			game.logPlayer(self, "You do not have enough hate to use %s.", ab.name)
+			return false
+		end
 	end
 
 	-- Equilibrium is special, it has no max, but the higher it is the higher the chance of failure (and loss of the turn)
@@ -1065,6 +1105,9 @@ function _M:postUseTalent(ab, ret)
 			if ab.sustain_negative then
 				trigger = true; self.max_negative = self.max_negative - ab.sustain_negative
 			end
+			if ab.sustain_hate then
+				trigger = true; self.max_hate = self.max_hate - ab.sustain_hate
+			end
 		else
 			if ab.sustain_mana then
 				trigger = true; self.max_mana = self.max_mana + ab.sustain_mana
@@ -1084,6 +1127,9 @@ function _M:postUseTalent(ab, ret)
 			if ab.sustain_negative then
 				trigger = true; self.max_negative = self.max_negative + ab.sustain_negative
 			end
+			if ab.sustain_hate then
+				trigger = true; self.max_hate = self.max_hate + ab.sustain_hate
+			end
 		end
 	else
 		if ab.mana then
@@ -1102,6 +1148,9 @@ function _M:postUseTalent(ab, ret)
 		if ab.negative then
 			trigger = true; self:incNegative(-ab.negative * (100 + self.fatigue) / 100)
 		end
+		if ab.hate then
+			trigger = true; self:incHate(-ab.hate * (100 + self.fatigue) / 100)
+		end
 		-- Equilibrium is not affected by fatigue
 		if ab.equilibrium then
 			self:incEquilibrium(ab.equilibrium)
@@ -1167,6 +1216,7 @@ function _M:getTalentFullDescription(t, addlevel)
 	if t.vim or t.sustain_vim then d[#d+1] = "#6fff83#Vim cost: #888888#"..(t.sustain_vim or t.vim) end
 	if t.positive or t.sustain_positive then d[#d+1] = "#6fff83#Positive energy cost: #GOLD#"..(t.sustain_positive or t.positive * (100 + self.fatigue) / 100) end
 	if t.negative or t.sustain_negative then d[#d+1] = "#6fff83#Negative energy cost: #GREY#"..(t.sustain_negative or t.negative * (100 + self.fatigue) / 100) end
+	if t.hate or t.sustain_hate then d[#d+1] = "#6fff83#Hate cost: #GREY#"..(t.hate or t.sustain_hate) end
 	if self:getTalentRange(t) > 1 then d[#d+1] = "#6fff83#Range: #FFFFFF#"..self:getTalentRange(t)
 	else d[#d+1] = "#6fff83#Range: #FFFFFF#melee/personal"
 	end
@@ -1267,6 +1317,13 @@ function _M:canSeeNoCache(actor, def, def_pct)
 		end
 	end
 
+	-- check if the actor is stalking you
+	if self.stalker then
+		if self.stalker == actor then
+			return false, 0
+		end
+	end
+
 	-- Check for invisibility. This is a "simple" checkHit between invisible and see_invisible attrs
 	if actor:attr("invisible") then
 		-- Special case, 0 see invisible, can NEVER see invisible things
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index f42577a75be63616a88738c1412cdfd7572577ab..85c076b727c05cde6e4955e45f6948ae2bc5972b 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -236,6 +236,18 @@ function _M:playerFOV()
 			end
 		end, true, true, true)
 	end
+
+
+	-- Handle Preternatural Senses talent, a simple FOV, using cache.
+	if self:knowTalent(self.T_PRETERNATURAL_SENSES) then
+		local t = self:getTalentFromId(self.T_PRETERNATURAL_SENSES)
+		local range = self:getTalentRange(t)
+		self:computeFOV(range, "block_sense", function(x, y)
+			if game.level.map(x, y, game.level.map.ACTOR) then
+				game.level.map.seens(x, y, 1)
+			end
+		end, true, true, true)
+	end
 end
 
 --- Called before taking a hit, overload mod.class.Actor:onTakeHit() to stop resting and running
diff --git a/game/modules/tome/class/PlayerDisplay.lua b/game/modules/tome/class/PlayerDisplay.lua
index f8e13bfbc1cd6a503217c808e16e52c72a704acd..f175734f901226810149ddd26ccb5aaff1e0c41e 100644
--- a/game/modules/tome/class/PlayerDisplay.lua
+++ b/game/modules/tome/class/PlayerDisplay.lua
@@ -108,6 +108,11 @@ function _M:display()
 		self.surface:erase(0x90 / 3, 0x40 / 3, 0x10 / 3, 255, self.bars_x, h, self.bars_w * player:getVim() / player.max_vim, self.font_h)
 		self.surface:drawColorStringBlended(self.font, ("#904010#Vim:     #ffffff#%d/%d"):format(player:getVim(), player.max_vim), 0, h, 255, 255, 255) h = h + self.font_h
 	end
+	if player:knowTalent(player.T_HATE_POOL) then
+		self.surface:erase(colors.GREY.r / 5, colors.GREY.g / 5, colors.GREY.b / 5, 255, self.bars_x, h, self.bars_w, self.font_h)
+		self.surface:erase(colors.GREY.r / 2, colors.GREY.g / 2, colors.GREY.b / 2, 255, self.bars_x, h, self.bars_w * player:getHate() / 10, self.font_h)
+		self.surface:drawColorStringBlended(self.font, ("#F53CBE#Hate:    #ffffff#%.1f/%d"):format(player:getHate(), 10), 0, h, 255, 255, 255) h = h + self.font_h
+	end
 
 	if savefile_pipe.saving then h = h + self.font_h self.surface:drawColorStringBlended(self.font, "#YELLOW#Saving...", 0, h, 255, 255, 255) h = h + self.font_h end
 
diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua
index f63619b49391049441ec3c765bde7910e7227d23..f652396eb50b0c3086847811a492390262194301 100644
--- a/game/modules/tome/class/interface/Combat.lua
+++ b/game/modules/tome/class/interface/Combat.lua
@@ -83,6 +83,11 @@ function _M:attackTarget(target, damtype, mult, noenergy)
 		game.logPlayer(self, "%s notices you at the last moment!", target.name:capitalize())
 	end
 
+	if target and target:hasEffect(self.EFF_DOMINATED) and target.dominatedSource and target.dominatedSource == self then
+		-- target is being dominated by self
+		mult = (mult or 1) * (target.dominatedDamMult or 1)
+	end
+
 	if not self:attr("disarmed") then
 		-- All weapons in main hands
 		if self:getInven(self.INVEN_MAINHAND) then
@@ -144,6 +149,12 @@ function _M:attackTarget(target, damtype, mult, noenergy)
 	if sound then game:playSoundNear(self, sound)
 	elseif sound_miss then game:playSoundNear(self, sound_miss) end
 
+	-- cleave second attack
+	if self:knowTalent(self.T_CLEAVE) then
+		local t = self:getTalentFromId(self.T_CLEAVE)
+		t.on_attackTarget(self, t, target, multiplier)
+	end
+
 	-- Cancel stealth!
 	self:breakStealth()
 	return hit
@@ -512,6 +523,13 @@ function _M:physicalCrit(dam, weapon, target)
 		return dam * (1.5 + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7), true
 	end
 
+	if target.stalker and target.stalker == self and self:knowTalent(self.T_STALK) then
+		local t = self:getTalentFromId(self.T_STALK)
+		if rng.percent(math.min(100, 40 + self:getTalentLevel(t) * 12)) then
+			return dam * 1.5, true
+		end
+	end
+
 	local chance = self:combatCrit(weapon)
 	local crit = false
 	if self:knowTalent(self.T_BACKSTAB) and target:attr("stunned") then chance = chance + self:getTalentLevel(self.T_BACKSTAB) * 10 end
@@ -610,6 +628,20 @@ function _M:hasStaffWeapon()
 	return weapon
 end
 
+--- Check if the actor has an axe weapon
+function _M:hasAxeWeapon()
+	if self:attr("disarmed") then
+		return nil, "disarmed"
+	end
+
+	if not self:getInven("MAINHAND") then return end
+	local weapon = self:getInven("MAINHAND")[1]
+	if not weapon or (weapon.subtype ~= "battleaxe" and weapon.subtype ~= "waraxe") then
+		return nil
+	end
+	return weapon
+end
+
 --- Check if the actor has a two handed weapon
 function _M:hasTwoHandedWeapon()
 	if self:attr("disarmed") then
diff --git a/game/modules/tome/data/birth/classes/afflicted.lua b/game/modules/tome/data/birth/classes/afflicted.lua
new file mode 100644
index 0000000000000000000000000000000000000000..a5f73b16b7f767627e5980b323dfebb0bc676c3b
--- /dev/null
+++ b/game/modules/tome/data/birth/classes/afflicted.lua
@@ -0,0 +1,74 @@
+-- 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
+
+newBirthDescriptor{
+	type = "class",
+	name = "Afflicted",
+	desc = {
+		"Afflicted classes have been twisted by their association with evil forces.",
+		"They can use these forces to their advantage, but at a cost...",
+	},
+	descriptor_choices =
+	{
+		subclass =
+		{
+			__ALL__ = "disallow",
+			Cursed = function() return profile.mod.allow_build.afflicted_cursed and "allow" or "disallow" end,
+		},
+	},
+	copy = {
+	},
+}
+
+newBirthDescriptor{
+	type = "subclass",
+	name = "Cursed",
+	desc = {
+		"Through ignorance, greed or folly the cursed served some dark design and are now doomed to pay for their sins.",
+		"Their only master now is the hatred they carry for every living thing.",
+		"Drawing strength from the death of all they encounter, the cursed become terrifying combatants.",
+		"Worse, any who approach the cursed can be driven mad by their terrible aura.",
+		"Their most important stats are: Strength and Willpower",
+	},
+	stats = { wil=4, str=5, },
+	talents_types = {
+		["cursed/gloom"]={true, 0.0},
+		["cursed/slaughter"]={true, 0.0},
+		["cursed/endless-hunt"]={true, 0.0},
+		["cursed/cursed-form"]={true, 0.0},
+		["technique/combat-training"]={true, 0.3},
+		["cunning/survival"]={false, 0.0}
+	},
+	talents = {
+		[ActorTalents.T_WEAPON_COMBAT] = 1,
+		[ActorTalents.T_UNNATURAL_BODY] = 1,
+		[ActorTalents.T_GLOOM] = 1,
+		[ActorTalents.T_SLASH] = 1,
+		[ActorTalents.T_DOMINATE] = 1,
+		[ActorTalents.T_AXE_MASTERY] = 1
+	},
+	copy = {
+		max_life = 110,
+		life_rating = 12,
+		resolvers.equip{ id=true,
+			{type="weapon", subtype="battleaxe", name="iron battleaxe", autoreq=true},
+			{type="armor", subtype="light", name="rough leather armour", autoreq=true}
+		},
+	},
+}
diff --git a/game/modules/tome/data/birth/descriptors.lua b/game/modules/tome/data/birth/descriptors.lua
index 54ee5014c9e440667e74c9957cfeb3ecfe681a64..1e1aaa2186f35c5a39819d6d4c27f0899ecfd609 100644
--- a/game/modules/tome/data/birth/descriptors.lua
+++ b/game/modules/tome/data/birth/descriptors.lua
@@ -188,3 +188,4 @@ load("/data/birth/classes/mage.lua")
 load("/data/birth/classes/wilder.lua")
 load("/data/birth/classes/divine.lua")
 load("/data/birth/classes/corrupted.lua")
+load("/data/birth/classes/afflicted.lua")
diff --git a/game/modules/tome/data/birth/worlds.lua b/game/modules/tome/data/birth/worlds.lua
index 20f2a6a01bb8744bccd97f4e32c14484bdebd569..8b48ccd91fdb5a2bde424a9a741e6f389dd49f22 100644
--- a/game/modules/tome/data/birth/worlds.lua
+++ b/game/modules/tome/data/birth/worlds.lua
@@ -54,6 +54,7 @@ newBirthDescriptor{
 				) and "allow" or "disallow"
 			end,
 			Corrupter = function() return profile.mod.allow_build.corrupter and "allow" or "disallow" end,
+			Afflicted = function() return profile.mod.allow_build.afflicted and "allow" or "disallow" end,
 		},
 	},
 }
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index 953fad62e23ff9b25697dba08e79d155e6a09239..368c2d740336d7ae562796378e8600bc03401d4a 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -62,15 +62,17 @@ setDefaultProjector(function(src, x, y, type, dam)
 		if target == game.player then flash = game.flash.BAD end
 		if src == game.player then flash = game.flash.GOOD end
 
-		local srcname = src.x and src.y and game.level.map.seens(src.x, src.y) and src.name:capitalize() or "Something"
-		game.logSeen(target, flash, "%s hits %s for %s%0.2f %s damage#LAST#.", srcname, target.name, DamageType:get(type).text_color or "#aaaaaa#", dam, DamageType:get(type).name)
+		if not DamageType:get(type).hideMessage then
+			local srcname = src.x and src.y and game.level.map.seens(src.x, src.y) and src.name:capitalize() or "Something"
+			game.logSeen(target, flash, "%s hits %s for %s%0.2f %s damage#LAST#.", srcname, target.name, DamageType:get(type).text_color or "#aaaaaa#", dam, DamageType:get(type).name)
+		end
 
 		local sx, sy = game.level.map:getTileToScreen(x, y)
 		if target:takeHit(dam, src) then
 			if rsrc == game.player or rtarget == game.player then
 				game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, "Kill!", {255,0,255})
 			end
-		else
+		elseif not DamageType:get(type).hideFlyer then
 			if rsrc == game.player then
 				game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -3, tostring(-math.ceil(dam)), {0,255,0})
 			elseif rtarget == game.player then
@@ -714,3 +716,12 @@ newDamageType{
 		end
 	end,
 }
+
+-- life leech (used cursed gloom skill)
+newDamageType{
+	name = "life leech",
+	type = "LIFE_LEECH",
+	text_color = "#F53CBE#",
+	hideMessage=true,
+	hideFlyer=true
+}
diff --git a/game/modules/tome/data/gfx/particles/dominated.lua b/game/modules/tome/data/gfx/particles/dominated.lua
new file mode 100644
index 0000000000000000000000000000000000000000..619e8e65a2067b7d4c91514a1a7132f7e98f05e0
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/dominated.lua
@@ -0,0 +1,46 @@
+-- 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
+
+return { generator = function()
+	local ad = rng.range(0, 360)
+	local a = math.rad(ad)
+	local dir = math.rad(ad)
+	local r = rng.range(15, 18)
+	local dirchance = rng.chance(2)
+
+	return {
+--		rail = 1,
+		life = 10,
+		size = 5, sizev = -0.3, sizea = 0,
+
+		x = r * math.cos(a), xv = 0, xa = 0,
+		y = r * math.sin(a), yv = 0, ya = 0,
+		dir = dir, dirv = 0, dira = 0,
+		vel = -0.4, velv = 0, vela = 0,
+
+		r = 88 / 255,  rv = 0, ra = 0,
+		g = 40 / 255,  gv = 0, ga = 0,
+		b = 48 / 255,  bv = 0, ba = 0,
+		a = 180 / 255,  av = 0 / 255, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(6)
+end,
+60
diff --git a/game/modules/tome/data/gfx/particles/gloom.lua b/game/modules/tome/data/gfx/particles/gloom.lua
new file mode 100644
index 0000000000000000000000000000000000000000..98937d26cee55b5c189bcbbc9c3636f73bef65de
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/gloom.lua
@@ -0,0 +1,65 @@
+-- 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 toggle = false
+
+return { generator = function()
+	local ad = rng.range(0, 360)
+	local a = math.rad(ad)
+	local dir = -math.rad(ad)
+	local r = rng.range(10, 90)
+	local dirchance = rng.chance(2)
+
+	return {
+		trail = 1,
+		life = 30,
+		size = 12, sizev = -0.3, sizea = 0,
+
+		x = r * math.cos(a), xv = 0, xa = 0,
+		y = r * math.sin(a), yv = 0, ya = 0,
+		dir = dir, dirv = 0, dira = 0,
+		vel = dirchance and 0.32 or -0.2, velv = 0, vela = dirchance and -0.01 or 0.01,
+
+		r = 32,  rv = 0, ra = 0,
+		g = 0,  gv = 0, ga = 0,
+		b = 64,  bv = 0, ba = 0,
+		a = rng.range(20, 50) / 255,  av = 0, aa = 0,
+
+--		trail = 1,
+--		life = 30,
+--		size = 12, sizev = -0.3, sizea = 0,
+
+--		x = r * math.cos(a), xv = 0, xa = 0,
+--		y = r * math.sin(a), yv = 0, ya = 0,
+--		dir = dir, dirv = 0.1, dira = 0,
+--		vel = dirchance and 0.32 or -0.2, velv = 0, vela = dirchance and -0.01 or 0.01,
+
+--		r = 0,  rv = 0, ra = 0,
+--		g = 0,  gv = 0, ga = 0,
+--		b = 0,  bv = 0, ba = 0,
+--		a = 60 / 255,  av = -.1, aa = 0,
+	}
+end, },
+function(self)
+--	toggle = not toggle
+--	if toggle then
+		self.ps:emit(1)
+--	end
+end,
+20
diff --git a/game/modules/tome/data/gfx/particles/gloom_confused.lua b/game/modules/tome/data/gfx/particles/gloom_confused.lua
new file mode 100644
index 0000000000000000000000000000000000000000..cf06180f2a841c90e1717f3b6ba72769be75f911
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/gloom_confused.lua
@@ -0,0 +1,46 @@
+-- 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
+
+return { generator = function()
+	local ad = rng.range(0, 360)
+	local a = math.rad(ad)
+	local dir = math.rad(ad + 90)
+	local r = rng.range(14, 18)
+	local dirchance = rng.chance(2)
+
+	return {
+--		rail = 1,
+		life = 6,
+		size = 5, sizev = -0.7, sizea = 0,
+
+		x = r * math.cos(a), xv = 0, xa = 0,
+		y = r * math.sin(a), yv = 0, ya = 0,
+		dir = dir, dirv = 0.04, dira = 0,
+		vel = 1.4, velv = 0,
+
+		r = 240 / 255,  rv = 0, ra = 0,
+		g = 240 / 255,  gv = 0, ga = 0,
+		b = 240 / 255,  bv = 0, ba = 0,
+		a = 180 / 255,  av = 0 / 255, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(10)
+end,
+60
diff --git a/game/modules/tome/data/gfx/particles/gloom_slow.lua b/game/modules/tome/data/gfx/particles/gloom_slow.lua
new file mode 100644
index 0000000000000000000000000000000000000000..157a72edeb26f1dea4f976522179bb2e9439bebc
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/gloom_slow.lua
@@ -0,0 +1,46 @@
+-- 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
+
+return { generator = function()
+	local ad = rng.range(0, 360)
+	local a = math.rad(ad)
+	local dir = math.rad(ad)
+	local r = rng.range(1, 20)
+	local dirchance = rng.chance(2)
+
+	return {
+--		rail = 1,
+		life = 20,
+		size = 8, sizev = -0.2, sizea = 0,
+
+		x = r * math.cos(a), xv = 0, xa = 0,
+		y = r * math.sin(a), yv = 0, ya = 0,
+		dir = dir, dirv = 0.1, dira = 0,
+		vel = dirchance and 0.01 or -0.01, velv = 0, vela = 0,
+
+		r = 255 / 255,  rv = 0, ra = 0,
+		g = 255 / 255,  gv = 0, ga = 0,
+		b = 255 / 255,  bv = 0, ba = 0,
+		a = 80 / 255,  av = -5 / 255, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(1)
+end,
+20
diff --git a/game/modules/tome/data/gfx/particles/gloom_stunned.lua b/game/modules/tome/data/gfx/particles/gloom_stunned.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f11e9d07f154e646df9059559d1d5e08c0d10e0b
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/gloom_stunned.lua
@@ -0,0 +1,47 @@
+-- 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
+
+return { generator = function()
+	local ad = rng.range(0, 360)
+	local a = math.rad(ad)
+	local dird = rng.range(0, 360)
+	local dir = math.rad(dird)
+	local r = rng.range(1, 20)
+	local dirchance = rng.chance(2)
+
+	return {
+--		rail = 1,
+		life = 5,
+		size = 3, sizev = 0, sizea = 0,
+
+		x = r * math.cos(a), xv = 0, xa = 0,
+		y = r * math.sin(a), yv = 0, ya = 0,
+		dir = dir, dirv = 0, dira = 0,
+		vel = dirchance and 0.6 or -0.6, velv = 0, vela = dirchance and -0.02 or 0.02,
+
+		r = 64 / 255,  rv = 0, ra = 0,
+		g = 196 / 255,  gv = 0, ga = 0,
+		b = 32 / 255,  bv = 0, ba = 0,
+		a = 255 / 255,  av = -5 / 255, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(10)
+end,
+60
diff --git a/game/modules/tome/data/gfx/particles/gloom_weakness.lua b/game/modules/tome/data/gfx/particles/gloom_weakness.lua
new file mode 100644
index 0000000000000000000000000000000000000000..e3aaae918e45e269f651a81e047322d2ec7ac5a9
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/gloom_weakness.lua
@@ -0,0 +1,45 @@
+-- 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
+
+return { generator = function()
+	local ad = 90 * rng.range(0, 3) + rng.range(0, 89)
+	local a = math.rad(ad)
+	local dir = math.rad(ad + 90)
+	local r = rng.range(2, 15)
+
+	return {
+--		rail = 1,
+		life = 30,
+		size = 8, sizev = -0.2, sizea = 0,
+
+		x = r * math.cos(a), xv = 0, xa = 0,
+		y = r * math.sin(a), yv = 0, ya = 0,
+		dir = dir, dirv = 1.57 / 30, dira = 0,
+		vel = 0.5, velv = 0, vela = 0,
+
+		r = 0 / 255,  rv = 0, ra = 0,
+		g = 0 / 255,  gv = 0, ga = 0,
+		b = 255 / 255,  bv = 0, ba = 0,
+		a = 40 / 255,  av = 0 / 255, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(4)
+end,
+60
diff --git a/game/modules/tome/data/gfx/particles/stalked.lua b/game/modules/tome/data/gfx/particles/stalked.lua
new file mode 100644
index 0000000000000000000000000000000000000000..06f26babfe7ac0327ede66b1bcefab385937d7a9
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/stalked.lua
@@ -0,0 +1,46 @@
+-- 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
+
+return { generator = function()
+	local ad = 90 * rng.range(0, 3)
+	local a = math.rad(ad)
+	local dir = math.rad(ad + 90)
+	local r = rng.range(2, 15)
+	local dirchance = rng.chance(2)
+
+	return {
+--		rail = 1,
+		life = 30,
+		size = 4, sizev = -0.1, sizea = 0,
+
+		x = r * math.cos(a), xv = 0, xa = 0,
+		y = r * math.sin(a), yv = 0, ya = 0,
+		dir = dir, dirv = 0, dira = 0,
+		vel = dirchance and 0.8 or -0.8, velv = 0, vela = 0,
+
+		r = 228 / 255,  rv = 0, ra = 0,
+		g = 40 / 255,  gv = 0, ga = 0,
+		b = 5 / 255,  bv = 0, ba = 0,
+		a = 255 / 255,  av = 0 / 255, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(60)
+end,
+60
diff --git a/game/modules/tome/data/talents.lua b/game/modules/tome/data/talents.lua
index 24611fb45c217c83132aae58c30a27dfe0b0f76a..e33fec559758bb86cc81a104a201100e18dee387 100644
--- a/game/modules/tome/data/talents.lua
+++ b/game/modules/tome/data/talents.lua
@@ -46,3 +46,4 @@ load("/data/talents/gifts/gifts.lua")
 load("/data/talents/divine/divine.lua")
 load("/data/talents/corruptions/corruptions.lua")
 load("/data/talents/undeads/undeads.lua")
+load("/data/talents/cursed/cursed.lua")
diff --git a/game/modules/tome/data/talents/cursed/cursed-form.lua b/game/modules/tome/data/talents/cursed/cursed-form.lua
new file mode 100644
index 0000000000000000000000000000000000000000..d048daf6bd5fc17b711458956fda753c3aea4069
--- /dev/null
+++ b/game/modules/tome/data/talents/cursed/cursed-form.lua
@@ -0,0 +1,222 @@
+-- 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 function getHateMultiplier(self, min, max)
+	return (min + ((max - min) * self.hate / 10))
+end
+
+newTalent{
+	name = "Unnatural Body",
+	type = {"cursed/cursed-form", 1},
+	mode = "passive",
+	require = cursed_str_req1,
+	points = 5,
+	on_learn = function(self, t)
+		-- assume on only learning one point at a time (true when this was written)
+		local level = self:getTalentLevelRaw(t)
+		if level == 1 then
+			-- baseline
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 25
+			self.combat_spellresist = self.combat_spellresist - 10
+			self.max_life = self.max_life + 15
+		elseif level == 2 then
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 5
+			self.combat_spellresist = self.combat_spellresist + 2
+			self.max_life = self.max_life + 15
+		elseif level == 3 then
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 5
+			self.combat_spellresist = self.combat_spellresist + 2
+			self.max_life = self.max_life + 15
+		elseif level == 4 then
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 5
+			self.combat_spellresist = self.combat_spellresist + 2
+			self.max_life = self.max_life + 15
+		elseif level == 5 then
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 5
+			self.combat_spellresist = self.combat_spellresist + 2
+			self.max_life = self.max_life + 15
+		end
+		return true
+	end,
+	on_unlearn = function(self, t)
+		-- assume on only learning one point at a time (true when this was written)
+		local level = self:getTalentLevelRaw(t)
+		if not level or level == 0 then
+			-- baseline
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) + 25
+			self.combat_spellresist = self.combat_spellresist + 10
+			self.max_life = self.max_life - 15
+		elseif level == 1 then
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 5
+			self.combat_spellresist = self.combat_spellresist - 2
+			self.max_life = self.max_life - 15
+		elseif level == 2 then
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 5
+			self.combat_spellresist = self.combat_spellresist - 2
+			self.max_life = self.max_life - 15
+		elseif level == 3 then
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 5
+			self.combat_spellresist = self.combat_spellresist - 2
+			self.max_life = self.max_life - 15
+		elseif level == 4 then
+			self.resists[DamageType.FIRE] = (self.resists[DamageType.FIRE] or 0) - 5
+			self.combat_spellresist = self.combat_spellresist - 2
+			self.max_life = self.max_life - 15
+		end
+
+		return true
+	end,
+	do_regenLife  = function(self, t)
+		heal = math.sqrt(self:getTalentLevel(t) * 2) * self.max_life * 0.003
+		if heal > 0 then
+			self:heal(heal)
+		end
+	end,
+	info = function(self, t)
+		heal = math.sqrt(self:getTalentLevel(t) * 2) * self.max_life * 0.003
+		local level = self:getTalentLevelRaw(t)
+		if level == 1 then
+			return ([[The curse has twisted your body into an unnatural form.
+			(-25%% fire resistance, -10 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal)
+		elseif level == 2 then
+			return ([[The curse has twisted your body into an unnatural form.
+			(-20%% fire resistance, -8 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal)
+		elseif level == 3 then
+			return ([[The curse has twisted your body into an unnatural form.
+			(-15%% fire resistance, -6 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal)
+		elseif level == 4 then
+			return ([[The curse has twisted your body into an unnatural form.
+			(-10%% fire resistance, -4 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal)
+		else
+			return ([[The curse has twisted your body into an unnatural form.
+			(-5%% fire resistance, -2 spell save, +15 maximum life, +%0.1f life per turn).]]):format(heal)
+		end
+	end,
+}
+
+--newTalent{
+--	name = "Obsession",
+--	type = {"cursed/cursed-form", 2},
+--	require = cursed_str_req2,
+--	mode = "passive",
+--	points = 5,
+--	on_learn = function(self, t)
+--		self.hate_per_kill = self.hate_per_kill + 0.1
+--	end,
+--	on_unlearn = function(self, t)
+--		self.hate_per_kill = self.hate_per_kill - 0.1
+--	end,
+--	info = function(self, t)
+--		return ([[Your suffering will become theirs. For every life that is taken you gain an extra %0.1f hate.]]):format(self:getTalentLevelRaw(t) * 0.1)
+--	end
+--}
+
+--newTalent{
+--	name = "Suffering",
+--	type = {"cursed/cursed-form", 2},
+--	require = cursed_str_req2,
+--	mode = "passive",
+--	points = 5,
+--	on_learn = function(self, t)
+--		return true
+--	end,
+--	on_unlearn = function(self, t)
+--		return true
+--	end,
+--	do_onTakeHit = function(self, t, damage)
+--		if damage > 0 then
+--			local hatePerLife = (1 + self:getTalentLevel(t)) / (self.max_life * 1.5)
+--			self.hate = math.max(self.max_hate, self.hate + damage * hatePerLife)
+--		end
+--	end,
+--	info = function(self, t)
+--		local hatePerLife = (1 + self:getTalentLevel(t)) / (self.max_life * 1.5)
+--		return ([[Your suffering will become theirs. For every %d life that is taken, you gain 1 hate.]]):format(1 / hatePerLife)
+--	end
+--}
+
+newTalent{
+	name = "Relentless",
+	type = {"cursed/cursed-form", 2},
+	mode = "passive",
+	require = cursed_str_req2,
+	points = 5,
+	on_learn = function(self, t)
+		self.fear_immune = self.stun_immune or 0 + 0.15
+		self.confusion_immune = self.stun_immune or 0 + 0.15
+		self.knockback_immune = self.knockback_immune or 0 + 0.15
+		self.stun_immune = self.stun_immune or 0 + 0.15
+		return true
+	end,
+	on_unlearn = function(self, t)
+		self.fear_immune = self.stun_immune or 0 + 0.15
+		self.confusion_immune = self.stun_immune or 0 + 0.15
+		self.knockback_immune = self.knockback_immune or 0 + 0.15
+		self.stun_immune = self.stun_immune or 0 + 0.15
+		return true
+	end,
+	info = function(self, t)
+		return ([[Your thirst for blood drives your movements. (+%d%% confusion, fear, knockback and stun immunity)]]):format(self:getTalentLevelRaw(t) * 15)
+	end,
+}
+
+newTalent{
+	name = "Seethe",
+	type = {"cursed/cursed-form", 3},
+	random_ego = "utility",
+	require = cursed_str_req3,
+	points = 5,
+	cooldown = 400,
+	action = function(self, t)
+		local increase = 2 + self:getTalentLevel(t) * 0.9
+		hate = math.min(self.max_hate, self.hate + increase)
+		self:incHate(hate)
+
+		local damage = self.max_life * 0.25
+		self:project({type="hit"}, self.x, self.y, DamageType.BLIGHT, damage)
+		game.level.map:particleEmitter(self.x, self.y, 5, "fireflash", {radius=5, tx=self.x, ty=self.y})
+		game:playSoundNear(self, "talents/fireflash")
+		return true
+	end,
+	info = function(self, t)
+		local increase = 2 + self:getTalentLevel(t) * 0.9
+		local damage = self.max_life * 0.25
+		return ([[Focus your rage gaining %0.1f hate at the cost of %d life.]]):format(increase, damage)
+	end,
+}
+
+newTalent{
+	name = "Enrage",
+	type = {"cursed/cursed-form", 4},
+	require = cursed_str_req4,
+	points = 5,
+	rage = 0.1,
+	cooldown = 50,
+	action = function(self, t)
+		local life = 50 + self:getTalentLevel(t) * 50
+		self:setEffect(self.EFF_INCREASED_LIFE, 20, { life = life })
+		return true
+	end,
+	info = function(self, t)
+		local life = 50 + self:getTalentLevel(t) * 50
+		return ([[In a burst of rage you become an even more fearsome opponent gaining %d extra life for 20 turns.]]):format(life)
+	end,
+}
+
+
diff --git a/game/modules/tome/data/talents/cursed/cursed.lua b/game/modules/tome/data/talents/cursed/cursed.lua
new file mode 100644
index 0000000000000000000000000000000000000000..81896e65f2b08fb807477cf0e519f98c1e09c927
--- /dev/null
+++ b/game/modules/tome/data/talents/cursed/cursed.lua
@@ -0,0 +1,73 @@
+-- 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
+
+-- Afflictions
+newTalentType{ type="cursed/cursed-form", name = "cursed form", description = "You are wracked with the dark energies of the curse." }
+newTalentType{ type="cursed/slaughter", name = "slaughter", description = "Your axe yearns for its next victim." }
+newTalentType{ type="cursed/endless-hunt", name = "endless hunt", description = "Each day you lift your weary body and begin the unending hunt." }
+newTalentType{ type="cursed/gloom", name = "gloom", description = "All those in your sight must share your despair." }
+
+
+-- Generic requires for corruptions based on talent level
+cursed_wil_req1 = {
+	stat = { wil=function(level) return 12 + (level-1) * 2 end },
+	level = function(level) return 0 + (level-1)  end,
+}
+cursed_wil_req2 = {
+	stat = { wil=function(level) return 20 + (level-1) * 2 end },
+	level = function(level) return 4 + (level-1)  end,
+}
+cursed_wil_req3 = {
+	stat = { wil=function(level) return 28 + (level-1) * 2 end },
+	level = function(level) return 8 + (level-1)  end,
+}
+cursed_wil_req4 = {
+	stat = { wil=function(level) return 36 + (level-1) * 2 end },
+	level = function(level) return 12 + (level-1)  end,
+}
+cursed_wil_req5 = {
+	stat = { wil=function(level) return 44 + (level-1) * 2 end },
+	level = function(level) return 16 + (level-1)  end,
+}
+
+cursed_str_req1 = {
+	stat = { str=function(level) return 12 + (level-1) * 2 end },
+	level = function(level) return 0 + (level-1)  end,
+}
+cursed_str_req2 = {
+	stat = { str=function(level) return 20 + (level-1) * 2 end },
+	level = function(level) return 4 + (level-1)  end,
+}
+cursed_str_req3 = {
+	stat = { str=function(level) return 28 + (level-1) * 2 end },
+	level = function(level) return 8 + (level-1)  end,
+}
+cursed_str_req4 = {
+	stat = { str=function(level) return 36 + (level-1) * 2 end },
+	level = function(level) return 12 + (level-1)  end,
+}
+cursed_str_req5 = {
+	stat = { str=function(level) return 44 + (level-1) * 2 end },
+	level = function(level) return 16 + (level-1)  end,
+}
+
+load("/data/talents/cursed/cursed-form.lua")
+load("/data/talents/cursed/slaughter.lua")
+load("/data/talents/cursed/endless-hunt.lua")
+load("/data/talents/cursed/gloom.lua")
diff --git a/game/modules/tome/data/talents/cursed/endless-hunt.lua b/game/modules/tome/data/talents/cursed/endless-hunt.lua
new file mode 100644
index 0000000000000000000000000000000000000000..fd5bbdc1997d359451ffa6b5694fc501cecfb1ac
--- /dev/null
+++ b/game/modules/tome/data/talents/cursed/endless-hunt.lua
@@ -0,0 +1,192 @@
+-- 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 function getHateMultiplier(self, min, max)
+	return (min + ((max - min) * self.hate / 10))
+end
+
+local function checkWillFailure(self, target, minChance, maxChance, attackStrength)
+	-- attack power is analogous to mental resist except all willpower and no cunning
+	local attack = self:getWil() * 0.5 * attackStrength
+	local defense = target:combatMentalResist()
+
+	-- used this instead of checkHit to get a curve that is a little more ratio dependent than difference dependent.
+	-- + 10 prevents large changes for low attack/defense values
+	-- 2 * log adjusts falloff to roughly get 0% break near attack = 0.5 * defense and 100% break near attack = 2 * defense
+	local chance = minChance + (1 + 2 * math.log((attack + 10) / (defense + 10))) * (maxChance - minChance) * 0.5
+
+	local result = rng.avg(1, 100)
+	print("checkWillFailure", self.name, self.level, target.name, target.level, minChance, chance, maxChance)
+
+	if result <= minChance then return true end
+	if result >= maxChance then return false end
+	return result <= chance
+end
+
+newTalent{
+	name = "Dominate",
+	type = {"cursed/endless-hunt", 1},
+	require = cursed_wil_req1,
+	points = 5,
+	random_ego = "attack",
+	cooldown = 6,
+	hate = 0.1,
+	action = function(self, t)
+		local target = {type="hit", range=self:getTalentRange(t)}
+		local x, y, target = self:getTarget(target)
+		if not x or not y or not target then return nil end
+		if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end
+
+		-- attempt domination
+		if checkWillFailure(self, target, 15, 85, 1) then
+			local damMult = 1 + self:combatTalentWeaponDamage(t, 0.1, 0.4)
+			target:setEffect(target.EFF_DOMINATED, 4, { dominatedSource = self, dominatedDamMult = damMult })
+		else
+			game.logSeen(target, "%s resists the domination!", target.name:capitalize())
+		end
+
+		-- just a regular attack (but maybe..with domination!)
+		self:attackTarget(target, nil, 1, true)
+
+		return true
+	end,
+	info = function(self, t)
+		local damMult = self:combatTalentWeaponDamage(t, 0.1, 0.4)
+		return ([[Combine strength and will to overpower your opponent. A failed save versus will will add %d%% melee damage to your attacks for 4 turns.]]):format(damMult * 100)
+	end,
+}
+
+newTalent{
+	name = "Preternatural Senses",
+	type = {"cursed/endless-hunt", 2},
+	mode = "passive",
+	require = cursed_wil_req2,
+	points = 5,
+	range = function(self, t)
+		return 2 + getHateMultiplier(self, 0, self:getTalentLevel(t) * 1.5)
+	end,
+	info = function(self, t)
+		local maxRange = 2 + math.floor(self:getTalentLevel(t) * 1.5)
+		return ([[Your preternatural senses aid you in your hunt for the next victim. You sense foes in a hate-based radius of %d to %d.]]):format(2, maxRange)
+	end,
+}
+
+newTalent{
+	name = "Blindside",
+	type = {"cursed/endless-hunt", 3},
+	require = cursed_wil_req3,
+	points = 5,
+	random_ego = "attack",
+	cooldown = 10,
+	hate = 0.1,
+	range = 10,
+	action = function(self, t)
+		local tg = {type="hit", range=self:getTalentRange(t)}
+		local x, y, target = self:getTarget(tg)
+		if not x or not y or not target then return nil end
+		if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end
+
+		local start = rng.range(0, 8)
+		for i = start, start + 8 do
+			local x = target.x + (i % 3) - 1
+			local y = target.y + math.floor((i % 9) / 3) - 1
+			if game.level.map:isBound(x, y)
+					and self:canMove(x, y)
+					and not game.level.map.attrs(x, y, "no_teleport") then
+				self:move(x, y, true)
+				game:playSoundNear(self, "talents/teleport")
+				local multiplier = self:combatTalentWeaponDamage(t, 0.9, 1.9) * getHateMultiplier(self, 0.3, 1.0)
+				self:attackTarget(target, nil, multiplier, true)
+				return true
+			end
+		end
+
+		return true
+	end,
+	info = function(self, t)
+		local multiplier = self:combatTalentWeaponDamage(t, 1.1, 1.9)
+		return ([[With blinding speed you suddenly appear next to a target up to %d spaces away and attack for %d%% to %d%% rage-based damage.]]):format(self:getTalentRange(t), multiplier * 30, multiplier * 100)
+	end,
+}
+
+newTalent{
+	name = "Stalk",
+	type = {"cursed/endless-hunt", 4},
+	require = cursed_wil_req4,
+	points = 5,
+	random_ego = "attack",
+	cooldown = 25,
+	hate = 0.1,
+	range = 10,
+	action = function(self, t)
+		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
+		local x, y, target = self:getTarget(tg)
+		if not x or not y or not target or target == self then return nil end
+
+		if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then
+			game.logPlayer(self, "You are too far to begin stalking that prey!")
+			return nil
+		end
+
+		if self:hasLOS(x, y) and target:canSee(self) then
+			game.logPlayer(self, "You cannot begin stalking prey that can see you!")
+			return nil
+		end
+
+		local duration = 4 + math.floor(self:getTalentLevel(t) * 2)
+		self:setEffect(self.EFF_STALKER, duration, {target=target})
+		target:setEffect(self.EFF_STALKED, duration, {target=self})
+
+		return true
+	end,
+	info = function(self, t)
+		local duration = 4 + math.floor(self:getTalentLevel(t) * 2)
+		local critical = math.min(100, 40 + self:getTalentLevel(t) * 12)
+		return ([[Stalk a single opponent starting from a position that is out of sight.
+		You will be invisible to your target for %d turns or until you attack (%d%% chance of a critical strike).
+		Your gloom will not affect the the stalked prey.]]):format(duration, critical)
+	end,
+}
+
+--newTalent{
+--	name = "Spite",
+--	type = {"cursed/endless-hunt", 4},
+--	require = cursed_wil_req4,
+--	points = 5,
+--	random_ego = "attack",
+--	cooldown = 4,
+--	hate = 0.1,
+--	range = 3,
+--	action = function(self, t)
+--		local tg = {type="hit", range=self:getTalentRange(t)}
+--		local x, y, target = self:getTarget(tg)
+--		if not x or not y or not target then return nil end
+--
+--		local damage = self:getWil() * self:getTalentLevel(t)
+--		self:project(tg, x, y, DamageType.BLIGHT, damage)
+--
+--		game.level.map:particleEmitter(target.x, target.y, 1, "ball_fire", {radius=1})
+--		game:playSoundNear(self, "talents/devouringflame")
+--		return true
+--	end,
+--	info = function(self, t)
+--		local damage = self:getWil() * self:getTalentLevel(t)
+--		return ([[Focusing your hate you blast your target for %d blight damage. Damage increases with willpower.]]):format(damage)
+--	end,
+--}
diff --git a/game/modules/tome/data/talents/cursed/gloom.lua b/game/modules/tome/data/talents/cursed/gloom.lua
new file mode 100644
index 0000000000000000000000000000000000000000..4d51dc2b13b28a99c439897ed0d3455b7c51da8c
--- /dev/null
+++ b/game/modules/tome/data/talents/cursed/gloom.lua
@@ -0,0 +1,236 @@
+-- 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 function getHateMultiplier(self, min, max)
+	return (min + ((max - min) * self.hate / 10))
+end
+
+local function getWillDamage(self, t, base, max)
+	-- Compute at "max"
+	local mod = max / ((base + 100) * ((math.sqrt(5) - 1) * 0.8 + 1))
+	-- Compute real
+	return (base + self:getWil()) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod
+end
+
+local function checkWillFailure(self, target, minChance, maxChance, attackStrength)
+	-- attack power is analogous to mental resist except all willpower and no cunning
+	local attack = self:getWil() * 0.5 * attackStrength
+	local defense = target:combatMentalResist()
+
+	-- used this instead of checkHit to get a curve that is a little more ratio dependent than difference dependent.
+	-- + 10 prevents large changes for low attack/defense values
+	-- 2 * log adjusts falloff to roughly get 0% break near attack = 0.5 * defense and 100% break near attack = 2 * defense
+	local chance = minChance + (1 + 2 * math.log((attack + 10) / (defense + 10))) * (maxChance - minChance) * 0.5
+
+	local result = rng.avg(1, 100)
+	--game.logSeen(target, "[%s]: %0.3f / %0.3f; %d vs %d; %d to %d", target.name:capitalize(), result, chance, attack, defense, minChance, maxChance)
+
+	if result <= minChance then return true end
+	if result >= maxChance then return false end
+	return result <= chance, chance
+end
+
+local function getWillFailureEffectiveness(self, minChance, maxChance, attackStrength)
+	return attackStrength * self:getWil() * 0.05 * (minChance + (maxChance - minChance) / 2)
+end
+
+newTalent{
+	name = "Gloom",
+	type = {"cursed/gloom", 1},
+	mode = "sustained",
+	require = cursed_wil_req1,
+	points = 5,
+	cooldown = 0,
+	range = 3,
+	activate = function(self, t)
+		self.torment_turns = nil -- restart torment
+		game:playSoundNear(self, "talents/arcane")
+		return {
+			particle = self:addParticles(Particles.new("gloom", 1)),
+		}
+	end,
+	deactivate = function(self, t, p)
+		self:removeParticles(p.particle)
+		return true
+	end,
+	do_gloom = function(self, tGloom)
+		-- all gloom effects are handled here
+		local tWeakness = self:getTalentFromId(self.T_WEAKNESS)
+		local tTorment = self:getTalentFromId(self.T_TORMENT)
+		local tAbsorbLife = self:getTalentFromId(self.T_ABSORB_LIFE)
+		local lifeAbsorbed = 0
+		local attackStrength = 0.3 + self:getTalentLevel(tGloom) * 0.12
+		local tormentHit = false
+
+		local doTorment = false
+		local resetTorment = false
+		if tTorment and self:getTalentLevelRaw(tTorment) > 0 then
+			if not self.torment_turns then
+				-- initialize turns
+				resetTorment = true
+			elseif self.torment_turns == 0 then
+				-- attack one of our targets
+				doTorment = true;
+			else
+				-- reduce Torment turns
+				self.torment_turns = self.torment_turns - 1
+			end
+		end
+
+		local grids = core.fov.circle_grids(self.x, self.y, self:getTalentRange(tGloom), true)
+		for x, yy in pairs(grids) do
+			for y, _ in pairs(grids[x]) do
+				local target = game.level.map(x, y, Map.ACTOR)
+				if target
+						and self:reactionToward(target) < 0
+						and (not target.stalker or target.stalker ~= self) then
+
+					-- Gloom
+					if tGloom and self:getTalentLevelRaw(tGloom) > 0 then
+						local attackStrength = 0.3 + self:getTalentLevel(tGloom) * 0.12
+						if checkWillFailure(self, target, 5, 35, attackStrength) then
+							local effect = rng.range(1, 3)
+							if effect == 1 then
+								-- confusion
+								if target:canBe("confusion") and not target:hasEffect(target.EFF_GLOOM_CONFUSED) then
+									target:setEffect(target.EFF_GLOOM_CONFUSED, 2, {power=0.75})
+									--game:playSoundNear(self, "talents/fire")
+									hit = true
+								end
+							elseif effect == 2 then
+								-- stun
+								if target:canBe("stun") and not target:hasEffect(target.EFF_GLOOM_STUNNED) then
+									target:setEffect(target.EFF_GLOOM_STUNNED, 2, {})
+									--game:playSoundNear(self, "talents/fire")
+									hit = true
+								end
+							elseif effect == 3 then
+								-- slow
+								if target:canBe("slow") and not target:hasEffect(target.EFF_GLOOM_SLOW) then
+									target:setEffect(target.EFF_GLOOM_SLOW, 2, {power=0.3})
+									--game:playSoundNear(self, "talents/fire")
+									hit = true
+								end
+							end
+						end
+					end
+
+					-- Weakness
+					if tWeakness and self:getTalentLevelRaw(tWeakness) > 0 then
+						local attackStrength = 0.3 + self:getTalentLevel(tWeakness) * 0.12
+						if checkWillFailure(self, target, 10, 60, attackStrength) and not target:hasEffect(target.EFF_GLOOM_WEAKNESS) then
+							local turns = 3 + math.ceil(self:getTalentLevel(tWeakness))
+
+							local weapon = target:getInven("MAINHAND")
+							if weapon then
+								weapon = weapon[1] and weapon[1].combat
+							end
+							weapon = weapon or target.combat
+							local attack = target:combatAttack(weapon) * (2 + self:getTalentLevel(tWeakness)) * 3.5 / 100
+							local damage = target:combatDamage(weapon) * (2 + self:getTalentLevel(tWeakness)) * 3.5 / 100
+							target:setEffect(target.EFF_GLOOM_WEAKNESS, turns, {atk=attack, dam=damage})
+							hit = true
+						end
+					end
+
+					-- Torment
+					if doTorment then
+						resetTorment = true
+						local attackStrength = 0.3 + self:getTalentLevel(tTorment) * 0.12
+						if checkWillFailure(self, target, 30, 95, attackStrength) then
+							local tg = {type="hit", friendlyfire=false, talent=tTorment}
+							local grids = self:project(tg, target.x, target.y, DamageType.BLIGHT, 30 + self:getWil() * 0.4 * self:getTalentLevel(tTorment), {type="slime"})
+							--game:playSoundNear(self, "talents/spell_generic")
+							doTorment = false
+						else
+							game.logSeen(target, "#F53CBE#%s resists your torment.", target.name:capitalize())
+						end
+					end
+
+					-- Absorb Life
+					if tAbsorbLife and self:getTalentLevelRaw(tAbsorbLife) > 0 then
+						local fraction = self:getTalentLevel(tAbsorbLife) * 0.002
+						local damage = math.min(target.max_life * fraction, self.max_life * fraction * 2)
+						local actualDamage = DamageType:get(DamageType.ABSORB_LIFE).projector(self, target.x, target.y, DamageType.ABSORB_LIFE, damage)
+						lifeAbsorbed = lifeAbsorbed + actualDamage
+						--game.logSeen(target, "#F53CBE#You absorb %.2f life from %s!", actualDamage, target.name:capitalize())
+					end
+				end
+			end
+		end
+
+		if resetTorment then
+			self.torment_turns = math.floor(15 - self:getTalentLevelRaw(tTorment))
+		end
+
+		-- absorb life
+		if lifeAbsorbed > 0 then
+			self:heal(lifeAbsorbed)
+			game.logPlayer(self, "#F53CBE#You absorb %0.1f life from your foes.", lifeAbsorbed)
+		end
+	end,
+	info = function(self, t)
+		local attackStrength = 0.3 + self:getTalentLevel(t) * 0.12
+		local effectiveness = getWillFailureEffectiveness(self, 5, 35, attackStrength)
+		return ([[A terrible gloom surrounds you affecting all those who approach you.
+		The weak-minded may suffer from slow, stun or confusion. (%d effectiveness)
+		This ability is innate and carries no cost to activate or deactivate.]]):format(effectiveness)
+	end,
+}
+
+newTalent{
+	name = "Weakness",
+	type = {"cursed/gloom", 2},
+	mode = "passive",
+	require = cursed_wil_req2,
+	points = 5,
+	info = function(self, t)
+		local turns = 3 + math.ceil(self:getTalentLevel(t))
+		local attack = (2 + self:getTalentLevel(t)) * 3.5
+		local damage = (2 + self:getTalentLevel(t)) * 3.5
+		local attackStrength = 0.3 + self:getTalentLevel(t) * 0.12
+		local effectiveness = getWillFailureEffectiveness(self, 10, 60, attackStrength)
+		return ([[The weak-minded caught in your gloom are crippled by fear for %d turns. (-%d%% attack, -%d%% damage, %d effectiveness)]]):format(turns, attack, damage, effectiveness)
+	end,
+}
+
+newTalent{
+	name = "Torment",
+	type = {"cursed/gloom", 3},
+	mode = "passive",
+	require = cursed_wil_req3,
+	points = 5,
+	info = function(self, t)
+		local baseDamage = 30 + self:getWil() * 0.4 * self:getTalentLevel(t)
+		local attackStrength = 0.3 + self:getTalentLevel(t) * 0.12
+		local effectiveness = getWillFailureEffectiveness(self, 30, 95, attackStrength)
+		return ([[Your rage builds within you for 12 turns then unleashes itself for %d to %d hate-based blight damage on the first one to enter your gloom. (%d effectiveness)]]):format(baseDamage * .5, baseDamage * 1.5, effectiveness)
+	end,
+}
+
+newTalent{
+	name = "Life Leech",
+	type = {"cursed/gloom", 4},
+	mode = "passive",
+	require = cursed_wil_req4,
+	points = 5,
+	info = function(self, t)
+		return ([[Each turn your gloom leeches up to %0.1f%% of the life of foes.]]):format(self:getTalentLevel(t) * 0.2)
+	end,
+}
diff --git a/game/modules/tome/data/talents/cursed/slaughter.lua b/game/modules/tome/data/talents/cursed/slaughter.lua
new file mode 100644
index 0000000000000000000000000000000000000000..920c50f2ec3aec0f545fdf06c637307308b6ae60
--- /dev/null
+++ b/game/modules/tome/data/talents/cursed/slaughter.lua
@@ -0,0 +1,235 @@
+-- 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 function getHateMultiplier(self, min, max)
+	return (min + ((max - min) * self.hate / 10))
+end
+
+newTalent{
+	name = "Slash",
+	type = {"cursed/slaughter", 1},
+	require = cursed_str_req1,
+	points = 5,
+	random_ego = "attack",
+	cooldown = 6,
+	hate = 0.1,
+	action = function(self, t)
+		local weapon = self:hasAxeWeapon()
+		if not weapon then
+			game.logPlayer(self, "You cannot use %s without an axe!", t.name)
+			return nil
+		end
+
+		local tg = {type="hit", range=self:getTalentRange(t)}
+		local x, y, target = self:getTarget(tg)
+		if not x or not y or not target then return nil end
+		if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end
+
+		local multiplier = 1 + (0.15 + .2 * self:getTalentLevel(t)) * getHateMultiplier(self, 0, 1)
+		local hit = self:attackTarget(target, nil, multiplier, true)
+
+		return true
+	end,
+	info = function(self, t)
+		local multiplier = (0.15 + .2 * self:getTalentLevel(t))
+		return ([[Slashes wildly at your target adding up to %d%% hate-based damage.]]):format(multiplier * 100)
+	end,
+}
+
+newTalent{
+	name = "Frenzy",
+	type = {"cursed/slaughter", 1},
+	require = cursed_str_req2,
+	points = 5,
+	random_ego = "attack",
+	cooldown = 15,
+	hate = 0.1,
+	action = function(self, t)
+		local weapon = self:hasAxeWeapon()
+		if not weapon then
+			game.logPlayer(self, "You cannot use %s without an axe!", t.name)
+			return nil
+		end
+
+		local targets = {}
+		for i = -1, 1 do
+			for j = -1, 1 do
+				local x, y = self.x + i, self.y + j
+				if (self.x ~= x or self.y ~= y) and game.level.map:isBound(x, y) and game.level.map(x, y, Map.ACTOR) then
+					local target = game.level.map(x, y, Map.ACTOR)
+					if target and self:reactionToward(target) < 0 then
+						targets[#targets+1] = target
+					end
+				end
+			end
+		end
+
+		if #targets <= 0 then return nil end
+
+		local multiplier = self:combatTalentWeaponDamage(t, 0.2, 0.7) * getHateMultiplier(self, 0.5, 1.0)
+		for i = 1, 4 do
+			local target = rng.table(targets)
+
+			self:attackTarget(target, nil, multiplier, true)
+		end
+
+		return true
+	end,
+	info = function(self, t)
+		local multiplier = self:combatTalentWeaponDamage(t, 0.2, 0.7)
+		return ([[Assault nearby foes with 4 fast attacks for %d%% to %d%% rage-based damage each.]]):format(multiplier * 50, multiplier * 100)
+	end,
+}
+
+newTalent{
+	name = "Reckless Charge",
+	type = {"cursed/slaughter", 3},
+	require = cursed_str_req3,
+	points = 5,
+	random_ego = "attack",
+	cooldown = 20,
+	hate = 0.1,
+	range = 4,
+	action = function(self, t)
+		local weapon = self:hasAxeWeapon()
+		if not weapon then
+			game.logPlayer(self, "You cannot use %s without an axe!", t.name)
+			return nil
+		end
+
+		local targeting = {type="bolt", range=self:getTalentRange(t), nolock=true}
+		local targetX, targetY, actualTarget = self:getTarget(targeting)
+		if not targetX or not targetY then return nil end
+		if math.floor(core.fov.distance(self.x, self.y, targetX, targetY)) > self:getTalentRange(t) then return nil end
+
+		local lineFunction = line.new(self.x, self.y, targetX, targetY)
+		local nextX, nextY = lineFunction()
+		local currentX, currentY = self.x, self.y
+		local blocked = false
+		while nextX and nextY do
+			local blockingTarget = game.level.map(nextX, nextY, Map.ACTOR)
+			if blockingTarget and self:reactionToward(blockingTarget) < 0 then
+				-- attempt a knockback
+				local level = self:getTalentLevelRaw(t)
+				local maxSize = 2
+				if level >= 5 then
+					maxSize = 4
+				elseif level >= 3 then
+					maxSize = 3
+				end
+
+				if blockingTarget.size_category <= maxSize then
+					if blockingTarget:checkHit(self:combatAttackStr(), blockingTarget:combatPhysicalResist(), 0, 95, 15) and blockingTarget:canBe("knockback") then
+						-- determine where to move the target (any adjacent square that isn't next to the attacker)
+						local start = rng.range(0, 8)
+						for i = start, start + 8 do
+							local x = nextX + (i % 3) - 1
+							local y = nextY + math.floor((i % 9) / 3) - 1
+							if math.floor(core.fov.distance(currentY, currentX, x, y)) > 1
+									and game.level.map:isBound(x, y)
+									and not game.level.map:checkAllEntities(x, y, "block_move", self) then
+								blockingTarget:move(x, y, true)
+								break
+							end
+						end
+					end
+				end
+			end
+
+			-- check that we can move
+			if not game.level.map:isBound(nextX, nextY) or game.level.map:checkAllEntities(nextX, nextY, "block_move", self) then break end
+
+			-- allow the move
+			currentX, currentY = nextX, nextY
+			nextX, nextY = lineFunction()
+
+			-- attack adjacent targets
+			for i = 0, 8 do
+				local x = currentX + (i % 3) - 1
+				local y = currentY + math.floor((i % 9) / 3) - 1
+				local target = game.level.map(x, y, Map.ACTOR)
+				if target and self:reactionToward(target) < 0 then
+					local multiplier = self:combatTalentWeaponDamage(t, 0.5, 1.5) * getHateMultiplier(self, 0.3, 1.0)
+					self:attackTarget(target, nil, multiplier, true)
+
+					game.level.map:particleEmitter(x, y, 1, "blood", {})
+					game:playSoundNear(self, "actions/melee")
+				end
+			end
+		end
+
+		self:move(currentX, currentY, true)
+
+		return true
+	end,
+	info = function(self, t)
+		local multiplier = self:combatTalentWeaponDamage(t, 0.5, 1.5)
+		local level = self:getTalentLevelRaw(t)
+		local size
+		if level >= 5 then
+			size = "Big"
+		elseif level >= 3 then
+			size = "Medium-sized"
+		else
+			size = "Small"
+		end
+		return ([[Charge through your opponents attacking anyone near your path for %d%% to %d%% rage-based damage. %s opponents may be knocked from your path.]]):format(multiplier * 30, multiplier * 100, size)
+	end,
+}
+
+newTalent{
+	name = "Cleave",
+	type = {"cursed/slaughter", 4},
+	mode = "passive",
+	require = cursed_str_req4,
+	points = 5,
+	on_attackTarget = function(self, t, target, multiplier)
+		local weapon = self:hasAxeWeapon()
+		if not weapon then
+			return nil
+		end
+
+		if inCleave then return end
+		inCleave = true
+
+		local chance = 28 + self:getTalentLevel(t) * 7
+		if rng.percent(chance) then
+			local start = rng.range(0, 8)
+			for i = start, start + 8 do
+				local x = self.x + (i % 3) - 1
+				local y = self.y + math.floor((i % 9) / 3) - 1
+				local secondTarget = game.level.map(x, y, Map.ACTOR)
+				if secondTarget and secondTarget ~= target and self:reactionToward(secondTarget) < 0 then
+					local multiplier = multiplier or 1 * self:combatTalentWeaponDamage(t, 0.2, 0.7) * getHateMultiplier(self, 0.5, 1.0)
+					game.logSeen(self, "%s cleaves through another foe!", self.name:capitalize())
+					self:attackTarget(secondTarget, nil, multiplier, true)
+					inCleave = false
+					return
+				end
+			end
+		end
+		inCleave = false
+
+	end,
+	info = function(self, t)
+		local chance = 28 + self:getTalentLevel(t) * 7
+		local multiplier = self:combatTalentWeaponDamage(t, 0.2, 0.7)
+		return ([[Every swing of your axe has a %d%% chance of striking a second target for %d%% to %d%% hate-based damage.]]):format(chance, multiplier * 50, multiplier * 100)
+	end,
+}
diff --git a/game/modules/tome/data/talents/misc/misc.lua b/game/modules/tome/data/talents/misc/misc.lua
index 537c24990b510e589fbfab05ab5a29192a5e3870..90c08c3abcc3102261a5d6c25fc1c2a7fff38691 100644
--- a/game/modules/tome/data/talents/misc/misc.lua
+++ b/game/modules/tome/data/talents/misc/misc.lua
@@ -68,6 +68,13 @@ newTalent{
 	mode = "passive",
 	hide = true,
 }
+newTalent{
+	name = "Hate Pool",
+	type = {"base/class", 1},
+	info = "Allows you to have a hate pool.",
+	mode = "passive",
+	hide = true,
+}
 
 newTalent{
 	name = "Improved Health I",
diff --git a/game/modules/tome/data/timed_effects.lua b/game/modules/tome/data/timed_effects.lua
index df558e98dd66f5b3890953087f1d47be821da621..b55a5b60f493de7dcf95b16a96df9b664208e6a0 100644
--- a/game/modules/tome/data/timed_effects.lua
+++ b/game/modules/tome/data/timed_effects.lua
@@ -1245,3 +1245,162 @@ newEffect{
 		self:removeTemporaryValue("move_project", eff.dig)
 	end,
 }
+
+newEffect{
+	name = "GLOOM_WEAKNESS",
+	desc = "Gloom Weakness",
+	type = "physical",
+	status = "detrimental",
+	parameters = { atk=10, dam=10 },
+	on_gain = function(self, err) return "#F53CBE##Target# is weakened by your gloom." end,
+	on_lose = function(self, err) return "#F53CBE##Target# is no longer weakened." end,
+	activate = function(self, eff)
+		eff.particle = self:addParticles(Particles.new("gloom_weakness", 1))
+		eff.atkid = self:addTemporaryValue("combat_atk", -eff.atk)
+		eff.damid = self:addTemporaryValue("combat_dam", -eff.dam)
+	end,
+	deactivate = function(self, eff)
+		self:removeParticles(eff.particle)
+		self:removeTemporaryValue("combat_atk", eff.atkid)
+		self:removeTemporaryValue("combat_dam", eff.damid)
+	end,
+}
+
+newEffect{
+	name = "GLOOM_SLOW",
+	desc = "Slowed by the gloom",
+	type = "magical",
+	status = "detrimental",
+	parameters = { power=0.1 },
+	on_gain = function(self, err) return "#F53CBE##Target# moves reluctantly!", "+Slow" end,
+	on_lose = function(self, err) return "#Target# overcomes the gloom.", "-Slow" end,
+	activate = function(self, eff)
+		eff.particle = self:addParticles(Particles.new("gloom_slow", 1))
+		eff.tmpid = self:addTemporaryValue("energy", {mod=-eff.power})
+		eff.dur = self:updateEffectDuration(eff.dur, "slow")
+	end,
+	deactivate = function(self, eff)
+		self:removeTemporaryValue("energy", eff.tmpid)
+		self:removeParticles(eff.particle)
+	end,
+}
+
+newEffect{
+	name = "GLOOM_STUNNED",
+	desc = "Stunned by the gloom",
+	type = "physical",
+	status = "detrimental",
+	parameters = {},
+	on_gain = function(self, err) return "#F53CBE##Target# is paralyzed with fear!", "+Stunned" end,
+	on_lose = function(self, err) return "#Target# overcomes the gloom", "-Stunned" end,
+	activate = function(self, eff)
+		eff.particle = self:addParticles(Particles.new("gloom_stunned", 1))
+		eff.tmpid = self:addTemporaryValue("stunned", 1)
+		eff.dur = self:updateEffectDuration(eff.dur, "stun")
+	end,
+	deactivate = function(self, eff)
+		self:removeParticles(eff.particle)
+		self:removeTemporaryValue("stunned", eff.tmpid)
+	end,
+}
+
+newEffect{
+	name = "GLOOM_CONFUSED",
+	desc = "Confused by the gloom",
+	type = "magical",
+	status = "detrimental",
+	parameters = {},
+	on_gain = function(self, err) return "#F53CBE##Target# is lost in dispair!", "+Confused" end,
+	on_lose = function(self, err) return "#Target# overcomes the gloom", "-Confused" end,
+	activate = function(self, eff)
+		eff.particle = self:addParticles(Particles.new("gloom_confused", 1))
+		eff.tmpid = self:addTemporaryValue("confused", eff.power)
+		eff.dur = self:updateEffectDuration(eff.dur, "confusion")
+	end,
+	deactivate = function(self, eff)
+		self:removeParticles(eff.particle)
+		self:removeTemporaryValue("confused", eff.tmpid)
+	end,
+}
+
+newEffect{
+	name = "STALKER",
+	desc = "Stalking",
+	type = "physical",
+	status = "beneficial",
+	parameters = {},
+	activate = function(self, eff)
+		if not self.stalkee then
+			self.stalkee = eff.target
+			game.logSeen(self, "#F53CBE#%s is being stalked by %s!", eff.target.name:capitalize(), self.name)
+		end
+	end,
+	deactivate = function(self, eff)
+		self.stalkee = nil
+		game.logSeen(self, "#F53CBE#%s is no longer being stalked by %s.", eff.target.name:capitalize(), self.name)
+	end,
+}
+
+newEffect{
+	name = "STALKED",
+	desc = "Being Stalked",
+	type = "physical",
+	status = "detrimental",
+	parameters = {},
+	activate = function(self, eff)
+		if not self.stalker then
+			eff.particle = self:addParticles(Particles.new("stalked", 1))
+			self.stalker = eff.target
+		end
+	end,
+	deactivate = function(self, eff)
+		self.stalker = nil
+		if eff.particle then self:removeParticles(eff.particle) end
+	end,
+}
+
+newEffect{
+	name = "INCREASED_LIFE",
+	desc = "Increased Life",
+	type = "physical",
+	status = "beneficial",
+	on_gain = function(self, err) return "#Target# gains extra life.", "+Life" end,
+	on_lose = function(self, err) return "#Target# loases extra life.", "-Life" end,
+	parameters = { life = 50 },
+	activate = function(self, eff)
+		self.max_life = self.max_life + eff.life
+		self.life = self.life + eff.life
+		self.changed = true
+	end,
+	deactivate = function(self, eff)
+		self.max_life = self.max_life - eff.life
+		self.life = self.life - eff.life
+		self.changed = true
+		if self.life <= 0 then
+			game.logSeen(self, "%s died when the effects of increased life wore off.", self.name:capitalize())
+			self:die(self)
+		end
+	end,
+}
+
+newEffect{
+	name = "DOMINATED",
+	desc = "Dominated",
+	type = "physical",
+	status = "detrimental",
+	on_gain = function(self, err) return "#F53CBE##Target# has been dominated!", "+Dominated" end,
+	on_lose = function(self, err) return "#F53CBE##Target# is no longer dominated.", "-Dominated" end,
+	parameters = { dominatedDamMult = 1.3 },
+	activate = function(self, eff)
+		if not self.dominatedSource then
+			self.dominatedSource = eff.dominatedSource
+			self.dominatedDamMult = 1.3 or eff.dominatedDamMult
+			eff.particle = self:addParticles(Particles.new("dominated", 1))
+		end
+	end,
+	deactivate = function(self, eff)
+		self.dominatedSource = nil
+		self.dominatedDamMult = nil
+		self:removeParticles(eff.particle)
+	end,
+}
\ No newline at end of file
diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua
index dab5e1c0c91bac7142d3461e57336be7892a91b7..cbe39f2854fd31448a5c7d343f7b21876a988259 100644
--- a/game/modules/tome/load.lua
+++ b/game/modules/tome/load.lua
@@ -93,6 +93,7 @@ ActorResource:defineResource("Equilibrium", "equilibrium", ActorTalents.T_EQUILI
 ActorResource:defineResource("Vim", "vim", ActorTalents.T_VIM_POOL, "vim_regen", "Vim represents the amount of life energies/souls you have stolen. Each corruption talents requires some.")
 ActorResource:defineResource("Positive", "positive", ActorTalents.T_POSITIVE_POOL, "positive_regen", "Positive energy represents your reserve of powitive power. It slowly decreases.")
 ActorResource:defineResource("Negative", "negative", ActorTalents.T_NEGATIVE_POOL, "negative_regen", "Negative energy represents your reserve of negative power. It slowly decreases.")
+ActorResource:defineResource("Hate", "hate", ActorTalents.T_HATE_POOL, "hate_regen", "Hate represents the level of frenzy of a cursed soul.")
 
 -- Actor stats
 ActorStats:defineStat("Strength",	"str", 10, 1, 100, "Strength defines your character's ability to apply physical force. It increases your melee damage, damage done with heavy weapons, your chance to resist physical effects, and carrying capacity.")