diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 00a197f90c38f1f91a941554d97dc26666e5447b..59f5986644f6fd816389379034b622ba969e4943 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -106,6 +106,7 @@ function _M:init(t, no_default)
 	t.vim_rating = t.vim_rating or 4
 	t.stamina_rating = t.stamina_rating or 3
 	t.positive_negative_rating = t.positive_negative_rating or 3
+	t.psi_rating = t.psi_rating or 1
 
 	t.esp = t.esp or {range=10}
 
@@ -136,6 +137,7 @@ function _M:init(t, no_default)
 	t.positive_regen = t.positive_regen or -0.2 -- Positive energy slowly decays
 	t.negative_regen = t.negative_regen or -0.2 -- Positive energy slowly decays
 	t.paradox_regen = t.paradox_regen or 0 -- Paradox does not regen
+	t.psi_regen = t.psi_regen or 0 -- Energy does not regen
 
 	t.max_positive = t.max_positive or 50
 	t.max_negative = t.max_negative or 50
@@ -177,16 +179,40 @@ function _M:init(t, no_default)
 	self:resetCanSeeCache()
 end
 
+function _M:useEnergy(val)
+	engine.Actor.useEnergy(self, val)
+
+	-- Do not fire those talents if this is not turn's end
+	if self:enoughEnergy() then return end
+	if self:isTalentActive(self.T_KINETIC_AURA) then
+		local t = self:getTalentFromId(self.T_KINETIC_AURA)
+		t.do_kineticaura(self, t)
+	end
+	if self:isTalentActive(self.T_THERMAL_AURA) then
+		local t = self:getTalentFromId(self.T_THERMAL_AURA)
+		t.do_thermalaura(self, t)
+	end
+	if self:isTalentActive(self.T_CHARGED_AURA) then
+		local t = self:getTalentFromId(self.T_CHARGED_AURA)
+		t.do_chargedaura(self, t)
+	end
+	if self:isTalentActive(self.T_BEYOND_THE_FLESH) then
+		local t = self:getTalentFromId(self.T_BEYOND_THE_FLESH)
+		t.do_tkautoattack(self, t)
+	end
+
+end
+
 function _M:act()
 	if not engine.Actor.act(self) then return end
 
 	self.changed = true
 
 	-- If ressources are too low, disable sustains
-	if self.mana < 1 or self.stamina < 1 then
+	if self.mana < 1 or self.stamina < 1 or self.psi < 1 then
 		for tid, _ in pairs(self.sustain_talents) do
 			local t = self:getTalentFromId(tid)
-			if (t.sustain_mana and self.mana < 1) or (t.sustain_stamina and self.stamina < 1) then
+			if (t.sustain_mana and self.mana < 1) or (t.sustain_stamina and self.stamina < 1) or (t.sustain_psi and self.psi < 1) then
 				self:forceUseTalent(tid, {ignore_energy=true})
 			end
 		end
@@ -253,6 +279,23 @@ function _M:act()
 		local t = self:getTalentFromId(self.T_UNSEEN_FORCE)
 		t.do_unseenForce(self, t)
 	end
+	-- Conduit talent prevents all auras from cooling down
+	if self:isTalentActive(self.T_CONDUIT) then
+		local auras = self:isTalentActive(self.T_CONDUIT)
+		if auras.k_aura_on then
+			local t_kinetic_aura = self:getTalentFromId(self.T_KINETIC_AURA)
+			self.talents_cd[self.T_KINETIC_AURA] = t_kinetic_aura.cooldown(self, t)
+		end
+		if auras.t_aura_on then
+			local t_thermal_aura = self:getTalentFromId(self.T_THERMAL_AURA)
+			self.talents_cd[self.T_THERMAL_AURA] = t_thermal_aura.cooldown(self, t)
+		end
+		if auras.c_aura_on then
+			local t_charged_aura = self:getTalentFromId(self.T_CHARGED_AURA)
+			self.talents_cd[self.T_CHARGED_AURA] = t_charged_aura.cooldown(self, t)
+		end
+	end
+
 
 	if self:attr("stunned") then
 		self.stunned_counter = (self.stunned_counter or 0) + (self:attr("stun_immune") or 0) * 100
@@ -1080,6 +1123,7 @@ function _M:resetToFull()
 	self.stamina = self.max_stamina
 	self.equilibrium = 0
 	self.air = self.max_air
+	self.psi = self.max_psi
 end
 
 function _M:levelup()
@@ -1135,6 +1179,7 @@ function _M:levelup()
 	self:incMaxStamina(self.stamina_rating)
 	self:incMaxPositive(self.positive_negative_rating)
 	self:incMaxNegative(self.positive_negative_rating)
+	self:incMaxPsi(self.psi_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)
@@ -1158,6 +1203,7 @@ function _M:onStatChange(stat, v)
 	elseif stat == self.STAT_WIL then
 		self:incMaxMana(5 * v)
 		self:incMaxStamina(2 * v)
+		self:incMaxPsi(1 * v)
 	elseif stat == self.STAT_STR then
 		self:checkEncumbrance()
 	end
@@ -1301,6 +1347,10 @@ function _M:learnTalent(t_id, force, nb)
 		self:learnTalent(self.T_PARADOX_POOL, true)
 		self.resource_pool_refs[self.T_PARADOX_POOL] = (self.resource_pool_refs[self.T_PARADOX_POOL] or 0) + 1
 	end
+	if t.type[1]:find("^psionic/") and not self:knowTalent(self.T_PSI_POOL) then
+		self:learnTalent(self.T_PSI_POOL, true)
+		self.resource_pool_refs[self.T_PSI_POOL] = (self.resource_pool_refs[self.T_PSI_POOL] or 0) + 1
+	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)
@@ -1377,6 +1427,10 @@ function _M:preUseTalent(ab, silent, fake)
 			if not silent then game.logPlayer(self, "You do not have enough hate to activate %s.", ab.name) end
 			return false
 		end
+		if ab.sustain_psi and self.max_psi < ab.sustain_psi and not self:isTalentActive(ab.id) then
+			if not silent then game.logPlayer(self, "You do not have enough energy to activate %s.", ab.name) end
+			return false
+		end
 	else
 		if ab.mana and self:getMana() < ab.mana * (100 + 2 * self:combatFatigue()) / 100 then
 			if not silent then game.logPlayer(self, "You do not have enough mana to cast %s.", ab.name) end
@@ -1402,6 +1456,10 @@ function _M:preUseTalent(ab, silent, fake)
 			if not silent then game.logPlayer(self, "You do not have enough hate to use %s.", ab.name) end
 			return false
 		end
+		if ab.psi and self:getPsi() < ab.psi * (100 + 2 * self.fatigue) / 100 then
+			if not silent then game.logPlayer(self, "You do not have enough energy to cast %s.", ab.name) end
+			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)
@@ -1510,6 +1568,9 @@ function _M:postUseTalent(ab, ret)
 			if ab.sustain_paradox then
 				self:incParadox(ab.sustain_paradox)
 			end
+			if ab.sustain_psi then
+				trigger = true; self.max_psi = self.max_psi - ab.sustain_psi
+			end
 		else
 			if ab.sustain_mana then
 				trigger = true; self.max_mana = self.max_mana + ab.sustain_mana
@@ -1535,6 +1596,9 @@ function _M:postUseTalent(ab, ret)
 			if ab.sustain_paradox then
 				self:incParadox(-ab.sustain_paradox)
 			end
+			if ab.sustain_psi then
+				trigger = true; self.max_psi = self.max_psi + ab.sustain_psi
+			end
 		end
 	else
 		if ab.mana then
@@ -1564,6 +1628,9 @@ function _M:postUseTalent(ab, ret)
 		if ab.paradox then
 			trigger = true; self:incParadox(ab.paradox * (1 + (self.paradox / 100)))
 		end
+		if ab.psi then
+			trigger = true; self:incPsi(-ab.psi * (100 + 2 * self.fatigue) / 100)
+		end
 	end
 	if trigger and self:hasEffect(self.EFF_BURNING_HEX) then
 		local p = self:hasEffect(self.EFF_BURNING_HEX)
@@ -1664,6 +1731,7 @@ function _M:getTalentFullDescription(t, addlevel)
 	if t.negative or t.sustain_negative then d:add({"color",0x6f,0xff,0x83}, "Negative energy cost: ", {"color", 127, 127, 127}, ""..(t.sustain_negative or t.negative * (100 + self:combatFatigue()) / 100), true) end
 	if t.hate or t.sustain_hate then d:add({"color",0x6f,0xff,0x83}, "Hate cost:  ", {"color", 127, 127, 127}, ""..(t.hate or t.sustain_hate), true) end
 	if t.paradox or t.sustain_paradox then d:add({"color",0x6f,0xff,0x83}, "Paradox cost: ", {"color",  176, 196, 222}, ("%0.2f"):format(t.sustain_paradox or t.paradox * (1 + (self.paradox / 100))), true) end
+	if t.psi or t.sustain_psi then d:add({"color",0x6f,0xff,0x83}, "Psi cost: ", {"color",0x7f,0xff,0xd4}, ""..(t.sustain_psi or t.psi * (100 + 2 * self.fatigue) / 100), true) end
 	if self:getTalentRange(t) > 1 then d:add({"color",0x6f,0xff,0x83}, "Range: ", {"color",0xFF,0xFF,0xFF}, ("%0.2f"):format(self:getTalentRange(t)), true)
 	else d:add({"color",0x6f,0xff,0x83}, "Range: ", {"color",0xFF,0xFF,0xFF}, "melee/personal", true)
 	end
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index e6eccb483b06f36d99b354485dd8584bdb52ec15..b7f699f9a71cbb40d6f40df79f01d3cf775d477b 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -736,10 +736,10 @@ function _M:setupCommands()
 			if config.settings.cheat then
 				self.player:forceLevelup(50)
 				self.player.no_breath = 1
-				self.player.invulnerable = 1
+--				self.player.invulnerable = 1
 				self.player.esp.all = 1
 				self.player.esp.range = 50
-				self.player.inc_damage.all = 100000
+--				self.player.inc_damage.all = 100000
 			end
 		end,
 		[{"_f","ctrl"}] = function()
diff --git a/game/modules/tome/class/PlayerDisplay.lua b/game/modules/tome/class/PlayerDisplay.lua
index 9d4a86e42f41b48c7e55f7f5770211579505ab79..b141de2475930945cd9bf377b8a89b06e7dd0277 100644
--- a/game/modules/tome/class/PlayerDisplay.lua
+++ b/game/modules/tome/class/PlayerDisplay.lua
@@ -156,6 +156,12 @@ function _M:display()
 		s:erase(176 / 2, 196 / 2, 222 / 2, 255, self.bars_x, h, self.bars_w * math.min(1, math.log(1 + player:getParadox() / 100)), self.font_h)
 		self:mouseTooltip(self.TOOLTIP_PARADOX, s:drawColorStringBlended(self.font, ("#LIGHT_STEEL_BLUE#Paradox:    #ffffff#%d"):format(player:getParadox()), 0, h, 255, 255, 255)) h = h + self.font_h
 	end
+	if player:knowTalent(player.T_PSI_POOL) then
+		s:erase(colors.BLUE.r / 5, colors.BLUE.g / 5, colors.BLUE.b / 5, 255, self.bars_x, h, self.bars_w, self.font_h)
+		s:erase(colors.BLUE.r / 2, colors.BLUE.g / 2, colors.BLUE.b / 2, 255, self.bars_x, h, self.bars_w * player:getPsi() / player.max_psi, self.font_h)
+		self:mouseTooltip(self.TOOLTIP_PSI, s:drawColorStringBlended(self.font, ("#7fffd4#Psi:     #ffffff#%d/%d"):format(player:getPsi(), player.max_psi), x, h, 255, 255, 255)) h = h + self.font_h
+	end
+
 	local quiver = player:getInven("QUIVER")
 	local ammo = quiver and quiver[1]
 	if ammo then
diff --git a/game/modules/tome/class/interface/TooltipsData.lua b/game/modules/tome/class/interface/TooltipsData.lua
index 24a543f08661a7720f28a632ffa9d65a440a2121..7da4c90cbb708839936e7d82bcb9768be1da1f10 100644
--- a/game/modules/tome/class/interface/TooltipsData.lua
+++ b/game/modules/tome/class/interface/TooltipsData.lua
@@ -83,6 +83,12 @@ As your Paradox grows your spells will cost more to use and have greater effect;
 Your control over chronomancy spells increases with your Willpower.
 ]]
 
