diff --git a/game/engine/Actor.lua b/game/engine/Actor.lua
index 703b0000221cf37dec337d88c33f994eff6eb600..68f34478f9314bd6a9a478e0d9c571651c510d35 100644
--- a/game/engine/Actor.lua
+++ b/game/engine/Actor.lua
@@ -104,6 +104,12 @@ function _M:canMove(x, y, terrain_only)
 	end
 end
 
+--- Get the "path string" for this actor
+-- See Map:addPathString() for more info
+function _M:getPathString()
+	return ""
+end
+
 --- Teleports randomly to a passable grid
 -- @param x the coord of the teleporatation
 -- @param y the coord of the teleporatation
@@ -133,17 +139,28 @@ end
 
 --- Knock back the actor
 function _M:knockback(srcx, srcy, dist)
+	print("[KNOCKBACK] from", srcx, srcy, "over", dist)
+
 	local l = line.new(srcx, srcy, self.x, self.y, true)
 	local lx, ly = l(true)
+	local ox, oy = lx, ly
 	dist = dist - 1
 
+	print("[KNOCKBACK] try", lx, ly, dist)
+
 	while game.level.map:isBound(lx, ly) and not game.level.map:checkAllEntities(lx, ly, "block_move", self) and dist > 0 do
 		dist = dist - 1
+		ox, oy = lx, ly
 		lx, ly = l(true)
+		print("[KNOCKBACK] try", lx, ly, dist, "::", game.level.map:checkAllEntities(lx, ly, "block_move", self))
 	end
 
 	if game.level.map:isBound(lx, ly) and not game.level.map:checkAllEntities(lx, ly, "block_move", self) then
+		print("[KNOCKBACK] ok knocked to", lx, ly, "::", game.level.map:checkAllEntities(lx, ly, "block_move", self))
 		self:move(lx, ly, true)
+	elseif game.level.map:isBound(ox, oy) and not game.level.map:checkAllEntities(ox, oy, "block_move", self) then
+		print("[KNOCKBACK] failsafe knocked to", ox, oy, "::", game.level.map:checkAllEntities(ox, oy, "block_move", self))
+		self:move(ox, oy, true)
 	end
 end
 
diff --git a/game/engine/Astar.lua b/game/engine/Astar.lua
index 7733af4fddb8b3b6e8988391dbe9f0caf77c4340..fa87eafaa07711c73228194b6a13b1ad0f8abe64 100644
--- a/game/engine/Astar.lua
+++ b/game/engine/Astar.lua
@@ -73,20 +73,42 @@ function _M:calc(sx, sy, tx, ty, use_has_seen)
 	local f_score = {[start] = self:heuristic(sx, sy, tx, ty)}
 	local came_from = {}
 
-	local checkPos = function(node, nx, ny)
-		local nnode = self:toSingle(nx, ny)
-		if not closed[nnode] and self.map:isBound(nx, ny) and ((use_has_seen and not self.map.has_seens(nx, ny)) or not self.map:checkEntity(nx, ny, Map.TERRAIN, "block_move", self.actor, nil, true)) then
-			local tent_g_score = g_score[node] + 1 -- we can adjust hre for difficult passable terrain
-			local tent_is_better = false
-			if not open[nnode] then open[nnode] = true; tent_is_better = true
-			elseif tent_g_score < g_score[nnode] then tent_is_better = true
+	local cache = self.map._fovcache.path_caches[self.actor:getPathString()]
+	local checkPos
+	if cache then
+		checkPos = function(node, nx, ny)
+			local nnode = self:toSingle(nx, ny)
+			if not closed[nnode] and self.map:isBound(nx, ny) and ((use_has_seen and not self.map.has_seens(nx, ny)) or not cache:get(nx, ny)) then
+				local tent_g_score = g_score[node] + 1 -- we can adjust hre for difficult passable terrain
+				local tent_is_better = false
+				if not open[nnode] then open[nnode] = true; tent_is_better = true
+				elseif tent_g_score < g_score[nnode] then tent_is_better = true
+				end
+
+				if tent_is_better then
+					came_from[nnode] = node
+					g_score[nnode] = tent_g_score
+					h_score[nnode] = self:heuristic(tx, ty, nx, ny)
+					f_score[nnode] = g_score[nnode] + h_score[nnode]
+				end
 			end
