diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index e4ac4aedbab044cd7fc19436019db011965d22f9..045a882b7b464576ce2a27ded08d7a63be1a41b3 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -935,7 +935,13 @@ function _M:move(x, y, force)
 			local eff = self:hasEffect(self.EFF_CURSE_OF_SHROUDS)
 			if eff then eff.moved = true end
 
-			self:useEnergy(game.energy_to_act * self:combatMovementSpeed(x, y))
+			local speed = self:combatMovementSpeed(x, y)
+			self:useEnergy(game.energy_to_act * speed)
+
+			if speed <= 0.125 and self:knowTalent(self.T_FAST_AS_LIGHTNING) then
+				local t = self:getTalentFromId(self.T_FAST_AS_LIGHTNING)
+				t.trigger(self, t, ox, oy)
+			end
 		end
 	end
 	self.did_energy = nil
@@ -1987,6 +1993,11 @@ function _M:onTakeHit(value, src)
 		game.logSeen(src, "#CRIMSON#%s leeches energies from its victim!", src.name:capitalize())
 	end
 
+	if self:knowTalent(self.T_DRACONIC_BODY) then
+		local t = self:getTalentFromId(self.T_DRACONIC_BODY)
+		t.trigger(self, t, value)
+	end
+
 	return value
 end
 
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index eeda549aafe923a8e04cc311c346943d8742555a..292199f3c349345e3a7d1da60a775bad16db95cd 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -1234,11 +1234,12 @@ function _M:setupCommands()
 			end end
 		end end,
 		[{"_g","ctrl"}] = function() if config.settings.cheat then
+			self.player:learnTalentType("uber/uber", true)
+do return end
 			local f, err = loadfile("/data/general/events/slimey-pool.lua")
 			print(f, err)
 			setfenv(f, setmetatable({level=self.level, zone=self.zone}, {__index=_G}))
 			print(pcall(f))
-do return end
 			self:registerDialog(require("mod.dialogs.DownloadCharball").new())
 		end end,
 		[{"_f","ctrl"}] = function() if config.settings.cheat then
