diff --git a/game/engines/default/engine/Map.lua b/game/engines/default/engine/Map.lua
index a9f0de62f266efe780016ce5570651a52ca81978..1a023587317d8c92aa82edc438f83a89d04c6932 100644
--- a/game/engines/default/engine/Map.lua
+++ b/game/engines/default/engine/Map.lua
@@ -882,7 +882,7 @@ function _M:addEffect(src, x, y, duration, damtype, dam, radius, dir, angle, ove
 
 	-- Handle any angle
 	if type(dir) == "table" then
-		grids = core.fov.beam_any_angle_grids(x, y, radius, dir.delta_x, dir.delta_y, angle, true)
+		grids = core.fov.beam_any_angle_grids(x, y, radius, angle, dir.source_x or src.x or x, dir.source_y or src.y or y, dir.delta_x, dir.delta_y, true)
 	-- Handle balls
 	elseif dir == 5 then
 		grids = core.fov.circle_grids(x, y, radius, true)
@@ -959,7 +959,7 @@ function _M:processEffects()
 			table.insert(todel, i)
 		elseif e.update_fct then
 			if e:update_fct() then
-				if type(dir) == "table" then e.grids = core.fov.beam_any_angle_grids(e.x, e.y, e.radius, e.dir.delta_x, e.dir.delta_y, e.angle, true)
+				if type(dir) == "table" then e.grids = core.fov.beam_any_angle_grids(e.x, e.y, e.radius, e.angle, e.dir.source_x or e.src.x or e.x, e.dir.source_y or e.src.y or e.y, e.dir.delta_x, e.dir.delta_y, true)
 				elseif e.dir == 5 then e.grids = core.fov.circle_grids(e.x, e.y, e.radius, true)
 				else e.grids = core.fov.beam_grids(e.x, e.y, e.radius, e.dir, e.angle, true) end
 				if e.particles then
diff --git a/game/engines/default/engine/Projectile.lua b/game/engines/default/engine/Projectile.lua
index 000ae27da5a6e8f08b9e4b6d2ecb928deab00b61..7bf5ac002a5d530106467598dd53fccd59484a5a 100644
--- a/game/engines/default/engine/Projectile.lua
+++ b/game/engines/default/engine/Projectile.lua
@@ -259,7 +259,7 @@ function _M:makeProject(src, display, def, do_move, do_act, do_stop)
 		travel_particle_args = display.particle_args,
 		trail_particle = display.trail,
 		src = src,
-		src_x = def.start_x or src.x, src_y = def.start_y or src.y,
+		start_x = def.start_x or src.x, start_y = def.start_y or src.y,
 		project = {def=def},
 		energy = {mod=speed},
 		tmp_proj = {},
diff --git a/game/engines/default/engine/Target.lua b/game/engines/default/engine/Target.lua
index 18e1d91ddee6fb42a886501f1fc257615bf71b4c..e9bbd9bd59b1355d41bada71da9dd9a0a9b70bfa 100644
--- a/game/engines/default/engine/Target.lua
+++ b/game/engines/default/engine/Target.lua
@@ -73,6 +73,9 @@ function _M:display(dispx, dispy)
 	self.target.x = self.target.x or self.source_actor.x
 	self.target.y = self.target.y or self.source_actor.y
 
+	self.target_type.start_x = self.target_type.start_x or self.target_type.x or self.target_type.source_actor and self.target_type.source_actor.x or self.x
+	self.target_type.start_y = self.target_type.start_y or self.target_type.y or self.target_type.source_actor and self.target_type.source_actor.y or self.y
+
 	local ox, oy = self.display_x, self.display_y
 	local sx, sy = game.level.map._map:getScroll()
 	sx = sx + game.level.map.display_x
@@ -87,9 +90,9 @@ function _M:display(dispx, dispy)
 	local s = self.sb
 	local l
 	if self.target_type.source_actor.lineFOV then
-		l = self.target_type.source_actor:lineFOV(self.target.x, self.target.y)
+		l = self.target_type.source_actor:lineFOV(self.target.x, self.target.y, nil, nil, self.target_type.start_x, self.target_type.start_y)
 	else
-		l = core.fov.line(self.source_actor.x, self.source_actor.y, self.target.x, self.target.y)
+		l = core.fov.line(self.target_type.start_x, self.target_type.start_y, self.target.x, self.target.y)
 	end
 	local block_corner = self.target_type.block_path and function(_, bx, by) local b, h, hr = self.target_type:block_path(bx, by, true) ; return b and h and not hr end
 		or function(_, bx, by) return false end
@@ -97,8 +100,8 @@ function _M:display(dispx, dispy)
 	l:set_corner_block(block_corner)
 	local lx, ly, blocked_corner_x, blocked_corner_y = l:step()
 
-	local stop_x, stop_y = self.source_actor.x, self.source_actor.y
-	local stop_radius_x, stop_radius_y = self.source_actor.x, self.source_actor.y
+	local stop_x, stop_y = self.target_type.start_x, self.target_type.start_y
+	local stop_radius_x, stop_radius_y = self.target_type.start_x, self.target_type.start_y
 	local stopped = false
 	local block, hit, hit_radius
 
@@ -108,7 +111,7 @@ function _M:display(dispx, dispy)
 		hit = true
 		hit_radius = false
 		stopped = true
-		if self.target_type.min_range and core.fov.distance(self.source_actor.x, self.source_actor.y, lx, ly) < self.target_type.min_range then
+		if self.target_type.min_range and core.fov.distance(self.target_type.start_x, self.target_type.start_y, lx, ly) < self.target_type.min_range then
 			s = self.sr
 		end
 		if game.level.map:isBound(blocked_corner_x, blocked_corner_y) then
@@ -136,7 +139,7 @@ function _M:display(dispx, dispy)
 			end
 			if self.target_type.min_range 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
+				if core.fov.distance(self.target_type.start_x, self.target_type.start_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 then
@@ -199,9 +202,40 @@ function _M:display(dispx, dispy)
 			game.level.map.w,
 			game.level.map.h,
 			self.target_type.cone,
-			self.target.x - self.source_actor.x,
-			self.target.y - self.source_actor.y,
 			self.target_type.cone_angle,
+			self.target_type.start_x,
+			self.target_type.start_y,
+			self.target.x - self.target_type.start_x,
+			self.target.y - self.target_type.start_y,
+			function(_, px, py)
+				if self.target_type.block_radius and self.target_type:block_radius(px, py, true) then return true end
+			end,
+			function(_, px, py)
+				if not self.target_type.no_restrict and not game.level.map.remembers(px, py) and not game.level.map.seens(px, py) then
+					self.syg:toScreen(self.display_x + (px - game.level.map.mx) * self.tile_w * Map.zoom,
+					self.display_y + (py - game.level.map.my + util.hexOffset(px)) * self.tile_h * Map.zoom,
+					self.tile_w * Map.zoom,
+					self.tile_h * Map.zoom)
+				else
+					self.sg:toScreen(self.display_x + (px - game.level.map.mx) * self.tile_w * Map.zoom,
+					self.display_y + (py - game.level.map.my + util.hexOffset(px)) * self.tile_h * Map.zoom,
+					self.tile_w * Map.zoom,
+					self.tile_h * Map.zoom)
+				end
+			end,
+		nil)
+	elseif self.target_type.wall and self.target_type.wall > 0 then
+		core.fov.calc_wall(
+			stop_radius_x,
+			stop_radius_y,
+			game.level.map.w,
+			game.level.map.h,
+			self.target_type.wall,
+			self.target_type.halfmax_spots,
+			self.target_type.start_x,
+			self.target_type.start_y,
+			self.target.x - self.target_type.start_x,
+			self.target.y - self.target_type.start_y,
 			function(_, px, py)
 				if self.target_type.block_radius and self.target_type:block_radius(px, py, true) then return true end
 			end,
@@ -308,6 +342,14 @@ function _M:getType(t)
 			target_type.cone_angle = t.cone_angle or 55
 			target_type.selffire = false
 		end
+		if t.type:find("wall") then
+			if util.isHex() then
+				--with a hex grid, a wall should only be defined by the number of spots
+				t.halfmax_spots = t.halflength
+				t.halflength = 2*t.halflength
+			end
+			target_type.wall = t.halflength
+		end
 		if t.type:find("bolt") then
 			target_type.stop_block = true
 		elseif t.type:find("beam") then
diff --git a/game/engines/default/engine/interface/ActorProject.lua b/game/engines/default/engine/interface/ActorProject.lua
index c46ac1807a7653af04b073c7b738f510f883db7b..90c1bd0e073cc0271ed1d8a6239bc35ad8d49fdf 100644
--- a/game/engines/default/engine/interface/ActorProject.lua
+++ b/game/engines/default/engine/interface/ActorProject.lua
@@ -49,6 +49,8 @@ function _M:project(t, x, y, damtype, dam, particles)
 --	if type(dam) == "number" and dam < 0 then return end
 	local typ = Target:getType(t)
 	typ.source_actor = self
+	typ.start_x = typ.start_x or typ.x or typ.source_actor and typ.source_actor.x or self.x
+	typ.start_y = typ.start_y or typ.y or typ.source_actor and typ.source_actor.y or self.x
 
 	local grids = {}
 	local function addGrid(x, y)
@@ -56,16 +58,14 @@ function _M:project(t, x, y, damtype, dam, particles)
 		grids[x][y] = true
 	end
 
-	local srcx, srcy = t.x or self.x, t.y or self.y
-
 	-- Stop at range or on block
-	local stop_x, stop_y = srcx, srcy
-	local stop_radius_x, stop_radius_y = srcx, srcy
+	local stop_x, stop_y = typ.start_x, typ.start_y
+	local stop_radius_x, stop_radius_y = typ.start_x, typ.start_y
 	local l, is_corner_blocked
 	if typ.source_actor.lineFOV then
-		l = typ.source_actor:lineFOV(x, y, nil, nil, srcx, srcy)
+		l = typ.source_actor:lineFOV(x, y, nil, nil, typ.start_x, typ.start_y)
 	else
-		l = core.fov.line(srcx, srcy, x, y)
+		l = core.fov.line(typ.start_x, typ.start_y, x, y)
 	end
 	local block_corner = typ.block_path and function(_, bx, by) local b, h, hr = typ:block_path(bx, by, true) ; return b and h and not hr end
 		or function(_, bx, by) return false end
@@ -134,9 +134,11 @@ function _M:project(t, x, y, damtype, dam, particles)
 			game.level.map.w,
 			game.level.map.h,
 			typ.cone,
-			x - self.x,
-			y - self.y,
 			typ.cone_angle,
+			typ.start_x,
+			typ.start_y,
+			x - typ.start_x,
+			y - typ.start_y,
 			function(_, px, py)
 				if typ.block_radius and typ:block_radius(px, py) then return true end
 			end,
@@ -145,13 +147,32 @@ function _M:project(t, x, y, damtype, dam, particles)
 			end,
 		nil)
 		addGrid(stop_x, stop_y)
+	elseif typ.wall and typ.wall > 0 then
+		core.fov.calc_wall(
+			stop_radius_x,
+			stop_radius_y,
+			game.level.map.w,
+			game.level.map.h,
+			typ.wall,
+			typ.halfmax_spots,
+			typ.start_x,
+			typ.start_y,
+			x - typ.start_x,
+			y - typ.start_y,
+			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)
 	else
 		-- Deal damage: single
 		addGrid(stop_x, stop_y)
 	end
 
 	-- Check for minimum range
-	if typ.min_range and core.fov.distance(self.x, self.y, stop_x, stop_y) < typ.min_range then
+	if typ.min_range and core.fov.distance(typ.start_x, typ.start_y, stop_x, stop_y) < typ.min_range then
 		return
 	end
 
@@ -198,17 +219,17 @@ end
 function _M:canProject(t, x, y)
 	local typ = Target:getType(t)
 	typ.source_actor = self
-	typ.start_x = self.x
-	typ.start_y = self.y
+	typ.start_x = typ.start_x or typ.x or typ.source_actor and typ.source_actor.x or self.x
+	typ.start_y = typ.start_y or typ.y or typ.source_actor and typ.source_actor.y or self.x
 
 	-- Stop at range or on block
-	local stop_x, stop_y = self.x, self.y
-	local stop_radius_x, stop_radius_y = self.x, self.y
+	local stop_x, stop_y = typ.start_x, typ.start_y
+	local stop_radius_x, stop_radius_y = typ.start_x, typ.start_y
 	local l, is_corner_blocked
 	if typ.source_actor.lineFOV then
-		l = typ.source_actor:lineFOV(x, y)
+		l = typ.source_actor:lineFOV(x, y, nil, nil, typ.start_x, typ.start_y)
 	else
-		l = core.fov.line(self.x, self.y, x, y)
+		l = core.fov.line(typ.start_x, typ.start_y, x, y)
 	end
 	local block_corner = typ.block_path and function(_, bx, by) local b, h, hr = typ:block_path(bx, by, true) ; return b and h and not hr end
 		or function(_, bx, by) return false end
@@ -243,7 +264,7 @@ function _M:canProject(t, x, y)
 	end
 
 	-- Check for minimum range
-	if typ.min_range and core.fov.distance(self.x, self.y, stop_x, stop_y) < typ.min_range then
+	if typ.min_range and core.fov.distance(typ.start_x, typ.start_y, stop_x, stop_y) < typ.min_range then
 		return
 	end
 
@@ -270,20 +291,20 @@ function _M:projectile(t, x, y, damtype, dam, particles)
 --	if type(dam) == "number" and dam < 0 then return end
 	local typ = Target:getType(t)
 	typ.source_actor = self
-	typ.start_x = t.x or self.x
-	typ.start_y = t.y or self.y
+	typ.start_x = typ.start_x or typ.x or typ.source_actor and typ.source_actor.x or self.x
+	typ.start_y = typ.start_y or typ.y or typ.source_actor and typ.source_actor.y or self.x
 	if self.lineFOV then
-		typ.line_function = self:lineFOV(x, y, nil, nil, t.x, t.y)
+		typ.line_function = self:lineFOV(x, y, nil, nil, typ.start_x, typ.start_y)
 	else
-		typ.line_function = core.fov.line(t.x or self.x, t.y or self.y, x, y)
+		typ.line_function = core.fov.line(typ.start_x, typ.start_y, x, y)
 	end
 	local block_corner = typ.block_path and function(_, bx, by) local b, h, hr = typ:block_path(bx, by, true) ; return b and h and not hr end
 		or function(_, bx, by) return false end
 
 	typ.line_function:set_corner_block(block_corner)
 
-	local proj = require(self.projectile_class):makeProject(self, t.display, {x=x, y=y, start_x = t.x or self.x, start_y = t.y or self.y, damtype=damtype, tg=t, typ=typ, dam=dam, particles=particles})
-	game.zone:addEntity(game.level, proj, "projectile", t.x or self.x, t.y or self.y)
+	local proj = require(self.projectile_class):makeProject(self, t.display, {x=x, y=y, start_x=typ.start_x, start_y=typ.start_y, damtype=damtype, tg=t, typ=typ, dam=dam, particles=particles})
+	game.zone:addEntity(game.level, proj, "projectile", typ.start_x, typ.start_y)
 end
 
 -- @param typ a target type table
@@ -386,9 +407,11 @@ function _M:projectDoStop(typ, tg, damtype, dam, particles, lx, ly, tmp, rx, ry)
 			game.level.map.w,
 			game.level.map.h,
 			typ.cone,
-			lx - typ.source_actor.x,
-			ly - typ.source_actor.y,
 			typ.cone_angle,
+			typ.start_x,
+			typ.start_y,
+			lx - typ.start_x,
+			ly - typ.start_y,
 			function(_, px, py)
 				if typ.block_radius and typ:block_radius(px, py) then return true end
 			end,
@@ -398,6 +421,26 @@ function _M:projectDoStop(typ, tg, damtype, dam, particles, lx, ly, tmp, rx, ry)
 			end,
 		nil)
 		addGrid(rx, ry)