+		end
+	else
+		checkPos = function(node, nx, ny)
+			local nnode = self:toSingle(nx, ny)
+			if not closed[nnode] and self.map:isBound(nx, ny) and ((use_has_seen and not self.map.has_seens(nx, ny)) or not self.map:checkEntity(nx, ny, Map.TERRAIN, "block_move", self.actor, nil, true)) then
+				local tent_g_score = g_score[node] + 1 -- we can adjust hre for difficult passable terrain
+				local tent_is_better = false
+				if not open[nnode] then open[nnode] = true; tent_is_better = true
+				elseif tent_g_score < g_score[nnode] then tent_is_better = true
+				end
 
-			if tent_is_better then
-				came_from[nnode] = node
-				g_score[nnode] = tent_g_score
-				h_score[nnode] = self:heuristic(tx, ty, nx, ny)
-				f_score[nnode] = g_score[nnode] + h_score[nnode]
+				if tent_is_better then
+					came_from[nnode] = node
+					g_score[nnode] = tent_g_score
+					h_score[nnode] = self:heuristic(tx, ty, nx, ny)
+					f_score[nnode] = g_score[nnode] + h_score[nnode]
+				end
 			end
 		end
 	end
diff --git a/game/engine/Map.lua b/game/engine/Map.lua
index 9f8f9ac86cfe3b55ba8e563d9b1815c54992bb35..a649c441a46999c604dafb5357f403ea7affeb4e 100644
--- a/game/engine/Map.lua
+++ b/game/engine/Map.lua
@@ -165,6 +165,7 @@ function _M:init(w, h)
 	self.has_seens = {}
 	self.remembers = {}
 	self.effects = {}
+	self.path_strings = {}
 	for i = 0, w * h - 1 do self.map[i] = {} end
 
 	self:loaded()
@@ -200,7 +201,23 @@ function _M:makeCMap()
 		block_sight = core.fov.newCache(self.w, self.h),
 		block_esp = core.fov.newCache(self.w, self.h),
 		block_sense = core.fov.newCache(self.w, self.h),
+		path_caches = {},
 	}