diff --git a/game/modules/tome/data/birth/classes/wilder.lua b/game/modules/tome/data/birth/classes/wilder.lua
index 1018e651d4bfc3ee62b1c08beff63fa2d778b7a6..8ee33c3a3bb4a6a25a636ad6a402ee6fdccd723e 100644
--- a/game/modules/tome/data/birth/classes/wilder.lua
+++ b/game/modules/tome/data/birth/classes/wilder.lua
@@ -138,6 +138,7 @@ newBirthDescriptor{
 		[ActorTalents.T_WEAPON_COMBAT] = 1,
 	},
 	copy = {
+		drake_touched = 2,
 		max_life = 110,
 		resolvers.equip{ id=true,
 			{type="weapon", subtype="battleaxe", name="iron battleaxe", autoreq=true, ego_chance=-1000},
diff --git a/game/modules/tome/data/gfx/particles/megaspeed.lua b/game/modules/tome/data/gfx/particles/megaspeed.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ae7a3850194a7f9cd00c936cb5ebfe254589e615
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/megaspeed.lua
@@ -0,0 +1,48 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+base_size = 64
+
+return { generator = function()
+	local pos = rng.range(-32, 32)
+	local power = 32 - math.abs(pos)
+	local life = power / 2
+	local size = rng.range(2, 5)
+	local angle = math.rad(angle)
+
+	return {
+		trail = 1,
+		life = life * 6,
+		size = size, sizev = -0.02, sizea = 0,
+
+		x = pos * math.cos(angle+math.rad(90)), xv = 0, xa = 0,
+		y = pos * math.sin(angle+math.rad(90)), yv = 0, ya = 0,
+		dir = angle, dirv = 0, dira = 0,
+		vel = 8, velv = 0, vela = 0,
+
+		r = 00,  rv = 0, ra = 0,
+		g = 0,  gv = 0, ga = 0,
+		b = 255,  bv = 0, ba = 0,
+		a = 1, av = -0.02, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(20)
+end,
+20 * 6
diff --git a/game/modules/tome/data/talents.lua b/game/modules/tome/data/talents.lua
index 34f3fd27acb7ebb3a1f8d649526b638d7b144a96..22ec31278b0af73220e35a8fc5e166837566920f 100644
--- a/game/modules/tome/data/talents.lua
+++ b/game/modules/tome/data/talents.lua
@@ -71,6 +71,7 @@ load("/data/talents/undeads/undeads.lua")
 load("/data/talents/cursed/cursed.lua")
 load("/data/talents/chronomancy/chronomancer.lua")
 load("/data/talents/psionic/psionic.lua")
+load("/data/talents/uber/uber.lua")
 
 print("[TALENTS TACTICS]")
 for k, _ in pairs(tacticals) do print(" * ", k) end
diff --git a/game/modules/tome/data/talents/uber/con.lua b/game/modules/tome/data/talents/uber/con.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f0bcaabf8e22c2f434841a6dad29a591c8e3d2d1
--- /dev/null
+++ b/game/modules/tome/data/talents/uber/con.lua
@@ -0,0 +1,36 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+uberTalent{
+	name = "Draconic Body",
+	mode = "passive",
+	cooldown = 40,
+	require = { special={desc="Be close to the draconic world", fct=function(self) return self:attr("drake_touched") and self:attr("drake_touched") >= 2 end} },
+	trigger = function(self, t, value)
+		if self.life - value < self.max_life * 0.3 and not self:isTalentCoolingDown(t) then
+			self:heal(self.max_life * 0.4)
+			self:startTalentCooldown(t)
+		end
+	end,
+	info = function(self, t)
+		return ([[Your body hardens, when pushed below 30%% life you are healed for 40%% of your total life.
+		This effect can only happen every 40 turns.]])
+		:format()
+	end,
+}
diff --git a/game/modules/tome/data/talents/uber/cun.lua b/game/modules/tome/data/talents/uber/cun.lua
new file mode 100644
index 0000000000000000000000000000000000000000..7324e46e97757011f3e68e313e7590c92c0db8e1
--- /dev/null
+++ b/game/modules/tome/data/talents/uber/cun.lua
@@ -0,0 +1,67 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+uberTalent{
+	name = "Fast As Lightning",
+	mode = "passive",
+	trigger = function(self, t, ox, oy)
+		local dx, dy = (self.x - ox), (self.y - oy)
+		if dx ~= 0 then dx = dx / math.abs(dx) end
+		if dy ~= 0 then dy = dy / math.abs(dy) end
+		local dir = util.coordToDir(dx, dy, 0)
+
+		local eff = self:hasEffect(self.EFF_FAST_AS_LIGHTNING)
+		if eff and eff.blink then
+			if eff.dir ~= dir then
+				self:removeEffect(self.EFF_FAST_AS_LIGHTNING)
+			else
+				return
+			end
+		end
+
+		self:setEffect(self.EFF_FAST_AS_LIGHTNING, 1, {})
+		eff = self:hasEffect(self.EFF_FAST_AS_LIGHTNING)
+
+		if not eff.dir then eff.dir = dir eff.nb = 0 end
+
+		if eff.dir ~= dir then
+			self:removeEffect(self.EFF_FAST_AS_LIGHTNING)
+			self:setEffect(self.EFF_FAST_AS_LIGHTNING, 1, {})
+			eff = self:hasEffect(self.EFF_FAST_AS_LIGHTNING)
+			eff.dir = dir eff.nb = 0
+			game.logSeen(self, "#LIGHT_BLUE#%s slows from critical velocity!", self.name:capitalize())
+		end
+
+		eff.nb = eff.nb + 1
+
+		if eff.nb >= 3 and not eff.blink then
+			self:effectTemporaryValue(eff, "prob_travel", 5)
+			game.logSeen(self, "#LIGHT_BLUE#%s reaches critical velocity!", self.name:capitalize())
+			local sx, sy = game.level.map:getTileToScreen(self.x, self.y)
+			game.flyers:add(sx, sy, 30, rng.float(-3, -2), (rng.range(0,2)-1) * 0.5, "CRITICAL VELOCITY!", {0,128,255})
+			eff.particle = self:addParticles(Particles.new("megaspeed", 1, {angle=util.dirToAngle((dir == 4 and 6) or (dir == 6 and 4 or dir))}))
+			eff.blink = true
+		end
+	end,
+	info = function(self, t)
+		return ([[When moving over 800%% speed for at least 3 turns in the same direction you become so fast you can blink throught obstacles as if they were not there.
+		Changing direction will break the effect.]])
+		:format()
+	end,
+}
diff --git a/game/modules/tome/data/talents/uber/dex.lua b/game/modules/tome/data/talents/uber/dex.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6b06aaffdf8c4d4f69419fcd635123d59f031894
--- /dev/null
+++ b/game/modules/tome/data/talents/uber/dex.lua
@@ -0,0 +1,18 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
diff --git a/game/modules/tome/data/talents/uber/mag.lua b/game/modules/tome/data/talents/uber/mag.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6b06aaffdf8c4d4f69419fcd635123d59f031894
--- /dev/null
+++ b/game/modules/tome/data/talents/uber/mag.lua
@@ -0,0 +1,18 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
diff --git a/game/modules/tome/data/talents/uber/str.lua b/game/modules/tome/data/talents/uber/str.lua
new file mode 100644
index 0000000000000000000000000000000000000000..6b06aaffdf8c4d4f69419fcd635123d59f031894
--- /dev/null
+++ b/game/modules/tome/data/talents/uber/str.lua
@@ -0,0 +1,18 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
diff --git a/game/modules/tome/data/talents/uber/uber.lua b/game/modules/tome/data/talents/uber/uber.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f0585b790277b6a1a39be248a8884d58456b09e8
--- /dev/null
+++ b/game/modules/tome/data/talents/uber/uber.lua
@@ -0,0 +1,81 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+newTalentType{ type="uber/uber", name = "uber", description = "Ultimate talents you may only know one." }
+
+
+uberTalent = function(t)
+	t.type = {"uber/uber", 1}
+	t.require = t.require or {}
+	t.require.level = 40
+	t.require.stat = t.require.stat or {}
+	t.require.stat.str = 80
+	newTalent(t)
+end
+load("/data/talents/uber/str.lua")
+
+uberTalent = function(t)
+	t.type = {"uber/uber", 1}
+	t.require = t.require or {}
+	t.require.stat = t.require.stat or {}
+	t.require.level = 40
+	t.require.stat.dex = 80
+	newTalent(t)
+end
+load("/data/talents/uber/dex.lua")
+
+uberTalent = function(t)
+	t.type = {"uber/uber", 1}
+	t.require = t.require or {}
+	t.require.stat = t.require.stat or {}
+	t.require.level = 40
+	t.require.stat.con = 80
+	newTalent(t)
+end
+load("/data/talents/uber/con.lua")
+
+uberTalent = function(t)
+	t.type = {"uber/uber", 1}
+	t.require = t.require or {}
+	t.require.stat = t.require.stat or {}
+	t.require.level = 40
+	t.require.stat.mag = 80
+	newTalent(t)
+end
+load("/data/talents/uber/mag.lua")
+
+uberTalent = function(t)
+	t.type = {"uber/uber", 1}
+	t.require = t.require or {}
+	t.require.level = 40
+	t.require.stat = t.require.stat or {}
+	t.require.stat.wil = 80
+	newTalent(t)
+end
+load("/data/talents/uber/wil.lua")
+
+uberTalent = function(t)
+	t.type = {"uber/uber", 1}
+	t.require = t.require or {}
+	t.require.level = 40
+	t.require.stat = t.require.stat or {}
+	t.require.stat.cun = 80
+	newTalent(t)
+end
+load("/data/talents/uber/cun.lua")
diff --git a/game/modules/tome/data/talents/uber/wil.lua b/game/modules/tome/data/talents/uber/wil.lua
new file mode 100644
index 0000000000000000000000000000000000000000..80d2584654d37ea0683e58ba84c1dd3df8dcacdd
--- /dev/null
+++ b/game/modules/tome/data/talents/uber/wil.lua
@@ -0,0 +1,34 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+uberTalent{
+	name = "Draconic Will",
+	cooldown = 20,
+	no_energy = true,
+	action = function(self, t)
+		self:setEffect(self.EFF_DRACONIC_WILL, 5, {})
+		return true
+	end,
+	require = { special={desc="Be close to the draconic world", fct=function(self) return self:attr("drake_touched") and self:attr("drake_touched") >= 2 end} },
+	info = function(self, t)
+		return ([[Your body is like that of a drake, easily resisting detrimental effects.
+		For 5 turns no detrimental effects may target you.]])
+		:format()
+	end,
+}
diff --git a/game/modules/tome/data/timed_effects/mental.lua b/game/modules/tome/data/timed_effects/mental.lua
index c7b4fc46ad9eeebf5a6ffd48eda58048ec8f59d9..662237348da479a2c9c2b1b296f32e26c5db7716 100644
--- a/game/modules/tome/data/timed_effects/mental.lua
+++ b/game/modules/tome/data/timed_effects/mental.lua
@@ -2749,4 +2749,19 @@ newEffect{
 	deactivate = function(self, eff)
 		self:removeParticles(eff.particle)
 	end,
-}
\ No newline at end of file
+}
+
+newEffect{
+	name = "DRACONIC_WILL", image = "talents/draconic_will.png",
+	desc = "Draconic Will",
+	long_desc = function(self, eff) return "The target is immune to all detrimental effects." end,
+	type = "mental",
+	subtype = { nature=true },
+	status = "detrimental",
+	on_gain = function(self, err) return "#Target#'s skin hardens.", "+Draconic Will" end,
+	on_lose = function(self, err) return "#Target#'s skin is back to normal.", "-Draconic Will" end,
+	parameters = { },
+	activate = function(self, eff)
+		self:effectTemporaryValue(eff, "negative_status_effect_immune", 1)
+	end,
+}
diff --git a/game/modules/tome/data/timed_effects/physical.lua b/game/modules/tome/data/timed_effects/physical.lua
index ebcc171cdb81bd487212b6abd0203e09eea0be7b..197dd425db69d7f84fdd70e9f67f1fa3a1d5c4aa 100644
--- a/game/modules/tome/data/timed_effects/physical.lua
+++ b/game/modules/tome/data/timed_effects/physical.lua
@@ -1883,3 +1883,25 @@ newEffect{
 --		if eff.ai then self:removeTemporaryValue("ai_state", eff.ai) end
 	end,
 }
+
+newEffect{
+	name = "FAST_AS_LIGHTNING", image = "talents/fast_as_lightning.png",
+	desc = "Fast As Lightning",
+	long_desc = function(self, eff) return ("The target is so fast it may blink throught obstacles if moving in the same direction for over two turns."):format() end,
+	type = "physical",
+	subtype = { speed=true },
+	status = "beneficial",
+	parameters = { },
+	on_merge = function(self, old_eff, new_eff)
+		return old_eff
+	end,
+	on_gain = function(self, err) return "#Target# is speeding up.", "+Fast As Lightning" end,
+	on_lose = function(self, err) return "#Target# is slowing down.", "-Fast As Lightning" end,
+	activate = function(self, eff)
+	end,
+	deactivate = function(self, eff)
+		if eff.particle then
+			self:removeParticles(eff.particle)
+		end
+	end,
+}
diff --git a/game/modules/tome/data/zones/sandworm-lair/objects.lua b/game/modules/tome/data/zones/sandworm-lair/objects.lua
index c4bd65cdcfdf31249ba3b37d7b77f5100bdaa880..dd4d9fff4d5213353ce30a6d8716e489ff8ea3b4 100644
--- a/game/modules/tome/data/zones/sandworm-lair/objects.lua
+++ b/game/modules/tome/data/zones/sandworm-lair/objects.lua
@@ -59,6 +59,8 @@ newEntity{
 			if who:getTalentTypeMastery("wild-gift/harmony") == 0 then who:setTalentTypeMastery("wild-gift/harmony", 1) end
 			game.logPlayer(who, "You are transformed by the heart of the Queen!.")
 			game.logPlayer(who, "#00FF00#You gain an affinity for nature. You can now learn new Harmony talents (press G).")
+
+			who:attr("drake_touched", 1)
 		end
 
 		game:setAllowedBuild("wilder_wyrmic", true)
@@ -90,6 +92,8 @@ newEntity{
 		who:incStat("con", con) if con >= 0 then con="+"..con end
 		game.logPlayer(who, "#00FF00#Your stats have changed! (Str %s, Dex %s, Mag %s, Wil %s, Cun %s, Con %s)", str, dex, mag, wil, cun, con)
 
+		who:attr("drake_touched", 1)
+
 		return {used=true, id=true, destroy=true}
 	end}
 }
diff --git a/game/modules/tome/dialogs/UberTalent.lua b/game/modules/tome/dialogs/UberTalent.lua
new file mode 100644
index 0000000000000000000000000000000000000000..2a9f68b518da73cda3337bdbec1cd30bf4937be8
--- /dev/null
+++ b/game/modules/tome/dialogs/UberTalent.lua
@@ -0,0 +1,871 @@
+-- ToME - Tales of Maj'Eyal
+-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even th+e 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
+
+require "engine.class"
+require "mod.class.interface.TooltipsData"
+
+local Dialog = require "engine.ui.Dialog"
+local Button = require "engine.ui.Button"
+local Textzone = require "engine.ui.Textzone"
+local TextzoneList = require "engine.ui.TextzoneList"
+local UIContainer = require "engine.ui.UIContainer"
+local TalentTrees = require "mod.dialogs.elements.TalentTrees"
+local Separator = require "engine.ui.Separator"
+local DamageType = require "engine.DamageType"
+
+module(..., package.seeall, class.inherit(Dialog, mod.class.interface.TooltipsData))
+
+function _M:init(actor, on_finish, on_birth)
+	self.on_birth = on_birth
+	actor.no_last_learnt_talents_cap = true
+	self.actor = actor
+	self.unused_stats = self.actor.unused_stats
+	self.new_stats_changed = false
+	self.new_talents_changed = false
+
+	self.talents_changed = {}
+	self.on_finish = on_finish
+	self.running = true
+	self.prev_stats = {}
+	self.font_h = self.font:lineSkip()
+	self.talents_learned = {}
+	self.talent_types_learned = {}
+	self.stats_increased = {}
+
+	self.font = core.display.newFont("/data/font/DroidSansMono.ttf", 12)
+	self.font_h = self.font:lineSkip()
+
+	self.actor.__hidden_talent_types = self.actor.__hidden_talent_types or {}
+	self.actor.__increased_talent_types = self.actor.__increased_talent_types or {}
+
+	self.actor_dup = actor:clone()
+	self.actor_dup.uid = actor.uid -- Yes ...
+
+	for _, v in pairs(game.engine.Birther.birth_descriptor_def) do
+		if v.type == "subclass" and v.name == actor.descriptor.subclass then self.desc_def = v break end
+	end
+
+	Dialog.init(self, "Levelup: "..actor.name, game.w * 0.9, game.h * 0.9, game.w * 0.05, game.h * 0.05)
+	if game.w * 0.9 >= 1000 then
+		self.no_tooltip = true
+	end
+
+	self:generateList()
+
+	self:loadUI(self:createDisplay())
+	self:setupUI()
+
+	self.key:addCommands{
+		__TEXTINPUT = function(c)
+			if c == "+" and self.focus_ui and self.focus_ui.ui.onUse then
+				self.focus_ui.ui:onUse(self.focus_ui.ui.last_mz.item, true)
+			elseif c == "-" then
+				self.focus_ui.ui:onUse(self.focus_ui.ui.last_mz.item, false)
+			end
+		end,
+	}
+	self.key:addBinds{
+		EXIT = function()
+			if self.actor.unused_stats~=self.actor_dup.unused_stats or self.actor.unused_talents_types~=self.actor_dup.unused_talents_types or
+			self.actor.unused_talents~=self.actor_dup.unused_talents or self.actor.unused_generics~=self.actor_dup.unused_generics then
+				self:yesnocancelPopup("Finish","Do you accept changes?", function(yes, cancel)
+				if cancel then
+					return nil
+				else
+					if yes then ok = self:finish() else ok = true self:cancel() end
+				end
+				if ok then
+					game:unregisterDialog(self)
+					self.actor_dup = {}
+					if self.on_finish then self.on_finish() end
+				end
+				end)
+			else
+				game:unregisterDialog(self)
+				self.actor_dup = {}
+				if self.on_finish then self.on_finish() end
+			end
+		end,
+	}
+end
+
+function _M:on_register()
+	game:onTickEnd(function() self.key:unicodeInput(true) end)
+end
+
+function _M:unload()
+	self.actor.no_last_learnt_talents_cap = nil
+	self.actor:capLastLearntTalents("class")
+	self.actor:capLastLearntTalents("generic")
+end
+
+function _M:cancel()
+	local ax, ay = self.actor.x, self.actor.y
+	self.actor_dup.replacedWith = false
+	self.actor:replaceWith(self.actor_dup)
+	self.actor.replacedWith = nil
+	self.actor.x, self.actor.y = ax, ay
+	self.actor.changed = true
+	self.actor:removeAllMOs()
+	if game.level and self.actor.x then game.level.map:updateMap(self.actor.x, self.actor.y) end
+end
+
+function _M:getMaxTPoints(t)
+	if t.points == 1 then return 1 end
+	return t.points + math.max(0, math.floor((self.actor.level - 50) / 10))
+end
+
+function _M:finish()
+	local ok, dep_miss = self:checkDeps(true)
+	if not ok then
+		self:simpleLongPopup("Impossible", "You cannot learn this talent(s): "..dep_miss, game.w * 0.4)
+		return nil
+	end
+
+	local txt = "#LIGHT_BLUE#Warning: You have increased some of your statistics or talent. Talent(s) actually sustained: \n %s If these are dependent on one of the stats you changed, you need to re-use them for the changes to take effect."
+	local talents = ""
+	local reset = {}
+	for tid, act in pairs(self.actor.sustain_talents) do
+		if act then
+			local t = self.actor:getTalentFromId(tid)
+			if t.no_sustain_autoreset and self.actor:knowTalent(tid) then
+				talents = talents.."#GOLD# - "..t.name.."#LAST#\n"
+			else
+				reset[#reset+1] = tid
+			end
+		end
+	end
+	if talents ~= "" then
+		game.logPlayer(self.actor, txt:format(talents))
+	end
+	for i, tid in ipairs(reset) do
+		self.actor:forceUseTalent(tid, {ignore_energy=true, ignore_cd=true, no_equilibrium_fail=true, no_paradox_fail=true})
+		if self.actor:knowTalent(tid) then self.actor:forceUseTalent(tid, {ignore_energy=true, ignore_cd=true, no_equilibrium_fail=true, no_paradox_fail=true, talent_reuse=true}) end
+	end
+
+	if not self.on_birth then
+		for t_id, _ in pairs(self.talents_learned) do
+			local t = self.actor:getTalentFromId(t_id)
+			if not self.actor:isTalentCoolingDown(t) and not self.actor_dup:knowTalent(t_id) then self.actor:startTalentCooldown(t) end
+		end
+	end
+	return true
+end
+
+function _M:incStat(sid, v)
+	if v == 1 then
+		if self.actor.unused_stats <= 0 then
+			self:simplePopup("Not enough stat points", "You have no stat points left!")
+			return
+		end
+		if self.actor:getStat(sid, nil, nil, true) >= self.actor.level * 1.4 + 20 then
+			self:simplePopup("Stat is at the maximum for your level", "You cannot increase this stat further until next level!")
+			return
+		end
+		if self.actor:isStatMax(sid) or self.actor:getStat(sid, nil, nil, true) >= 60 + math.max(0, (self.actor.level - 50)) then
+			self:simplePopup("Stat is at the maximum", "You cannot increase this stat further!")
+			return
+		end
+	else
+		if self.actor_dup:getStat(sid, nil, nil, true) == self.actor:getStat(sid, nil, nil, true) then
+			self:simplePopup("Impossible", "You cannot take out more points!")
+			return
+		end
+	end
+
+	self.actor:incStat(sid, v)
+	self.actor.unused_stats = self.actor.unused_stats - v
+
+	self.stats_increased[sid] = (self.stats_increased[sid] or 0) + v
+	self:updateTooltip()
+end
+
+function _M:computeDeps(t)
+	local d = {}
+	self.talents_deps[t.id] = d
+
+	-- Check prerequisites
+	if rawget(t, "require") then
+		local req = t.require
+		if type(req) == "function" then req = req(self.actor, t) end
+
+		if req.talent then
+			for _, tid in ipairs(req.talent) do
+				if type(tid) == "table" then
+					d[tid[1]] = true
+--					print("Talent deps: ", t.id, "depends on", tid[1])
+				else
+					d[tid] = true
+--					print("Talent deps: ", t.id, "depends on", tid)
+				end
+			end
+		end
+	end
+
+	-- Check number of talents
+	for id, nt in pairs(self.actor.talents_def) do
+		if nt.type[1] == t.type[1] then
+			d[id] = true
+--			print("Talent deps: ", t.id, "same category as", id)
+		end
+	end
+end
+
+function _M:checkDeps(simple)
+	local talents = ""
+	local stats_ok = true
+
+	local checked = {}
+
+	local function check(t_id)
+		if checked[t_id] then return end
+		checked[t_id] = true
+
+		local t = self.actor:getTalentFromId(t_id)
+		local ok, reason = self.actor:canLearnTalent(t, 0)
+		if not ok and self.actor:knowTalent(t) then talents = talents.."\n#GOLD##{bold}#    - "..t.name.."#{normal}##LAST#("..reason..")" end
+		if reason == "not enough stat" then
+			stats_ok = false
+		end
+
+		local dlist = self.talents_deps[t_id]
+		if dlist and not simple then for dtid, _ in pairs(dlist) do check(dtid) end end
+	end
+
+	for t_id, _ in pairs(self.talents_changed) do check(t_id) end
+
+	if talents ~="" then
+		return false, talents, stats_ok
+	else
+		return true, "", stats_ok
+	end
+end
+
+function _M:isUnlearnable(t, limit)
+	if not self.actor.last_learnt_talents then return end
+	if self.on_birth and self.actor:knowTalent(t.id) and not t.no_unlearn_last then return 1 end -- On birth we can reset any talents except a very few
+	local list = self.actor.last_learnt_talents[t.generic and "generic" or "class"]
+	local max = self.actor:lastLearntTalentsMax(t.generic and "generic" or "class")
+	local min = 1
+	if limit then min = math.max(1, #list - (max - 1)) end
+	for i = #list, min, -1 do
+		if list[i] == t.id then return i end
+	end
+	return nil
+end
+
+function _M:learnTalent(t_id, v)
+	self.talents_learned[t_id] = self.talents_learned[t_id] or 0
+	local t = self.actor:getTalentFromId(t_id)
+	if not t.generic then
+		if v then
+			if self.actor.unused_talents < 1 then
+				self:simplePopup("Not enough class talent points", "You have no class talent points left!")
+				return
+			end
+			if not self.actor:canLearnTalent(t) then
+				self:simplePopup("Cannot learn talent", "Prerequisites not met!")
+				return
+			end
+			if self.actor:getTalentLevelRaw(t_id) >= self:getMaxTPoints(t) then
+				self:simplePopup("Already known", "You already fully know this talent!")
+				return
+			end
+			self.actor:learnTalent(t_id, true)
+			self.actor.unused_talents = self.actor.unused_talents - 1
+			self.talents_changed[t_id] = true
+			self.talents_learned[t_id] = self.talents_learned[t_id] + 1
+			self.new_talents_changed = true
+		else
+			if not self.actor:knowTalent(t_id) then
+				self:simplePopup("Impossible", "You do not know this talent!")
+				return
+			end
+			if not self:isUnlearnable(t, true) and self.actor_dup:getTalentLevelRaw(t_id) >= self.actor:getTalentLevelRaw(t_id) then
+				self:simplePopup("Impossible", "You cannot unlearn talents!")
+				return
+			end
+			self.actor:unlearnTalent(t_id, nil, true)
+			self.talents_changed[t_id] = true
+			local _, reason = self.actor:canLearnTalent(t, 0)
+			local ok, dep_miss, stats_ok = self:checkDeps()
+			if ok or reason == "not enough stat" or not stats_ok then
+				self.actor.unused_talents = self.actor.unused_talents + 1
+				self.talents_learned[t_id] = self.talents_learned[t_id] - 1
+				self.new_talents_changed = true
+			else
+				self:simpleLongPopup("Impossible", "You cannot unlearn this talent because of talent(s): "..dep_miss, game.w * 0.4)
+				self.actor:learnTalent(t_id)
+				return
+			end
+		end
+	else
+		if v then
+			if self.actor.unused_generics < 1 then
+				self:simplePopup("Not enough generic talent points", "You have no generic talent points left!")
+				return
+			end
+			if not self.actor:canLearnTalent(t) then
+				self:simplePopup("Cannot learn talent", "Prerequisites not met!")
+				return
+			end
+			if self.actor:getTalentLevelRaw(t_id) >= self:getMaxTPoints(t) then
+				self:simplePopup("Already known", "You already fully know this talent!")
+				return
+			end
+			self.actor:learnTalent(t_id)
+			self.actor.unused_generics = self.actor.unused_generics - 1
+			self.talents_changed[t_id] = true
+			self.talents_learned[t_id] = self.talents_learned[t_id] + 1
+			self.new_talents_changed = true
+		else
+			if not self.actor:knowTalent(t_id) then
+				self:simplePopup("Impossible", "You do not know this talent!")
+				return
+			end
+			if not self:isUnlearnable(t, true) and self.actor_dup:getTalentLevelRaw(t_id) >= self.actor:getTalentLevelRaw(t_id) then
+				self:simplePopup("Impossible", "You cannot unlearn talents!")
+				return
+			end
+			self.actor:unlearnTalent(t_id, nil, true)
+			self.talents_changed[t_id] = true
+			local _, reason = self.actor:canLearnTalent(t, 0)
+			local ok, dep_miss, stats_ok = self:checkDeps()
+			if ok or reason == "not enough stat" or not stats_ok then
+				self.actor.unused_generics = self.actor.unused_generics + 1
+				self.talents_learned[t_id] = self.talents_learned[t_id] - 1
+				self.new_talents_changed = true
+			else
+				self:simpleLongPopup("Impossible", "You can not unlearn this talent because of talent(s): "..dep_miss, game.w * 0.4)
+				self.actor:learnTalent(t_id)
+				return
+			end
+		end
+	end
+	self:updateTooltip()
+end
+
+function _M:learnType(tt, v)
+	self.talent_types_learned[tt] = self.talent_types_learned[tt] or {}
+	if v then
+		if self.actor:knowTalentType(tt) and self.actor.__increased_talent_types[tt] and self.actor.__increased_talent_types[tt] >= 1 then
+			self:simplePopup("Impossible", "You can only improve a category mastery once!")
+			return
+		end
+		if self.actor.unused_talents_types <= 0 then
+			self:simplePopup("Not enough talent category points", "You have no category points left!")
+			return
+		end
+		if not self.actor.talents_types_def[tt] or (self.actor.talents_types_def[tt].min_lev or 0) > self.actor.level then
+			self:simplePopup("Too low level", ("This talent tree only provides talents starting at level %d. Learning it now would be useless."):format(self.actor.talents_types_def[tt].min_lev))
+			return
+		end
+		if not self.actor:knowTalentType(tt) then
+			self.actor:learnTalentType(tt)
+			self.talent_types_learned[tt][1] = true
+		else
+			self.actor.__increased_talent_types[tt] = (self.actor.__increased_talent_types[tt] or 0) + 1
+			self.actor:setTalentTypeMastery(tt, self.actor:getTalentTypeMastery(tt) + 0.2)
+			self.talent_types_learned[tt][2] = true
+		end
+		self:triggerHook{"PlayerLevelup:addTalentType", actor=self.actor, tt=tt}
+		self.actor.unused_talents_types = self.actor.unused_talents_types - 1
+		self.new_talents_changed = true
+	else
+		if self.actor_dup:knowTalentType(tt) == true and self.actor:knowTalentType(tt) == true and (self.actor_dup.__increased_talent_types[tt] or 0) >= (self.actor.__increased_talent_types[tt] or 0) then
+			self:simplePopup("Impossible", "You cannot take out more points!")
+			return
+		end
+		if self.actor_dup:knowTalentType(tt) == true and self.actor:knowTalentType(tt) == true and (self.actor.__increased_talent_types[tt] or 0) == 0 then
+			self:simplePopup("Impossible", "You cannot unlearn this category!")
+			return
+		end
+		if not self.actor:knowTalentType(tt) then
+			self:simplePopup("Impossible", "You do not know this category!")
+			return
+		end
+
+		if (self.actor.__increased_talent_types[tt] or 0) > 0 then
+			self.actor.__increased_talent_types[tt] = (self.actor.__increased_talent_types[tt] or 0) - 1
+			self.actor:setTalentTypeMastery(tt, self.actor:getTalentTypeMastery(tt) - 0.2)
+			self.actor.unused_talents_types = self.actor.unused_talents_types + 1
+			self.new_talents_changed = true
+			self.talent_types_learned[tt][2] = nil
+		else
+			self.actor:unlearnTalentType(tt)
+			local ok, dep_miss = self:checkDeps()
+			if ok then
+				self.actor.unused_talents_types = self.actor.unused_talents_types + 1
+				self.new_talents_changed = true
+				self.talent_types_learned[tt][1] = nil
+			else
+				self:simpleLongPopup("Impossible", "You cannot unlearn this category because of: "..dep_miss, game.w * 0.4)
+				self.actor:learnTalentType(tt)
+				return
+			end
+		end
+		self:triggerHook{"PlayerLevelup:subTalentType", actor=self.actor, tt=tt}
+	end
+	self:updateTooltip()
+end
+
+function _M:generateList()
+	self.actor.__show_special_talents = self.actor.__show_special_talents or {}
+
+	-- Makes up the list
+	local ctree = {}
+	local gtree = {}
+	self.talents_deps = {}
+	for i, tt in ipairs(self.actor.talents_types_def) do
+		if not tt.hide and not (self.actor.talents_types[tt.type] == nil) then
+			local cat = tt.type:gsub("/.*", "")
+			local ttknown = self.actor:knowTalentType(tt.type)
+			local isgeneric = self.actor.talents_types_def[tt.type].generic
+			local tshown = (self.actor.__hidden_talent_types[tt.type] == nil and ttknown) or (self.actor.__hidden_talent_types[tt.type] ~= nil and not self.actor.__hidden_talent_types[tt.type])
+			local node = {
+				name=function(item) return tstring{{"font", "bold"}, cat:capitalize().." / "..tt.name:capitalize() ..(" (%s)"):format((isgeneric and "generic" or "class")), {"font", "normal"}} end,
+				rawname=function(item) return cat:capitalize().." / "..tt.name:capitalize() ..(" (x%.2f)"):format(self.actor:getTalentTypeMastery(item.type)) end,
+				type=tt.type,
+				color=function(item) return ((self.actor:knowTalentType(item.type) ~= self.actor_dup:knowTalentType(item.type)) or ((self.actor.__increased_talent_types[item.type] or 0) ~= (self.actor_dup.__increased_talent_types[item.type] or 0))) and {255, 215, 0} or self.actor:knowTalentType(item.type) and {0,200,0} or {175,175,175} end,
+				shown = tshown,
+				status = function(item) return self.actor:knowTalentType(item.type) and tstring{{"font", "bold"}, ((self.actor.__increased_talent_types[item.type] or 0) >=1) and {"color", 255, 215, 0} or {"color", 0x00, 0xFF, 0x00}, ("%.2f"):format(self.actor:getTalentTypeMastery(item.type)), {"font", "normal"}} or tstring{{"color",  0xFF, 0x00, 0x00}, "unknown"} end,
+				nodes = {},
+				isgeneric = isgeneric and 0 or 1,
+				order_id = i,
+			}
+			if isgeneric then gtree[#gtree+1] = node
+			else ctree[#ctree+1] = node end
+
+			local list = node.nodes
+
+			-- Find all talents of this school
+			for j, t in ipairs(tt.talents) do
+				if not t.hide or self.actor.__show_special_talents[t.id] then
+					self:computeDeps(t)
+					local isgeneric = self.actor.talents_types_def[tt.type].generic
+
+					-- Pregenenerate icon with the Tiles instance that allows images
+					if t.display_entity then t.display_entity:getMapObjects(game.uiset.hotkeys_display_icons.tiles, {}, 1) end
+
+					list[#list+1] = {
+						__id=t.id,
+						name=t.name:toTString(),
+						rawname=t.name,
+						entity=t.display_entity,
+						talent=t.id,
+						isgeneric=isgeneric and 0 or 1,
+						_type=tt.type,
+						do_shadow = function(item) if not self.actor:canLearnTalent(t) then return true else return false end end,
+						color=function(item)
+							if ((self.actor.talents[item.talent] or 0) ~= (self.actor_dup.talents[item.talent] or 0)) then return {255, 215, 0}
+							elseif self:isUnlearnable(t, true) then return colors.simple(colors.LIGHT_BLUE)
+							elseif self.actor:knowTalentType(item._type) then return {255,255,255}
+							else return {175,175,175}
+							end
+						end,
+					}
+					list[#list].status = function(item)
+						local t = self.actor:getTalentFromId(item.talent)
+						local ttknown = self.actor:knowTalentType(item._type)
+						if self.actor:getTalentLevelRaw(t.id) == self:getMaxTPoints(t) then
+							return tstring{{"color", 0x00, 0xFF, 0x00}, self.actor:getTalentLevelRaw(t.id).."/"..self:getMaxTPoints(t)}
+						else
+							if not self.actor:canLearnTalent(t) then
+								return tstring{(ttknown and {"color", 0xFF, 0x00, 0x00} or {"color", 0x80, 0x80, 0x80}), self.actor:getTalentLevelRaw(t.id).."/"..self:getMaxTPoints(t)}
+							else
+								return tstring{(ttknown and {"color", "WHITE"} or {"color", 0x80, 0x80, 0x80}), self.actor:getTalentLevelRaw(t.id).."/"..self:getMaxTPoints(t)}
+							end
+						end
+					end
+				end
+			end
+		end
+	end
+	table.sort(ctree, function(a, b)
+		return a.order_id < b.order_id
+	end)
+	self.ctree = ctree
+	table.sort(gtree, function(a, b)
+		return a.order_id < b.order_id
+	end)
+	self.gtree = gtree
+
+	-- Makes up the stats list
+	local stats = {}
+	self.tree_stats = stats
+
+	for i, sid in ipairs{self.actor.STAT_STR, self.actor.STAT_DEX, self.actor.STAT_CON, self.actor.STAT_MAG, self.actor.STAT_WIL, self.actor.STAT_CUN } do
+		local s = self.actor.stats_def[sid]
+		local e = engine.Entity.new{image="stats/"..s.name:lower()..".png", is_stat=true}
+		e:getMapObjects(game.uiset.hotkeys_display_icons.tiles, {}, 1)
+
+		stats[#stats+1] = {shown=true, nodes={{
+			name=s.name,
+			rawname=s.name,
+			entity=e,
+			stat=sid,
+			desc=s.description,
+			color=function(item)
+				if self.actor:getStat(sid, nil, nil, true) ~= self.actor_dup:getStat(sid, nil, nil, true) then return {255, 215, 0}
+				elseif self.actor:getStat(sid, nil, nil, true) >= self.actor.level * 1.4 + 20 or
+				   self.actor:isStatMax(sid) or
+				   self.actor:getStat(sid, nil, nil, true) >= 60 + math.max(0, (self.actor.level - 50)) then
+					return {0, 255, 0}
+				else
+					return {175,175,175}
+				end
+			end,
+			status = function(item)
+				if self.actor:getStat(sid, nil, nil, true) >= self.actor.level * 1.4 + 20 or
+				   self.actor:isStatMax(sid) or
+				   self.actor:getStat(sid, nil, nil, true) >= 60 + math.max(0, (self.actor.level - 50)) then
+					return tstring{{"color", 175, 175, 175}, ("%d (%d)"):format(self.actor:getStat(sid), self.actor:getStat(sid, nil, nil, true))}
+				else
+					return tstring{{"color", 0x00, 0xFF, 0x00}, ("%d (%d)"):format(self.actor:getStat(sid), self.actor:getStat(sid, nil, nil, true))}
+				end
+			end,
+		}}}
+	end
+end
+
+-----------------------------------------------------------------
+-- UI Stuff
+-----------------------------------------------------------------
+
+local _points_left = [[
+Stats points left: #00FF00#%d#LAST#
+Category points left: #00FF00#%d#LAST#
+Class talent points left: #00FF00#%d#LAST#
+Generic talent points left: #00FF00#%d#LAST#]]
+
+local desc_stats = ([[Stat points allow you to increase your core stats.
+Each level you gain 3 new stat points to use.
+
+You may only increase stats to a natural maximum of 60 or lower (relative to your level).]]):toTString()
+
+local desc_class = ([[Class talent points allow you to learn new class talents or improve them.
+Class talents are core to your class and can not be learnt by training.
+
+Each level you gain 1 new class point to use.
+Each five levels you gain one more.
+]]):toTString()
+
+local desc_generic = ([[Generic talent points allow you to learn new generic talents or improve them.
+Generic talents comes from your class, your race or various outside training you can get during your adventures.
+
+Each level you gain 1 new class point to use.
+Each five levels you gain one less.
+]]):toTString()
+
+local desc_types = ([[Talent category points allow you to either:
+- learn a new talent (class or generic) category
+- improve a known talent category efficiency by 0.2
+- learn a new inscription slot (up to a maximum of 5, learning it is automatic when using an inscription)
+
+You gain a new point at level 10, 20 and 30.
+Some races or items may increase them as well.]]):toTString()
+
+function _M:createDisplay()
+	self.c_ctree = TalentTrees.new{
+		font = core.display.newFont("/data/font/DroidSans.ttf", 14),
+		tiles=game.uiset.hotkeys_display_icons,
+		tree=self.ctree,
+		width=320, height=self.ih-50,
+		tooltip=function(item)
+			local x = self.display_x + self.uis[5].x - game.tooltip.max
+			if self.display_x + self.w + game.tooltip.max <= game.w then x = self.display_x + self.w end
+			local ret = self:getTalentDesc(item), x, nil
+			if self.no_tooltip then
+				self.c_desc:erase()
+				self.c_desc:switchItem(ret, ret)
+			end
+			return ret
+		end,
+		on_use = function(item, inc) self:onUseTalent(item, inc) end,
+		on_expand = function(item) self.actor.__hidden_talent_types[item.type] = not item.shown end,
+		scrollbar = true, no_tooltip = self.no_tooltip,
+	}
+
+	self.c_gtree = TalentTrees.new{
+		font = core.display.newFont("/data/font/DroidSans.ttf", 14),
+		tiles=game.uiset.hotkeys_display_icons,
+		tree=self.gtree,
+		width=320, height=self.ih-50,
+		tooltip=function(item)
+			local x = self.display_x + self.uis[8].x - game.tooltip.max
+			if self.display_x + self.w + game.tooltip.max <= game.w then x = self.display_x + self.w end
+			local ret = self:getTalentDesc(item), x, nil
+			if self.no_tooltip then
+				self.c_desc:erase()
+				self.c_desc:switchItem(ret, ret)
+			end
+			return ret
+		end,
+		on_use = function(item, inc) self:onUseTalent(item, inc) end,
+		on_expand = function(item) self.actor.__hidden_talent_types[item.type] = not item.shown end,
+		scrollbar = true, no_tooltip = self.no_tooltip,
+	}
+
+	self.c_stat = TalentTrees.new{
+		font = core.display.newFont("/data/font/DroidSans.ttf", 14),
+		tiles=game.uiset.hotkeys_display_icons,
+		tree=self.tree_stats, no_cross = true,
+		width=50, height=self.ih,
+		dont_select_top = true,
+		tooltip=function(item)
+			local x = self.display_x + self.uis[2].x + self.uis[2].ui.w
+			if self.display_x + self.w + game.tooltip.max <= game.w then x = self.display_x + self.w end
+			local ret = self:getStatDesc(item), x, nil
+			if self.no_tooltip then
+				self.c_desc:erase()
+				self.c_desc:switchItem(ret, ret)
+			end
+			return ret
+		end,
+		on_use = function(item, inc) self:onUseTalent(item, inc) end,
+		no_tooltip = self.no_tooltip,
+	}
+
+	local vsep1 = Separator.new{dir="horizontal", size=self.ih - 20}
+	local vsep2 = Separator.new{dir="horizontal", size=self.ih - 20}
+	local hsep = Separator.new{dir="vertical", size=180}
+
+	self.b_stat = Button.new{can_focus = false, can_focus_mouse=true, text="Stats: "..self.actor.unused_stats, fct=function() end, on_select=function()
+		local str = desc_stats
+		if self.no_tooltip then
+			self.c_desc:erase()
+			self.c_desc:switchItem(str, str, true)
+		else
+			game:tooltipDisplayAtMap(self.b_stat.last_display_x + self.b_stat.w, self.b_stat.last_display_y, str)
+		end
+	end}
+	self.b_class = Button.new{can_focus = false, can_focus_mouse=true, text="Class points: "..self.actor.unused_talents, fct=function() end, on_select=function()
+		local str = desc_class
+		if self.no_tooltip then
+			self.c_desc:erase()
+			self.c_desc:switchItem(str, str, true)
+		else
+			game:tooltipDisplayAtMap(self.b_stat.last_display_x + self.b_stat.w, self.b_stat.last_display_y, str)
+		end
+	end}
+	self.b_generic = Button.new{can_focus = false, can_focus_mouse=true, text="Generic points: "..self.actor.unused_generics, fct=function() end, on_select=function()
+		local str = desc_generic
+		if self.no_tooltip then
+			self.c_desc:erase()
+			self.c_desc:switchItem(str, str, true)
+		else
+			game:tooltipDisplayAtMap(self.b_stat.last_display_x + self.b_stat.w, self.b_stat.last_display_y, str)
+		end
+	end}
+	self.b_types = Button.new{can_focus = false, can_focus_mouse=true, text="Category points: "..self.actor.unused_talents_types, fct=function() end, on_select=function()
+		local str = desc_types
+		if self.no_tooltip then
+			self.c_desc:erase()
+			self.c_desc:switchItem(str, str, true)
+		else
+			game:tooltipDisplayAtMap(self.b_stat.last_display_x + self.b_stat.w, self.b_stat.last_display_y, str)
+		end
+	end}
+
+	local ret = {
+		{left=-10, top=0, ui=self.b_stat},
+		{left=0, top=self.b_stat.h+10, ui=self.c_stat},
+
+		{left=self.c_stat, top=40, ui=vsep1},
+
+		{left=vsep1, top=0, ui=self.b_class},
+		{left=vsep1, top=self.b_class.h + 10, ui=self.c_ctree},
+
+		{left=self.c_ctree, top=40, ui=vsep2},
+
+		{left=580, top=0, ui=self.b_generic},
+		{left=vsep2, top=self.b_generic.h + 10, ui=self.c_gtree},
+
+		{left=330, top=0, ui=self.b_types},
+	}
+
+	if self.no_tooltip then
+		local vsep3 = Separator.new{dir="horizontal", size=self.ih - 20}
+        self.c_desc = TextzoneList.new{ focus_check = true, scrollbar = true, width=self.iw - 200 - 530 - 40, height = self.ih, dest_area = { h = self.ih } }
+		ret[#ret+1] = {right=0, top=0, ui=self.c_desc}
+		ret[#ret+1] = {right=self.c_desc.w, top=0, ui=vsep3}
+	end
+
+	return ret
+end
+
+function _M:getStatDesc(item)
+	local stat_id = item.stat
+	if not stat_id then return item.desc end
+	local text = tstring{}
+	text:merge(item.desc:toTString())
+	text:add(true, true)
+	local diff = self.actor:getStat(stat_id, nil, nil, true) - self.actor_dup:getStat(stat_id, nil, nil, true)
+	local color = diff >= 0 and {"color", "LIGHT_GREEN"} or {"color", "RED"}
+	local dc = {"color", "LAST"}
+
+	text:add("Current value: ", {"color", "LIGHT_GREEN"}, ("%d"):format(self.actor:getStat(stat_id)), dc, true)
+	text:add("Base value: ", {"color", "LIGHT_GREEN"}, ("%d"):format(self.actor:getStat(stat_id, nil, nil, true)), dc, true, true)
+
+	text:add({"color", "LIGHT_BLUE"}, "Stat gives:", dc, true)
+	if stat_id == self.actor.STAT_CON then
+		local multi_life = 4 + (self.actor.inc_resource_multi.life or 0)
+		text:add("Max life: ", color, ("%0.2f"):format(diff * multi_life), dc, true)
+		text:add("Physical save: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+	elseif stat_id == self.actor.STAT_WIL then
+		if self.actor:knowTalent(self.actor.T_MANA_POOL) then
+			local multi_mana = 5 + (self.actor.inc_resource_multi.mana or 0)
+			text:add("Max mana: ", color, ("%0.2f"):format(diff * multi_mana), dc, true)
+		end
+		if self.actor:knowTalent(self.actor.T_STAMINA_POOL) then
+			local multi_stamina = 2.5 + (self.actor.inc_resource_multi.stamina or 0)
+			text:add("Max stamina: ", color, ("%0.2f"):format(diff * multi_stamina), dc, true)
+		end
+		if self.actor:knowTalent(self.actor.T_PSI_POOL) then
+			local multi_psi = 1 + (self.actor.inc_resource_multi.psi or 0)
+			text:add("Max psi: ", color, ("%0.2f"):format(diff * multi_psi), dc, true)
+		end
+		text:add("Mindpower: ", color, ("%0.2f"):format(diff * 0.7), dc, true)
+		text:add("Mental save: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+		text:add("Spell save: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+		if self.actor.use_psi_combat then
+			text:add("Accuracy: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+		end
+	elseif stat_id == self.actor.STAT_STR then
+		text:add("Physical power: ", color, ("%0.2f"):format(diff), dc, true)
+		text:add("Max encumberance: ", color, ("%0.2f"):format(diff * 1.8), dc, true)
+		text:add("Physical save: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+	elseif stat_id == self.actor.STAT_CUN then
+		text:add("Crit. chance: ", color, ("%0.2f"):format(diff * 0.3), dc, true)
+		text:add("Mental save: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+		text:add("Mindpower: ", color, ("%0.2f"):format(diff * 0.4), dc, true)
+		if self.actor.use_psi_combat then
+			text:add("Accuracy: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+		end
+	elseif stat_id == self.actor.STAT_MAG then
+		text:add("Spell save: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+		text:add("Spellpower: ", color, ("%0.2f"):format(diff * 1), dc, true)
+	elseif stat_id == self.actor.STAT_DEX then
+		text:add("Defense: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+		text:add("Ranged defense: ", color, ("%0.2f"):format(diff * 0.35), dc, true)
+		text:add("Accuracy: ", color, ("%0.2f"):format(diff), dc, true)
+	end
+
+	if self.actor.player and self.desc_def and self.desc_def.getStatDesc and self.desc_def.getStatDesc(stat_id, self.actor) then
+		text:add({"color", "LIGHT_BLUE"}, "Class powers:", dc, true)
+		text:add(self.desc_def.getStatDesc(stat_id, self.actor))
+	end
+	return text
+end
+
+
+function _M:getTalentDesc(item)
+	local text = tstring{}
+
+ 	text:add({"color", "GOLD"}, {"font", "bold"}, util.getval(item.rawname, item), {"color", "LAST"}, {"font", "normal"})
+	text:add(true, true)
+
+	if item.type then
+		text:add({"color",0x00,0xFF,0xFF}, "Talent Category", true)
+		text:add({"color",0x00,0xFF,0xFF}, "A talent category contains talents you may learn. You gain a talent category point at level 10, 20 and 30. You may also find trainers or artifacts that allow you to learn more.\nA talent category point can be used either to learn a new category or increase the mastery of a known one.", true, true, {"color", "WHITE"})
+
+		if self.actor.talents_types_def[item.type].generic then
+			text:add({"color",0x00,0xFF,0xFF}, "Generic talent tree", true)
+			text:add({"color",0x00,0xFF,0xFF}, "A generic talent allows you to perform various utility actions and improve your character. It represents a skill anybody can learn (should you find a trainer for it). You gain one point every level (except every 5th level). You may also find trainers or artifacts that allow you to learn more.", true, true, {"color", "WHITE"})
+		else
+			text:add({"color",0x00,0xFF,0xFF}, "Class talent tree", true)
+			text:add({"color",0x00,0xFF,0xFF}, "A class talent allows you to perform new combat moves, cast spells, and improve your character. It represents the core function of your class. You gain one point every level and two every 5th level. You may also find trainers or artifacts that allow you to learn more.", true, true, {"color", "WHITE"})
+		end
+
+		text:add(self.actor:getTalentTypeFrom(item.type).description)
+
+	else
+		local t = self.actor:getTalentFromId(item.talent)
+
+		if self:isUnlearnable(t, true) then
+			local max = tostring(self.actor:lastLearntTalentsMax(t.generic and "generic" or "class"))
+			text:add({"color","LIGHT_BLUE"}, "This talent was recently learnt, you can still unlearn it.", true, "The last ", max, t.generic and " generic" or " class", " talents you learnt are always unlearnable.", {"color","LAST"}, true, true)
+		elseif t.no_unlearn_last then
+			text:add({"color","YELLOW"}, "This talent can alter the world in a permanent way, as such you can never unlearn it once known.", {"color","LAST"}, true, true)
+		end
+
+		local traw = self.actor:getTalentLevelRaw(t.id)
+		local diff = function(i2, i1, res)
+			res:add({"color", "LIGHT_GREEN"}, i1, {"color", "LAST"}, " [->", {"color", "YELLOW_GREEN"}, i2, {"color", "LAST"}, "]")
+		end
+		if traw == 0 then
+			local req = self.actor:getTalentReqDesc(item.talent, 1):toTString():tokenize(" ()[]")
+			text:add{"color","WHITE"}
+			text:add({"font", "bold"}, "First talent level: ", tostring(traw+1), {"font", "normal"})
+			text:add(true)
+			text:merge(req)
+			text:merge(self.actor:getTalentFullDescription(t, 1))
+		elseif traw < self:getMaxTPoints(t) then
+			local req = self.actor:getTalentReqDesc(item.talent):toTString():tokenize(" ()[]")
+			local req2 = self.actor:getTalentReqDesc(item.talent, 1):toTString():tokenize(" ()[]")
+			text:add{"color","WHITE"}
+			text:add({"font", "bold"}, traw == 0 and "Next talent level" or "Current talent level: ", tostring(traw), " [-> ", tostring(traw + 1), "]", {"font", "normal"})
+			text:add(true)
+			text:merge(req2:diffWith(req, diff))
+			text:merge(self.actor:getTalentFullDescription(t, 1):diffWith(self.actor:getTalentFullDescription(t), diff))
+		else
+			local req = self.actor:getTalentReqDesc(item.talent)
+			text:add({"font", "bold"}, "Current talent level: "..traw, {"font", "normal"})
+			text:add(true)
+			text:merge(req)
+			text:merge(self.actor:getTalentFullDescription(t))
+		end
+	end
+
+	return text
+end
+
+function _M:onUseTalent(item, inc)
+	if item.type then
+		self:learnType(item.type, inc)
+		item.shown = (self.actor.__hidden_talent_types[item.type] == nil and self.actor:knowTalentType(item.type)) or (self.actor.__hidden_talent_types[item.type] ~= nil and not self.actor.__hidden_talent_types[item.type])
+		local t = (item.isgeneric==0 and self.c_gtree or self.c_ctree)
+		t:redrawAllItems()
+	elseif item.talent then
+		self:learnTalent(item.talent, inc)
+		local t = (item.isgeneric==0 and self.c_gtree or self.c_ctree)
+		t:redrawAllItems()
+	elseif item.stat then
+		self:incStat(item.stat, inc and 1 or -1)
+		self.c_stat:redrawAllItems()
+		self.c_ctree:redrawAllItems()
+		self.c_gtree:redrawAllItems()
+	end
+
+	self.b_stat.text = "Stats: "..self.actor.unused_stats
+	self.b_stat:generate()
+	self.b_class.text = "Class points: "..self.actor.unused_talents
+	self.b_class:generate()
+	self.b_generic.text = "Generic points: "..self.actor.unused_generics
+	self.b_generic:generate()
+	self.b_types.text = "Category points: "..self.actor.unused_talents_types
+	self.b_types:generate()
+end
+
+function _M:updateTooltip()
+	self.c_gtree:updateTooltip()
+	self.c_ctree:updateTooltip()
+	self.c_stat:updateTooltip()
+	if self.focus_ui and self.focus_ui.ui and self.focus_ui.ui.updateTooltip then self.focus_ui.ui:updateTooltip() end
+end