+	elseif typ.wall and typ.wall > 0 then
+		core.fov.calc_wall(
+			rx,
+			rx,
+			game.level.map.w,
+			game.level.map.h,
+			typ.wall,
+			typ.halfmax_spots,
+			typ.start_x,
+			typ.start_y,
+			lx - typ.start_x,
+			ly - typ.start_y,
+			function(_, px, py)
+				if typ.block_radius and typ:block_radius(px, py) then return true end
+			end,
+			function(_, px, py)
+				-- Deal damage: wall
+				addGrid(px, py)
+			end,
+		nil)
 	else
 		-- Deal damage: single
 		addGrid(lx, ly)
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index e797858b48ca54b7d015955d4ba19aa2cc8c8c93..09e8d87ea4cb458384f15c4b9f8fc9f5ec9d079a 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -1347,17 +1347,125 @@ function util.loadfilemods(file, env)
 	return prev
 end
 
+-- if these functions are ever desired elsewhere, don't be shy to make these accessible beyond utils.lua
+local function deltaCoordsToReal(dx, dy, source_x, source_y)
+	if util.isHex() then
+		dy = dy + (math.floor(math.abs(dx) + 0.5) % 2) * (0.5 - math.floor(source_x) % 2)
+		dx = dx * math.sqrt(3) / 2
+	end
+	return dx, dy
+end
+
+local function deltaRealToCoords(dx, dy, source_x, source_y)
+	if util.isHex() then
+		dx = dx < 0 and math.ceil(dx * 2 / math.sqrt(3) - 0.5) or math.floor(dx * 2 / math.sqrt(3) + 0.5)
+		dy = dy - (math.floor(math.abs(dx) + 0.5) % 2) * (0.5 - math.floor(source_x) % 2)
+	end
+	return source_x + dx, source_y + dy
+end
+
+function core.fov.calc_wall(x, y, w, h, halflength, halfmax_spots, source_x, source_y, delta_x, delta_y, block, apply)
+	apply(_, x, y)
+	delta_x, delta_y = deltaCoordsToReal(delta_x, delta_y, source_x, source_y)
+
+	local angle = math.atan2(delta_y, delta_x) + math.pi / 2
+
+	local dx, dy = math.cos(angle) * halflength, math.sin(angle) * halflength
+	local adx, ady = math.abs(dx), math.abs(dy)
+
+	local x1, y1 = deltaRealToCoords( dx,  dy, x, y)
+	local x2, y2 = deltaRealToCoords(-dx, -dy, x, y)
+
+	local spots = 1
+	local wall_block_corner = function(_, bx, by)
+		if halfmax_spots and spots > halfmax_spots or math.floor(core.fov.distance(x2, y2, bx, by, true) - 0.25) > 2*halflength then return true end
+		apply(_, bx, by)
+		spots = spots + 1
+		return block(_, bx, by)
+	end
+
+	local l = core.fov.line(x+0.5, y+0.5, x1+0.5, y1+0.5, function(_, bx, by) return true end)
+	l:set_corner_block(wall_block_corner)
+	-- use the correct tangent (not approximate) and round corner tie-breakers toward the player (via wiggles!)
+	if adx < ady then
+		l:change_step(dx/ady, dy/ady)
+		if delta_y < 0 then l:wiggle(true) else l:wiggle() end
+	else
+		l:change_step(dx/adx, dy/adx)
+		if delta_x < 0 then l:wiggle(true) else l:wiggle() end
+	end
+	while true do
+		local lx, ly, is_corner_blocked = l:step(true)
+		if not lx or is_corner_blocked or halfmax_spots and spots > halfmax_spots or math.floor(core.fov.distance(x2, y2, lx, ly, true) + 0.25) > 2*halflength then break end
+		apply(_, lx, ly)
+		spots = spots + 1
+		if block(_, lx, ly) then break end
+	end
+
+	spots = 1
+	wall_block_corner = function(_, bx, by)
+		if halfmax_spots and spots > halfmax_spots or math.floor(core.fov.distance(x1, y1, bx, by, true) - 0.25) > 2*halflength then return true end
+		apply(_, bx, by)
+		spots = spots + 1
+		return block(_, bx, by)
+	end
+
+	local l = core.fov.line(x+0.5, y+0.5, x2+0.5, y2+0.5, function(_, bx, by) return true end)
+	l:set_corner_block(wall_block_corner)
+	-- use the correct tangent (not approximate) and round corner tie-breakers toward the player (via wiggles!)
+	if adx < ady then
+		l:change_step(-dx/ady, -dy/ady)
+		if delta_y < 0 then l:wiggle(true) else l:wiggle() end
+	else
+		l:change_step(-dx/adx, -dy/adx)
+		if delta_x < 0 then l:wiggle(true) else l:wiggle() end
+	end
+	while true do
+		local lx, ly, is_corner_blocked = l:step(true)
+		if not lx or is_corner_blocked or halfmax_spots and spots > halfmax_spots or math.floor(core.fov.distance(x1, y1, lx, ly, true) + 0.25) > 2*halflength then break end
+		apply(_, lx, ly)
+		spots = spots + 1
+		if block(_, lx, ly) then break end
+	end
+end
+
+function core.fov.wall_grids(x, y, halflength, halfmax_spots, source_x, source_y, delta_x, delta_y, block)
+	if not x or not y then return {} end
+	local grids = {}
+	core.fov.calc_wall(x, y, game.level.map.w, game.level.map.h, halflength, halfmax_spots, source_x, source_y, delta_x, delta_y,
+		function(_, lx, ly)
+			if type(block) == "function" then
+				return block(_, lx, ly)
+			elseif block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end
+		end,
+		function(_, lx, ly)
+			if not grids[lx] then grids[lx] = {} end
+			grids[lx][ly] = true
+		end,
+	nil)
+
+	-- point of origin
+	if not grids[x] then grids[x] = {} end
+	grids[x][y] = true
+
+	return grids
+end
+
 function core.fov.circle_grids(x, y, radius, block)
 	if not x or not y then return {} end
 	if radius == 0 then return {[x]={[y]=true}} end
 	local grids = {}
