diff --git a/game/engines/default/engine/Projectile.lua b/game/engines/default/engine/Projectile.lua
index 3ee1f020b867f703737791ea2431680fc0b8e805..a4afb08e6831d3c6a24190d6ccb80f35bd11b9b3 100644
--- a/game/engines/default/engine/Projectile.lua
+++ b/game/engines/default/engine/Projectile.lua
@@ -195,12 +195,14 @@ function _M:act()
 			if x and y then self:move(x, y) end
 			if act then self.src:projectDoAct(self.project.def.typ, self.project.def.tg, self.project.def.damtype, self.project.def.dam, self.project.def.particles, self.x, self.y, self.tmp_proj) end
 			if stop then
-				-- Correct the explosion source position if we exploded on terrain
-				local radius_x, radius_y
+				local block, hit, hit_radius = false, true, true
 				if self.project.def.typ.block_path then
-					_, radius_x, radius_y = self.project.def.typ:block_path(self.x, self.y)
+					block, hit, hit_radius = self.project.def.typ:block_path(self.x, self.y)
 				end
-				if not radius_x then
+				local radius_x, radius_y
+				if hit_radius then
+					radius_x, radius_y = self.x, self.y
+				else
 					radius_x, radius_y = self.old_x, self.old_y
 				end
 				game.level:removeEntity(self)
diff --git a/game/engines/default/engine/Target.lua b/game/engines/default/engine/Target.lua
index 5e2170f7a21949c639b2b8733fba8a085edb26fa..294dd5a3e1038a98b7c5be11a7d2ef40de9219a1 100644
--- a/game/engines/default/engine/Target.lua
+++ b/game/engines/default/engine/Target.lua
@@ -75,44 +75,46 @@ function _M:display(dispx, dispy)
 	local lx, ly = l()
 	local initial_dir = lx and coord_to_dir[lx - self.source_actor.x][ly - self.source_actor.y] or 5
 	local stop_x, stop_y = self.source_actor.x, self.source_actor.y
-	local stop_radius_x, stop_radius_y = stopx, stopy
-	local blocked = false
+	local stop_radius_x, stop_radius_y = self.source_actor.x, self.source_actor.y
+	local stopped = false
 	while lx and ly do
-		if s == self.sb then
-			stop_radius_x, stop_radius_y = stop_x, stop_y
+		local block, hit, hit_radius = false, true, true
+		if self.target_type.block_path then
+			block, hit, hit_radius = self.target_type:block_path(lx, ly)
+		end
+		
+		-- Update coordinates and set color
+		if hit and not stopped then
 			stop_x, stop_y = lx, ly
+		else
+			s = self.sr
+		end
+		if hit_radius and not stopped then
+			stop_radius_x, stop_radius_y = lx, ly
 		end
-		if self.target_type.min_range then
+		if self.target_type.min_range and not stopped then
 			-- Check if we should be "red"
 			if core.fov.distance(self.source_actor.x, self.source_actor.y, lx, ly) < self.target_type.min_range then
 				s = self.sr
 			-- Check if we were only "red" because of minimum distance