+	for i, ps in ipairs(self.path_strings) do
+		self._fovcache.path_caches[ps] = core.fov.newCache(self.w, self.h)
+	end
+end
+
+--- Adds a "path string" to the map
+-- "Path strings" are strings defining what terrain an actor can cross. Their format is left to the module to decide (by overloading Actor:getPathString() )<br/>
+-- They are totally optional as they re only used to compute A* paths adn the likes and even then the algorithms still work without them, only slower<br/>
+-- If you use them the block_move function of your Grid class must be able to handle either an actor or a "path string" as their third argument
+function _M:addPathString(ps)
+	for i, eps in ipairs(self.path_strings) do
+		if eps == ps then return end
+	end
+	self.path_strings[#self.path_strings+1] = ps
+	if self._fovcache then self._fovcache.path_caches[ps] = core.fov.newCache(self.w, self.h) end
 end
 
 function _M:loaded()
@@ -291,7 +308,7 @@ function _M:cleanFOV()
 end
 
 --- Updates the map on the given spot
--- This updates many things, from the C map object, the FOC caches, the minimap if it exists, ...
+-- This updates many things, from the C map object, the FOV caches, the minimap if it exists, ...
 function _M:updateMap(x, y)
 	local g = self(x, y, TERRAIN)
 	local o = self(x, y, OBJECT)
@@ -354,6 +371,12 @@ function _M:updateMap(x, y)
 	else self._fovcache.block_esp:set(x, y, false) end
 	if self:checkAllEntities(x, y, "block_sense", self.actor_player) then self._fovcache.block_sense:set(x, y, true)
 	else self._fovcache.block_sense:set(x, y, false) end
+
+	-- Update path caches from path strings
+	for i = 1, #self.path_strings do
+		local ps = self.path_strings[i]
+		self._fovcache.path_caches[ps]:set(x, y, self:checkEntity(x, y, "block_move", ps, false, true))
+	end
 end
 
 --- Sets/gets a value from the map
diff --git a/game/modules/example/class/Actor.lua b/game/modules/example/class/Actor.lua
index 7e54dd2dd92fe3cbca41913329cb457b56ef4a10..e79da0035897e375b961ec8cbd13f751f5354aca 100644
--- a/game/modules/example/class/Actor.lua
+++ b/game/modules/example/class/Actor.lua
@@ -161,7 +161,7 @@ function _M:preUseTalent(ab, silent)
 			return false
 		end
 	else
-		if ab.power and self:getMana() < ab.power * (100 + self.fatigue) / 100 then
+		if ab.power and self:getPower() < ab.power then
 			game.logPlayer(self, "You do not have enough power to cast %s.", ab.name)
 			return false
 		end
@@ -223,7 +223,7 @@ function _M:getTalentFullDescription(t)
 	else d[#d+1] = "#6fff83#Use mode: #00FF00#Activable"
 	end
 
-	if t.power or t.sustain_power then d[#d+1] = "#6fff83#Mana cost: #7fffd4#"..(t.power or t.sustain_power) end
+	if t.power or t.sustain_power then d[#d+1] = "#6fff83#Power cost: #7fffd4#"..(t.power or t.sustain_power) end
 	if self:getTalentRange(t) > 1 then d[#d+1] = "#6fff83#Range: #FFFFFF#"..self:getTalentRange(t)
 	else d[#d+1] = "#6fff83#Range: #FFFFFF#melee/personal"
 	end
diff --git a/game/modules/example/data/birth/descriptors.lua b/game/modules/example/data/birth/descriptors.lua
index e3c71b1907915f235ef837e458d2e917a5c37be5..64f6113656c2bec3f749ad28a715ac178670766b 100644
--- a/game/modules/example/data/birth/descriptors.lua
+++ b/game/modules/example/data/birth/descriptors.lua
@@ -36,7 +36,10 @@ newBirthDescriptor{
 	name = "Destroyer",
 	desc =
 	{
-		"Boom!",
+		"Crashhhhh!",
+	},
+	talents = {
+		[ActorTalents.T_KICK]=1,
 	},
 }
 
@@ -47,4 +50,7 @@ newBirthDescriptor{
 	{
 		"Zshhhhhhhh!",
 	},
+	talents = {
+		[ActorTalents.T_ACID_SPRAY]=1,
+	},
 }
diff --git a/game/modules/example/data/talents.lua b/game/modules/example/data/talents.lua
index da57bd68b205d77cc81be4d184b9a2d487f148f8..02d1c92317aba8f1a791ba7613ab7676fc34e3a1 100644
--- a/game/modules/example/data/talents.lua
+++ b/game/modules/example/data/talents.lua
@@ -25,10 +25,36 @@ newTalent{
 	points = 1,
 	cooldown = 6,
 	power = 2,
+	range = 1,
 	action = function(self, t)
+		local tg = {type="hit", range=self:getTalentRange(t)}
+		local x, y, target = self:getTarget(tg)
+		if not x or not y or not target then return nil end
+		if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end
+
+		target:knockback(self.x, self.y, 2 + self:getDex())
 		return true
 	end,
 	info = function(self, t)
 		return "Kick!"
 	end,
 }
+
+newTalent{
+	name = "Acid Spray",
+	type = {"role/combat", 1},
+	points = 1,
+	cooldown = 6,
+	power = 2,
+	range = 6,
+	action = function(self, t)
+		local tg = {type="ball", range=self:getTalentRange(t), radius=1, talent=t}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+		self:project(tg, x, y, DamageType.ACID, 1 + self:getDex(), {type="acid"})
+		return true
+	end,
+	info = function(self, t)
+		return "Zshhhhhhhhh!"
+	end,
+}
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 3daac2c678ca1b2d752d3c2667dbb23f8d38ffb7..7a4059f5e87e4b34de3d7977c4749da506a78652 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -205,6 +205,18 @@ function _M:move(x, y, force)
 	return moved
 end
 
+--- Get the "path string" for this actor
+-- See Map:addPathString() for more info
+function _M:getPathString()
+	local ps = self.open_door and "open_door=true;can_pass={" or "can_pass={"
+	for what, check in pairs(self.can_pass) do
+		ps = ps .. what.."="..check..","
+	end
+	ps = ps.."}"
+	print("[PATH STRING] for", self.name, " :=: ", ps)
+	return ps
+end
+
 --- Blink through walls
 function _M:probabilityTravel(x, y, dist)
 	local dirx, diry = x - self.x, y - self.y
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 055a6a79cf3d2058e3edd61fbf54c27aa822c117..647ca26579eb3d7950728aeb37798706972bdd7a 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -301,7 +301,14 @@ function _M:changeLevel(lev, zone)
 		self:stopMusic()
 	end
 
+	-- Update the minimap
 	self:setupMiniMap()
+
+	-- Tell the map to use path strings to speed up path calculations
+	for uid, e in pairs(self.level.entities) do
+		self.level.map:addPathString(e:getPathString())
+	end
+	self.level.map:redisplay()
 end
 
 function _M:getPlayer()
diff --git a/game/modules/tome/class/Grid.lua b/game/modules/tome/class/Grid.lua
index f6df667897e586d0233680e33dcbeee9a9c34af2..7a2c6fb32239cb25dfe5b9b0a5e346eb81f7635e 100644
--- a/game/modules/tome/class/Grid.lua
+++ b/game/modules/tome/class/Grid.lua
@@ -28,16 +28,22 @@ function _M:init(t, no_default)
 end
 
 function _M:block_move(x, y, e, act, couldpass)
+	-- Path strings
+	if not e then e = {}
+	elseif type(e) == "string" then
+		e = loadstring(e)()
+	end
+
 	-- Open doors
-	if self.door_opened and act then
-		game.level.map(x, y, engine.Map.TERRAIN, game.zone.grid_list.DOOR_OPEN)
+	if self.door_opened and e.open_door and act then
+		game.level.map(x, y, engine.Map.TERRAIN, game.zone.grid_list[self.door_opened])
 		return true
-	elseif self.door_opened and not couldpass then
+	elseif self.door_opened and e.open_door and not couldpass then
 		return true
 	end
 
 	-- Pass walls
-	if e and self.can_pass and e.can_pass then
+	if self.can_pass and e.can_pass then
 		for what, check in pairs(e.can_pass) do
 			if self.can_pass[what] and self.can_pass[what] <= check then return false end
 		end
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index 64eedde2f17e352d69414717b9cf68d2ae432e5d..22163119faf8f6468b5295808a5262daf78e7950 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -63,6 +63,7 @@ function _M:init(t, no_default)
 	t.color_b=t.color_b or 230
 
 	t.player = true
+	t.open_door = true
 	t.type = t.type or "humanoid"
 	t.subtype = t.subtype or "player"
 	t.faction = t.faction or "players"
diff --git a/game/modules/tome/data/general/npcs/aquatic_demon.lua b/game/modules/tome/data/general/npcs/aquatic_demon.lua
index 18637d801f6ea03ca74fb905de8c699822119eb6..69fc005bc2e6075c07a7db2c0f88773df5eafab3 100644
--- a/game/modules/tome/data/general/npcs/aquatic_demon.lua
+++ b/game/modules/tome/data/general/npcs/aquatic_demon.lua
@@ -33,6 +33,7 @@ newEntity{
 	combat_armor = 1, combat_def = 1,
 	combat = { dam=5, atk=15, apr=7, dammod={str=0.6} },
 	max_life = resolvers.rngavg(100,120),
+	open_door = true,
 	rank = 2,
 	size_category = 3,
 	can_breath={water=1},
diff --git a/game/modules/tome/data/general/npcs/ghoul.lua b/game/modules/tome/data/general/npcs/ghoul.lua
index 4f80d4ce9443274bc73c232f195f2d9824ebea58..fd07dba3bdd76953ee8bd60352ff133fd8400888 100644
--- a/game/modules/tome/data/general/npcs/ghoul.lua
+++ b/game/modules/tome/data/general/npcs/ghoul.lua
@@ -37,6 +37,8 @@ newEntity{
 
 	resolvers.tmasteries{ ["technique/other"]=1, },
 
+	open_door = true,
+
 	blind_immune = 1,
 	see_invisible = 2,
 	undead = 1,
diff --git a/game/modules/tome/data/general/npcs/minotaur.lua b/game/modules/tome/data/general/npcs/minotaur.lua
index de2e7417a4bbf420138adb3b27ce9b580ab0cada..17ef1ca51d8ea062f4382ae56bbc896286c9075f 100644
--- a/game/modules/tome/data/general/npcs/minotaur.lua
+++ b/game/modules/tome/data/general/npcs/minotaur.lua
@@ -36,6 +36,8 @@ newEntity{
 	rank = 2,
 	size_category = 4,
 
+	open_door = true,
+
 	autolevel = "warrior",
 	ai = "dumb_talented_simple", ai_state = { talent_in=5, },
 	energy = { mod=1.2 },
diff --git a/game/modules/tome/data/general/npcs/mummy.lua b/game/modules/tome/data/general/npcs/mummy.lua
index 5e7ac091c9a6530df5b750aa672c54b5646ad72f..9a04641a89d6b9c8770c128ee9f3268944474c85 100644
--- a/game/modules/tome/data/general/npcs/mummy.lua
+++ b/game/modules/tome/data/general/npcs/mummy.lua
@@ -34,6 +34,8 @@ newEntity{
 	rank = 2,
 	size_category = 3,
 
+	open_door = true,
+
 	resolvers.tmasteries{ ["technique/2hweapon"]=1, },
 
 	blind_immune = 1,
diff --git a/game/modules/tome/data/general/npcs/orc.lua b/game/modules/tome/data/general/npcs/orc.lua
index 5d6551a0f329e4c2fb382182379ca46ec2a7f064..29d12693e6aea35ccaba7171a125f4e18a3f1b28 100644
--- a/game/modules/tome/data/general/npcs/orc.lua
+++ b/game/modules/tome/data/general/npcs/orc.lua
@@ -34,6 +34,8 @@ newEntity{
 	rank = 2,
 	size_category = 3,
 
+	open_door = true,
+
 	autolevel = "warrior",
 	ai = "dumb_talented_simple", ai_state = { talent_in=3, },
 	energy = { mod=1 },
diff --git a/game/modules/tome/data/general/npcs/skeleton.lua b/game/modules/tome/data/general/npcs/skeleton.lua
index 09a9e38cb75337dc4308642559be18c674bf4ad9..58ed27472590ca146151a200ee21f48a257b53a7 100644
--- a/game/modules/tome/data/general/npcs/skeleton.lua
+++ b/game/modules/tome/data/general/npcs/skeleton.lua
@@ -39,6 +39,8 @@ newEntity{
 
 	resolvers.tmasteries{ ["technique/other"]=0.3, ["technique/2hweapon-offense"]=0.3, ["technique/2hweapon-cripple"]=0.3 },
 
+	open_door = true,
+
 	blind_immune = 1,
 	fear_immune = 1,
 	see_invisible = 2,
diff --git a/game/modules/tome/data/general/npcs/thieve.lua b/game/modules/tome/data/general/npcs/thieve.lua
index ca6737f8bf212445882afe09c8b525ce8284f50a..98b27b627a9a92bd93371df3a6796fe26517a89c 100644
--- a/game/modules/tome/data/general/npcs/thieve.lua
+++ b/game/modules/tome/data/general/npcs/thieve.lua
@@ -39,6 +39,8 @@ newEntity{
 	rank = 2,
 	size_category = 3,
 
+	open_door = true,
+
 	autolevel = "rogue",
 	ai = "dumb_talented_simple", ai_state = { talent_in=5, },
 	energy = { mod=1 },
diff --git a/game/modules/tome/data/general/npcs/troll.lua b/game/modules/tome/data/general/npcs/troll.lua
index daa2985dfac68a35f03a99132c818da2b8325099..af16334cffb44448ad854a027cba8ee8c6ef2fd1 100644
--- a/game/modules/tome/data/general/npcs/troll.lua
+++ b/game/modules/tome/data/general/npcs/troll.lua
@@ -41,6 +41,8 @@ newEntity{
 	energy = { mod=1 },
 	stats = { str=20, dex=8, mag=6, con=16 },
 
+	open_door = true,
+
 	resolvers.tmasteries{ ["technique/other"]=0.3 },
 
 	resists = { [DamageType.FIRE] = -50 },
diff --git a/game/modules/tome/data/general/npcs/vampire.lua b/game/modules/tome/data/general/npcs/vampire.lua
index 60ad6044e8304b17782ea321b19a8ab4a0f98c31..099e9e7627607f14540d3bfd7c2c5f42346e0022 100644
--- a/game/modules/tome/data/general/npcs/vampire.lua
+++ b/game/modules/tome/data/general/npcs/vampire.lua
@@ -55,6 +55,8 @@ newEntity{
 	size_category = 3,
 	rank = 2,
 
+	open_door = true,
+
 	resolvers.tmasteries{ ["technique/other"]=0.5, ["spell/phantasm"]=0.8, },
 
 	resists = { [DamageType.COLD] = 80, [DamageType.NATURE] = 80, [DamageType.LIGHT] = -50,  },
diff --git a/game/modules/tome/data/general/npcs/wight.lua b/game/modules/tome/data/general/npcs/wight.lua
index 0f7717cbe5e49003761b21d5f554ff1269d68c39..14d2b4f756e0c178ec7fc76d2cf987c6bc25550e 100644
--- a/game/modules/tome/data/general/npcs/wight.lua
+++ b/game/modules/tome/data/general/npcs/wight.lua
@@ -45,6 +45,8 @@ newEntity{
 	rank = 2,
 	size_category = 3,
 
+	open_door = true,
+
 	resolvers.tmasteries{ ["technique/other"]=0.3, ["spell/air"]=0.3, ["spell/fire"]=0.3 },
 
 	resists = { [DamageType.COLD] = 80, [DamageType.FIRE] = 20, [DamageType.LIGHTNING] = 40, [DamageType.PHYSICAL] = 35, [DamageType.LIGHT] = -50, },
diff --git a/src/core_lua.c b/src/core_lua.c
index d4a38880c955cfe7393210d60949d23352c49a78..b9dbf5e42407429a7213ccaf73214be0f7c0bfa5 100644
--- a/src/core_lua.c
+++ b/src/core_lua.c
@@ -274,6 +274,16 @@ static int lua_fovcache_set(lua_State *L)
 	return 0;
 }
 
+static int lua_fovcache_get(lua_State *L)
+{
+	struct lua_fovcache *cache = (struct lua_fovcache*)auxiliar_checkclass(L, "fov{cache}", 1);
+	int x = luaL_checknumber(L, 2);
+	int y = luaL_checknumber(L, 3);
+
+	lua_pushboolean(L, cache->cache[x + y * cache->w]);
+	return 1;
+}
+
 static const struct luaL_reg fovlib[] =
 {
 	{"new", lua_new_fov},
@@ -295,6 +305,7 @@ static const struct luaL_reg fov_reg[] =
 static const struct luaL_reg fovcache_reg[] =
 {
 	{"set", lua_fovcache_set},
+	{"get", lua_fovcache_get},
 	{NULL, NULL},
 };