-	core.fov.calc_circle(x, y, game.level.map.w, game.level.map.h, radius, function(_, lx, ly)
-		if block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end
-	end,
-	function(_, lx, ly)
-		if not grids[lx] then grids[lx] = {} end
-		grids[lx][ly] = true
-	end, nil)
+	core.fov.calc_circle(x, y, game.level.map.w, game.level.map.h, radius,
+		function(_, lx, ly)
+			if type(block) == "function" then
+				return block(_, lx, ly)
+			elseif block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end
+		end,
+		function(_, lx, ly)
+			if not grids[lx] then grids[lx] = {} end
+			grids[lx][ly] = true
+		end,
+	nil)
 
 	-- point of origin
 	if not grids[x] then grids[x] = {} end
@@ -1370,13 +1478,17 @@ function core.fov.beam_grids(x, y, radius, dir, angle, block)
 	if not x or not y then return {} end
 	if radius == 0 then return {[x]={[y]=true}} end
 	local grids = {}
-	core.fov.calc_beam(x, y, game.level.map.w, game.level.map.h, radius, dir, angle, function(_, lx, ly)
-		if block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end
-	end,
-	function(_, lx, ly)
-		if not grids[lx] then grids[lx] = {} end
-		grids[lx][ly] = true
-	end, nil)
+	core.fov.calc_beam(x, y, game.level.map.w, game.level.map.h, radius, dir, angle,
+		function(_, lx, ly)
+			if type(block) == "function" then
+				return block(_, lx, ly)
+			elseif block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end
+		end,
+		function(_, lx, ly)
+			if not grids[lx] then grids[lx] = {} end
+			grids[lx][ly] = true
+		end,
+	nil)
 
 	-- point of origin
 	if not grids[x] then grids[x] = {} end