+TOOLTIP_PSI = [[#GOLD#Psi#LAST#
+Psi represents how much energy your mind can harness. Like matter, it can be neither created nor destroyed.
+It does not regenerate naturally. You must absorb energy through shields or through various other talents.
+Your capacity for storing energy is determined by your Willpower.
+]]
+
 TOOLTIP_LEVEL = [[#GOLD#Level and experience#LAST#
 Each time you kill a creature that is over your own level - 5 you gain some experience.
 When you reach enough experience you advance to the next level. There is a maximum of 50 levels you can gain.
diff --git a/game/modules/tome/data/birth/classes/psionic.lua b/game/modules/tome/data/birth/classes/psionic.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6d43eee82fc0d71ad7327e30d97273e54afddc80
--- /dev/null
+++ b/game/modules/tome/data/birth/classes/psionic.lua
@@ -0,0 +1,92 @@
+-- ToME - Tales of Maj'Eyal
+-- 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 = "Psionic",
+	desc = {
+		"Psionics find their power within themselves. Their highly trained mind can harness energy from many different source and manipulate it to produce their own effects.",
+	},
+	descriptor_choices =
+	{
+		subclass =
+		{
+			__ALL__ = "disallow",
+			Mindslayer = function() return profile.mod.allow_build.psionic_mindslayer and "allow" or "disallow" end,
+		},
+	},
+	copy = {
+
+	},
+	body = { PSIONIC_FOCUS = 1, },
+}
+
+newBirthDescriptor{
+	type = "subclass",
+	name = "Mindslayer",
+	desc = {
+		"Mindslayers specialize in direct and brutal application of mental forces to their immediate surroundings.",
+		"When Mindslayers do battle, they will most often be found in the thick of the fighting, vast energies churning around them and telekinetically-wielded weapons hewing nearby foes at the speed of thought.",
+		"Their most important stats are: Willpower and Cunning",
+		"#GOLD#Stats modifiers:",
+		"#LIGHT_BLUE# * +1 Strength, +0 Dexterity, +0 Constitution",
+		"#LIGHT_BLUE# * +0 Magic, +4 Willpower, +3 Cunning",
+	},
+	stats = { str=1, wil=4, cun=3, },
+	talents_types = {
+		["psionic/absorption"]={true, 0.3},
+		["psionic/projection"]={true, 0.3},
+		["psionic/psi-fighting"]={true, 0.3},
+		["psionic/focus"]={false, 0.3},
+		["psionic/augmented-mobility"]={false, 0},
+		["psionic/voracity"]={true, 0.3},
+		["psionic/finer-energy-manipulations"]={false, 0},
+		["psionic/mental-discipline"]={true, 0.3},
+		["cunning/survival"]={true, 0},
+		["technique/combat-training"]={true, 0},
+	},
+	talents = {
+		[ActorTalents.T_KINETIC_SHIELD] = 1,
+		[ActorTalents.T_KINETIC_AURA] = 1,
+		[ActorTalents.T_KINETIC_LEECH] = 1,
+		[ActorTalents.T_BEYOND_THE_FLESH] = 1,
+		[ActorTalents.T_TRAP_DETECTION] = 1,
+		[ActorTalents.T_TELEKINETIC_GRASP] = 1,
+		[ActorTalents.T_TELEKINETIC_SMASH] = 1,
+	},
+	copy = {
+		max_life = 110,
+		resolvers.equip{ id=true,
+			{type="armor", subtype="cloth", name="linen robe", autoreq=true},
+			{type="weapon", subtype="greatsword", name="iron greatsword", autoreq=true},
+		},
+		resolvers.generic(function(self)
+			-- Make and wield some alchemist gems
+			local gs = game.zone:makeEntity(game.level, "object", {type="weapon", subtype="greatsword", name="iron greatsword", ego_chance=-1000}, nil, true)
+			if gs then
+				local pf = self:getInven("PSIONIC_FOCUS")
+				self:addObject(pf, gs)
+				gs:identify(true)
+			end
+		end),
+	},
+	copy_add = {
+		life_rating = -1,
+	},
+}
diff --git a/game/modules/tome/data/birth/descriptors.lua b/game/modules/tome/data/birth/descriptors.lua
index bd857773d6e6897074ae1e5ae49986bafa9a9c05..f85644a1dfb722e68bdb27683d8e14d5e931e8d3 100644
--- a/game/modules/tome/data/birth/descriptors.lua
+++ b/game/modules/tome/data/birth/descriptors.lua
@@ -202,3 +202,4 @@ load("/data/birth/classes/divine.lua")
 load("/data/birth/classes/corrupted.lua")
 load("/data/birth/classes/afflicted.lua")
 load("/data/birth/classes/chronomancer.lua")
+load("/data/birth/classes/psionic.lua")
diff --git a/game/modules/tome/data/birth/worlds.lua b/game/modules/tome/data/birth/worlds.lua
index 19ce7dc3f4cc87decfc7f48fdd11b992d0ab713f..f39441d15ad0016f5f0c10000c7b261b8433426a 100644
--- a/game/modules/tome/data/birth/worlds.lua
+++ b/game/modules/tome/data/birth/worlds.lua
@@ -81,6 +81,8 @@ newBirthDescriptor{
 		class =
 		{
 			__ALL__ = "disallow",
+			-- currently psionics consist only of mindslayers
+			Psionic = "allow",
 			Warrior = "allow",
 			Archer = "allow",
 			Rogue = "allow",
@@ -94,6 +96,7 @@ newBirthDescriptor{
 			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,
 			Chronomancer = function() return profile.mod.allow_build.chronomancer and "allow" or "disallow" end,
+			Psionic = function() return profile.mod.allow_build.psionic and "allow" or "disallow" end,
 		},
 	},
 }
@@ -122,6 +125,8 @@ newBirthDescriptor{
 		class =
 		{
 			__ALL__ = "disallow",
+			-- currently psionics consist only of mindslayers
+			Psionic = "allow",
 			Warrior = "allow",
 			Archer = "allow",
 			Rogue = "allow",
@@ -135,6 +140,7 @@ newBirthDescriptor{
 			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,
 			Chronomancer = function() return profile.mod.allow_build.chronomancer and "allow" or "disallow" end,
+			Psionic = function() return profile.mod.allow_build.psionic and "allow" or "disallow" end,
 		},
 	},
 	copy = {
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index 7d2579e50059555aaefc2768e794bda6bd3995a5..5606eb73bb55ec9eb75fe16e21fb8aad21d93c38 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -81,6 +81,37 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 			dam = t.on_damage(target, t, type, dam)
 		end
 
+		-- Static reduce damage for psionic kinetic shield
+		if target.isTalentActive and target:isTalentActive(target.T_KINETIC_SHIELD) then
+			local t = target:getTalentFromId(target.T_KINETIC_SHIELD)
+			dam = t.ks_on_damage(target, t, type, dam)
+		end
+		-- Static reduce damage for psionic spiked kinetic shield
+		if target:attr("kinspike_shield") then
+			local t = target:getTalentFromId(target.T_KINETIC_SHIELD)
+			dam = t.kss_on_damage(target, t, type, dam)
+		end
+		-- Static reduce damage for psionic thermal shield
+		if target.isTalentActive and target:isTalentActive(target.T_THERMAL_SHIELD) then
+			local t = target:getTalentFromId(target.T_THERMAL_SHIELD)
+			dam = t.ts_on_damage(target, t, type, dam)
+		end
+		-- Static reduce damage for psionic spiked thermal shield
+		if target:attr("thermspike_shield") then
+			local t = target:getTalentFromId(target.T_THERMAL_SHIELD)
+			dam = t.tss_on_damage(target, t, type, dam)
+		end
+		-- Static reduce damage for psionic charged shield
+		if target.isTalentActive and target:isTalentActive(target.T_CHARGED_SHIELD) then
+			local t = target:getTalentFromId(target.T_CHARGED_SHIELD)
+			dam = t.cs_on_damage(target, t, type, dam)
+		end
+		-- Static reduce damage for psionic spiked charged shield
+		if target:attr("chargespike_shield") then
+			local t = target:getTalentFromId(target.T_CHARGED_SHIELD)
+			dam = t.css_on_damage(target, t, type, dam)
+		end
+
 		print("[PROJECTOR] final dam", dam)
 
 		local flash = game.flash.NEUTRAL
@@ -1118,3 +1149,35 @@ newDamageType{
 		end
 	end,
 }
+
+newDamageType{
+	name = "batter", type = "BATTER",
+	projector = function(src, x, y, type, dam)
+		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam)
+		local target = game.level.map(x, y, Map.ACTOR)
+		if target then
+			if target:checkHit(src:combatMindpower(), target:combatPhysicalResist(), 0, 95, 15) and target:canBe("knockback") then
+				target:knockback(src.x, src.y, 2)
+				game.logSeen(target, "%s is knocked back!", target.name:capitalize())
+			else
+				game.logSeen(target, "%s resists the knockback!", target.name:capitalize())
+			end
+		end
+	end,
+}
+
+newDamageType{
+	name = "mindslow", type = "MINDSLOW",
+	projector = function(src, x, y, type, dam)
+		local target = game.level.map(x, y, Map.ACTOR)
+		if target then
+			-- Freeze it, if we pass the test
+			local sx, sy = game.level.map:getTileToScreen(x, y)
+			if target:checkHit(src:combatMindpower(), target:combatPhysicalResist(), 0, 95, 20) then
+				target:setEffect(target.EFF_SLOW, 4, {power=dam})
+			else
+				game.logSeen(target, "%s resists!", target.name:capitalize())
+			end
+		end
+	end,
+}
diff --git a/game/modules/tome/data/general/objects/gem.lua b/game/modules/tome/data/general/objects/gem.lua
index d40931befd332d970ac9cc721d5dae17c148bfa1..65a10366d9217bf021cc3fa506d4dbfaef44844f 100644
--- a/game/modules/tome/data/general/objects/gem.lua
+++ b/game/modules/tome/data/general/objects/gem.lua
@@ -38,6 +38,7 @@ local function newGem(name, image, cost, rarity, color, min_level, max_level, ti
 		rarity = rarity, cost = cost,
 		material_level = tier,
 		imbue_powers = imbue,
+		wielder = imbue,
 	}
 	-- Alchemist gems, not lootable, only created by talents
 	newEntity{ base = "BASE_GEM", define_as = "ALCHEMIST_GEM_"..name:upper(),
diff --git a/game/modules/tome/data/gfx/particles/ball_earth.lua b/game/modules/tome/data/gfx/particles/ball_earth.lua
index 9f050e6288b25e12ff6c5654a2db37fec6a81776..70983faa164fb2a7a82f1faff71b15947460c1a3 100644
--- a/game/modules/tome/data/gfx/particles/ball_earth.lua
+++ b/game/modules/tome/data/gfx/particles/ball_earth.lua
@@ -1,63 +1,63 @@
--- ToME - Tales of Maj'Eyal
--- 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 { generator = function()
-	local radius = radius
-	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
-	local ad = rng.float(0, 360)
-	local a = math.rad(ad)
-	local r = rng.float(0, sradius / 2)
-	local x = r * math.cos(a)
-	local y = r * math.sin(a)
-	local bx = math.floor(x / engine.Map.tile_w)
-	local by = math.floor(y / engine.Map.tile_h)
-	local static = rng.percent(40)
-
-	return {
-		trail = 1,
-		life = 10,
-		size = 3, sizev = 0, sizea = 0,
-
-		x = x, xv = 0, xa = 0,
-		y = y, yv = 0, ya = 0,
-		dir = a, dirv = 0, dira = 0,
-		vel = sradius / 2 / 10, 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 < 5 then
-		self.ps:emit(radius*266)
-		nb = nb + 1
-		self.ps:emit(radius*266)
-		nb = nb + 1
-		self.ps:emit(radius*266)
-		nb = nb + 1
-		self.ps:emit(radius*266)
-		nb = nb + 1
-		self.ps:emit(radius*266)
-		nb = nb + 1
-	end
-end,
-5*radius*266
+-- ToME - Tales of Maj'Eyal
+-- 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 { generator = function()
+	local radius = radius
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(0, 360)
+	local a = math.rad(ad)
+	local r = rng.float(0, sradius / 2)
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local bx = math.floor(x / engine.Map.tile_w)
+	local by = math.floor(y / engine.Map.tile_h)
+	local static = rng.percent(40)
+
+	return {
+		trail = 1,
+		life = 10,
+		size = 3, sizev = 0, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = a, dirv = 0, dira = 0,
+		vel = sradius / 2 / 10, 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 < 5 then
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+	end
+end,
+5*radius*266
diff --git a/game/modules/tome/data/gfx/particles/gravityspike.lua b/game/modules/tome/data/gfx/particles/gravityspike.lua
index 7b9149222509f87e3046279e529bb102ee50c912..205bcde785edfdd04850fe7f21aa882a4413756e 100644
--- a/game/modules/tome/data/gfx/particles/gravityspike.lua
+++ b/game/modules/tome/data/gfx/particles/gravityspike.lua
@@ -1,67 +1,67 @@
--- ToME - Tales of Maj'Eyal
--- 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 { generator = function()
-	local radius = radius
-	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
-	local ad = rng.float(0, 360)
-	local a = math.rad(ad)
-	local r = rng.float(sradius - 5, sradius)
-	local x = r * math.cos(a)
-	local y = r * math.sin(a)
-	local bx = math.floor(x / engine.Map.tile_w)
-	local by = math.floor(y / engine.Map.tile_h)
-	local static = rng.percent(40)
-
-	return {
-		trail = 1,
-		life = 24,
-		size = 3, sizev = static and 0.05 or 0.15, sizea = 0,
-
-		x = x, xv = 0, xa = 0,
-		y = y, yv = 0, ya = 0,
-		dir = static and a + math.rad(90 - rng.range(10, 20)) or a, dirv = 0, dira = 0,
-		vel = static and -2 or 0.5 * (-1-nb) * radius / 2.7, velv = 0, vela = static and -0.01 or rng.float(-0.3, -0.2) * 0.3,
-
-		r = rng.range(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 < 5 then
-		self.ps:emit(radius*266)
-		nb = nb + 1
-		self.ps:emit(radius*266)
-		nb = nb + 1
-		self.ps:emit(radius*266)
-		nb = nb + 1
-		self.ps:emit(radius*266)
-		nb = nb + 1
-		self.ps:emit(radius*266)
-		nb = nb + 1
-	end
-end,
+-- ToME - Tales of Maj'Eyal
+-- 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 { generator = function()
+	local radius = radius
+	local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+	local ad = rng.float(0, 360)
+	local a = math.rad(ad)
+	local r = rng.float(sradius - 5, sradius)
+	local x = r * math.cos(a)
+	local y = r * math.sin(a)
+	local bx = math.floor(x / engine.Map.tile_w)
+	local by = math.floor(y / engine.Map.tile_h)
+	local static = rng.percent(40)
+
+	return {
+		trail = 1,
+		life = 24,
+		size = 3, sizev = static and 0.05 or 0.15, sizea = 0,
+
+		x = x, xv = 0, xa = 0,
+		y = y, yv = 0, ya = 0,
+		dir = static and a + math.rad(90 - rng.range(10, 20)) or a, dirv = 0, dira = 0,
+		vel = static and -2 or 0.5 * (-1-nb) * radius / 2.7, velv = 0, vela = static and -0.01 or rng.float(-0.3, -0.2) * 0.3,
+
+		r = rng.range(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 < 5 then
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+		self.ps:emit(radius*266)
+		nb = nb + 1
+	end
+end,
 5*radius*266
\ No newline at end of file
diff --git a/game/modules/tome/data/talents.lua b/game/modules/tome/data/talents.lua
index a81612aac82b14d545021e9931d52d6e5c2d56f5..b78ddeb6d525a18bc6e1aab391e5ec53e755908b 100644
--- a/game/modules/tome/data/talents.lua
+++ b/game/modules/tome/data/talents.lua
@@ -58,3 +58,4 @@ load("/data/talents/corruptions/corruptions.lua")
 load("/data/talents/undeads/undeads.lua")
 load("/data/talents/cursed/cursed.lua")
 load("/data/talents/chronomancy/chronomancer.lua")
+load("/data/talents/psionic/psionic.lua")
diff --git a/game/modules/tome/data/talents/misc/misc.lua b/game/modules/tome/data/talents/misc/misc.lua
index 602c44f3a9dcfbe9353efb2d3e4cae5d59c1b8f9..63471d680a0fa9a487373175f8d768159e4be14e 100644
--- a/game/modules/tome/data/talents/misc/misc.lua
+++ b/game/modules/tome/data/talents/misc/misc.lua
@@ -27,6 +27,15 @@ newTalentType{ is_spell=true, type="inscriptions/runes", name = "runes", hide =
 load("/data/talents/misc/inscriptions.lua")
 load("/data/talents/misc/npcs.lua")
 
+--mindslayer resource
+newTalent{
+	name = "Psi Pool",
+	type = {"base/class", 1},
+	info = "Allows you to have an energy pool. Energy is used to perform psionic manipulations.",
+	mode = "passive",
+	hide = true,
+}
+
 newTalent{
 	name = "Mana Pool",
 	type = {"base/class", 1},
diff --git a/game/modules/tome/data/talents/psionic/absorption.lua b/game/modules/tome/data/talents/psionic/absorption.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6ac728ce116f0585326e04fef07e03931829dde4
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/absorption.lua
@@ -0,0 +1,339 @@
+-- ToME - Tales of Maj'Eyal
+-- 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 getGemLevel(self)
+		local gem_level = 0
+		if not self:getInven("PSIONIC_FOCUS")[1] then return gem_level end
+		local tk_item = self:getInven("PSIONIC_FOCUS")[1]
+		if tk_item.type == "gem" then 
+			gem_level = tk_item.material_level
+		else
+			gem_level = 0
+		end
+		return gem_level
+end
+
+local function getShieldStrength(self, t)
+	local add = 0
+	if self:knowTalent(self.T_FOCUSED_CHANNELING) then
+		add = getGemLevel(self)*(1 + 0.1*(self:getTalentLevel(self.T_FOCUSED_CHANNELING) or 0))
+	end
+	return 2 + (1+ self:getWil(8))*self:getTalentLevel(t) + add
+end
+
+local function getSpikeStrength(self, t)
+	local ss = getShieldStrength(self, t)
+	return  100*self:getTalentLevel(t) + ss*math.sqrt(ss)
+end
+
+newTalent{
+	name = "Kinetic Shield",
+	type = {"psionic/absorption", 1},
+	require = psi_absorb,
+	mode = "sustained", no_sustain_autoreset = true,
+	points = 5,
+	sustain_psi = 30,
+	cooldown = 20,
+	range = 25,
+	
+	--called when damage gets absorbed by kinetic shield
+	ks_on_damage = function(self, t, damtype, dam)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local total_dam = dam
+		local absorbable_dam = 0.01*(60 + math.min(self:getCun(50), 40))* total_dam
+		local guaranteed_dam = total_dam - absorbable_dam
+		dam = absorbable_dam
+		if damtype ~= DamageType.PHYSICAL and damtype ~= DamageType.ACID then return total_dam end
+		
+		if dam <= self.kinetic_shield then
+			self:incPsi(2 + dam/mast)
+			dam = 0
+		else
+			self:incPsi(2 + self.kinetic_shield/mast)
+			dam = dam - self.kinetic_shield
+		end
+		
+		return dam + guaranteed_dam
+	end,
+
+
+	activate = function(self, t)
+		game:playSoundNear(self, "talents/heal")
+		local s_str = getShieldStrength(self, t)
+		return {
+			am = self:addTemporaryValue("kinetic_shield", s_str),
+		}
+	end,
+	deactivate = function(self, t, p)
+		local spike_str = getSpikeStrength(self, t)
+		self:removeTemporaryValue("kinetic_shield", p.am)
+		self:setEffect(self.EFF_KINSPIKE_SHIELD, 5, {power=spike_str})
+		return true
+	end,
+
+	--called when damage gets absorbed by kinetic shield spike
+	kss_on_damage = function(self, t, damtype, dam)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local total_dam = dam
+		local absorbable_dam = 0.01*(60 + math.min(self:getCun(50), 40))* total_dam
+		local guaranteed_dam = total_dam - absorbable_dam
+		dam = absorbable_dam
+
+		if damtype == DamageType.PHYSICAL or damtype == DamageType.ACID then
+			-- Absorb damage into the shield
+			if dam <= self.kinspike_shield_absorb then
+				self.kinspike_shield_absorb = self.kinspike_shield_absorb - dam
+				self:incPsi(2 + dam/mast)
+				dam = 0
+			else
+				self:incPsi(2 + self.kinspike_shield_absorb/mast)
+				dam = dam - self.kinspike_shield_absorb
+				self.kinspike_shield_absorb = 0
+			end
+	
+			if self.kinspike_shield_absorb <= 0 then
+				game.logPlayer(self, "Your spiked kinetic shield crumbles under the damage!")
+				self:removeEffect(self.EFF_KINSPIKE_SHIELD)
+			end
+			return dam + guaranteed_dam
+		else
+			return total_dam
+		end
+	end,
+
+	info = function(self, t)
+		local s_str = getShieldStrength(self, t)
+		local spike_str = getSpikeStrength(self, t)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local absorb = 60 + math.min(self:getCun(50), 40)
+		return ([[Surround yourself with a shield that will absorb at most %d physical or acid damage per attack. Deactivating the shield spikes it up to a temporary %d point shield. The effect will increase with your Willpower stat.
+		Every time your shield absorbs damage, you gain two points of energy plus an additional point for every %d points of damage absorbed.
+		%d%% of any given attack is subject to absorption by this shield. The rest gets through as normal. Improve this by increasing Cunning.]]):
+		format(s_str, spike_str, mast, absorb)
+	end,
+}
+
+
+
+newTalent{
+	name = "Thermal Shield",
+	type = {"psionic/absorption", 1},
+	require = psi_absorb,
+	mode = "sustained", no_sustain_autoreset = true,
+	points = 5,
+	sustain_psi = 30,
+	cooldown = 20,
+	range = 25,
+	
+	--called when damage gets absorbed by thermal shield
+	ts_on_damage = function(self, t, damtype, dam)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local total_dam = dam
+		local absorbable_dam = 0.01*(60 + math.min(self:getCun(50), 40))* total_dam
+		local guaranteed_dam = total_dam - absorbable_dam
+		dam = absorbable_dam
+		if damtype ~= DamageType.FIRE and damtype ~= DamageType.COLD then return total_dam end
+		
+		if dam <= self.thermal_shield then
+			self:incPsi(2 + dam/mast)
+			dam = 0
+		else
+			self:incPsi(2 + self.thermal_shield/mast)
+			dam = dam - self.thermal_shield
+		end
+		return dam + guaranteed_dam
+	end,
+
+
+	activate = function(self, t)
+		game:playSoundNear(self, "talents/heal")
+		local s_str = getShieldStrength(self, t)
+		return {
+			am = self:addTemporaryValue("thermal_shield", s_str),
+		}
+	end,
+	deactivate = function(self, t, p)
+		local spike_str = getSpikeStrength(self, t)
+		self:removeTemporaryValue("thermal_shield", p.am)
+		self:setEffect(self.EFF_THERMSPIKE_SHIELD, 5, {power=spike_str})
+		return true
+	end,
+
+	--called when damage gets absorbed by thermal shield spike
+	tss_on_damage = function(self, t, damtype, dam)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local total_dam = dam
+		local absorbable_dam = 0.01*(60 + math.min(self:getCun(50), 40))* total_dam
+		local guaranteed_dam = total_dam - absorbable_dam
+		dam = absorbable_dam
+
+		if damtype == DamageType.FIRE or damtype == DamageType.COLD then
+			-- Absorb damage into the shield
+			if dam <= self.thermspike_shield_absorb then
+				self.thermspike_shield_absorb = self.thermspike_shield_absorb - dam
+				self:incPsi(2 + dam/mast)
+				dam = 0
+			else
+				self:incPsi(2 + self.thermspike_shield_absorb/mast)
+				dam = dam - self.thermspike_shield_absorb
+				self.thermspike_shield_absorb = 0
+			end
+	
+			if self.thermspike_shield_absorb <= 0 then
+				game.logPlayer(self, "Your spiked thermal shield crumbles under the damage!")
+				self:removeEffect(self.EFF_THERMSPIKE_SHIELD)
+			end
+			return dam + guaranteed_dam
+		else
+			return total_dam
+		end
+	end,
+
+	info = function(self, t)
+		local s_str = getShieldStrength(self, t)
+		local spike_str = getSpikeStrength(self, t)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local absorb = 60 + math.min(self:getCun(50), 40)
+		return ([[Surround yourself with a shield that will absorb at most %d thermal damage per attack. Deactivating the shield spikes it up to a temporary %d point shield. The effect will increase with your Willpower stat.
+		Every time your shield absorbs damage, you gain two points of energy plus an additional point for every %d points of damage absorbed.
+		%d%% of any given attack is subject to absorption by this shield. The rest gets through as normal. Improve this by increasing Cunning.]]):
+		format(s_str, spike_str, mast, absorb)
+	end,
+}
+
+newTalent{
+	name = "Charged Shield",
+	type = {"psionic/absorption", 1},
+	require = psi_absorb,
+	mode = "sustained", no_sustain_autoreset = true,
+	points = 5,
+	sustain_psi = 30,
+	cooldown = 20,
+	range = 25,
+	
+	--called when damage gets absorbed by charged shield
+	cs_on_damage = function(self, t, damtype, dam)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local total_dam = dam
+		local absorbable_dam = 0.01*(60 + math.min(self:getCun(50), 40))* total_dam
+		local guaranteed_dam = total_dam - absorbable_dam
+		dam = absorbable_dam
+		if damtype ~= DamageType.LIGHTNING and damtype ~= DamageType.BLIGHT then return total_dam end
+		
+		if dam <= self.charged_shield then
+			self:incPsi(2 + dam/mast)
+			dam = 0
+		else
+			self:incPsi(2 + self.charged_shield/mast)
+			dam = dam - self.charged_shield
+		end
+		return dam + guaranteed_dam
+	end,
+
+
+	activate = function(self, t)
+		game:playSoundNear(self, "talents/heal")
+		local s_str = getShieldStrength(self, t)
+		return {
+			am = self:addTemporaryValue("charged_shield", s_str),
+		}
+	end,
+	deactivate = function(self, t, p)
+		local spike_str = getSpikeStrength(self, t)
+		self:removeTemporaryValue("charged_shield", p.am)
+		self:setEffect(self.EFF_CHARGESPIKE_SHIELD, 5, {power=spike_str})
+		return true
+	end,
+
+	--called when damage gets absorbed by charged shield spike
+	css_on_damage = function(self, t, damtype, dam)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local total_dam = dam
+		local absorbable_dam = 0.01*(60 + math.min(self:getCun(50), 40))* total_dam
+		local guaranteed_dam = total_dam - absorbable_dam
+		dam = absorbable_dam
+		if damtype == DamageType.LIGHTNING or damtype == DamageType.BLIGHT then
+			-- Absorb damage into the shield
+			if dam <= self.chargespike_shield_absorb then
+				self.chargespike_shield_absorb = self.chargespike_shield_absorb - dam
+				self:incPsi(2 + dam/mast)
+				dam = 0
+			else
+				self:incPsi(2 + self.chargespike_shield_absorb/mast)
+				dam = dam - self.chargespike_shield_absorb
+				self.chargespike_shield_absorb = 0
+			end
+	
+			if self.chargespike_shield_absorb <= 0 then
+				game.logPlayer(self, "Your spiked charged shield crumbles under the damage!")
+				self:removeEffect(self.EFF_CHARGESPIKE_SHIELD)
+			end
+			return dam + guaranteed_dam
+		else
+			return total_dam
+		end
+	end,
+
+	info = function(self, t)
+		local s_str = getShieldStrength(self, t)
+		local spike_str = getSpikeStrength(self, t)
+		local mast = 20 - (2*self:getTalentLevel(self.T_ABSORPTION_MASTERY) or 0) - 0.4*getGemLevel(self)
+		local absorb = 60 + math.min(self:getCun(50), 40)
+		return ([[Surround yourself with a shield that will absorb at most %d lightning or blight damage per attack. Deactivating the shield spikes it up to a temporary %d point shield. The effect will increase with your Willpower stat.
+		Every time your shield absorbs damage, you gain two points of energy plus an additional point for every %d points of damage absorbed.
+		%d%% of any given attack is subject to absorption by this shield. The rest gets through as normal. Improve this by increasing Cunning.]]):
+		format(s_str, spike_str, mast, absorb)
+	end,
+}
+
+newTalent{
+	name = "Absorption Mastery",
+	type = {"psionic/absorption", 4},
+	require = psi_wil_req2,
+	points = 5,
+	mode = "passive",
+	on_learn = function(self, t)
+		local tKinshield = self:getTalentFromId(self.T_KINETIC_SHIELD)
+		tKinshield.cooldown = tKinshield.cooldown - 2
+
+		local tThermshield = self:getTalentFromId(self.T_THERMAL_SHIELD)
+		tThermshield.cooldown = tThermshield.cooldown - 2
+
+		local tChargeshield = self:getTalentFromId(self.T_CHARGED_SHIELD)
+		tChargeshield.cooldown = tChargeshield.cooldown - 2
+	end,
+	on_unlearn = function(self, t)
+		local tKinshield = self:getTalentFromId(self.T_KINETIC_SHIELD)
+		tKinshield.cooldown = tKinshield.cooldown + 2
+
+		local tThermshield = self:getTalentFromId(self.T_THERMAL_SHIELD)
+		tThermshield.cooldown = tThermshield.cooldown + 2
+
+		local tChargeshield = self:getTalentFromId(self.T_CHARGED_SHIELD)
+		tChargeshield.cooldown = tChargeshield.cooldown + 2
+	end,
+	getCooldown = function(self, t) return 2 * math.floor(self:getTalentLevelRaw(t)) end,
+	getMast = function(self, t) return 1 + 0.2*self:getTalentLevel(t) end,
+	info = function(self, t)
+		local cooldown = 2*self:getTalentLevelRaw(t)
+		local mast = 2*self:getTalentLevel(t)
+		return ([[Your expertise in the art of energy absorption grows. Shield cooldowns are all reduced by %d turns, and the amount of damage absorption required to gain a point of energy is reduced by %0.2f.]]):
+		format(cooldown, mast)
+	end,
+}
diff --git a/game/modules/tome/data/talents/psionic/augmented-mobility.lua b/game/modules/tome/data/talents/psionic/augmented-mobility.lua
new file mode 100644
index 0000000000000000000000000000000000000000..c39140c539994ece507f115e8f9bef44fc08184f
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/augmented-mobility.lua
@@ -0,0 +1,200 @@
+-- ToME - Tales of Maj'Eyal
+-- 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 getGemLevel(self)
+		local gem_level = 0
+		if not self:getInven("PSIONIC_FOCUS")[1] then return gem_level end
+		local tk_item = self:getInven("PSIONIC_FOCUS")[1]
+		if tk_item.type == "gem" then 
+			gem_level = tk_item.material_level
+		else
+			gem_level = 0
+		end
+		return gem_level
+end
+
+newTalent{
+	name = "Mindhook",
+	type = {"psionic/augmented-mobility", 1},
+	require = psi_wil_high1,
+	cooldown = 40,
+	psi = 20,
+	points = 5,
+	range = function(self, t)
+		local r = 3+self:getTalentLevel(t)+self:getWil(4)
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.floor(r*mult)
+	end,
+	--range = function(self, t) return 3+self:getTalentLevel(t)+self:getWil(4) end,
+	action = function(self, t)
+		local tg = {type="bolt", range=self:getTalentRange(t)}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+
+		self:project(tg, x, y, function(px, py)
+			local target = game.level.map(px, py, engine.Map.ACTOR)
+			if not target then return end
+			local nx, ny = util.findFreeGrid(self.x, self.y, 5, true, {[Map.ACTOR]=true})
+			if not nx then return end
+			target:move(nx, ny, true)
+
+		end)
+		game:playSoundNear(self, "talents/arcane")
+
+		return true
+	end,
+	info = function(self, t)
+		return ([[Briefly extend your telekinetic reach to grab an enemy and haul them towards you.
+		Works on enemies up to %d squares away.]]):
+		format(3+self:getTalentLevel(t)+self:getWil(4))
+	end,
+}
+
+
+
+newTalent{
+	name = "Quick as Thought",
+	type = {"psionic/augmented-mobility", 2},
+	points = 5,
+	random_ego = "utility",
+	cooldown = 80,
+	psi = 30,
+	require = psi_wil_high2,
+	action = function(self, t)
+		self:setEffect(self.EFF_QUICKNESS, 10+self:getWil(10), {power=self:getTalentLevel(t) * 0.1})
+		return true
+	end,
+	info = function(self, t)
+		local inc = self:getTalentLevel(t)*0.1
+		local percentinc = ((1/(1-inc))-1)*100
+		return ([[You encase your legs in precise sheathes of force, increasing your movement speed by %d%% for %d turns.]]):
+		format(percentinc, 10+self:getWil(10))
+	end,
+}
+
+
+newTalent{
+	name = "Superhuman Leap",
+	type = {"psionic/augmented-mobility", 3},
+	require = psi_wil_high3,
+	cooldown = 15,
+	psi = 10,
+	points = 5,
+	range = function(self, t)
+		local r = 1 + self:getWil(4) + self:getTalentLevel(t)
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.floor(r*mult)
+	end,
+	action = function(self, t)
+		local tg = {default_target=self, type="ball", nolock=true, pass_terrain=false, nowarning=true, range=self:getTalentRange(t), radius=0, requires_knowledge=false}
+		x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+		-- Target code doesnot restrict the target coordinates to the range, it lets the poject function do it
+		-- but we cant ...
+		local _ _, x, y = self:canProject(tg, x, y)
+		if self:hasLOS(x, y) and not game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, Map.ACTOR, "block_move") then
+			--self:teleportRandom(x, y, 0)
+			self:move(x, y, true)
+		else
+			game.logSeen(self, "You can't move there.")
+			return nil
+		end
+		return true
+	end,
+	info = function(self, t)
+		local range = self:getTalentRange(t)
+		return ([[You perform a precision, telekinetically-enhanced leap, landing up to %d squares away.]]):
+		format(range)
+	end,
+}
+
+newTalent{
+	name = "Shattering Charge",
+	type = {"psionic/augmented-mobility", 4},
+	require = psi_wil_high4,
+	points = 5,
+	random_ego = "attack",
+	psi = 60,
+	cooldown = 10,
+	tactical = {
+		ATTACK = 10,
+	},
+	range = function(self, t)
+		local r = 2 + self:getTalentLevel(t) + self:getWil(4)
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.floor(r*mult)
+	end,
+	--range = function(self, t) return 3+self:getTalentLevel(t)+self:getWil(4) end,
+	direct_hit = true,
+	requires_target = true,
+	action = function(self, t)
+		if not self:hasEffect(self.EFF_KINSPIKE_SHIELD) then game.logSeen(self, "You must have a spiked kinetic shield active. Cancelling charge.") return end
+		if self:getTalentLevelRaw(t) < 5 then
+			local tg = {type="beam", range=self:getTalentRange(t), friendlyfire=false, talent=t}
+			local x, y = self:getTarget(tg)
+			if not x or not y then return nil end
+			if self:hasLOS(x, y) and not game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, Map.ACTOR, "block_move") then
+				local dam = self:spellCrit(self:combatTalentMindDamage(t, 20, 600))
+				self:project(tg, x, y, DamageType.BATTER, self:spellCrit(rng.avg(2*dam/3, dam, 3)))
+				local _ _, x, y = self:canProject(tg, x, y)
+				game.level.map:particleEmitter(self.x, self.y, tg.radius, "flamebeam", {tx=x-self.x, ty=y-self.y})
+				game:playSoundNear(self, "talents/lightning")
+				self:move(x, y, true)
+			else
+				game.logSeen(self, "You can't move there.")
+				return nil
+			end
+			return true
+		else
+
+			local tg = {type="beam", range=self:getTalentRange(t), nolock=true, talent=t, display={particle="bolt_earth", trail="earthtrail"}}
+			local x, y = self:getTarget(tg)
+			if not x or not y then return nil end
+			local dam = self:spellCrit(self:combatTalentMindDamage(t, 20, 600))
+
+			for i = 1, self:getTalentRange(t) do
+				self:project(tg, x, y, DamageType.DIG, 1)
+			end
+			self:project(tg, x, y, DamageType.BATTER, self:spellCrit(rng.avg(2*dam/3, dam, 3)))
+			local _ _, x, y = self:canProject(tg, x, y)
+			game.level.map:particleEmitter(self.x, self.y, tg.radius, "flamebeam", {tx=x-self.x, ty=y-self.y})
+			game:playSoundNear(self, "talents/lightning")
+			local l = line.new(self.x, self.y, x, y)
+			local lx, ly = l()
+			local tx, ty = self.x, self.y
+			lx, ly = l()
+			while lx and ly do
+				if game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move", self) then break end
+				tx, ty = lx, ly
+				lx, ly = l()
+			end
+			self:move(tx, ty, true)
+			return true		
+		end
+	end,
+	info = function(self, t)
+		local dam = self:combatTalentMindDamage(t, 20, 600)
+		return ([[You expend massive amounts of energy to launch yourself forward at incredible speed. All enemies in your path will be knocked flying and dealt between %d and %d damage. At high levels, you can batter through solid walls.
+		You must have a spiked kinetic shield erected in order to use this ability.]]):
+		format(2*dam/3, dam)
+	end,
+}
diff --git a/game/modules/tome/data/talents/psionic/finer-energy-manipulations.lua b/game/modules/tome/data/talents/psionic/finer-energy-manipulations.lua
new file mode 100644
index 0000000000000000000000000000000000000000..81a814b16ae5e4f2cafa10f9c49d8953dda2bef0
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/finer-energy-manipulations.lua
@@ -0,0 +1,140 @@
+-- ToME - Tales of Maj'Eyal
+-- 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
+
+newTalent{
+	name = "Perfect Control",
+	type = {"psionic/finer-energy-manipulations", 1},
+	require = psi_cun_req1,
+	cooldown = 100,
+	psi = 15,
+	points = 5,
+	action = function(self, t)
+		self:setEffect(self.EFF_CONTROL, 5 + self:getTalentLevelRaw(t), {power=15 + math.ceil(self:getTalentLevel(t)*(1 + self:getCun(8)))})
+		return true
+	end,
+	info = function(self, t)
+		local boost = 15 + math.ceil(self:getTalentLevel(t)*(1 + self:getCun(8)))
+		local dur = 5 + self:getTalentLevelRaw(t)
+		return ([[Encase your body in a sheath of thought-quick forces, allowing you to control your body's movements directly without the inefficiency of dealing with crude mechanisms like nerves and muscles.
+		Increases attack by %d and critical strike chance by %0.2f%% for %d turns. The effect scales with Cunning.]]):
+		format(boost, 0.3*boost, dur)
+	end,
+}
+
+newTalent{
+	name = "Reshape Weapon",
+	type = {"psionic/finer-energy-manipulations", 2},
+	require = psi_cun_req2,
+	cooldown = 1,
+	psi = 100,
+	points = 5,
+	action = function(self, t)
+		self:showInventory("Reshape which weapon?", self:getInven("INVEN"), function(o) return o.type == "weapon" and not o.fully_reshaped end, function(o, item)
+			--o.wielder = o.wielder or {}
+			if (o.old_atk or 0) < math.floor(self:getTalentLevel(t)*(1 + self:getWil(4))) then
+				o.combat.atk = (o.combat.atk or 0) - (o.old_atk or 0)
+				o.combat.dam = (o.combat.dam or 0) - (o.old_dam or 0)
+				o.combat.atk = (o.combat.atk or 0) + math.floor(self:getTalentLevel(t)*(1 + self:getWil(4)))
+				o.combat.dam = (o.combat.dam or 0) + math.floor(self:getTalentLevel(t)*(1 + self:getWil(4)))
+				o.old_atk = math.floor(self:getTalentLevel(t)*(1 + self:getWil(4)))
+				o.old_dam = math.floor(self:getTalentLevel(t)*(1 + self:getWil(4)))
+				game.logPlayer(self, "You reshape your %s.", o:getName{do_colour=true, no_count=true})
+				if not o.been_reshaped then
+					o.name = "reshaped" .. " "..o.name..""
+					o.been_reshaped = true
+				end
+			else
+				game.logPlayer(self, "You cannot reshape your %s any further.", o:getName{do_colour=true, no_count=true})
+			end
+		end)
+		return true
+	end,
+	info = function(self, t)
+		return ([[Manipulate forces on the molecular level to realign, rebalance, and hone your weapon. Permanently increases the attack and damage of any weapon by %d.
+		This value scales with Willpower.]]):
+		format(math.floor(self:getTalentLevel(t)*(1 + self:getWil(4))))
+	end,
+}
+
+newTalent{
+	name = "Reshape Armor",
+	type = {"psionic/finer-energy-manipulations", 3},
+	require = psi_cun_req3,
+	cooldown = 1,
+	psi = 100,
+	points = 5,
+	action = function(self, t)
+		self:showInventory("Reshape which piece of armor?", self:getInven("INVEN"), function(o) return o.type == "armor" and not o.fully_reshaped end, function(o, item)
+			if (o.old_fat or 0) < math.ceil(0.5*self:getTalentLevel(t)*(1 + self:getWil(4))) then
+				o.wielder = o.wielder or {}
+				if not o.been_reshaped then
+					o.orig_arm = (o.wielder.combat_armor or 0)
+					o.orig_fat = (o.wielder.fatigue or 0)
+				end
+				o.wielder.combat_armor = o.orig_arm
+				o.wielder.fatigue = o.orig_fat
+				o.wielder.combat_armor = (o.wielder.combat_armor or 0) + math.ceil(0.1*self:getTalentLevel(t)*(1 + self:getWil(4)))
+				o.wielder.fatigue = (o.wielder.fatigue or 0) - math.ceil(0.5*self:getTalentLevel(t)*(1 + self:getWil(4)))
+				if o.wielder.fatigue < 0 then o.wielder.fatigue = 0 end
+				o.old_fat = math.ceil(0.5*self:getTalentLevel(t)*(1 + self:getWil(4)))
+				game.logPlayer(self, "You reshape your %s.", o:getName{do_colour=true, no_count=true})
+				if not o.been_reshaped then
+					o.name = "reshaped" .. " "..o.name..""
+					o.been_reshaped = true
+				end
+			else
+				game.logPlayer(self, "You cannot reshape your %s any further.", o:getName{do_colour=true, no_count=true})
+			end
+		end)
+		return true
+	end,
+	info = function(self, t)
+		local arm = math.ceil(0.1*self:getTalentLevel(t)*(1 + self:getWil(4)))
+		local fat = math.ceil(0.5*self:getTalentLevel(t)*(1 + self:getWil(4)))
+		return ([[Manipulate forces on the molecular level to realign, rebalance, and hone your weapon. Permanently increases the armor rating of any piece of armor by %d. Also permanently reduces the fatigue rating of any piece of armor by %d.
+		These values scale with Willpower.]]):
+		format(arm, fat)
+	end,
+}
+
+newTalent{
+	name = "Matter is Energy",
+	type = {"psionic/finer-energy-manipulations", 4},
+	require = psi_cun_req4,
+	cooldown = 50,
+	psi = 0,
+	points = 5,
+	action = function(self, t)
+		self:showInventory("Use which gem?", self:getInven("INVEN"), function(gem) return gem.type == "gem" and gem.material_level and gem.material_level == 5 end, function(gem, gem_item)
+			self:removeObject(self:getInven("INVEN"), gem_item)
+			--game.logPlayer(self, "You imbue your %s with %s.", o:getName{do_colour=true, no_count=true}, gem:getName{do_colour=true, no_count=true})
+			local quant = 30 + self:getTalentLevel(t)*self:getCun(20)
+			self:incPsi(quant)
+			self.changed = true
+		end)
+		return true
+	end,
+	info = function(self, t)
+		local quant = 30 + self:getTalentLevel(t)*self:getCun(20)
+		return ([[Matter is energy, as any good Mindslayer knows. Unfortunately, the various bonds and particles involved are just too numerous and complex to make the conversion feasible in most cases. Fortunately, the organized, crystalline structure of gems makes it possible to transform a small percentage of its matter into usable energy.
+		Turns a high-quality (material level 5) gem into %d energy. This value scales with Cunning.]]):
+		format(quant)
+	end,
+}
+
diff --git a/game/modules/tome/data/talents/psionic/focus.lua b/game/modules/tome/data/talents/psionic/focus.lua
new file mode 100644
index 0000000000000000000000000000000000000000..29d271b16ee20ed6ad15d3aa09eb4bd1dc29ce9e
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/focus.lua
@@ -0,0 +1,152 @@
+-- ToME - Tales of Maj'Eyal
+-- 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
+
+--Mindlash: ranged physical rad-0 ball
+--Pyrokinesis: LOS burn attack
+--Reach: gem-based range improvements
+--Channeling: gem-based shield and improvement
+
+local function getGemLevel(self)
+		local gem_level = 0
+		if not self:getInven("PSIONIC_FOCUS")[1] then return gem_level end
+		local tk_item = self:getInven("PSIONIC_FOCUS")[1]
+		if tk_item.type == "gem" then 
+			gem_level = tk_item.material_level
+		else
+			gem_level = 0
+		end
+		return gem_level
+end
+
+
+newTalent{
+	name = "Mindlash",
+	type = {"psionic/focus", 1},
+	require = psi_wil_high1,
+	points = 5,
+	random_ego = "attack",
+	cooldown = function(self, t)
+		local c = 15
+		local gem_level = getGemLevel(self)
+		return c - gem_level
+	end,
+	psi = 15,
+	range = function(self, t)
+		local r = 10
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.floor(r*mult)
+	end,
+
+	action = function(self, t)
+		local gem_level = getGemLevel(self)
+		local dam = (5 + self:getTalentLevel(t) * self:getWil(40))*(1 + 0.3*gem_level)
+		local tg = {type="ball", range=self:getTalentRange(t), radius=0, 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.PHYSICAL, self:spellCrit(rng.avg(0.8*dam, dam)), {type="flame"})
+		return true
+	end,
+	info = function(self, t)
+		local gem_level = getGemLevel(self)
+		local dam = (5 + self:getTalentLevel(t) * self:getWil(40))*(1 + 0.3*gem_level)
+		return ([[Focus energies on a distant target to lash it with physical force, doing %d damage.
+		Mindslayers do not do this sort of ranged attack naturally. The use of a telekinetically-wielded gem as a focus will improve the effects considerably.]]):
+		format(dam)
+	end,
+}
+
+newTalent{
+	name = "Pyrokinesis",
+	type = {"psionic/focus", 2},
+	require = psi_wil_high2,
+	points = 5,
+	random_ego = "attack",
+	cooldown = function(self, t)
+		local c = 20
+		local gem_level = getGemLevel(self)
+		return c - gem_level
+	end,
+	psi = 20,
+	range = function(self, t)
+		local r = 10
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.floor(r*mult)
+	end,
+	action = function(self, t)
+		local gem_level = getGemLevel(self)
+		local dam = (20 + self:getTalentLevel(t) * self:getWil(40))*(1 + 0.3*gem_level)
+		local tgts = {}
+		local grids = core.fov.circle_grids(self.x, self.y, self:getTalentRange(t), true)
+		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
+			local a = game.level.map(x, y, Map.ACTOR)
+			if a and self:reactionToward(a) < 0 then
+				tgts[#tgts+1] = a
+			end
+		end end
+
+		-- Burn each target
+		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
+		local targ_num = #tgts
+		for i = 1, targ_num do
+			if #tgts <= 0 then break end
+			local a, id = rng.table(tgts)
+			table.remove(tgts, id)
+			self:project(tg, a.x, a.y, DamageType.FIREBURN, {dur=10, initial=0, dam=(20 + self:getTalentLevel(t) * self:getWil(40))*(1 + 0.3*gem_level)})
+			game.level.map:particleEmitter(a.x, a.y, tg.radius, "ball_fire", {radius=1})
+		end
+
+		return true
+	end,
+	info = function(self, t)
+		local range = self:getTalentRange(t)
+		local gem_level = getGemLevel(self)
+		local dam = (20 + self:getTalentLevel(t) * self:getWil(40))*(1 + 0.3*gem_level)
+		return ([[Focus energies on all targets within %d squares, setting them ablaze. Does %d damage over ten turns.
+		Mindslayers do not do this sort of ranged attack naturally. The use of a telekinetically-wielded gem as a focus will improve the effects considerably.]]):
+		format(range, dam)
+	end,
+}
+
+newTalent{
+	name = "Reach",
+	type = {"psionic/focus", 3},
+	require = psi_wil_high3,
+	mode = "passive",
+	points = 5,
+	info = function(self, t)
+		local inc = 2*self:getTalentLevel(t)
+		return ([[You can extend your mental reach beyond your natural limits using a telekinetically-wielded gemstone as a focus. Increases the range of various abilities by %d%% to %d%%, depending on the quality of the gem used as a focus.]]):
+		format(inc, 5*inc)
+	end,
+}
+
+newTalent{
+	name = "Focused Channeling",
+	type = {"psionic/focus", 4},
+	require = psi_wil_high4,
+	mode = "passive",
+	points = 5,
+	info = function(self, t)
+		local inc = 1 + 0.1*self:getTalentLevel(t)
+		return ([[You can channel more energy with your auras and shields using a telekinetically-wielded gemstone as a focus. Increases the base strength of all auras and shields by %0.2f to %0.2f, depending on the quality of the gem used as a focus.]]):
+		format(inc, 5*inc)
+	end,
+}
\ No newline at end of file
diff --git a/game/modules/tome/data/talents/psionic/mental-discipline.lua b/game/modules/tome/data/talents/psionic/mental-discipline.lua
new file mode 100644
index 0000000000000000000000000000000000000000..b9c6582e3b7e93eed7122f7d9f4eab338416c46c
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/mental-discipline.lua
@@ -0,0 +1,133 @@
+-- ToME - Tales of Maj'Eyal
+-- 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
+
+
+newTalent{
+	name = "Highly Trained Mind",
+	type = {"psionic/mental-discipline", 1},
+	mode = "passive",
+	require = psi_wil_req1,
+	points = 5,
+	on_learn = function(self, t)
+		self.inc_stats[self.STAT_WIL] = self.inc_stats[self.STAT_WIL] + 1
+		self:onStatChange(self.STAT_WIL, 1)
+		self.inc_stats[self.STAT_CUN] = self.inc_stats[self.STAT_CUN] + 1
+		self:onStatChange(self.STAT_CUN, 1)
+	end,
+	on_unlearn = function(self, t)
+		self.inc_stats[self.STAT_WIL] = self.inc_stats[self.STAT_WIL] - 1
+		self:onStatChange(self.STAT_WIL, -1)
+		self.inc_stats[self.STAT_CUN] = self.inc_stats[self.STAT_CUN] - 1
+		self:onStatChange(self.STAT_CUN, -1)
+	end,
+	info = function(self, t)
+		return ([[A life of the mind has had predictably good effects on your Willpower and Cunning.
+		Increases Willpower and Cunning (as well as their maximum values) by %d.]]):format(self:getTalentLevelRaw(t))
+	end,
+}
+
+newTalent{
+	name = "Iron Will",
+	type = {"psionic/mental-discipline", 2},
+	require = psi_wil_req2,
+	points = 5,
+	mode = "passive",
+	on_learn = function(self, t)
+		self.combat_mentalresist = self.combat_mentalresist + 6
+		self.stun_immune = (self.stun_immune or 0) + .1
+	end,
+
+	on_unlearn = function(self, t)
+		self.combat_mentalresist = self.combat_mentalresist - 6
+		self.stun_immune = (self.stun_immune or 0) - .1
+	end,
+	info = function(self, t)
+		return ([[Improves mental saves by %d and stun immunity by %d%%]]):
+		format(self:getTalentLevelRaw(t)*6, self:getTalentLevelRaw(t)*10)
+	end,
+}
+
+newTalent{
+	name = "Shield Discipline",
+	type = {"psionic/mental-discipline", 3},
+	require = psi_wil_req3,
+	cooldown = function(self, t)
+		return 120 - self:getTalentLevel(t)*12
+	end,
+	psi = 15,
+	points = 5,
+	no_energy = true,
+	action = function(self, t)
+		if self.talents_cd[self.T_KINETIC_SHIELD] == nil and self.talents_cd[self.T_THERMAL_SHIELD] == nil and self.talents_cd[self.T_CHARGED_SHIELD] == nil then
+			return
+		else
+			self.talents_cd[self.T_KINETIC_SHIELD] = (self.talents_cd[self.T_KINETIC_SHIELD] or 0) - 10 - 2 * self:getTalentLevelRaw(t)
+			self.talents_cd[self.T_THERMAL_SHIELD] = (self.talents_cd[self.T_THERMAL_SHIELD] or 0) - 10 - 2 * self:getTalentLevelRaw(t)
+			self.talents_cd[self.T_CHARGED_SHIELD] = (self.talents_cd[self.T_CHARGED_SHIELD] or 0) - 10 - 2 * self:getTalentLevelRaw(t)
+			return true
+		end
+	end,
+
+	info = function(self, t)
+		return ([[When activated, reduces the cooldowns of all shields by %d. Additional talent points spent in Shield Discipline improve this value and allow it to be used more frequently.]]):
+		format(10+self:getTalentLevelRaw(t)*2)
+	end,
+}
+
+newTalent{
+	name = "Aura Discipline",
+	type = {"psionic/mental-discipline", 4},
+	require = psi_wil_req4,
+	cooldown = function(self, t)
+		return 120 - self:getTalentLevel(t)*12
+	end,
+	psi = 15,
+	points = 5,
+	no_energy = true,
+	action = function(self, t)
+		if self.talents_cd[self.T_KINETIC_AURA] == nil and self.talents_cd[self.T_THERMAL_AURA] == nil and self.talents_cd[self.T_CHARGED_AURA] == nil then
+			return
+		else
+			if self:isTalentActive(self.T_CONDUIT) then
+				local auras = self:isTalentActive(self.T_CONDUIT)
+				if not auras.k_aura_on then
+					self.talents_cd[self.T_KINETIC_AURA] = (self.talents_cd[self.T_KINETIC_AURA] or 0) - 4 - 1 * self:getTalentLevelRaw(t)
+				end
+				if not auras.t_aura_on then
+					self.talents_cd[self.T_THERMAL_AURA] = (self.talents_cd[self.T_THERMAL_AURA] or 0) - 4 - 1 * self:getTalentLevelRaw(t)
+				end
+				if not auras.c_aura_on then
+					self.talents_cd[self.T_CHARGED_AURA] = (self.talents_cd[self.T_CHARGED_AURA] or 0) - 4 - 1 * self:getTalentLevelRaw(t)
+				end
+			else
+				self.talents_cd[self.T_KINETIC_AURA] = (self.talents_cd[self.T_KINETIC_AURA] or 0) - 4 - 1 * self:getTalentLevelRaw(t)
+				self.talents_cd[self.T_THERMAL_AURA] = (self.talents_cd[self.T_THERMAL_AURA] or 0) - 4 - 1 * self:getTalentLevelRaw(t)
+				self.talents_cd[self.T_CHARGED_AURA] = (self.talents_cd[self.T_CHARGED_AURA] or 0) - 4 - 1 * self:getTalentLevelRaw(t)
+			end
+			return true
+		end
+	end,
+
+	info = function(self, t)
+		local red = 4 + self:getTalentLevelRaw(t)
+		return ([[When activated, reduces the cooldown of all auras by %d. Additional talent points spent in Aura Discipline improve this value and allow it to be used more frequently.]]):
+		format(red)
+	end,
+}
+
diff --git a/game/modules/tome/data/talents/psionic/projection.lua b/game/modules/tome/data/talents/psionic/projection.lua
new file mode 100644
index 0000000000000000000000000000000000000000..7c9acbc5e6e9b9e045fe8f03b89f75b3942574f0
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/projection.lua
@@ -0,0 +1,443 @@
+-- ToME - Tales of Maj'Eyal
+-- 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 getGemLevel(self)
+		local gem_level = 0
+		if not self:getInven("PSIONIC_FOCUS")[1] then return gem_level end
+		local tk_item = self:getInven("PSIONIC_FOCUS")[1]
+		if tk_item.type == "gem" then 
+			gem_level = tk_item.material_level
+		else
+			gem_level = 0
+		end
+		return gem_level
+end
+
+local function combatTalentDamage(self, t, min, max)
+	return self:combatTalentSpellDamage(t, min, max, self.level + self:getWil())
+end
+
+-- damage: initial physical damage and used for fractional knockback damage
+-- knockback: distance to knockback
+-- knockbackDamage: when knockback strikes something, both parties take damage - percent of damage * remaining knockback
+-- power: used to determine the initial radius of particles
+local function forceHit(self, target, sourceX, sourceY, damage, knockback, knockbackDamage, power)
+	-- apply initial damage
+	if not target then return end
+	if damage > 0 then
+		self:project(target, target.x, target.y, DamageType.PHYSICAL, damage)
+		game.level.map:particleEmitter(target.x, target.y, 1, "force_hit", {power=power, dx=target.x - sourceX, dy=target.y - sourceY})
+	end
+	
+	-- knockback?
+	if not target.dead and knockback and knockback > 0 and target:canBe("knockback") and (target.never_move or 0) < 1 then
+		-- give direct hit a direction?
+		if sourceX == target.x and sourceY == target.y then
+			local newDirection = rng.range(1, 8)
+			sourceX = sourceX + dir_to_coord[newDirection][1]
+			sourceY = sourceY + dir_to_coord[newDirection][2]
+		end
+	
+		local lineFunction = line.new(sourceX, sourceY, target.x, target.y, true)
+		local finalX, finalY = target.x, target.y
+		local knockbackCount = 0
+		local blocked = false
+		while knockback > 0 do
+			blocked = true
+			local x, y = lineFunction(true)
+			
+			if not game.level.map:isBound(x, y) or game.level.map:checkAllEntities(x, y, "block_move", target) then
+				-- blocked
+				local nextTarget = game.level.map(x, y, Map.ACTOR)
+				if nextTarget then
+					if knockbackCount > 0 then
+						game.logPlayer(self, "%s was blasted %d spaces into %s!", target.name:capitalize(), knockbackCount, nextTarget.name)
+					else
+						game.logPlayer(self, "%s was blasted into %s!", target.name:capitalize(), nextTarget.name)
+					end
+				elseif knockbackCount > 0 then
+					game.logPlayer(self, "%s was smashed back %d spaces!", target.name:capitalize(), knockbackCount)
+				else
+					game.logPlayer(self, "%s was smashed!", target.name:capitalize())
+				end
+				
+				-- take partial damage
+				local blockDamage = damage * knockback * knockbackDamage / 100
+				self:project(target, target.x, target.y, DamageType.PHYSICAL, blockDamage)
+				
+				if nextTarget then
+					-- start a new force hit with the knockback damage and current knockback
+					
+					forceHit(self, nextTarget, sourceX, sourceY, blockDamage, knockback, knockbackDamage, power / 2)
+				end
+				
+				knockback = 0
+			else
+				-- allow move
+				finalX, finalY = x, y
+				knockback = knockback - 1
+				knockbackCount = knockbackCount + 1
+			end
+		end
+		
+		if not blocked and knockbackCount > 0 then
+			game.logPlayer(self, "%s was blasted back %d spaces!", target.name:capitalize())
+		end
+		
+		if not target.dead and (finalX ~= target.x or finalY ~= target.y) then
+			target:move(finalX, finalY, true)
+		end
+	end
+end
+
+newTalent{
+	name = "Kinetic Aura",
+	type = {"psionic/projection", 1},
+	require = psi_wil_req1, no_sustain_autoreset = true,
+	points = 5,
+	mode = "sustained",
+	sustain_psi = 30,
+	cooldown = function(self, t)
+		return 15 - (self:getTalentLevelRaw(self.T_PROJECTION_MASTERY) or 0)
+	end,
+	tactical = {
+		ATTACKAREA = 10,
+	},
+	range = 1,
+	direct_hit = true,
+	getAuraStrength = function(self, t)
+		local add = 0
+		if self:knowTalent(self.T_FOCUSED_CHANNELING) then
+			add = getGemLevel(self)*(1 + 0.1*(self:getTalentLevel(self.T_FOCUSED_CHANNELING) or 0))
+		end
+		return 5 + (1+ self:getWil(5))*self:getTalentLevel(t) + add
+	end,
+	getKnockback = function(self, t)
+		return 3 + math.floor(self:getTalentLevel(t))
+	end,
+	do_kineticaura = function(self, t)
+
+		local mast = 3 + (self:getTalentLevel(self.T_PROJECTION_MASTERY) or 0) + getGemLevel(self)
+		local dam = t.getAuraStrength(self, t)
+		local tgts = {}
+		local grids = core.fov.circle_grids(self.x, self.y, 1, true)
+		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
+			local a = game.level.map(x, y, Map.ACTOR)
+			if a and self:reactionToward(a) < 0 then
+				tgts[#tgts+1] = a
+			end
+		end end
+
+		-- Randomly take targets
+		local tg = {type="hit", range=1, talent=t}
+		for i = 1, 10 do
+			if #tgts <= 0 then break end
+			local a, id = rng.table(tgts)
+			table.remove(tgts, id)
+			self:project(tg, a.x, a.y, DamageType.PHYSICAL, dam)
+			--game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(a.x-self.x), math.abs(a.y-self.y)), "lightning", {tx=a.x-self.x, ty=a.y-self.y})
+			self:incPsi(-dam/mast)
+		end
+
+	end,
+	activate = function(self, t)
+		return true
+	end,
+	deactivate = function(self, t, p)
+		local dam = 50 + 0.25 * t.getAuraStrength(self, t)*t.getAuraStrength(self, t)
+		local cost = t.sustain_psi - 2*getGemLevel(self)
+		--if self:isTalentActive(self.T_CONDUIT) then return true end
+		if self:getPsi() <= cost then 
+			game.logPlayer(self, "The aura dissipates without producing a spike.")
+			return true 
+		end
+		local tg = {type="hit", range=self:getTalentRange(t)}
+		local x, y, target = self:getTarget(tg)
+		if not x or not y then return nil end
+		if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end
+		local knockback = t.getKnockback(self, t)
+		forceHit(self, target, self.x, self.y, dam, knockback, 15, 1)		
+		--self:project(tg, x, y, DamageType.BATTER, dam)
+		self:incPsi(-cost)
+		
+		return true
+	end,
+
+	info = function(self, t)
+		local dam = t.getAuraStrength(self, t)
+		local spikedam = 50 + 0.25 * dam * dam
+		local mast = 3 + (self:getTalentLevel(self.T_PROJECTION_MASTERY) or 0) + getGemLevel(self)
+		local spikecost = t.sustain_psi - 2*getGemLevel(self)
+		return ([[Fills the air around you with reactive currents of force that do %d physical damage to all who approach. All damage done by the aura will drain one point of energy per %0.2f points of damage dealt.
+		When deactivated, if you have at least %d energy, a massive spike of kinetic energy is released, smashing a target for %d physical damage and sending it flying. Telekinetically wielding a gem instead of a weapon will result in improved spike efficiency.
+		The damage will increase with the Willpower stat.]]):format(dam, mast, spikecost, spikedam)
+	end,
+}
+
+
+newTalent{
+	name = "Thermal Aura",
+	type = {"psionic/projection", 2},
+	require = psi_wil_req2, no_sustain_autoreset = true,
+	points = 5,
+	mode = "sustained",
+	sustain_psi = 40,
+	cooldown = function(self, t)
+		return 15 - (self:getTalentLevelRaw(self.T_PROJECTION_MASTERY) or 0)
+	end,
+	tactical = {
+		ATTACKAREA = 10,
+	},
+	range = function(self, t)
+		local r = 8
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.floor(r*mult)
+	end,
+	direct_hit = true,
+	getAuraStrength = function(self, t)
+		local add = 0
+		if self:knowTalent(self.T_FOCUSED_CHANNELING) then
+			add = getGemLevel(self)*(1 + 0.1*(self:getTalentLevel(self.T_FOCUSED_CHANNELING) or 0))
+		end
+		return 5 + (1+ self:getWil(5))*self:getTalentLevel(t) + add
+	end,
+	do_thermalaura = function(self, t)
+
+		local mast = 3 + (self:getTalentLevel(self.T_PROJECTION_MASTERY) or 0) + getGemLevel(self)
+		local dam = t.getAuraStrength(self, t)
+		local tgts = {}
+		local grids = core.fov.circle_grids(self.x, self.y, 1, true)
+		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
+			local a = game.level.map(x, y, Map.ACTOR)
+			if a and self:reactionToward(a) < 0 then
+				tgts[#tgts+1] = a
+			end
+		end end
+
+		-- Randomly take targets
+		local tg = {type="hit", range=1, talent=t}
+		for i = 1, 10 do
+			if #tgts <= 0 then break end
+			local a, id = rng.table(tgts)
+			table.remove(tgts, id)
+			self:project(tg, a.x, a.y, DamageType.FIRE, dam)
+			--game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(a.x-self.x), math.abs(a.y-self.y)), "lightning", {tx=a.x-self.x, ty=a.y-self.y})
+			self:incPsi(-dam/mast)
+		end
+
+	end,
+	activate = function(self, t)
+		return true
+	end,
+	deactivate = function(self, t, p)
+		local dam = 50 + 0.4 * t.getAuraStrength(self, t)*t.getAuraStrength(self, t)
+		local cost = t.sustain_psi - 2*getGemLevel(self)
+		--if self:isTalentActive(self.T_CONDUIT) then return true end
+		if self:getPsi() <= cost then 
+			game.logPlayer(self, "The aura dissipates without producing a spike.")
+			return true 
+		end
+
+		local tg = {type="beam", range=self:getTalentRange(t), talent=t, display={particle="bolt_fire", trail="firetrail"}}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+		self:project(tg, x, y, DamageType.FIREBURN, self:spellCrit(rng.avg(0.8*dam, dam)))
+		local _ _, x, y = self:canProject(tg, x, y)
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "flamebeam", {tx=x-self.x, ty=y-self.y})
+		
+		game:playSoundNear(self, "talents/fire")
+		self:incPsi(-cost)
+		return true
+	end,
+
+	info = function(self, t)
+		local dam = t.getAuraStrength(self, t)
+		local spikedam = 50 + 0.4 * dam * dam
+		local mast = 3 + (self:getTalentLevel(self.T_PROJECTION_MASTERY) or 0) + getGemLevel(self)
+		local spikecost = t.sustain_psi - 2*getGemLevel(self)
+		return ([[Fills the air around you with reactive currents of furnace-like heat that do %d fire damage to all who approach. All damage done by the aura will drain one point of energy per %0.2f points of damage dealt.
+		When deactivated, if you have at least %d energy, a massive spike of thermal energy is released as a tunnel of superheated air. Anybody caught in it will suffer %d fire damage. Telekinetically wielding a gem instead of a weapon will result in improved spike efficiency.
+		The damage will increase with the Willpower stat.]]):format(dam, mast, spikecost, spikedam)
+	end,
+}
+
+
+newTalent{
+	name = "Charged Aura",
+	type = {"psionic/projection", 3},
+	require = psi_wil_req3, no_sustain_autoreset = true,
+	points = 5,
+	mode = "sustained",
+	sustain_psi = 50,
+	cooldown = function(self, t)
+		return 15 - (self:getTalentLevelRaw(self.T_PROJECTION_MASTERY) or 0)
+	end,
+	tactical = {
+		ATTACKAREA = 10,
+	},
+	range = function(self, t)
+		local r = 6
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.ceil(r*mult)
+	end,
+	direct_hit = true,
+	getAuraStrength = function(self, t)
+		local add = 0
+		if self:knowTalent(self.T_FOCUSED_CHANNELING) then
+			add = getGemLevel(self)*(1 + 0.1*(self:getTalentLevel(self.T_FOCUSED_CHANNELING) or 0))
+		end
+		return 5 + (1+ self:getWil(5))*self:getTalentLevel(t) + add
+	end,
+	do_chargedaura = function(self, t)
+		local mast = 3 + (self:getTalentLevel(self.T_PROJECTION_MASTERY) or 0) + getGemLevel(self)
+		local dam = t.getAuraStrength(self, t)
+		local tgts = {}
+		local grids = core.fov.circle_grids(self.x, self.y, 1, true)
+		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
+			local a = game.level.map(x, y, Map.ACTOR)
+			if a and self:reactionToward(a) < 0 then
+				tgts[#tgts+1] = a
+			end
+		end end
+
+		-- Randomly take targets
+		local tg = {type="hit", range=1, talent=t}
+		for i = 1, 10 do
+			if #tgts <= 0 then break end
+			local a, id = rng.table(tgts)
+			table.remove(tgts, id)
+			self:project(tg, a.x, a.y, DamageType.LIGHTNING, dam)
+			--game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(a.x-self.x), math.abs(a.y-self.y)), "lightning", {tx=a.x-self.x, ty=a.y-self.y})
+			self:incPsi(-dam/mast)
+		end
+	end,
+	activate = function(self, t)
+		game:playSoundNear(self, "talents/thunderstorm")
+		return true
+	end,
+	deactivate = function(self, t, p)
+		local dam = 50 + 0.4 * t.getAuraStrength(self, t)*t.getAuraStrength(self, t)
+		local cost = t.sustain_psi - 2*getGemLevel(self)
+		--if self:isTalentActive(self.T_CONDUIT) then return true end
+		if self:getPsi() <= cost then 
+			game.logPlayer(self, "The aura dissipates without producing a spike.")
+			return true 
+		end
+		
+		local tg = {type="bolt", range=self:getTalentRange(t), talent=t}
+		local fx, fy = self:getTarget(tg)
+		if not fx or not fy then return nil end
+
+		local nb = 1 + math.floor(0.5*self:getTalentLevel(t)) + getGemLevel(self)
+		local affected = {}
+		local first = nil
+		--Here's the part where deactivating the aura fires off a huge chain lightning
+		self:project(tg, fx, fy, function(dx, dy)
+			print("[Chain lightning] targetting", fx, fy, "from", self.x, self.y)
+			local actor = game.level.map(dx, dy, Map.ACTOR)
+			if actor and not affected[actor] then
+				ignored = false
+				affected[actor] = true
+				first = actor
+
+				print("[Chain lightning] looking for more targets", nb, " at ", dx, dy, "radius ", 10, "from", actor.name)
+				self:project({type="ball", friendlyfire=false, x=dx, y=dy, radius=self:getTalentRange(t), range=0}, dx, dy, function(bx, by)
+					local actor = game.level.map(bx, by, Map.ACTOR)
+					if actor and not affected[actor] and self:reactionToward(actor) < 0 then
+						print("[Chain lightning] found possible actor", actor.name, bx, by, "distance", core.fov.distance(dx, dy, bx, by))
+						affected[actor] = true
+					end
+				end)
+				return true
+			end
+		end)
+
+		if not first then return true end
+		local targets = { first }
+		affected[first] = nil
+		local possible_targets = table.listify(affected)
+		print("[Chain lightning] Found targets:", #possible_targets)
+		for i = 2, nb do
+			if #possible_targets == 0 then break end
+			local act = rng.tableRemove(possible_targets)
+			targets[#targets+1] = act[1]
+		end
+
+		local sx, sy = self.x, self.y
+		for i, actor in ipairs(targets) do
+			local tgr = {type="beam", range=self:getTalentRange(t), friendlyfire=false, talent=t, x=sx, y=sy}
+			print("[Chain lightning] jumping from", sx, sy, "to", actor.x, actor.y)
+			self:project(tgr, actor.x, actor.y, DamageType.LIGHTNING, self:spellCrit(rng.avg(0.8*dam, dam)))
+			game.level.map:particleEmitter(sx, sy, math.max(math.abs(actor.x-sx), math.abs(actor.y-sy)), "lightning", {tx=actor.x-sx, ty=actor.y-sy, nb_particles=150, life=6})
+			sx, sy = actor.x, actor.y
+		end
+		game:playSoundNear(self, "talents/lightning")
+		self:incPsi(-cost)
+		return true
+	end,
+
+	info = function(self, t)
+		local dam = t.getAuraStrength(self, t)
+		local spikedam = 50 + 0.4 * dam * dam
+		local mast = 3 + (self:getTalentLevel(self.T_PROJECTION_MASTERY) or 0) + getGemLevel(self)
+		local spikecost = t.sustain_psi - 2*getGemLevel(self)
+		local nb = 3 + self:getTalentLevelRaw(t)
+		return ([[Fills the air around you with crackling energy, doing %d lightning damage to all who stand nearby. All damage done by the aura will drain one point of energy per %0.2f points of damage dealt.
+		When deactivated, if you have at least %d energy, a massive spike of electrical energy jumps between up to %d nearby targets, doing %d lightning damage to each. Telekinetically wielding a gem instead of a weapon will result in improved spike efficiency.
+		The damage will increase with the Willpower stat.]]):format(dam, mast, spikecost, nb, spikedam)
+	end,
+}
+
+newTalent{
+	name = "Projection Mastery",
+	type = {"psionic/projection", 4},
+	require = psi_wil_req4,
+	points = 5,
+	mode = "passive",
+--	on_learn = function(self, t)
+--		local t_kinaura = self:getTalentFromId(self.T_KINETIC_AURA)
+--		t_kinaura.cooldown = t_kinaura.cooldown - 1
+--
+--		local t_thermaura = self:getTalentFromId(self.T_THERMAL_AURA)
+--		t_thermaura.cooldown = t_thermaura.cooldown - 1
+--
+--		local t_chargeaura = self:getTalentFromId(self.T_CHARGED_AURA)
+--		t_chargeaura.cooldown = t_chargeaura.cooldown - 1
+--	end,
+--	on_unlearn = function(self, t)
+--		local t_kinaura = self:getTalentFromId(self.T_KINETIC_AURA)
+--		t_kinaura.cooldown = t_kinaura.cooldown + 1
+--
+--		local t_thermaura = self:getTalentFromId(self.T_THERMAL_AURA)
+--		t_thermaura.cooldown = t_thermaura.cooldown + 1
+--
+--		local t_chargeaura = self:getTalentFromId(self.T_CHARGED_AURA)
+--		t_chargeaura.cooldown = t_chargeaura.cooldown + 1
+--	end,
+--	getCooldown = function(self, t) return 1 * math.floor(self:getTalentLevelRaw(t)) end,
+--	getMast = function(self, t) return 7*self:getTalentLevel(t) end,
+	info = function(self, t)
+		local cooldown = self:getTalentLevelRaw(t)
+		local mast = (self:getTalentLevel(t) or 0)
+		return ([[Your expertise in the art of energy projection grows.
+		Aura cooldowns are all reduced by %d turns. Aura damage drains energy more slowly (+%0.2f damage required to lose a point of energy).]]):format(cooldown, mast)
+	end,
+}
diff --git a/game/modules/tome/data/talents/psionic/psi-fighting.lua b/game/modules/tome/data/talents/psionic/psi-fighting.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6b47b479ee32d21611c3a14a49d9e9184dfc1f47
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/psi-fighting.lua
@@ -0,0 +1,510 @@
+-- ToME - Tales of Maj'Eyal
+-- 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 cancelAuras(self)
+	local auras = {self.T_CHARGED_AURA, self.T_THERMAL_AURA, self.T_KINETIC_AURA,}
+	for i, t in ipairs(auras) do
+		if self:isTalentActive(t) then
+			self:forceUseTalent(t, {ignore_energy=true})
+		end
+	end
+end
+
+local function TKcombatDamage(weapon, self)
+	weapon = weapon or self.combat or {}
+
+	local totstat = 0
+	-- mswilbonus and mscunbonus replace strength and dexterity bonuses, respectively, when calculating damage. Only applicable to mindslayers.
+	local mswilbonus = self:getStat("wil")
+	local mscunbonus = self:getStat("cun")
+	local dammod = weapon.dammod or {str=0.6}
+	for stat, mod in pairs(dammod) do
+		if stat == "str" then stat = "wil" end
+		if stat == "dex" then stat = "cun" end
+		totstat = totstat + self:getStat(stat) * mod
+	end
+
+	local add = 0
+	if self:knowTalent(Talents.T_ARCANE_DESTRUCTION) then
+		add = add + self:combatSpellpower() * self:getTalentLevel(Talents.T_ARCANE_DESTRUCTION) / 9
+	end
+	if self:isTalentActive(Talents.T_BLOOD_FRENZY) then
+		add = add + self.blood_frenzy
+	end
+
+	local talented_mod = math.sqrt(self:combatCheckTraining(weapon) / 10) + 1
+	local power = math.max(self.combat_dam + (weapon.dam or 1) + add, 1)
+	power = (math.sqrt(power / 10) - 1) * 0.8 + 1
+	print(("[COMBAT DAMAGE] power(%f) totstat(%f) talent_mod(%f)"):format(power, totstat, talented_mod))
+	return totstat / 2 * power * talented_mod
+end
+
+local function TKattackTargetWith(target, weapon, damtype, mult, self)
+	damtype = damtype or weapon.damtype or DamageType.PHYSICAL
+	mult = mult or 1
+
+	-- Does the blow connect? yes .. complex :/
+	local def = target:combatDefense()
+	local msatkbonus = ((self:getWil(50) - 5) + (self:getCun(50) - 5))
+	local atk = self.combat_atk + self:getTalentLevel(Talents.T_WEAPON_COMBAT) * 5 + (weapon.atk or 0) + msatkbonus + (self:getLck() - 50) * 0.4
+	if not self:canSee(target) then atk = atk / 3 end
+	local dam, apr, armor = TKcombatDamage(weapon, self), self:combatAPR(weapon), target:combatArmor()
+	print("[ATTACK] to ", target.name, " :: ", dam, apr, armor, "::", mult)
+
+	-- If hit is over 0 it connects, if it is 0 we still have 50% chance
+	local hitted = false
+	local crit = false
+	local evaded = false
+	if self:checkEvasion(target) then
+		evaded = true
+		game.logSeen(target, "%s evades %s.", target.name:capitalize(), self.name)
+	elseif self:checkHit(atk, def) then
+		print("[ATTACK] raw dam", dam, "versus", armor, "with APR", apr)
+		dam = math.max(0, dam - math.max(0, armor - apr))
+		local damrange = self:combatDamageRange(weapon)
+		dam = rng.range(dam, dam * damrange)
+		print("[ATTACK] after range", dam)
+		dam, crit = self:physicalCrit(dam, weapon, target)
+		print("[ATTACK] after crit", dam)
+		dam = dam * mult
+		print("[ATTACK] after mult", dam)
+		if crit then game.logSeen(self, "%s performs a critical strike!", self.name:capitalize()) end
+		DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam))
+		hitted = true
+	else
+		local srcname = game.level.map.seens(self.x, self.y) and self.name:capitalize() or "Something"
+		game.logSeen(target, "%s misses %s.", srcname, target.name)
+	end
+
+	-- Spread diseases
+	if hitted and self:knowTalent(self.T_CARRIER) and rng.percent(4 * self:getTalentLevelRaw(self.T_CARRIER)) then
+		-- Use epidemic talent spreading
+		local t = self:getTalentFromId(self.T_EPIDEMIC)
+		t.do_spread(self, t, target)
+	end
+
+	-- Melee project
+	if hitted and not target.dead and weapon.melee_project then for typ, dam in pairs(weapon.melee_project) do
+		if dam > 0 then
+			DamageType:get(typ).projector(self, target.x, target.y, typ, dam)
+		end
+	end end
+	if hitted and not target.dead then for typ, dam in pairs(self.melee_project) do
+		if dam > 0 then
+			DamageType:get(typ).projector(self, target.x, target.y, typ, dam)
+		end
+	end end
+
+	-- Weapon of light cast
+	if hitted and not target.dead and self:knowTalent(self.T_WEAPON_OF_LIGHT) and self:isTalentActive(self.T_WEAPON_OF_LIGHT) then
+		local dam = 7 + self:getTalentLevel(self.T_WEAPON_OF_LIGHT) * self:combatSpellpower(0.092)
+		DamageType:get(DamageType.LIGHT).projector(self, target.x, target.y, DamageType.LIGHT, dam)
+		self:incPositive(-3)
+		if self:getPositive() <= 0 then
+			self:forceUseTalent(self.T_WEAPON_OF_LIGHT, {ignore_energy=true})
+		end
+	end
+
+	-- Mindslayer Conduit talent damage added
+	if hitted and not target.dead and self:knowTalent(self.T_CONDUIT) and self:isTalentActive(self.T_CONDUIT) then
+		local t = self:getTalentFromId(self.T_CONDUIT)
+		t.do_combat(self, t, target)
+	end
+
+	-- Shadow cast
+	if hitted and not target.dead and self:knowTalent(self.T_SHADOW_COMBAT) and self:isTalentActive(self.T_SHADOW_COMBAT) and self:getMana() > 0 then
+		local dam = 3 + self:getTalentLevel(self.T_SHADOW_COMBAT) * 2
+		local mana = 1 + self:getTalentLevelRaw(t) / 1.5
+		DamageType:get(DamageType.DARKNESS).projector(self, target.x, target.y, DamageType.DARKNESS, dam)
+		self:incMana(-mana)
+	end
+
+	-- Autospell cast
+	if hitted and not target.dead and self:knowTalent(self.T_ARCANE_COMBAT) and self:isTalentActive(self.T_ARCANE_COMBAT) and rng.percent(20 + self:getTalentLevel(self.T_ARCANE_COMBAT) * (1 + self:getDex(9, true))) then
+		local spells = {}
+		if self:knowTalent(self.T_FLAME) then spells[#spells+1] = self.T_FLAME end
+		if self:knowTalent(self.T_LIGHTNING) then spells[#spells+1] = self.T_LIGHTNING end
+		local tid = rng.table(spells)
+		if tid then
+			print("[ARCANE COMBAT] autocast ",self:getTalentFromId(tid).name)
+			local old_cd = self:isTalentCoolingDown(self:getTalentFromId(tid))
+			self:forceUseTalent(tid, {ignore_energy=true, force_target=target})
+			-- Do not setup a cooldown
+			if not old_cd then
+				self.talents_cd[tid] = nil
+			end
+			self.changed = true
+		end
+	end
+
+	-- On hit talent
+	if hitted and not target.dead and weapon.talent_on_hit and next(weapon.talent_on_hit) then
+		for tid, data in pairs(weapon.talent_on_hit) do
+			if rng.percent(data.chance) then
+				self:forceUseTalent(tid, {ignore_energy=true, force_target=target, force_level=data.level})
+			end
+		end
+	end
+
+	-- Shattering Impact
+	if hitted and self:attr("shattering_impact") then
+		local dam = dam * self.shattering_impact
+		self:project({type="ball", radius=1, friendlyfire=false}, target.x, target.y, DamageType.PHYSICAL, dam)
+		self:incStamina(-15)
+	end
+
+	-- Onslaught
+	if hitted and self:attr("onslaught") then
+		local dir = util.getDir(target.x, target.y, self.x, self.y)
+		local lx, ly = util.coordAddDir(self.x, self.y, dir_sides[dir].left)
+		local rx, ry = util.coordAddDir(self.x, self.y, dir_sides[dir].right)
+		local lt, rt = game.level.map(lx, ly, Map.ACTOR), game.level.map(rx, ry, Map.ACTOR)
+
+		if target:checkHit(self:combatAttack(weapon), target:combatPhysicalResist(), 0, 95, 10) and target:canBe("knockback") then
+			target:knockback(self.x, self.y, self:attr("onslaught"))
+		end
+		if lt and lt:checkHit(self:combatAttack(weapon), lt:combatPhysicalResist(), 0, 95, 10) and lt:canBe("knockback") then
+			lt:knockback(self.x, self.y, self:attr("onslaught"))
+		end
+		if rt and rt:checkHit(self:combatAttack(weapon), rt:combatPhysicalResist(), 0, 95, 10) and r+t:canBe("knockback") then
+			rt:knockback(self.x, self.y, self:attr("onslaught"))
+		end
+	end
+
+	-- Reactive target on hit damage
+	if hitted then for typ, dam in pairs(target.on_melee_hit) do
+		if dam > 0 then
+			DamageType:get(typ).projector(target, self.x, self.y, typ, dam)
+		end
+	end end
+
+	-- Acid splash
+	if hitted and target:knowTalent(target.T_ACID_BLOOD) then
+		local t = target:getTalentFromId(target.T_ACID_BLOOD)
+		t.do_splash(target, t, self)
+	end
+
+	-- Bloodbath
+	if hitted and crit and self:knowTalent(self.T_BLOODBATH) then
+		local t = self:getTalentFromId(self.T_BLOODBATH)
+		t.do_bloodbath(self, t)
+	end
+
+	-- Mortal Terror
+	if hitted and not target.dead and self:knowTalent(self.T_MORTAL_TERROR) then
+		local t = self:getTalentFromId(self.T_MORTAL_TERROR)
+		t.do_terror(self, t, target, dam)
+	end
+
+	-- Special effect
+	if hitted and not target.dead and weapon.special_on_hit and weapon.special_on_hit.fct then
+		weapon.special_on_hit.fct(weapon, self, target)
+	end
+
+	-- Regen on being hit
+	if hitted and not target.dead and target:attr("stamina_regen_on_hit") then target:incStamina(target.stamina_regen_on_hit) end
+	if hitted and not target.dead and target:attr("mana_regen_on_hit") then target:incMana(target.mana_regen_on_hit) end
+	if hitted and not target.dead and target:attr("equilibrium_regen_on_hit") then target:incEquilibrium(-target.equilibrium_regen_on_hit) end
+
+	-- Riposte!
+	if not hitted and not target.dead and not evaded and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:knowTalent(target.T_RIPOSTE) and rng.percent(target:getTalentLevel(target.T_RIPOSTE) * (5 + target:getDex(5))) then
+		game.logSeen(self, "%s ripostes!", target.name:capitalize())
+		target:attackTarget(self, nil, nil, true)
+	end
+
+	return self:combatSpeed(weapon), hitted
+end
+
+
+
+newTalent{
+	name = "Telekinetic Smash",
+	type = {"psionic/psi-fighting", 1},
+	require = psi_wil_req1,
+	points = 5,
+	random_ego = "attack",
+	cooldown = 10,
+	psi = 10,
+	range = 1,
+	action = function(self, t)
+
+		local tkweapon = self:getInven("MAINHAND")[1]
+		if type(tkweapon) == "boolean" then tkweapon = nil end
+		if not tkweapon then
+			game.logPlayer(self, "You cannot do that without a weapon in your hands.")
+			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
+		self:attackTargetWith(target, tkweapon.combat, nil, self:combatTalentWeaponDamage(t, 1.5, 3))
+		return true
+	end,
+	info = function(self, t)
+		return ([[Gather your will and brutally smash the target with your mainhand weapon, doing %d%% weapon damage.]]):
+		format(100 * self:combatTalentWeaponDamage(t, 1.5, 3))
+	end,
+}
+
+newTalent{
+	name = "Augmentation",
+	type = {"psionic/psi-fighting", 2},
+	require = psi_wil_req2,
+	points = 5,
+	mode = "sustained",
+	cooldown = 0,
+	sustain_psi = 10,
+	activate = function(self, t)
+		local str_power = math.floor(0.1*self:getTalentLevel(t)*self:getWil())
+		local dex_power = math.floor(0.1*self:getTalentLevel(t)*self:getCun())
+		return {
+			stats = self:addTemporaryValue("inc_stats", {
+				[self.STAT_STR] = str_power,
+				[self.STAT_DEX] = dex_power,
+			}),
+		}
+	end,
+	deactivate = function(self, t, p)
+		self:removeTemporaryValue("inc_stats", p.stats)
+		return true
+	end,
+	info = function(self, t)
+		local inc = 10*self:getTalentLevel(t)
+		local str_power = math.floor(0.1*self:getTalentLevel(t)*self:getWil())
+		local dex_power = math.floor(0.1*self:getTalentLevel(t)*self:getCun())
+		return ([[While active, you give your flesh and blood body a little aid in the form of precisely applied mental forces. Increases Strength and Dexterity by %d%% of your Willpower and Cunning, respectively.
+		Strength increased by %d
+		Dexterity increased by %d]]):
+		format(inc, str_power, dex_power)
+	end,
+}
+
+newTalent{
+	name = "Conduit",
+	type = {"psionic/psi-fighting", 3},
+	require = psi_wil_req3, no_sustain_autoreset = true,
+	cooldown = 1,
+	mode = "sustained",
+	sustain_psi = 0,
+	points = 5,
+	
+
+	activate = function(self, t)
+		local ret = {
+		k_aura_on = self:isTalentActive(self.T_KINETIC_AURA),
+		t_aura_on = self:isTalentActive(self.T_THERMAL_AURA),
+		c_aura_on = self:isTalentActive(self.T_CHARGED_AURA),	
+		}
+		local cur_psi = self:getPsi()
+		self:incPsi(-5000)
+		--self.sustain_talents[t.id] = {}
+		cancelAuras(self)
+		self:incPsi(cur_psi)
+		return ret
+	end,
+
+	do_combat = function(self, t, target)
+		local mult = 1 + 0.1*(self:getTalentLevel(t))
+		local auras = self:isTalentActive(t.id)
+		if auras.k_aura_on then
+			local k_aura = self:getTalentFromId(self.T_KINETIC_AURA)
+			local k_dam = mult * k_aura.getAuraStrength(self, k_aura)
+			DamageType:get(DamageType.PHYSICAL).projector(self, target.x, target.y, DamageType.PHYSICAL, k_dam)
+		end
+		if auras.t_aura_on then
+			local t_aura = self:getTalentFromId(self.T_THERMAL_AURA)
+			local t_dam = mult * t_aura.getAuraStrength(self, t_aura)
+			DamageType:get(DamageType.FIRE).projector(self, target.x, target.y, DamageType.FIRE, t_dam)
+		end
+		if auras.c_aura_on then
+			local c_aura = self:getTalentFromId(self.T_CHARGED_AURA)
+			local c_dam = mult * c_aura.getAuraStrength(self, c_aura)
+			DamageType:get(DamageType.LIGHTNING).projector(self, target.x, target.y, DamageType.LIGHTNING, c_dam)
+		end
+	end,
+
+	deactivate = function(self, t)
+		return true
+	end,
+	info = function(self, t)
+		local mult = 1 + 0.1*(self:getTalentLevel(t))
+		return ([[When activated, turns off any active auras and uses your telekinetically-wielded weapon as a conduit for the energies that were being channeled through those auras.
+		Any auras used by Conduit will not start to cool down until Conduit has been deactivated. The damage from each aura applied by Conduit is multiplied by %0.2f, and does not drain energy.]]):
+		format(mult)
+	end,
+}
+
+newTalent{
+	name = "Frenzied Psifighting",
+	type = {"psionic/psi-fighting", 4},
+	require = psi_wil_req4,
+	cooldown = 20,
+	psi = 30,
+	points = 5,
+	action = function(self, t)
+		local targets = 1 + math.ceil(self:getTalentLevel(t)/5)
+		self:setEffect(self.EFF_PSIFRENZY, 3 * self:getTalentLevelRaw(t), {power=targets})
+		return true
+	end,
+	--getTargNum = function(self, t) return 1 + math.ceil(self:getTalentLevel(t)/5) end,
+	info = function(self, t)
+		local targets = 1 + math.ceil(self:getTalentLevel(t)/5)
+		local dur = 3 * self:getTalentLevelRaw(t)
+		return ([[Your telekinetically wielded weapon enters a frenzy for %d turns, striking up to %d targets every turn.]]):
+		format(dur, targets)
+	end,
+}
+
+newTalent{
+	name = "Telekinetic Grasp",
+	type = {"psionic/other", 1},
+	points = 1,
+	cooldown = 0,
+	psi = 0,
+	type_no_req = true,
+	no_npc_use = true,
+	action = function(self, t)
+		local inven = self:getInven("INVEN")
+		self:showInventory("Telekinetically grasp which item?", inven, function(o)
+			return (o.type == "weapon" or o.type == "gem") and o.subtype ~= "longbow" and o.subtype ~= "sling" and o.material_level
+		end, function(o, item)
+			o = self:removeObject(inven, item)
+			local pf = self:getInven("PSIONIC_FOCUS")
+			-- Remove old one
+			local old = self:removeObject(pf, 1, true)
+			
+			-- Force "wield"
+			self:addObject(pf, o)
+			game.logSeen(self, "%s wears: %s.", self.name:capitalize(), o:getName{do_color=true})
+			
+			-- Put back the old one in inventory
+			if old then self:addObject(inven, old) end
+			self:sortInven()
+		end)
+		end,
+	info = function(self, t)
+		return ([[Encase a weapon or gem in mentally-controlled forces, holding it aloft and bringing it to bear with the power of your mind alone.]])
+	end,
+}
+
+newTalent{
+	name = "Beyond the Flesh",
+	type = {"psionic/other", 1},
+	points = 1,
+	mode = "sustained",
+	cooldown = 0,
+	sustain_psi = 0,
+	range = 1,
+	direct_hit = true,
+	btf_damage= function(self)
+		local o = self:getInven("PSIONIC_FOCUS")[1]
+		if o.type == "weapon" then
+			return TKcombatDamage(o, self)
+		else
+			return 0
+		end
+	end,
+	do_tkautoattack = function(self, t)
+		local targnum = 1
+		if self:hasEffect(self.EFF_PSIFRENZY) then targnum = 1 + math.ceil(0.2*self:getTalentLevel(self.T_FRENZIED_PSIFIGHTING)) end
+		local speed, hit = nil, false
+		local sound, sound_miss = nil, nil
+		--dam = self:getTalentLevel(t)
+		local tgts = {}
+		local grids = core.fov.circle_grids(self.x, self.y, 1, true)
+		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
+			local a = game.level.map(x, y, Map.ACTOR)
+			if a and self:reactionToward(a) < 0 then
+				tgts[#tgts+1] = a
+			end
+		end end
+
+		-- Randomly pick a target
+		local tg = {type="hit", range=1, talent=t}
+		for i = 1, targnum do
+			if #tgts <= 0 then break end
+			local a, id = rng.table(tgts)
+			table.remove(tgts, id)
+			
+			if self:getInven(self.INVEN_PSIONIC_FOCUS) then
+				for i, o in ipairs(self:getInven(self.INVEN_PSIONIC_FOCUS)) do
+					if o.combat and not o.archery then
+						print("[ATTACK] attacking with", o.name)
+						--local s, h = self:TKattackTargetWith(a, o.combat, nil, self:combatTalentWeaponDamage(t, 0.7, 1.7))
+						local s, h = TKattackTargetWith(a, o.combat, nil, 1, self)
+						speed = math.max(speed or 0, s)
+						hit = hit or h
+						if hit and not sound then sound = o.combat.sound
+						elseif not hit and not sound_miss then sound_miss = o.combat.sound_miss end
+						if not o.combat.no_stealth_break then break_stealth = true end
+					end
+				end
+			else
+				return nil
+			end
+
+		end
+		return hit
+	end,	
+	activate = function (self, t)
+		local tkweapon = self:getInven("PSIONIC_FOCUS")[1]
+		if type(tkweapon) == "boolean" then tkweapon = nil end
+		if not tkweapon or tkweapon.type == "gem" then
+			game.logPlayer(self, "You cannot do that without a telekinetically-wielded weapon.")
+			return nil
+		end
+		return true
+	end,
+	deactivate =  function (self, t)
+		return true
+	end,
+	info = function(self, t)
+		local atk = 0
+		local dam = 0
+		local apr = 0
+		local crit = 0
+		local speed = 1
+		local o = self:getInven("PSIONIC_FOCUS")[1]
+		if type(o) == "boolean" then o = nil end
+		if not o then
+			return ([[Allows you to wield a weapon telekinetically, directing it with your willpower and cunning rather than crude flesh. When activated, the telekinetically-wielded weapon will attack a random melee-range target each turn.
+			The telekinetically-wielded weapon uses Willpower in place of Strength and Cunning in place of Dexterity to determine attack and damage.
+			You are not telekinetically wielding anything right now.]])
+		end
+		if o.type == "weapon" then
+			local msatkbonus = ((self:getWil(50) - 5) + (self:getCun(50) - 5))
+			atk = self.combat_atk + self:getTalentLevel(Talents.T_WEAPON_COMBAT) * 5 + (o.atk or 0) + msatkbonus + (self:getLck() - 50) * 0.4 
+			dam = TKcombatDamage(o.combat, self)
+			apr = self:combatAPR(o.combat)
+			crit = self:combatCrit(o.combat)
+			speed = self:combatSpeed(o.combat)
+		end
+		return ([[Allows you to wield a weapon telekinetically, directing it with your willpower and cunning rather than crude flesh. When activated, the telekinetically-wielded weapon will attack a random melee-range target each turn.
+		The telekinetically-wielded weapon uses Willpower in place of Strength and Cunning in place of Dexterity to determine attack and damage.
+		Combat stats:
+		Attack %d 
+		Damage: %d
+		APR: %d
+		Crit: %0.2f
+		Speed: %0.2f]]):
+		format(atk, dam, apr, crit, speed)
+	end,
+}
+
diff --git a/game/modules/tome/data/talents/psionic/psionic.lua b/game/modules/tome/data/talents/psionic/psionic.lua
new file mode 100644
index 0000000000000000000000000000000000000000..d8bce8d30a13dadceb664b512966e5a48e18e191
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/psionic.lua
@@ -0,0 +1,126 @@
+-- ToME - Tales of Maj'Eyal
+-- 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
+
+-- Talent trees
+newTalentType{ allow_random=true, type="psionic/absorption", name = "absorption", description = "Absorb damage and gain energy." }
+newTalentType{ allow_random=true, type="psionic/projection", name = "projection", description = "Project energy to damage foes." }
+newTalentType{ allow_random=true, type="psionic/psi-fighting", name = "psi-fighting", description = "Wield melee weapons with mentally-manipulated forces." }
+newTalentType{ allow_random=true, type="psionic/focus", name = "focus", description = "Use gems to focus your energies." }
+newTalentType{ allow_random=true, type="psionic/augmented-mobility", name = "augmented mobility", description = "Use energy to move yourself and others." }
+newTalentType{ allow_random=true, type="psionic/voracity", generic = true, name = "voracity", description = "Pull energy from your surroundings." }
+newTalentType{ allow_random=true, type="psionic/finer-energy-manipulations", generic = true, name = "finer energy manipulations", description = "Subtle applications of the psionic arts." }
+newTalentType{ allow_random=true, type="psionic/mental-discipline", generic = true, name = "mental discipline", description = "Increase mental capacity, endurance, and flexibility." }
+newTalentType{ no_silence=true, type="psionic/other", name = "other", description = "Various psionic talents." }
+
+-- Generic requires for psionic talents based on talent level
+psi_absorb = {
+	stat = { wil=function(level) return 12 + (level-1) * 8 end },
+	level = function(level) return 0 + 5*(level-1)  end,
+}
+psi_wil_req1 = {
+	stat = { wil=function(level) return 12 + (level-1) * 2 end },
+	level = function(level) return 0 + (level-1)  end,
+}
+psi_wil_req2 = {
+	stat = { wil=function(level) return 20 + (level-1) * 2 end },
+	level = function(level) return 4 + (level-1)  end,
+}
+psi_wil_req3 = {
+	stat = { wil=function(level) return 28 + (level-1) * 2 end },
+	level = function(level) return 8 + (level-1)  end,
+}
+psi_wil_req4 = {
+	stat = { wil=function(level) return 36 + (level-1) * 2 end },
+	level = function(level) return 12 + (level-1)  end,
+}
+psi_wil_req5 = {
+	stat = { wil=function(level) return 44 + (level-1) * 2 end },
+	level = function(level) return 16 + (level-1)  end,
+}
+psi_wil_high1 = {
+	stat = { wil=function(level) return 22 + (level-1) * 2 end },
+	level = function(level) return 10 + (level-1)  end,
+}
+psi_wil_high2 = {
+	stat = { wil=function(level) return 30 + (level-1) * 2 end },
+	level = function(level) return 14 + (level-1)  end,
+}
+psi_wil_high3 = {
+	stat = { wil=function(level) return 38 + (level-1) * 2 end },
+	level = function(level) return 18 + (level-1)  end,
+}
+psi_wil_high4 = {
+	stat = { wil=function(level) return 46 + (level-1) * 2 end },
+	level = function(level) return 22 + (level-1)  end,
+}
+psi_wil_high5 = {
+	stat = { wil=function(level) return 54 + (level-1) * 2 end },
+	level = function(level) return 26 + (level-1)  end,
+}
+-- For cunning trees
+psi_cun_req1 = {
+	stat = { cun=function(level) return 12 + (level-1) * 2 end },
+	level = function(level) return 0 + (level-1)  end,
+}
+psi_cun_req2 = {
+	stat = { cun=function(level) return 20 + (level-1) * 2 end },
+	level = function(level) return 4 + (level-1)  end,
+}
+psi_cun_req3 = {
+	stat = { cun=function(level) return 28 + (level-1) * 2 end },
+	level = function(level) return 8 + (level-1)  end,
+}
+psi_cun_req4 = {
+	stat = { cun=function(level) return 36 + (level-1) * 2 end },
+	level = function(level) return 12 + (level-1)  end,
+}
+psi_cun_req5 = {
+	stat = { cun=function(level) return 44 + (level-1) * 2 end },
+	level = function(level) return 16 + (level-1)  end,
+}
+psi_cun_high1 = {
+	stat = { cun=function(level) return 22 + (level-1) * 2 end },
+	level = function(level) return 10 + (level-1)  end,
+}
+psi_cun_high2 = {
+	stat = { cun=function(level) return 30 + (level-1) * 2 end },
+	level = function(level) return 14 + (level-1)  end,
+}
+psi_cun_high3 = {
+	stat = { cun=function(level) return 38 + (level-1) * 2 end },
+	level = function(level) return 18 + (level-1)  end,
+}
+psi_cun_high4 = {
+	stat = { cun=function(level) return 46 + (level-1) * 2 end },
+	level = function(level) return 22 + (level-1)  end,
+}
+psi_cun_high5 = {
+	stat = { cun=function(level) return 54 + (level-1) * 2 end },
+	level = function(level) return 26 + (level-1)  end,
+}
+
+
+load("/data/talents/psionic/absorption.lua")
+load("/data/talents/psionic/finer-energy-manipulations.lua")
+load("/data/talents/psionic/mental-discipline.lua")
+load("/data/talents/psionic/projection.lua")
+load("/data/talents/psionic/psi-fighting.lua")
+load("/data/talents/psionic/voracity.lua")
+load("/data/talents/psionic/augmented-mobility.lua")
+load("/data/talents/psionic/focus.lua")
diff --git a/game/modules/tome/data/talents/psionic/voracity.lua b/game/modules/tome/data/talents/psionic/voracity.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6c1ca429fb8085a6c5abd3245541d0973786f839
--- /dev/null
+++ b/game/modules/tome/data/talents/psionic/voracity.lua
@@ -0,0 +1,202 @@
+-- ToME - Tales of Maj'Eyal
+-- 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 getGemLevel(self)
+		local gem_level = 0
+		if not self:getInven("PSIONIC_FOCUS")[1] then return gem_level end
+		local tk_item = self:getInven("PSIONIC_FOCUS")[1]
+		if tk_item.type == "gem" then 
+			gem_level = tk_item.material_level
+		else
+			gem_level = 0
+		end
+		return gem_level
+end
+
+newTalent{
+	name = "Kinetic Leech",
+	type = {"psionic/voracity", 1},
+	require = psi_wil_req1,
+	points = 5,
+	psi = 0,
+	cooldown = 40,
+	tactical = {
+		ATTACKAREA = 10,
+		DEFEND = 4,
+	},
+	direct_hit = true,
+	range = function(self, t)
+		local r = 2
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.ceil(r*mult)
+	end,
+	action = function(self, t)
+		local tgts = {}
+		local grids = core.fov.circle_grids(self.x, self.y, self:getTalentRange(t), true)
+		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
+			local a = game.level.map(x, y, Map.ACTOR)
+			if a and self:reactionToward(a) < 0 then
+				tgts[#tgts+1] = a
+			end
+		end end
+		local en = ( 3 + self:getTalentLevel(t)) * (100 + self:getWil())/100
+		self:incPsi(en*#tgts)
+		local tg = {type="ball", range=0, radius=self:getTalentRange(t), friendlyfire=false, talent=t}
+		local dam = .1 + 0.03*self:getTalentLevel(t)
+		self:project(tg, self.x, self.y, DamageType.MINDSLOW, dam)
+		local x, y = self.x, self.y
+		return true
+	end,
+	info = function(self, t)
+		local range = self:getTalentRange(t)
+		local slow = 3 * self:getTalentLevel(t) + 10
+		local en = ( 3 + self:getTalentLevel(t)) * (100 + self:getWil())/100
+		return ([[You suck the kinetic energy out of your surroundings, slowing all enemies in a radius of %d by %d%%.
+		For each enemy drained, you gain %d energy.
+		The effect scales with Willpower.]]):format(range, slow, en)
+	end,
+}
+
+newTalent{
+	name = "Thermal Leech",
+	type = {"psionic/voracity", 2},
+	require = psi_wil_req2,
+	points = 5,
+	cooldown = 50,
+	psi = 0,
+	tactical = {
+		ATTACKAREA = 10,
+		DEFEND = 4,
+	},
+	range = function(self, t)
+		local r = 1
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.ceil(r*mult)
+	end,
+	action = function(self, t)
+		local tgts = {}
+		local grids = core.fov.circle_grids(self.x, self.y, self:getTalentRange(t), true)
+		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
+			local a = game.level.map(x, y, Map.ACTOR)
+			if a and self:reactionToward(a) < 0 then
+				tgts[#tgts+1] = a
+			end
+		end end
+		local en = ( 4 + self:getTalentLevel(t)) * (100 + self:getWil())/85
+		self:incPsi(en*#tgts)
+		local duration = self:getTalentLevel(t) + 2
+		local radius = self:getTalentRange(t)
+		local dam = 1 + 0.3*self:getTalentLevel(t)
+		local tg = {type="ball", range=0, radius=radius, friendlyfire=false}
+		self:project(tg, self.x, self.y, DamageType.FREEZE, dam)
+		-- Add a lasting map effect
+		--game.level.map:addEffect(self,
+		--	self.x, self.y, duration,
+		--	DamageType.FREEZE, dam,
+		--	radius,
+		--	5, nil,
+		--	{type="freezequake"},
+		--	nil, false
+		--)
+		return true
+	end,
+	info = function(self, t)
+		local range = self:getTalentRange(t)
+		local en = ( 4 + self:getTalentLevel(t)) * (100 + self:getWil())/85
+		--local duration = self:getTalentLevel(t) + 2
+		return ([[You leech the heat out of all foes in a radius of %d, gaining %d energy for each in range. 
+		The effect scales with Willpower.]]):
+		format(range, en)
+	end,
+}
+
+newTalent{
+	name = "Charge Leech",
+	type = {"psionic/voracity", 3},
+	require = psi_wil_req3,
+	points = 5,
+	psi = 0,
+	cooldown = 60,
+	tactical = {
+		ATTACKAREA = 10,
+		DEFEND = 4,
+	},
+	direct_hit = true,
+	range = function(self, t)
+		local r = 2
+		local gem_level = getGemLevel(self)
+		local mult = (1 + 0.02*gem_level*(self:getTalentLevel(self.T_REACH)))
+		return math.ceil(r*mult)
+	end,
+	action = function(self, t)
+		local tgts = {}
+		local grids = core.fov.circle_grids(self.x, self.y, self:getTalentRange(t), true)
+		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
+			local a = game.level.map(x, y, Map.ACTOR)
+			if a and self:reactionToward(a) < 0 then
+				tgts[#tgts+1] = a
+			end
+		end end
+		local en = ( 5 + self:getTalentLevel(t)) * (100 + self:getWil())/75
+		self:incPsi(en*#tgts)
+		local tg = {type="ball", range=0, radius=self:getTalentRange(t), friendlyfire=false, talent=t}
+		local dam = self:spellCrit(self:combatTalentMindDamage(t, 28, 170))
+		self:project(tg, self.x, self.y, DamageType.LIGHTNING_DAZE, rng.avg(dam / 3, dam, 3))
+		local x, y = self.x, self.y
+		-- Lightning ball gets a special treatment to make it look neat
+		local sradius = (tg.radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
+		local nb_forks = 16
+		local angle_diff = 360 / nb_forks
+		for i = 0, nb_forks - 1 do
+			local a = math.rad(rng.range(0+i*angle_diff,angle_diff+i*angle_diff))
+			local tx = x + math.floor(math.cos(a) * tg.radius)
+			local ty = y + math.floor(math.sin(a) * tg.radius)
+			game.level.map:particleEmitter(x, y, tg.radius, "lightning", {radius=tg.radius, grids=grids, tx=tx-x, ty=ty-y, nb_particles=25, life=8})
+		end
+
+		game:playSoundNear(self, "talents/lightning")
+		return true
+	end,
+	info = function(self, t)
+		local range = self:getTalentRange(t)
+		local en = ( 5 + self:getTalentLevel(t)) * (100 + self:getWil())/75
+		local dam = damDesc(self, DamageType.LIGHTNING, self:combatTalentMindDamage(t, 28, 170))
+		return ([[You pull electric potential from the foes around you in a radius of %d, gaining %d energy for each one affected and giving them a nasty shock in the process. Deals between %d and %d damage.
+		The effect scales with Willpower.]]):format(range, en, dam / 3, dam)
+	end,
+}
+newTalent{
+	name = "Insatiable",
+	type = {"psionic/voracity", 4},
+	mode = "passive",
+	points = 5,
+	require = psi_wil_req4,
+	on_learn = function(self, t)
+		self.max_psi = self.max_psi + 10
+	end,
+	on_unlearn = function(self, t)
+		self.max_psi = self.max_psi - 10
+	end,
+	info = function(self, t)
+		return ([[Increases your maximum Energy by %d]]):format(10 * self:getTalentLevelRaw(t))
+	end,
+}
+
diff --git a/game/modules/tome/data/timed_effects.lua b/game/modules/tome/data/timed_effects.lua
index f9ff765cd6adbe025691f1e8827bf2a44998c349..f3028579e13c6dcfdcff4e07eb24c0e263ddb32e 100644
--- a/game/modules/tome/data/timed_effects.lua
+++ b/game/modules/tome/data/timed_effects.lua
@@ -2667,3 +2667,104 @@ newEffect{
 	end,
 }
 
+newEffect{
+	name = "QUICKNESS",
+	desc = "Quick",
+	long_desc = function(self, eff) return ("Increases run speed by %d%%."):format((1 / (1 - eff.power) - 1) * 100) end,
+	type = "mental",
+	status = "beneficial",
+	parameters = { power=0.1 },
+	on_gain = function(self, err) return "#Target# speeds up.", "+Quick" end,
+	on_lose = function(self, err) return "#Target# slows down.", "-Quick" end,
+	activate = function(self, eff)
+		--eff.tmpid = self:addTemporaryValue("movement_speed", {mod=-eff.power})
+		self.movement_speed = (self.movement_speed or 0) - eff.power
+	end,
+	deactivate = function(self, eff)
+		--self:removeTemporaryValue("movement_speed", eff.tmpid)
+		self.movement_speed = self.movement_speed + eff.power
+	end,
+}
+newEffect{
+	name = "PSIFRENZY",
+	desc = "Frenzied Psi-fighting",
+	long_desc = function(self, eff) return ("Causes telekinetically-wielded weapons to hit up to %d targets each turn."):format(eff.power) end,
+	type = "mental",
+	status = "beneficial",
+	parameters = {dam=10},
+	on_gain = function(self, err) return "#Target# enters a frenzy!", "+Frenzy" end,
+	on_lose = function(self, err) return "#Target# is no longer frenzied.", "-Frenzy" end,
+}
+
+newEffect{
+	name = "KINSPIKE_SHIELD",
+	desc = "Spiked Kinetic Shield",
+	long_desc = function(self, eff) return ("The target erects a powerful kinetic shield capable of absorbing %d physical or acid damage before it crumbles."):format(eff.power) end,
+	type = "magical",
+	status = "beneficial",
+	parameters = { power=100 },
+	on_gain = function(self, err) return "A powerful kinetic shield forms around #target#.", "+Shield" end,
+	on_lose = function(self, err) return "The powerful kinetic shield around #target# crumbles.", "-Shield" end,
+	activate = function(self, eff)
+		eff.tmpid = self:addTemporaryValue("kinspike_shield", eff.power)
+		self.kinspike_shield_absorb = eff.power
+	end,
+	deactivate = function(self, eff)
+		self:removeTemporaryValue("kinspike_shield", eff.tmpid)
+		self.kinspike_shield_absorb = nil
+	end,
+}
+newEffect{
+	name = "THERMSPIKE_SHIELD",
+	desc = "Spiked Thermal Shield",
+	long_desc = function(self, eff) return ("The target erects a powerful thermal shield capable of absorbing %d thermal damage before it crumbles."):format(eff.power) end,
+	type = "magical",
+	status = "beneficial",
+	parameters = { power=100 },
+	on_gain = function(self, err) return "A powerful thermal shield forms around #target#.", "+Shield" end,
+	on_lose = function(self, err) return "The powerful thermal shield around #target# crumbles.", "-Shield" end,
+	activate = function(self, eff)
+		eff.tmpid = self:addTemporaryValue("thermspike_shield", eff.power)
+		self.thermspike_shield_absorb = eff.power
+	end,
+	deactivate = function(self, eff)
+		self:removeTemporaryValue("thermspike_shield", eff.tmpid)
+		self.thermspike_shield_absorb = nil
+	end,
+}
+newEffect{
+	name = "CHARGESPIKE_SHIELD",
+	desc = "Spiked Charged Shield",
+	long_desc = function(self, eff) return ("The target erects a powerful charged shield capable of absorbing %d lightning or blight damage before it crumbles."):format(eff.power) end,
+	type = "magical",
+	status = "beneficial",
+	parameters = { power=100 },
+	on_gain = function(self, err) return "A powerful charged shield forms around #target#.", "+Shield" end,
+	on_lose = function(self, err) return "The powerful charged shield around #target# crumbles.", "-Shield" end,
+	activate = function(self, eff)
+		eff.tmpid = self:addTemporaryValue("chargespike_shield", eff.power)
+		self.chargespike_shield_absorb = eff.power
+	end,
+	deactivate = function(self, eff)
+		self:removeTemporaryValue("chargespike_shield", eff.tmpid)
+		self.chargespike_shield_absorb = nil
+	end,
+}
+
+newEffect{
+	name = "CONTROL",
+	desc = "Perfect control",
+	long_desc = function(self, eff) return ("The target's combat attack and crit chance are improved by %d and %d%%, respectively."):format(eff.power, 0.3*eff.power) end,
+	type = "physical",
+	status = "beneficial",
+	parameters = { power=10 },
+	activate = function(self, eff)
+		eff.attack = self:addTemporaryValue("combat_atk", eff.power)
+		eff.crit = self:addTemporaryValue("combat_physcrit", 0.3*eff.power)
+	end,
+	deactivate = function(self, eff)
+		self:removeTemporaryValue("combat_atk", eff.attack)
+		self:removeTemporaryValue("combat_physcrit", eff.crit)
+	end,
+}
+
diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua
index 82169e54cbb7e2c36ed0a9f8729c5d7d0c390250..20feaf71ec241172a0469ee59c420842cf6962e0 100644
--- a/game/modules/tome/load.lua
+++ b/game/modules/tome/load.lua
@@ -68,6 +68,7 @@ dofile("/mod/resolvers.lua")
 -- Body parts
 ActorInventory:defineInventory("MAINHAND", "In main hand", true, "Most weapons are wielded in the main hand.")
 ActorInventory:defineInventory("OFFHAND", "In off hand", true, "You can use shields or a second weapon in your off-hand, if you have the talents for it.")
+ActorInventory:defineInventory("PSIONIC_FOCUS", "Psionic Focus", true, "Object help in your telekinetic grasp. It can be a weapon or some other item to provide a benefit to your psionic powers.")
 ActorInventory:defineInventory("FINGER", "On fingers", true, "Rings are worn on fingers.")
 ActorInventory:defineInventory("NECK", "Around neck", true, "Amulets are worn around the neck.")
 ActorInventory:defineInventory("LITE", "Light source", true, "A light source allows you to see in the dark places of the world.")
@@ -103,8 +104,9 @@ ActorResource:defineResource("Positive", "positive", ActorTalents.T_POSITIVE_POO
 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.")
 ActorResource:defineResource("Paradox", "paradox", ActorTalents.T_PARADOX_POOL, "paradox_regen", "Paradox represents how much damage you've done to the space-time continuum. A high Paradox score makes Chronomancy less reliable and more dangerous to use but also amplifies the effects.")
-
+ActorResource:defineResource("Psi", "psi", ActorTalents.T_PSI_POOL, "psi_regen", "Psi represents the power available to your mind.")
 -- 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.")
 ActorStats:defineStat("Dexterity",	"dex", 10, 1, 100, "Dexterity defines your character's ability to be agile and alert. It increases your chance to hit, your ability to avoid attacks, and your damage with light or ranged weapons.")
 ActorStats:defineStat("Magic",		"mag", 10, 1, 100, "Magic defines your character's ability to manipulate the magical energy of the world. It increases your spell power, and the effect of spells and other magic items.")