-			elseif s == self.sr and not blocked then
+			elseif s == self.sr then
 				s = self.sb
 			end
 		end
 		s:toScreen(self.display_x + (lx - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (ly - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
-		if self.target_type.block_path and self.target_type:block_path(lx, ly) then
+		if block then
 			s = self.sr
-			blocked = true
+			stopped = true
 		end
+		
 		lx, ly = l()
 	end
 	self.cursor:toScreen(self.display_x + (self.target.x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (self.target.y - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
 
-	-- Correct the explosion source position if we exploded on terrain
-	local radius_x, radius_y
-	if self.target_type.block_path and self.target_type.radius and self.target_type.radius > 0 then
-		_, radius_x, radius_y = self.target_type:block_path(stop_x, stop_y)
-	end
-	if not radius_x then
-		radius_x, radius_y = stop_radius_x, stop_radius_y
-	end
-
 	if self.target_type.ball and self.target_type.ball > 0 then
 		core.fov.calc_circle(
-			radius_x,
-			radius_y,
+			stop_radius_x,
+			stop_radius_y,
 			game.level.map.w,
 			game.level.map.h,
 			self.target_type.ball,
@@ -128,8 +130,8 @@ function _M:display(dispx, dispy)
 		nil)
 	elseif self.target_type.cone and self.target_type.cone > 0 then
 		core.fov.calc_beam(
-			radius_x,
-			radius_y,
+			stop_radius_x,
+			stop_radius_y,
 			game.level.map.w,
 			game.level.map.h,
 			self.target_type.cone,
@@ -164,7 +166,7 @@ end
 -- @param t.stop_block Boolean that stops the target on the first tile that has an entity that blocks move.
 -- @param t.range The range the target can be from the origin.
 -- @param t.pass_terrain Boolean that allows the target to pass through terrain to remembered tiles on the other side.
--- @param t.block_path(typ, lx, ly) Function called on each tile to determine if the targeting is blocked.  Automatically set when using t.typ, but the user can provide their own if they know what they are doing.
+-- @param t.block_path(typ, lx, ly) Function called on each tile to determine if the targeting is blocked.  Automatically set when using t.typ, but the user can provide their own if they know what they are doing.  It should return three arguments: block, hit, hit_radius
 -- @param t.block_radius(typ, lx, ly) Function called on each tile when projecting the radius to determine if the radius projection is blocked.  Automatically set when using t.typ, but the user can provide their own if they know what they are doing.
 function _M:getType(t)
 	if not t then return {} end
@@ -177,14 +179,27 @@ function _M:getType(t)
 		friendlyfire=true,
 		block_path = function(typ, lx, ly)
 			if not typ.no_restrict then
-				if typ.requires_knowledge and not game.level.map.remembers(lx, ly) and not game.level.map.seens(lx, ly) then return true end
-				if not typ.pass_terrain and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true
+				if typ.range and typ.source_actor and typ.source_actor.x then
+					local dist = core.fov.distance(typ.source_actor.x, typ.source_actor.y, lx, ly)
+					-- Need to handle range 1 separately to allow diagonal hits
+					if typ.range == 1 then
+						if math.floor(dist) > 1 then return true, false, false end
+					else
+						if dist > typ.range then return true, false, false end
+					end
+				end
+				if typ.requires_knowledge and not game.level.map.remembers(lx, ly) and not game.level.map.seens(lx, ly) then
+					return true, false, false
+				end
+				if not typ.pass_terrain and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then
+					return true, true, false
 				-- If we explode due to something other than terrain, then we should explode ON the tile, not before it
-				elseif typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then return true, lx, ly end
-				if typ.range and typ.source_actor and typ.source_actor.x and core.fov.distance(typ.source_actor.x, typ.source_actor.y, lx, ly) > typ.range then return true end
+				elseif typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then
+					return true, true, true
+				end
 			end
 			-- If we don't block the path, then the explode point should be here
-			return false, lx, ly
+			return false, true, true
 		end,
 		block_radius=function(typ, lx, ly)
 			return not typ.no_restrict and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move")
diff --git a/game/engines/default/engine/interface/ActorProject.lua b/game/engines/default/engine/interface/ActorProject.lua
index 22933c49610cb5b7c7720fce4c417f7353c4aa17..42f023e89137a3820296faf8cb3c4aacdab1e414 100644
--- a/game/engines/default/engine/interface/ActorProject.lua
+++ b/game/engines/default/engine/interface/ActorProject.lua
@@ -60,42 +60,60 @@ function _M:project(t, x, y, damtype, dam, particles)
 	lx, ly = l()
 	local initial_dir = lx and coord_to_dir[lx - srcx][ly - srcy] or 5
 	while lx and ly do
-		stop_radius_x, stop_radius_y = stop_x, stop_y
-		stop_x, stop_y = lx, ly
-
-		-- Deal damage: beam
-		if typ.line then addGrid(lx, ly) end
-
-		-- Call the on project of the target grid if possible
-		if not t.bypass and game.level.map:checkAllEntities(lx, ly, "on_project", self, t, lx, ly, damtype, dam, particles) then
-			return
+		local block, hit, hit_radius = false, true, true
+		if typ.block_path then
+			block, hit, hit_radius = typ:block_path(lx, ly)
+		end
+		if hit then
+			stop_x, stop_y = lx, ly
+			-- Deal damage: beam
+			if typ.line then addGrid(lx, ly) end
+			-- WHAT DOES THIS DO AGAIN?
+			-- Call the on project of the target grid if possible
+			if not t.bypass and game.level.map:checkAllEntities(lx, ly, "on_project", self, t, lx, ly, damtype, dam, particles) then
+				return
+			end
+		end
+		if hit_radius then
+			stop_radius_x, stop_radius_y = lx, ly
 		end
 
-		if typ.block_path and typ:block_path(lx, ly) then break end
+		if block then break end
 		lx, ly = l()
 	end
 
-	-- Correct the explosion source position if we exploded on terrain
-	local radius_x, radius_y
-	if typ.block_path then
-		_, radius_x, radius_y = typ:block_path(stop_x, stop_y)
-	end
-	if not radius_x then
-		radius_x, radius_y = stop_radius_x, stop_radius_y
-	end
 	if typ.ball and typ.ball > 0 then
-		core.fov.calc_circle(radius_x, radius_y, game.level.map.w, game.level.map.h, typ.ball, function(_, px, py)
-			-- Deal damage: ball
-			addGrid(px, py)
-			if typ.block_radius and typ:block_radius(px, py) then return true end
-		end, function()end, nil)
+		core.fov.calc_circle(
+			stop_radius_x,
+			stop_radius_y,
+			game.level.map.w,
+			game.level.map.h,
+			typ.ball,
+			function(_, px, py)
+				if typ.block_radius and typ:block_radius(px, py) then return true end
+			end,
+			function(_, px, py)
+				-- Deal damage: ball
+				addGrid(px, py)
+			end,
+		nil)
 		addGrid(stop_x, stop_y)
 	elseif typ.cone and typ.cone > 0 then
-		core.fov.calc_beam(radius_x, radius_y, game.level.map.w, game.level.map.h, typ.cone, initial_dir, typ.cone_angle, function(_, px, py)
-			-- Deal damage: cone
-			addGrid(px, py)
-			if typ.block_radius and typ:block_radius(px, py) then return true end
-		end, function()end, nil)
+		core.fov.calc_beam(
+			stop_radius_x,
+			stop_radius_y,
+			game.level.map.w,
+			game.level.map.h,
+			typ.cone,
+			initial_dir,
+			typ.cone_angle,
+			function(_, px, py)
+				if typ.block_radius and typ:block_radius(px, py) then return true end
+			end,
+			function(_, px, py)
+				addGrid(px, py)
+			end,
+		nil)
 		addGrid(stop_x, stop_y)
 	else
 		-- Deal damage: single
@@ -154,19 +172,19 @@ function _M:canProject(t, x, y)
 	local l = line.new(self.x, self.y, x, y)
 	lx, ly = l()
 	while lx and ly do
-		stop_radius_x, stop_radius_y = stop_x, stop_y
-		stop_x, stop_y = lx, ly
-		if typ.block_path and typ:block_path(lx, ly) then break end
-		lx, ly = l()
-	end
+		local block, hit, hit_radius = false, true, true
+		if typ.block_path then
+			block, hit, hit_radius = typ:block_path(lx, ly)
+		end
+		if hit then
+			stop_x, stop_y = lx, ly
+		end
+		if hit_radius then
+			stop_radius_x, stop_radius_y = lx, ly
+		end
 
-	-- Correct the explosion source position if we exploded on terrain
-	local radius_x, radius_y
-	if typ.block_path then
-		_, radius_x, radius_y = typ:block_path(stop_x, stop_y)
-	end
-	if not radius_x then
-		radius_x, radius_y = stop_radius_x, stop_radius_y
+		if block then break end
+		lx, ly = l()
 	end
 
 	-- Check for minimum range
@@ -175,7 +193,7 @@ function _M:canProject(t, x, y)
 	end
 
 	if stop_x == x and stop_y == y then return true, stop_x, stop_y, stop_x, stop_y end
-	return false, stop_x, stop_y, radius_x, radius_y
+	return false, stop_x, stop_y, stop_radius_x, stop_radius_y
 end
 
 --- Project damage to a distance using a moving projectile
@@ -196,6 +214,13 @@ function _M:projectile(t, x, y, damtype, dam, particles)
 	game.zone:addEntity(game.level, proj, "projectile", self.x, self.y)
 end
 
+-- @param typ a target type table
+-- @param tgtx the target's x-coordinate
+-- @param tgty the target's y-coordinate
+-- @param x the projectile's x-coordinate
+-- @param y the projectile's x-coordinate
+-- @param srcx the sources's x-coordinate
+-- @param srcx the source's x-coordinate
 -- @return lx x-coordinate the projectile travels to next
 -- @return ly y-coordinate the projectile travels to next
 -- @return act should we call projectDoAct (usually only for beam)
@@ -210,10 +235,24 @@ function _M:projectDoMove(typ, tgtx, tgty, x, y, srcx, srcy)
 	if lx and ly then lx, ly = l() end
 
 	if lx and ly then
-		if typ.block_path and typ:block_path(lx, ly) then return lx, ly, false, true end
+		local block, hit, hit_radius = false, true, true
+		if typ.block_path then
+			block, hit, hit_radius = typ:block_path(lx, ly)
+		end
+
+		if block then
+			if hit then
+				return lx, ly, false, true
+			-- If we don't hit the tile, pass back nils to stop on the current spot
+			else
+				return nil, nil, false, true
+			end
+		end
 
 		-- End of the map
-		if lx < 0 or lx >= game.level.map.w or ly < 0 or ly >= game.level.map.h then return lx, ly, false, true end
+		if lx < 0 or lx >= game.level.map.w or ly < 0 or ly >= game.level.map.h then
+			return nil, nil, false, true
+		end
 
 		-- Deal damage: beam
 		if typ.line and (lx ~= tgtx or ly ~= tgty) then return lx, ly, true, false end
@@ -251,19 +290,39 @@ function _M:projectDoStop(typ, tg, damtype, dam, particles, lx, ly, tmp, rx, ry)
 	end
 
 	if typ.ball and typ.ball > 0 then
-		core.fov.calc_circle(rx, ry, game.level.map.w, game.level.map.h, typ.ball, function(_, px, py)
-			-- Deal damage: ball
-			addGrid(px, py)
-			if typ.block_radius and typ:block_radius(px, py) then return true end
-		end, function()end, nil)
+		core.fov.calc_circle(
+			rx, 
+			ry, 
+			game.level.map.w, 
+			game.level.map.h, 
+			typ.ball, 
+			function(_, px, py)
+				if typ.block_radius and typ:block_radius(px, py) then return true end
+			end,
+			function(_, px, py)
+				-- Deal damage: ball
+				addGrid(px, py)
+			end,
+		nil)
 		addGrid(rx, ry)
 	elseif typ.cone and typ.cone > 0 then
 		local initial_dir = lx and util.getDir(lx, ly, x, y) or 5
-		core.fov.calc_beam(rx, ry, game.level.map.w, game.level.map.h, typ.cone, initial_dir, typ.cone_angle, function(_, px, py)
-			-- Deal damage: cone
-			addGrid(px, py)
-			if typ.block_radius and typ:block_radius(px, py) then return true end
-		end, function()end, nil)
+		core.fov.calc_beam(
+			rx, 
+			ry, 
+			game.level.map.w, 
+			game.level.map.h, 
+			typ.cone, 
+			initial_dir, 
+			typ.cone_angle, 
+			function(_, px, py)
+				if typ.block_radius and typ:block_radius(px, py) then return true end
+			end,
+			function(_, px, py)
+				-- Deal damage: cone
+				addGrid(px, py)
+			end,
+		nil)
 		addGrid(rx, ry)
 	else
 		-- Deal damage: single
diff --git a/game/modules/tome/dialogs/MapMenu.lua b/game/modules/tome/dialogs/MapMenu.lua
index a72b28ea2221608b232ac94310f5843901fb9a7b..39d928d54ffb9fa931575681ea6264b54cf7815d 100644
--- a/game/modules/tome/dialogs/MapMenu.lua
+++ b/game/modules/tome/dialogs/MapMenu.lua
@@ -103,17 +103,26 @@ function _M:generateList()
 
 	-- Talents
 	if game.zone and not game.zone.wilderness then
+		local canHit = function(tg, x, y)
+			local hit = false
+			player:project(tg, x, y, function(px, py)
+				if px == x and py == y then
+					hit = true
+				end
+			end)
+			return hit
+		end
 		local tals = {}
 		for tid, _ in pairs(player.talents) do
 			local t = player:getTalentFromId(tid)
 			local t_avail = false
 			local tg = player:getTalentTarget(t)
-			local default_tg = {type=util.getval(t.direct_hit, player, t) and "hit" or "bolt"}
-			local total_dist = (player:getTalentRange(t) + player:getTalentRadius(t))
+			local total_range = player:getTalentRange(t) + player:getTalentRadius(t)
+			local default_tg = {type=util.getval(t.direct_hit, player, t) and "hit" or "bolt", range=total_range}
 			if t.mode == "activated" and not player:isTalentCoolingDown(t) and
 			  player:preUseTalent(t, true, true) and
 			  (not player:getTalentRequiresTarget(t) or
-			   player:canProject(tg or default_tg, self.tmx, self.tmy))
+			   canHit(tg or default_tg, self.tmx, self.tmy))
 			  then
 			   	t_avail = true
 			elseif t.mode == "sustained" and not t.no_npc_use and