@@ -1385,17 +1497,21 @@ function core.fov.beam_grids(x, y, radius, dir, angle, block)
 	return grids
 end
 
-function core.fov.beam_any_angle_grids(x, y, radius, delta_x, delta_y, angle, block)
+function core.fov.beam_any_angle_grids(x, y, radius, angle, source_x, source_y, delta_x, delta_y, block)
 	if not x or not y then return {} end
 	if radius == 0 then return {[x]={[y]=true}} end
 	local grids = {}
-	core.fov.calc_beam_any_angle(x, y, game.level.map.w, game.level.map.h, radius, delta_x, delta_y, angle, function(_, lx, ly)
-		if block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end
-	end,
-	function(_, lx, ly)
-		if not grids[lx] then grids[lx] = {} end
-		grids[lx][ly] = true
-	end, nil)
+	core.fov.calc_beam_any_angle(x, y, game.level.map.w, game.level.map.h, radius, angle, source_x, source_y, delta_x, delta_y,
+		function(_, lx, ly)
+			if type(block) == "function" then
+				return block(_, lx, ly)
+			elseif block and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true end
+		end,
+		function(_, lx, ly)
+			if not grids[lx] then grids[lx] = {} end
+			grids[lx][ly] = true
+		end,
+	nil)
 
 	-- point of origin
 	if not grids[x] then grids[x] = {} end
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 5b89f674384afb66082b8ac02e70b0461d367f7f..ca7a356a4e8eed2c0e079a42bb7773762e641ef2 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -3508,7 +3508,7 @@ function _M:on_projectile_target(x, y, p)
 	if self:isTalentActive(self.T_GLOOM) and self:knowTalent(self.T_SANCTUARY) then
 		-- mark temp table with the sanctuary damage change (to lower using tmp from DamageType:project)
 		local t = self:getTalentFromId(self.T_GLOOM)
-		if core.fov.distance(self.x, self.y, p.src_x, p.src_y) > self:getTalentRange(t) then
+		if core.fov.distance(self.x, self.y, p.start_x, p.start_y) > self:getTalentRange(t) then
 			t = self:getTalentFromId(self.T_SANCTUARY)
 			p.tmp_proj.sanctuaryDamageChange = t.getDamageChange(self, t)
 			print("Sanctuary marking reduced damage on projectile:", p.tmp_proj.sanctuaryDamageChange)
diff --git a/game/modules/tome/data/general/objects/mindstars.lua b/game/modules/tome/data/general/objects/mindstars.lua
index bcb8ad6b73d2435d602dc37c8fa5eb674216e3ea..f9b39c3fcd56652bb284693e5c63a63a48d8de4c 100644
--- a/game/modules/tome/data/general/objects/mindstars.lua
+++ b/game/modules/tome/data/general/objects/mindstars.lua
@@ -36,7 +36,7 @@ newEntity{
 		sound = {"actions/melee", pitch=0.6, vol=1.2}, sound_miss = {"actions/melee", pitch=0.6, vol=1.2},
 		damtype = resolvers.rngtable{DamageType.NATURE, DamageType.MIND},
 	},
-	desc = [[Mindstars are natural products. Natural gems covered in living matter, they are used to focus the mental powers of all nature defenders.]],
+	desc = [[Mindstars are natural products. Natural gems covered in living matter, they are used to focus the mental powers of all nature defenders and psionics.]],
 	egos = "/data/general/objects/egos/mindstars.lua", egos_chance = { prefix=resolvers.mbonus(40, 5), suffix=resolvers.mbonus(40, 5) },
 }
 
diff --git a/game/modules/tome/data/talents/gifts/cold-drake.lua b/game/modules/tome/data/talents/gifts/cold-drake.lua
index 834bd6f188c90bce0c9797c65943e61b11663786..118f5fcdd8fa68e0fb54ee350479ba7e3d17777b 100644
--- a/game/modules/tome/data/talents/gifts/cold-drake.lua
+++ b/game/modules/tome/data/talents/gifts/cold-drake.lua
@@ -93,10 +93,14 @@ newTalent{
 	on_learn = function(self, t) self.resists[DamageType.COLD] = (self.resists[DamageType.COLD] or 0) + 1 end,
 	on_unlearn = function(self, t) self.resists[DamageType.COLD] = (self.resists[DamageType.COLD] or 0) - 1 end,
 	action = function(self, t)
-		local tg = {type="bolt", range=self:getTalentRange(t), nolock=true, talent=t}
+		local halflength = 1 + math.floor(self:getTalentLevel(t) / 2)
+		local block = function(_, lx, ly)
+			return game.level.map:checkAllEntities(lx, ly, "block_move")
+		end
+		local tg = {type="wall", range=self:getTalentRange(t), halflength=halflength, talent=t, halfmax_spots=halflength+1, block_radius=block}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		local _ _, x, y = self:canProject(tg, x, y)
+		local _ _, _, _, x, y = self:canProject(tg, x, y)
 		if game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") then return nil end
 
 		local addwall = function(x, y)
@@ -132,51 +136,7 @@ newTalent{
 			game.level.map(x, y, Map.TRAP, e)
 		end
 
-		local size = 1 + math.floor(self:getTalentLevel(t) / 2)
-		local angle = math.atan2(y - self.y, x - self.x) + math.pi / 2
-		local x1, y1 = x + math.cos(angle) * size, y + math.sin(angle) * size
-		local x2, y2 = x - math.cos(angle) * size, y - math.sin(angle) * size
-
-		local dx1, dy1 = math.abs(x1 - x), math.abs(y1 - y)
-		local dx2, dy2 = math.abs(x2 - x), math.abs(y2 - y)
-		local block_corner = function(_, bx, by)
-				if game.level.map:checkAllEntities(bx, by, "block_move") then return true
-				else addwall(bx, by) ; return false end
-			end
-
-		local l = core.fov.line(x, y, x1, y1, function(_, bx, by) return true end)
-		l:set_corner_block(block_corner)
-		-- use the correct tangent (not approximate) and round corner tie-breakers toward the player (via wiggles!)
-		if dx1 < dy1 then
-			l:change_step((x1-x)/dy1, (y1-y)/dy1)
-			if y < self.y then l:wiggle(true) else l:wiggle() end
-		else
-			l:change_step((x1-x)/dx1, (y1-y)/dx1)
-			if x < self.x then l:wiggle(true) else l:wiggle() end
-		end
-		while true do
-			local lx, ly, is_corner_blocked = l:step()
-			if not lx or is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move") then break end
-			addwall(lx, ly)
-		end
-
-		local l = core.fov.line(x, y, x2, y2, function(_, bx, by) return true end)
-		l:set_corner_block(block_corner)
-		-- use the correct tangent (not approximate) and round corner tie-breakers toward the player (via wiggles!)
-		if dx2 < dy2 then
-			l:change_step((x2-x)/dy2, (y2-y)/dy2)
-			if y < self.y then l:wiggle(true) else l:wiggle() end
-		else
-			l:change_step((x2-x)/dx2, (y2-y)/dx2)
-			if x < self.x then l:wiggle(true) else l:wiggle() end
-		end
-		while true do
-			local lx, ly, is_corner_blocked = l:step()
-			if not lx or is_corner_blocked or game.level.map:checkAllEntities(lx, ly, "block_move") then break end
-			addwall(lx, ly)
-		end
-
-		if not game.level.map:checkAllEntities(x, y, "block_move") then addwall(x, y) end
+		self:project(tg, x, y, addwall)
 
 		game.level.map:redisplay()
 		return true
diff --git a/game/modules/tome/data/talents/gifts/summon-distance.lua b/game/modules/tome/data/talents/gifts/summon-distance.lua
index 1324037d5b3b123a8fd1a2cb384bdc1d4454c1b8..93b66bf17c8e162ca489419abecd15a7fe595172 100644
--- a/game/modules/tome/data/talents/gifts/summon-distance.lua
+++ b/game/modules/tome/data/talents/gifts/summon-distance.lua
@@ -90,7 +90,7 @@ newTalent{
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.ACID, self:combatTalentStatDamage(t, "wil", 30, 430))
+		self:project(tg, x, y, DamageType.ACID, self:mindCrit(self:combatTalentStatDamage(t, "wil", 30, 430)))
 		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_acid", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/breath")
 		return true
@@ -121,7 +121,7 @@ newTalent{
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
 		local dam = self:combatTalentStatDamage(t, "wil", 30, 500)
-		self:project(tg, x, y, DamageType.LIGHTNING, rng.avg(dam / 3, dam, 3))
+		self:project(tg, x, y, DamageType.LIGHTNING, self:mindCrit(rng.avg(dam / 3, dam, 3)))
 		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_lightning", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/lightning")
 		return true
@@ -155,7 +155,7 @@ newTalent{
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.POISON, self:combatTalentStatDamage(t, "wil", 30, 460))
+		self:project(tg, x, y, DamageType.POISON, self:mindCrit(self:combatTalentStatDamage(t, "wil", 30, 460)))
 		game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_slime", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
 		game:playSoundNear(self, "talents/breath")
 		return true
diff --git a/src/fov.c b/src/fov.c
index fae425e04a381fe4318c65d4cf55616290751786..ff51c7702fd0f5a52362143f47a7641cc6239d8f 100644
--- a/src/fov.c
+++ b/src/fov.c
@@ -36,6 +36,9 @@
 #include <math.h>
 #include <time.h>
 
+#define SQRT_3       1.73205080756887729353
+#define INV_SQRT_3   0.577350269189625764509
+#define SQRT_3_2     0.866025403784438646764
 #define INV_SQRT_3_2 1.15470053837925152902
 
 /******************************************************************
@@ -344,13 +347,15 @@ static int lua_fov_calc_beam_any_angle(lua_State *L)
 	int w = luaL_checknumber(L, 3);
 	int h = luaL_checknumber(L, 4);
 	int radius = luaL_checknumber(L, 5);
-	float dx = luaL_checknumber(L, 6);
-	float dy = luaL_checknumber(L, 7);
-	float beam_angle = luaL_checknumber(L, 8);
+	float beam_angle = luaL_checknumber(L, 6);
+	int sx = luaL_checknumber(L, 7);
+	int sy = luaL_checknumber(L, 8);
+	float dx = luaL_checknumber(L, 9);
+	float dy = luaL_checknumber(L, 10);
 	struct lua_fov fov;
-	if (lua_isuserdata(L, 11))
+	if (lua_isuserdata(L, 13))
 	{
-		fov.cache = (struct lua_fovcache*)auxiliar_checkclass(L, "fov{cache}", 10);
+		fov.cache = (struct lua_fovcache*)auxiliar_checkclass(L, "fov{cache}", 12);
 		fov.cache_ref = luaL_ref(L, LUA_REGISTRYINDEX);
 	}
 	else
@@ -374,7 +379,7 @@ static int lua_fov_calc_beam_any_angle(lua_State *L)
 	lua_fov_get_vision_shape(L);
 	fov.fov_settings.shape = luaL_checknumber(L, -1);
 
-	fov_beam_any_angle(&(fov.fov_settings), &fov, NULL, x, y, radius, dx, dy, beam_angle);
+	fov_beam_any_angle(&(fov.fov_settings), &fov, NULL, x, y, radius, sx, sy, dx, dy, beam_angle);
 	map_seen(&fov, x, y, 0, 0, radius, NULL);
 	fov_settings_free(&(fov.fov_settings));
 
@@ -1058,9 +1063,18 @@ static int lua_hex_fov_line_change_step(lua_State *L)
 	hex_fov_line_data *line = &(lua_line->line);
 	float step_x = lua_tonumber(L, 2);
 	float step_y = lua_tonumber(L, 3);
+	float ax = fabs(step_x);
+	float ay = fabs(step_y);
+
+	// lines are a little weird in hex, so lets enforce unit step sizes
+	if (SQRT_3*ay < ax) {
+		line->step_x = SQRT_3_2 * step_x / ax;
+		line->step_y = SQRT_3_2 * step_y / ax;
+	} else {
+		line->step_x = step_x / (INV_SQRT_3*ax + ay);
+		line->step_y = step_y / (INV_SQRT_3*ax + ay);
+	}
 
-	line->step_x = step_x;
-	line->step_y = step_y;
 	return 0;
 }
 
diff --git a/src/fov/fov.c b/src/fov/fov.c
index 64169ad0423d64f7fa01f7058cf61ee298f39286..2c807a78e3fa087081b0c644a276910f522622a3 100644
--- a/src/fov/fov.c
+++ b/src/fov/fov.c
@@ -716,7 +716,7 @@ void fov_beam(fov_settings_type *settings, void *map, void *source,
 }}}}}}}}
 
 void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source,
-                        int source_x, int source_y, unsigned radius,
+                        int source_x, int source_y, unsigned radius, int sx, int sy,
                         float dx, float dy, float beam_angle) {
 
     /* Note: angle_begin and angle_end are misnomers, since FoV calculation uses slopes, not angles.
@@ -749,7 +749,7 @@ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source,
         /* time for some slightly odd conventions.  We're assuming that dx and dy are still in coordinate space so
          * that "source_x + dx" gives the target tile coordinate.  dx, dy are floats, so we have sub-tile resolution.
          * We will then calculate the "real space" x's and y's to allow beam-casting at any angle. */
-        dy += (float)(((int)(abs(dx) + 0.5f)) & 1) * (0.5f - (float)(source_x & 1));
+        dy += (float)(((int)(abs(dx) + 0.5f)) & 1) * (0.5f - (float)(sx & 1));
         dx *= SQRT_3_2;
     }
 
diff --git a/src/fov/fov.h b/src/fov/fov.h
index c074e4a970db94f7faa43514c15e408221fb3256..39d0810db491376bdef4139e4a1d83d55eef4a88 100644
--- a/src/fov/fov.h
+++ b/src/fov/fov.h
@@ -343,7 +343,7 @@ void fov_beam(fov_settings_type *settings, void *map, void *source,
  * \param beam_angle The angle at the base of the beam of light, in degrees.
  */
 void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source,
-                        int source_x, int source_y, unsigned radius,
+                        int source_x, int source_y, unsigned radius, int sx, int sy,
                         float dx, float dy, float beam_angle
 );