diff --git a/game/engines/default/engine/Actor.lua b/game/engines/default/engine/Actor.lua
index 9b14c9d687622a00a7a5016fcf6b5b3a86928cee..c5bf859a9e0f68ca86bd496fdfe471268b1512f7 100644
--- a/game/engines/default/engine/Actor.lua
+++ b/game/engines/default/engine/Actor.lua
@@ -191,8 +191,26 @@ end
 
 --- Moves into the given direction (calls actor:move() internally)
 function _M:moveDir(dir)
-	local dx, dy = dir_to_coord[dir][1], dir_to_coord[dir][2]
+	local dx, dy = util.dirToCoord(dir, self.x, self.y)
+	if dir ~= 5 then self.doPlayerSlide = config.settings.player_slide end
+
+	-- Handles zig-zagging for non-square grids
+	local zig_zag = util.dirZigZag(dir, self.x, self.y)
+	local next_zig_zag = util.dirNextZigZag(dir, self.x, self.y)
+	if next_zig_zag then -- in hex mode, {1,2,3,7,8,9} dirs
+		self.zig_zag = next_zig_zag
+	elseif zig_zag then -- in hex mode, {4,6} dirs
+		self.zig_zag  = self.zig_zag or "zig"
+		local dir2 = zig_zag[self.zig_zag]
+		dx, dy = util.dirToCoord(dir2, self.x, self.y)
+		local nx, ny = util.coordAddDir(self.x, self.y, dir2)
+		self.zig_zag = util.dirNextZigZag(self.zig_zag, nx, ny)
+		if dir ~= 5 then self.doPlayerSlide = true end
+	end
+
 	local x, y = self.x + dx, self.y + dy
+	self.move_dir = dir
+	
 	return self:move(x, y)
 end
 
diff --git a/game/engines/default/engine/Astar.lua b/game/engines/default/engine/Astar.lua
index c9c87531a7f286e046775a04ac64795f2e6f9c7b..84cf2be9bb5abe8fc6913c4746c4e3a668a7f415 100644
--- a/game/engines/default/engine/Astar.lua
+++ b/game/engines/default/engine/Astar.lua
@@ -32,11 +32,20 @@ end
 
 --- The default heuristic for A*, tries to come close to the straight path
 function _M:heuristicCloserPath(sx, sy, cx, cy, tx, ty)
+	local h
+	if util.isHex() then
+		h = core.fov.distance(cx, cy, tx, ty)
+	else
+		-- Chebyshev  distance
+		h = math.max(math.abs(tx - cx), math.abs(ty - cy))
+	end
+
+	-- tie-breaker rule for straighter paths
 	local dx1 = cx - tx
 	local dy1 = cy - ty
 	local dx2 = sx - tx
 	local dy2 = sy - ty
-	return math.abs(dx1*dy2 - dx2*dy1)
+	return h + 0.01*math.abs(dx1*dy2 - dx2*dy1)
 end
 
 --- The a simple heuristic for A*, using distance
@@ -147,15 +156,8 @@ function _M:calc(sx, sy, tx, ty, use_has_seen, heuristic, add_check, forbid_diag
 		local x, y = self:toDouble(node)
 
 		-- Check sides
-		checkPos(node, x + 1, y)
-		checkPos(node, x - 1, y)
-		checkPos(node, x, y + 1)
-		checkPos(node, x, y - 1)
-		if not forbid_diagonals then
-			checkPos(node, x + 1, y + 1)
-			checkPos(node, x + 1, y - 1)
-			checkPos(node, x - 1, y + 1)
-			checkPos(node, x - 1, y - 1)
+		for _, coord in pairs(util.adjacentCoords(x, y, forbid_diagonals)) do
+			checkPos(node, coord[1], coord[2])
 		end
 	end
 end
diff --git a/game/engines/default/engine/Map.lua b/game/engines/default/engine/Map.lua
index 3ca848f443ff9464d605e3ee4e775bf5d893e876..891a7a398b72864c209993688fb0ac79c8f54697 100644
--- a/game/engines/default/engine/Map.lua
+++ b/game/engines/default/engine/Map.lua
@@ -211,7 +211,7 @@ end
 
 function _M:makeCMap()
 	--util.show_backtrace()
-	self._map = core.map.newMap(self.w, self.h, self.mx, self.my, self.viewport.mwidth, self.viewport.mheight, self.tile_w, self.tile_h, self.zdepth)
+	self._map = core.map.newMap(self.w, self.h, self.mx, self.my, self.viewport.mwidth, self.viewport.mheight, self.tile_w, self.tile_h, self.zdepth, util.isHex() and 1 or 0)
 	self._map:setObscure(unpack(self.color_obscure))
 	self._map:setShown(unpack(self.color_shown))
 	self._fovcache =
@@ -788,14 +788,14 @@ end
 function _M:getMouseTile(mx, my)
 --	if mx < self.display_x or my < self.display_y or mx >= self.display_x + self.viewport.width or my >= self.display_y + self.viewport.height then return end
 	local tmx = math.floor((mx - self.display_x) / (self.tile_w * self.zoom)) + self.mx
-	local tmy = math.floor((my - self.display_y) / (self.tile_h * self.zoom)) + self.my
+	local tmy = math.floor((my - self.display_y) / (self.tile_h * self.zoom) - util.hexOffset(tmx)) + self.my
 	return tmx, tmy
 end
 
 --- Get the screen position corresponding to a tile
 function _M:getTileToScreen(tx, ty)
 	local x = (tx - self.mx) * self.tile_w * self.zoom + self.display_x
-	local y = (ty - self.my) * self.tile_h * self.zoom + self.display_y
+	local y = (ty - self.my + util.hexOffset(tx)) * self.tile_h * self.zoom + self.display_y
 	return x, y
 end
 
@@ -1088,14 +1088,14 @@ function _M:displayParticles(nb_keyframes)
 			if nb_keyframes == 0 and e.x and e.y then
 				-- Just display it, not updating, no emitting
 				if e.x + e.radius >= self.mx and e.x - e.radius < self.mx + self.viewport.mwidth and e.y + e.radius >= self.my and e.y - e.radius < self.my + self.viewport.mheight then
-					e.ps:toScreen(dx + (adx + e.x - self.mx + 0.5) * self.tile_w * self.zoom, dy + (ady + e.y - self.my + 0.5) * self.tile_h * self.zoom, self.seens(e.x, e.y) or e.always_seen, e.zoom * self.zoom)
+					e.ps:toScreen(dx + (adx + e.x - self.mx + 0.5) * self.tile_w * self.zoom, dy + (ady + e.y - self.my + 0.5 + util.hexOffset(e.x)) * self.tile_h * self.zoom, self.seens(e.x, e.y) or e.always_seen, e.zoom * self.zoom)
 				end
 			elseif e.x and e.y then
 				alive = e.ps:isAlive()
 
 				-- Update more, if needed
 				if alive and e.x + e.radius >= self.mx and e.x - e.radius < self.mx + self.viewport.mwidth and e.y + e.radius >= self.my and e.y - e.radius < self.my + self.viewport.mheight then
-					e.ps:toScreen(dx + (adx + e.x - self.mx + 0.5) * self.tile_w * self.zoom, dy + (ady + e.y - self.my + 0.5) * self.tile_h * self.zoom, self.seens(e.x, e.y) or e.always_seen)
+					e.ps:toScreen(dx + (adx + e.x - self.mx + 0.5) * self.tile_w * self.zoom, dy + (ady + e.y - self.my + 0.5 + util.hexOffset(e.x)) * self.tile_h * self.zoom, self.seens(e.x, e.y) or e.always_seen)
 				end
 
 				if not alive then
diff --git a/game/engines/default/engine/Projectile.lua b/game/engines/default/engine/Projectile.lua
index 7aed1cdb7f39882693e24c71df7574659ffe5058..2a9a8226fb1e707e9de55b6c990162609fec2edc 100644
--- a/game/engines/default/engine/Projectile.lua
+++ b/game/engines/default/engine/Projectile.lua
@@ -46,11 +46,13 @@ end
 
 function _M:loaded()
 	if self.project and self.project.def and self.project.def.typ and self.project.def.typ.line_function and type(self.project.def.typ.line_function.line) == "table" then
-		self.project.def.typ.line_function.line = core.fov.line_import(unpack(self.project.def.typ.line_function.line))
+		self.project.def.typ.line_function.line = util.isHex() and core.fov.hex_line_import(unpack(self.project.def.typ.line_function.line)) or 
+			core.fov.line_import(unpack(self.project.def.typ.line_function.line))
 
 		-- The metatable gets lost somewhere in the save, so let's remake it
 		local mt = {}
 		mt.__index = function(t, key, ...) if t.line[key] then return t.line[key] end end
+		mt.__call = function(t, ...) return t.line:step() end
 		setmetatable(self.project.def.typ.line_function, mt)
 
 		-- The block function apparently uses an "upvalue" that is no longer available upon save/reload, so let's remake it
@@ -157,18 +159,6 @@ function _M:tooltip()
 	return "Projectile: "..self.name
 end
 
-local coords = {
-	[1] = { 4, 2, 7, 3 },
-	[2] = { 1, 3, 4, 6 },
-	[3] = { 2, 6, 1, 9 },
-	[4] = { 7, 1, 8, 2 },
-	[5] = {},
-	[6] = { 9, 3, 8, 2 },
-	[7] = { 4, 8, 1, 9 },
-	[8] = { 7, 9, 4, 6 },
-	[9] = { 8, 6, 7, 3 },
-}
-
 --- Move one step to the given target if possible
 -- This tries the most direct route, if not available it checks sides and always tries to get closer
 function _M:moveDirection(x, y)
@@ -176,18 +166,17 @@ function _M:moveDirection(x, y)
 	local lx, ly = l()
 	if lx and ly then
 		-- if we are blocked, try some other way
-		if game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") then
+		if game.level.map:checkEntity(lx, ly, Map.TERRAIN, "block_move") and not game.level.map:checkEntity(lx, ly, Map.TERRAIN, "pass_projectile") then
 			local dirx = lx - self.x
 			local diry = ly - self.y
-			local dir = coord_to_dir[dirx][diry]
-
-			local list = coords[dir]
+			local dir = util.coordToDir(dirx, diry, self.x, self.y)
+			local list = util.dirSides(dir, self.x, self.y)
 			local l = {}
 			-- Find possibilities
-			for i = 1, #list do
-				local dx, dy = self.x + dir_to_coord[list[i]][1], self.y + dir_to_coord[list[i]][2]
-				if not game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") then
-					l[#l+1] = {dx,dy, (dx-x)^2 + (dy-y)^2}
+			for _, dir in pairs(list) do
+				local dx, dy = util.coordAddDir(self.x, self.y, dir)
+				if not game.level.map:checkEntity(dx, dy, Map.TERRAIN, "block_move") or game.level.map:checkEntity(dx, dy, Map.TERRAIN, "pass_projectile") then
+					l[#l+1] = {dx,dy, core.fov.distance(x,y,dx,dy)^2}
 				end
 			end
 			-- Move to closest
diff --git a/game/engines/default/engine/Target.lua b/game/engines/default/engine/Target.lua
index 0c436dcbb8c4b8c067f759ffda0ae73ef5e17996..4feb72f7de45f1bb7f7c54327af1002be9991f2a 100644
--- a/game/engines/default/engine/Target.lua
+++ b/game/engines/default/engine/Target.lua
@@ -109,7 +109,7 @@ function _M:display(dispx, dispy)
 		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
 			s = self.sr
 		end
-		s:toScreen(self.display_x + (blocked_corner_x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (blocked_corner_y - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
+		s:toScreen(self.display_x + (blocked_corner_x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (blocked_corner_y - game.level.map.my + util.hexOffset(blocked_corner_x)) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
 		s = self.sr
 	end
 
@@ -140,7 +140,7 @@ function _M:display(dispx, dispy)
 				end
 			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)
+		s:toScreen(self.display_x + (lx - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (ly - game.level.map.my + util.hexOffset(lx)) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
 		if block then
 			s = self.sr
 			stopped = true
@@ -154,12 +154,12 @@ function _M:display(dispx, dispy)
 			hit_radius = false
 			s = self.sr
 			-- double the fun :-P
-			s:toScreen(self.display_x + (blocked_corner_x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (blocked_corner_y - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
-			s:toScreen(self.display_x + (blocked_corner_x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (blocked_corner_y - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
+			s:toScreen(self.display_x + (blocked_corner_x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (blocked_corner_y - game.level.map.my + util.hexOffset(blocked_corner_x)) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
+			s:toScreen(self.display_x + (blocked_corner_x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (blocked_corner_y - game.level.map.my + util.hexOffset(blocked_corner_x)) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
 		end
 
 	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)
+	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 + util.hexOffset(self.target.x)) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom)
 
 	if self.target_type.ball and self.target_type.ball > 0 then
 		core.fov.calc_circle(
@@ -174,12 +174,12 @@ function _M:display(dispx, dispy)
 			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) * self.tile_h * 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) * self.tile_h * 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
@@ -202,12 +202,12 @@ function _M:display(dispx, dispy)
 			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) * self.tile_h * 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) * self.tile_h * 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
@@ -326,17 +326,17 @@ function _M:setActive(v, type)
 end
 
 function _M:freemove(dir)
-	local d = dir_to_coord[dir]
-	self.target.x = self.target.x + d[1]
-	self.target.y = self.target.y + d[2]
+	local dx, dy = util.dirToCoord(dir, self.target.x, self.target.y)
+	self.target.x = self.target.x + dx
+	self.target.y = self.target.y + dy
 	self.target.entity = game.level.map(self.target.x, self.target.y, engine.Map.ACTOR)
 	if self.on_set_target then self:on_set_target("freemove") end
 end
 
 function _M:setDirFrom(dir, src)
-	local d = dir_to_coord[dir]
-	self.target.x = src.x + d[1]
-	self.target.y = src.y + d[2]
+	local dx, dy = util.dirToCoord(dir, src.x, src.y)
+	self.target.x = src.x + dx
+	self.target.y = src.y + dy
 	self.target.entity = game.level.map(self.target.x, self.target.y, engine.Map.ACTOR)
 	if self.on_set_target then self:on_set_target("dir_from") end
 end
diff --git a/game/engines/default/engine/ai/simple.lua b/game/engines/default/engine/ai/simple.lua
index c5dd06f910c0881f405e04ce6f25230eee31deed..2cd4744b69c1acc710fd2906c78505bcc2a0bcdb 100644
--- a/game/engines/default/engine/ai/simple.lua
+++ b/game/engines/default/engine/ai/simple.lua
@@ -37,7 +37,7 @@ newAI("move_dmap", function(self)
 		local c = a:distanceMap(self.x, self.y)
 		if not c then return self:runAI("move_simple") end
 		local dir = 5
-		for i = 1, 9 do
+		for _, i in ipairs(util.adjacentDirs()) do
 			local sx, sy = util.coordAddDir(self.x, self.y, i)
 			local cd = a:distanceMap(sx, sy)
 --			print("looking for dmap", dir, i, "::", c, cd)
@@ -55,7 +55,7 @@ newAI("flee_dmap", function(self)
 		local c = a:distanceMap(self.x, self.y)
 		if not c then return end
 		local dir = 5
-		for i = 1, 9 do
+		for _, i in ipairs(util.adjacentDirs()) do
 			local sx, sy = util.coordAddDir(self.x, self.y, i)
 			local cd = a:distanceMap(sx, sy)
 --			print("looking for dmap", dir, i, "::", c, cd)
diff --git a/game/engines/default/engine/ai/special_movements.lua b/game/engines/default/engine/ai/special_movements.lua
index 356ade2c889315d2ac53632c4e4b4c53176000b2..76169a1e2aee50d7246af3176b8652144241cb1e 100644
--- a/game/engines/default/engine/ai/special_movements.lua
+++ b/game/engines/default/engine/ai/special_movements.lua
@@ -46,11 +46,13 @@ newAI("move_snake", function(self)
 			elseif rd == 2 then
 				-- move to the left
 				local dir = util.getDir(tx, ty, self.x, self.y)
-				tx, ty = util.coordAddDir(self.x, self.y, dir_sides[dir].left)
+				local nextDir = util.dirSides(dir, self.x, self.y)
+				tx, ty = util.coordAddDir(self.x, self.y, nextDir and nextDir.left or dir)
 			elseif rd == 3 then
 				-- move to the right
 				local dir = util.getDir(tx, ty, self.x, self.y)
-				tx, ty = util.coordAddDir(self.x, self.y, dir_sides[dir].right)
+				local nextDir = util.dirSides(dir, self.x, self.y)
+				tx, ty = util.coordAddDir(self.x, self.y, nextDir and nextDir.right or dir)
 			end
 			return self:moveDirection(tx, ty)
 		end
diff --git a/game/engines/default/engine/generator/map/GOL.lua b/game/engines/default/engine/generator/map/GOL.lua
index 37ebfe3f08e35a7dcfb1080f1f202a78dc51395c..eeedfa9370cfc4d05b92b310d93b0b5009ff96b6 100644
--- a/game/engines/default/engine/generator/map/GOL.lua
+++ b/game/engines/default/engine/generator/map/GOL.lua
@@ -58,10 +58,10 @@ end
 
 function _M:liveOrDie(x, y)
 	local nb = 0
-	for i = -1, 1 do for j = -1, 1 do if i ~= 0 or j ~= 0 then
-		local g = self.map(x+i, y+j, Map.TERRAIN)
+	for _, coord in pairs(util.adjacentCoords(x, y)) if self.map:isBound(coord[1], coord[2]) then
+		local g = self.map(coord[1], coord[2], Map.TERRAIN)
 		if g and g == self.wall then nb = nb + 1 end
-	end end end
+	end end
 
 	if nb < 4 or nb > 7 then self.map(x, y, Map.TERRAIN, self.floor)
 	elseif nb == 5 or nb == 6 then self.map(x, y, Map.TERRAIN, self.wall)
diff --git a/game/engines/default/engine/generator/map/Roomer.lua b/game/engines/default/engine/generator/map/Roomer.lua
index e27af4419deab45803da8fe60039e895d552b5f5..339c833e48385597cfaae0ddfbca6e5781ebcd62 100644
--- a/game/engines/default/engine/generator/map/Roomer.lua
+++ b/game/engines/default/engine/generator/map/Roomer.lua
@@ -37,14 +37,14 @@ function _M:init(zone, map, level, data)
 end
 
 --- Random tunnel dir
-function _M:randDir()
-	local dirs = {4,6,8,2}
-	local d = dir_to_coord[dirs[rng.range(1, #dirs)]]
-	return d[1], d[2]
+function _M:randDir(sx, sy)
+	local dirs = util.primaryDirs() --{4,6,8,2}
+	return util.dirToCoord(dirs[rng.range(1, #dirs)], sx, sy)
 end
 
 --- Find the direction in which to tunnel
 function _M:tunnelDir(x1, y1, x2, y2)
+	-- HEX TODO ?
 	local xdir = (x1 == x2) and 0 or ((x1 < x2) and 1 or -1)
 	local ydir = (y1 == y2) and 0 or ((y1 < y2) and 1 or -1)
 	if xdir ~= 0 and ydir ~= 0 then
@@ -55,21 +55,17 @@ function _M:tunnelDir(x1, y1, x2, y2)
 	return xdir, ydir
 end
 
-local mark_dirs = {
-	[4] = {9,6,3},
-	[6] = {7,4,1},
-	[8] = {1,2,3},
-	[2] = {7,8,9},
-}
 --- Marks a tunnel as a tunnel and the space behind it
 function _M:markTunnel(x, y, xdir, ydir, id)
 	-- Disable the many prints of tunnelling
 	local print = function()end
 
 	x, y = x - xdir, y - ydir
-	local dir = coord_to_dir[xdir][ydir]
-	for i, d in ipairs(mark_dirs[dir]) do
-		local xd, yd = dir_to_coord[d][1], dir_to_coord[d][2]
+	local dir = util.coordToDir(xdir, ydir, x, y)
+	local sides = util.dirSides(dir, x, y)
+	local mark_dirs = {dir, sides.left, sides.right}
+	for i, d in ipairs(mark_dirs) do
+		local xd, yd = util.dirToCoord(d, x, y)
 		if self.map:isBound(x+xd, y+yd) and not self.map.room_map[x+xd][y+yd].tunnel then self.map.room_map[x+xd][y+yd].tunnel = id print("mark tunnel", x+xd, y+yd , id) end
 	end
 	if not self.map.room_map[x][y].tunnel then self.map.room_map[x][y].tunnel = id print("mark tunnel", x, y , id) end
@@ -78,32 +74,28 @@ end
 
 --- Can we create a door (will it lead anywhere)
 function _M:canDoor(x, y)
-	local g1 = self.map:checkEntity(x-1, y+1, Map.TERRAIN, "block_move")
-	local g2 = self.map:checkEntity(x  , y+1, Map.TERRAIN, "block_move")
-	local g3 = self.map:checkEntity(x+1, y+1, Map.TERRAIN, "block_move")
-	local g4 = self.map:checkEntity(x-1, y  , Map.TERRAIN, "block_move")
-	local g6 = self.map:checkEntity(x+1, y  , Map.TERRAIN, "block_move")
-	local g7 = self.map:checkEntity(x-1, y-1, Map.TERRAIN, "block_move")
-	local g8 = self.map:checkEntity(x  , y-1, Map.TERRAIN, "block_move")
-	local g9 = self.map:checkEntity(x+1, y-1, Map.TERRAIN, "block_move")
-
-	local w1 = not g1
-	local w2 = not g2
-	local w3 = not g3
-	local w4 = not g4
-	local w6 = not g6
-	local w7 = not g7
-	local w8 = not g8
-	local w9 = not g9
-
-	if
-		(not w1 and not w4 and not w6 and not w3 and w2 and w8) or
-		(not w7 and not w8 and not w1 and not w2 and w4 and w6) or
-		(not w8 and not w9 and not w2 and not w3 and w6 and w4) or
-		(not w4 and not w7 and not w6 and not w9 and w8 and w2)
-		then
-
-		return true
+	local open_spaces = {}
+	for dir, coord in pairs(util.adjacentCoords(x, y)) do
+		open_spaces[dir] = not self.map:checkEntity(coord[1], coord[2], Map.TERRAIN, "block_move")
+	end
+
+	-- Check the cardinal directions
+	for i, dir in pairs(util.primaryDirs()) do
+		local opposed_dir = util.opposedDir(dir, x, y)
+		if open_spaces[dir] and open_spaces[opposed_dir] then
+			local sides = util.dirSides(opposed_dir, x, y)
+			-- HEX TODO: hex tiles should not use hard left/right, but this is hackish
+			if util.isHex() then
+				sides = table.clone(sides)
+				sides.hard_left = nil
+				sides.hard_right = nil
+			end
+			local blocked = true
+			for _, check_dir in pairs(sides) do
+				blocked = blocked and not open_spaces[check_dir]
+			end
+			if blocked then return true end
+		end
 	end
 	return false
 end
@@ -124,7 +116,7 @@ function _M:tunnel(x1, y1, x2, y2, id)
 	local no_move_tries = 0
 	while tries > 0 do
 		if rng.percent(self.data.tunnel_change) then
-			if rng.percent(self.data.tunnel_random) then xdir, ydir = self:randDir()
+			if rng.percent(self.data.tunnel_random) then xdir, ydir = self:randDir(x1, x2)
 			else xdir, ydir = self:tunnelDir(x1, y1, x2, y2)
 			end
 		end
@@ -133,12 +125,12 @@ function _M:tunnel(x1, y1, x2, y2, id)
 		while true do
 			if self.map:isBound(nx, ny) then break end
 
-			if rng.percent(self.data.tunnel_random) then xdir, ydir = self:randDir()
+			if rng.percent(self.data.tunnel_random) then xdir, ydir = self:randDir(nx, ny)
 			else xdir, ydir = self:tunnelDir(x1, y1, x2, y2)
 			end
 			nx, ny = x1 + xdir, y1 + ydir
 		end
-		print(feat, "try pos", nx, ny, "dir", coord_to_dir[xdir][ydir])
+		print(feat, "try pos", nx, ny, "dir", util.coordToDir(xdir, ydir, nx, ny))
 
 		if self.map.room_map[nx][ny].special then
 			print(feat, "refuse special")
@@ -149,10 +141,12 @@ function _M:tunnel(x1, y1, x2, y2, id)
 		elseif self.map.room_map[nx][ny].can_open ~= nil then
 			if self.map.room_map[nx][ny].can_open then
 				print(feat, "tunnel crossing can_open", nx,ny)
-				for i = -1, 1 do for j = -1, 1 do if self.map:isBound(nx + i, ny + j) and self.map.room_map[nx + i][ny + j].can_open then
-					self.map.room_map[nx + i][ny + j].can_open = false
-					print(feat, "forbidding crossing at ", nx+i,ny+j)
-				end end end
+				for _, coord in pairs(util.adjacentCoords(nx, ny)) do
+					if self.map:isBound(coord[1], coord[2]) and self.map.room_map[coord[1]][coord[2]].can_open then
+						self.map.room_map[coord[1]][coord[2]].can_open = false
+						print(feat, "forbidding crossing at ", coord[1], coord[2])
+					end
+				end
 				tun[#tun+1] = {nx,ny,true}
 				x1, y1 = nx, ny
 				print(feat, "accept can_open")
diff --git a/game/engines/default/engine/generator/map/TileSet.lua b/game/engines/default/engine/generator/map/TileSet.lua
index f5ee61e9ac8b4ffab1ce1451e0e808164d690aab..c28d8541732147ac320647445705383f4744c5f3 100644
--- a/game/engines/default/engine/generator/map/TileSet.lua
+++ b/game/engines/default/engine/generator/map/TileSet.lua
@@ -287,15 +287,15 @@ function _M:buildTile(tile, bx, by, rid)
 	local opens = {}
 	for i, o in ipairs(tile.openings) do
 		print(" * opening in dir ", o[3], "::", o[1], o[2])
-		local coord = dir_to_coord[o[3]]
+		local x, y = util.dirToCoord(o[3], o[1], o[2])
 		local mts, type = self:findMatchingTiles(tile, o[3])
 		-- if we found no match for the given type try the other one
 		if #mts == 0 then mts, type = self:findMatchingTiles(tile, o[3], type == "room" and "tunnel" or "room") end
 
 		if #mts > 0 then
 			local mt = mts[rng.range(1, #mts)]
-			opens[#opens+1] = {bx + coord[1] + mt.stw, by + coord[2] + mt.sth, tile=mt.tile}
-			print("room at ",bx,by,"opens to",o[3],"::",bx + coord[1], by + coord[2])
+			opens[#opens+1] = {bx + x + mt.stw, by + y + mt.sth, tile=mt.tile}
+			print("room at ",bx,by,"opens to",o[3],"::",bx + x, by + y)
 		end
 	end
 
diff --git a/game/engines/default/engine/interface/ActorAI.lua b/game/engines/default/engine/interface/ActorAI.lua
index a58421319f0f8657edb7e7bc56b40a751f4b2b06..797a49683af3342d092e75cbed6e0a90c4c71f2b 100644
--- a/game/engines/default/engine/interface/ActorAI.lua
+++ b/game/engines/default/engine/interface/ActorAI.lua
@@ -58,18 +58,6 @@ function _M:autoLoadedAI()
 	setmetatable(self.ai_target, {__mode='v'})
 end
 
-local coords = {
-	[1] = { 4, 2, 7, 3 },
-	[2] = { 1, 3, 4, 6 },
-	[3] = { 2, 6, 1, 9 },
-	[4] = { 7, 1, 8, 2 },
-	[5] = {},
-	[6] = { 9, 3, 8, 2 },
-	[7] = { 4, 8, 1, 9 },
-	[8] = { 7, 9, 4, 6 },
-	[9] = { 8, 6, 7, 3 },
-}
-
 function _M:aiCanPass(x, y)
 	-- Nothing blocks, just go on
 	if not game.level.map:checkAllEntities(x, y, "block_move", self, true) then return true end
@@ -96,16 +84,15 @@ function _M:moveDirection(x, y, force)
 		-- if we are blocked, try some other way
 		if not self:aiCanPass(lx, ly) then
 			local dir = util.getDir(lx, ly, self.x, self.y)
-
-			local list = coords[dir]
+			local list = util.dirSides(dir, self.x, self.y)
 			local l = {}
 			-- Find possibilities
-			for i = 1, #list do if dir_to_coord[list[i]] then
-				local dx, dy = self.x + (dir_to_coord[list[i]][1] or 0), self.y + (dir_to_coord[list[i]][2] or 0)
+			for _, dir in pairs(list) do
+				local dx, dy = util.coordAddDir(self.x, self.y, dir)
 				if self:aiCanPass(dx, dy) then
-					l[#l+1] = {dx,dy, (dx-x)^2 + (dy-y)^2}
+					l[#l+1] = {dx,dy, core.fov.distance(x,y,dx,dy)^2}
 				end
-			end end
+			end
 			-- Move to closest
 			if #l > 0 then
 				table.sort(l, function(a,b) return a[3]<b[3] end)
diff --git a/game/engines/default/engine/interface/PlayerRun.lua b/game/engines/default/engine/interface/PlayerRun.lua
index 2ac98847675221f8f00395e6a1a0f79443817748..185779b90267499c00e3a999be31c0e5f26eda7c 100644
--- a/game/engines/default/engine/interface/PlayerRun.lua
+++ b/game/engines/default/engine/interface/PlayerRun.lua
@@ -24,22 +24,10 @@ local Dialog = require "engine.ui.Dialog"
 -- This should work for running inside tunnel, alongside walls, in open spaces.<br/>
 module(..., package.seeall, class.make)
 
-local sides =
-{
-	[1] = {hard_left=3, soft_left=2, soft_right=4, hard_right=7},
-	[4] = {hard_left=2, soft_left=1, soft_right=7, hard_right=8},
-	[7] = {hard_left=1, soft_left=4, soft_right=8, hard_right=9},
-	[8] = {hard_left=4, soft_left=7, soft_right=9, hard_right=6},
-	[9] = {hard_left=7, soft_left=8, soft_right=6, hard_right=3},
-	[6] = {hard_left=8, soft_left=9, soft_right=3, hard_right=2},
-	[3] = {hard_left=9, soft_left=6, soft_right=2, hard_right=1},
-	[2] = {hard_left=6, soft_left=3, soft_right=1, hard_right=4},
-}
-
 local function checkDir(a, dir, dist)
 	dist = dist or 1
-	local dx, dy = dir_to_coord[dir][1], dir_to_coord[dir][2]
-	local x, y = a.x + dx * dist, a.y + dy * dist
+	local x, y = a.x, a.y
+	for i = 1, dist do util.coordAddDir(x, y, dir) end
 	-- don't treat other actors as terrain or as something to notice (let the module handle this)
 	if game.level.map(x, y, game.level.map.ACTOR) and not game.level.map:checkEntity(x, y, game.level.map.TERRAIN, "block_move") then return false end
 	return (game.level.map:checkAllEntities(x, y, "block_move", a) or not game.level.map:isBound(x, y)) and true or false
@@ -63,17 +51,18 @@ function _M:runInit(dir)
 	}
 
 	-- Check sides
-	if sides[dir] then
-		if checkDir(self, sides[dir].hard_left) then self.running.block_hard_left = true end
-		if checkDir(self, sides[dir].hard_right) then self.running.block_hard_right = true end
+	local sides = util.dirSides(dir, self.x, self.y)
+	if sides then
+		if checkDir(self, sides.hard_left) then self.running.block_hard_left = true end
+		if checkDir(self, sides.hard_right) then self.running.block_hard_right = true end
 
-		if checkDir(self, sides[dir].soft_left) then
+		if checkDir(self, sides.left) then
 			self.running.block_left = true
 		else
 			self.running.ignore_left = 2
 		end
 
-		if checkDir(self, sides[dir].soft_right) then
+		if checkDir(self, sides.right) then
 			self.running.block_right = true
 		else
 			self.running.ignore_right = 2
@@ -157,30 +146,33 @@ function _M:runStep()
 		else
 			-- Try to move around known traps if possible
 			local dir_is_cardinal = self.running.dir == 2 or self.running.dir == 4 or self.running.dir == 6 or self.running.dir == 8
-			local dx, dy = dir_to_coord[self.running.dir][1], dir_to_coord[self.running.dir][2]
-			local x, y = self.x + dx, self.y + dy
+			local sides = util.dirSides(self.running_dir, self.x, self.y)
+			local dx, dy = util.dirToCoord(self.running.dir, self.x, self.y)
+			local x, y = util.coordAddDir(self.x, self.y, self.running.dir)
 			local trap = game.level.map(x, y, game.level.map.TRAP)
 			if trap and trap:knownBy(self) then
 				-- Take a phantom step forward and check path; backup current data first
 				local running_bak = table.clone(self.running)
 				self.x, self.y = x, y
 				local ret2, msg2 = self:runCheck(true) -- don't remember other items or traps from phantom steps
-				if self.running.dir == sides[running_bak.dir].hard_left then
-					running_bak.dir = sides[running_bak.dir].soft_left
-				elseif self.running.dir == sides[running_bak.dir].hard_right then
-					running_bak.dir = sides[running_bak.dir].soft_right
+				local sides_bak = util.dirSides(running_bak.dir, self.x, self.y)
+				local sides_dir = util.dirSides(self.running.dir, self.x, self.y)
+				if self.running.dir == sides_bak.hard_left then
+					running_bak.dir = sides_bak.left
+				elseif self.running.dir == sides_bak.hard_right then
+					running_bak.dir = sides_bak.right
 				else
 					ret2 = false
 				end
 				if self.running.ignore_left then
 					running_bak.ignore_left = running_bak.ignore_left - 1
 					if running_bak.ignore_left <= 0 then running_bak.ignore_left = nil end
-					if checkDir(self, sides[self.running.dir].soft_left) and (not checkDir(self, self.running.dir) or not dir_is_cardinal) then running_bak.block_left = true end
+					if checkDir(self, sides_dir.left) and (not checkDir(self, self.running.dir) or not dir_is_cardinal) then running_bak.block_left = true end
 				end
 				if self.running.ignore_right then
 					running_bak.ignore_right = running_bak.ignore_right - 1
 					if running_bak.ignore_right <= 0 then running_bak.ignore_right = nil end
-					if checkDir(self, sides[self.running.dir].soft_right) and (not checkDir(self, self.running.dir) or not dir_is_cardinal) then running_bak.block_right = true end
+					if checkDir(self, sides_dir.right) and (not checkDir(self, self.running.dir) or not dir_is_cardinal) then running_bak.block_right = true end
 				end
 				if self.running.block_left then running_bak.ignore_left = nil end
 				if self.running.block_right then running_bak.ignore_right = nil end
@@ -200,18 +192,19 @@ function _M:runStep()
 			if self.x == oldx and self.y == oldy then self:runStop() end
 			if not self.running then return false end
 
+			local sides = util.dirSides(self.running.dir, self.x, self.y)
 			if self.running.block_left then self.running.ignore_left = nil end
 			if self.running.ignore_left then
 				self.running.ignore_left = self.running.ignore_left - 1
 				if self.running.ignore_left <= 0 then
 					self.running.ignore_left = nil
 					-- We do this check here because it is path/time dependent, not terrain configuration dependent
-					if dir_is_cardinal and checkDir(self, sides[self.running.dir].soft_left) and checkDir(self, self.running.dir, 2) then
+					if dir_is_cardinal and checkDir(self, sides.left) and checkDir(self, self.running.dir, 2) then
 						self:runStop("terrain change on the left")
 						return false
 					end
 				end
-				if checkDir(self, sides[self.running.dir].soft_left) and (not checkDir(self, self.running.dir) or not dir_is_cardinal) then self.running.block_left = true end
+				if checkDir(self, sides.left) and (not checkDir(self, self.running.dir) or not dir_is_cardinal) then self.running.block_left = true end
 			end
 			if self.running.block_right then self.running.ignore_right = nil end
 			if self.running.ignore_right then
@@ -219,12 +212,12 @@ function _M:runStep()
 				if self.running.ignore_right <= 0 then
 					self.running.ignore_right = nil
 					-- We do this check here because it is path/time dependent, not terrain configuration dependent
-					if dir_is_cardinal and checkDir(self, sides[self.running.dir].soft_right) and checkDir(self, self.running.dir, 2) then
+					if dir_is_cardinal and checkDir(self, sides.right) and checkDir(self, self.running.dir, 2) then
 						self:runStop("terrain change on the right")
 						return false
 					end
 				end
-				if checkDir(self, sides[self.running.dir].soft_right) and (not checkDir(self, self.running.dir) or not dir_is_cardinal) then self.running.block_right = true end
+				if checkDir(self, sides.right) and (not checkDir(self, self.running.dir) or not dir_is_cardinal) then self.running.block_right = true end
 			end
 
 		end
@@ -247,36 +240,38 @@ function _M:runCheck()
 	if not self.running.path then
 		local dir_is_cardinal = self.running.dir == 2 or self.running.dir == 4 or self.running.dir == 6 or self.running.dir == 8
 		local blocked_ahead = checkDir(self, self.running.dir)
-		local blocked_soft_left = checkDir(self, sides[self.running.dir].soft_left)
-		local blocked_hard_left = checkDir(self, sides[self.running.dir].hard_left)
-		local blocked_soft_right = checkDir(self, sides[self.running.dir].soft_right)
-		local blocked_hard_right = checkDir(self, sides[self.running.dir].hard_right)
+		local sides = util.dirSides(self.running.dir, self.x, self.y)
+		local blocked_left = checkDir(self, sides.left)
+		local blocked_hard_left = checkDir(self, sides.hard_left)
+		local blocked_right = checkDir(self, sides.right)
+		local blocked_hard_right = checkDir(self, sides.hard_right)
 
 		-- Do we change run direction ? We can only choose to change for left or right, never backwards.
 		-- We must also be in a tunnel (both sides blocked)
 		if (self.running.block_left or self.running.ignore_left) and (self.running.block_right or self.running.ignore_right) then
 			if blocked_ahead then
-				if blocked_soft_right and (blocked_hard_right or self.running.ignore_right) then
-					local blocked_back_left = checkDir(self, sides[sides[self.running.dir].hard_left].soft_left)
+				if blocked_right and (blocked_hard_right or self.running.ignore_right) then
+					local back_left_x, back_left_y = util.coordAddDir(self.x, self.y, sides.hard_left)
+					local blocked_back_left = checkDir(self, util.dirSides(sides.hard_left, back_left_x, back_left_y).left)
 					-- Turn soft left
-					if not blocked_soft_left and (blocked_hard_left or not dir_is_cardinal) then
-						if not dir_is_cardinal and not blocked_hard_left and not (checkDir(self, sides[self.running.dir].soft_left, 2) and blocked_back_left) then
+					if not blocked_left and (blocked_hard_left or not dir_is_cardinal) then
+						if not dir_is_cardinal and not blocked_hard_left and not (checkDir(self, sides.left, 2) and blocked_back_left) then
 							return false, "terrain changed ahead"
 						end
-						self.running.dir = sides[self.running.dir].soft_left
+						self.running.dir = util.dirSides(self.running.dir, self.x, self.y).left
 						self.running.block_right = true
 						if blocked_hard_left then self.running.block_left = true end
 						return true
 					end
 					-- Turn hard left
 					if not blocked_hard_left and (not self.running.ignore_left or (self.running.block_hard_left and self.running.block_right)) then
-						if dir_is_cardinal and not blocked_soft_left and not checkDir(self, sides[self.running.dir].hard_left, 2) then
+						if dir_is_cardinal and not blocked_left and not checkDir(self, sides.hard_left, 2) then
 							return false, "terrain change on the left"
 						end
 						if not dir_is_cardinal and not blocked_back_left then
 							return false, "terrain ahead blocks"
 						end
-						self.running.dir = sides[self.running.dir].hard_left
+						self.running.dir = sides.hard_left
 						if self.running.block_hard_left and self.running.ignore_left and self.running.ignore_left == 1 then
 							self.running.block_left = true
 						end
@@ -284,27 +279,28 @@ function _M:runCheck()
 					end
 				end
 
-				if blocked_soft_left and (blocked_hard_left or self.running.ignore_left) then
-					local blocked_back_right = checkDir(self, sides[sides[self.running.dir].hard_right].soft_right)
+				if blocked_left and (blocked_hard_left or self.running.ignore_left) then
+					local back_right_x, back_right_y = util.coordAddDir(self.x, self.y, sides.hard_right)
+					local blocked_back_right = checkDir(self, util.dirSides(sides.hard_right, back_right_x, back_right_y).right)
 					-- Turn soft right
-					if not blocked_soft_right and (blocked_hard_right or not dir_is_cardinal) then
-						if not dir_is_cardinal and not blocked_hard_right and not (checkDir(self, sides[self.running.dir].soft_right, 2) and blocked_back_right) then
+					if not blocked_right and (blocked_hard_right or not dir_is_cardinal) then
+						if not dir_is_cardinal and not blocked_hard_right and not (checkDir(self, sides.right, 2) and blocked_back_right) then
 							return false, "terrain changed ahead"
 						end
-						self.running.dir = sides[self.running.dir].soft_right
+						self.running.dir = sides.right
 						self.running.block_left = true
 						if blocked_hard_right then self.running.block_right = true end
 						return true
 					end
 					-- Turn hard right
 					if not blocked_hard_right and (not self.running.ignore_right or (self.running.block_hard_right and self.running.block_left)) then
-						if dir_is_cardinal and not blocked_soft_right and not checkDir(self, sides[self.running.dir].hard_right, 2) then
+						if dir_is_cardinal and not blocked_right and not checkDir(self, sides.hard_right, 2) then
 							return false, "terrain change on the right"
 						end
 						if not dir_is_cardinal and not blocked_back_right then
 							return false, "terrain ahead blocks"
 						end
-						self.running.dir = sides[self.running.dir].hard_right
+						self.running.dir = sides.hard_right
 						if self.running.block_hard_right and self.running.ignore_right and self.running.ignore_right == 1 then
 							self.running.block_right = true
 						end
@@ -315,9 +311,9 @@ function _M:runCheck()
 				-- Favor cardinal directions if possible, otherwise we may miss something interesting
 				if not dir_is_cardinal then
 					-- Turn soft left
-					if blocked_soft_right and blocked_hard_left and not blocked_soft_left and (blocked_hard_right or self.running.ignore_right) and (not self.running.ignore_left or self.running.ignore_left ~= 2) then
-						if checkDir(self, sides[self.running.dir].soft_left, 2) then
-							self.running.dir = sides[self.running.dir].soft_left
+					if blocked_right and blocked_hard_left and not blocked_left and (blocked_hard_right or self.running.ignore_right) and (not self.running.ignore_left or self.running.ignore_left ~= 2) then
+						if checkDir(self, sides.left, 2) then
+							self.running.dir = sides.left
 							self.running.block_left = true
 							self.running.block_right = true
 							return true
@@ -326,9 +322,9 @@ function _M:runCheck()
 						end
 					end
 					-- Turn soft right
-					if blocked_soft_left and blocked_hard_right and not blocked_soft_right and (blocked_hard_left or self.running.ignore_left) and (not self.running.ignore_right or self.running.ignore_right ~= 2) then
-						if checkDir(self, sides[self.running.dir].soft_right, 2) then
-							self.running.dir = sides[self.running.dir].soft_right
+					if blocked_left and blocked_hard_right and not blocked_right and (blocked_hard_left or self.running.ignore_left) and (not self.running.ignore_right or self.running.ignore_right ~= 2) then
+						if checkDir(self, sides.right, 2) then
+							self.running.dir = sides.right
 							self.running.block_left = true
 							self.running.block_right = true
 							return true
@@ -342,15 +338,15 @@ function _M:runCheck()
 						return false, "terrain changed ahead"
 					end
 					-- Continue forward so we may turn
-					if (blocked_soft_left and not blocked_soft_right) or (blocked_soft_right and not blocked_soft_left) then return true end
+					if (blocked_left and not blocked_right) or (blocked_right and not blocked_left) then return true end
 				end
 			end
 		end
 
-		if not self.running.ignore_left and (self.running.block_left ~= blocked_soft_left or self.running.block_left ~= blocked_hard_left) then
+		if not self.running.ignore_left and (self.running.block_left ~= blocked_left or self.running.block_left ~= blocked_hard_left) then
 			return false, "terrain change on left side"
 		end
-		if not self.running.ignore_right and (self.running.block_right ~= blocked_soft_right or self.running.block_right ~= blocked_hard_right) then
+		if not self.running.ignore_right and (self.running.block_right ~= blocked_right or self.running.block_right ~= blocked_hard_right) then
 			return false, "terrain change on right side"
 		end
 		if blocked_ahead then
@@ -384,28 +380,29 @@ function _M:runScan(fct)
 	fct(self.x, self.y, "self")
 	if not self.running.path then
 		-- Ahead
-		local dx, dy = dir_to_coord[self.running.dir][1], dir_to_coord[self.running.dir][2]
+		local dx, dy = util.dirToCoord(self.running.dir, self.x, self.y)
 		local x, y = self.x + dx, self.y + dy
 		fct(x, y, "ahead")
 
-		if sides[self.running.dir] then
+		local sides = util.dirSides(self.running.dir, self.x, self.y)
+		if sides then 
 			-- Ahead left
-			local dx, dy = dir_to_coord[sides[self.running.dir].soft_left][1], dir_to_coord[sides[self.running.dir].soft_left][2]
+			local dx, dy = util.dirToCoord(sides.left, self.x, self.y)
 			local x, y = self.x + dx, self.y + dy
 			fct(x, y, "ahead left")
 
 			-- Ahead right
-			local dx, dy = dir_to_coord[sides[self.running.dir].soft_right][1], dir_to_coord[sides[self.running.dir].soft_right][2]
+			local dx, dy = util.dirToCoord(sides.right, self.x, self.y)
 			local x, y = self.x + dx, self.y + dy
 			fct(x, y, "ahead right")
 
 			-- Left
-			local dx, dy = dir_to_coord[sides[self.running.dir].hard_left][1], dir_to_coord[sides[self.running.dir].hard_left][2]
+			local dx, dy = util.dirToCoord(sides.hard_left, self.x, self.y)
 			local x, y = self.x + dx, self.y + dy
 			fct(x, y, "left")
 
 			-- Right
-			local dx, dy = dir_to_coord[sides[self.running.dir].hard_right][1], dir_to_coord[sides[self.running.dir].hard_right][2]
+			local dx, dy = util.dirToCoord(sides.hard_right, self.x, self.y)
 			local x, y = self.x + dx, self.y + dy
 			fct(x, y, "right")
 		end
diff --git a/game/engines/default/engine/interface/PlayerSlide.lua b/game/engines/default/engine/interface/PlayerSlide.lua
index 10b293d6ee237cda7cc364dc27b0650cb662103a..f6f91c5c72c4370bd42f9757363bb17a723aa845 100644
--- a/game/engines/default/engine/interface/PlayerSlide.lua
+++ b/game/engines/default/engine/interface/PlayerSlide.lua
@@ -27,7 +27,31 @@ module(..., package.seeall, class.make)
 function _M:tryPlayerSlide(x, y, force)
 	-- Try to slide along walls if possible
 	if game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move", self, false) and not force then
-		local dir = util.getDir(x, y, self.x, self.y)
+		local zig_zag = util.dirZigZag(self.move_dir, self.x, self.y)
+		local slide_dir = zig_zag and zig_zag[self.zig_zag] or util.dirSides(self.move_dir, self.x, self.y)
+		if slide_dir then
+			if type(slide_dir) == "table" and slide_dir.left and slide_dir.right then
+				tx1, ty1 = util.coordAddDir(self.x, self.y, slide_dir.left)
+				tx2, ty2 = util.coordAddDir(self.x, self.y, slide_dir.right)
+				if not game.level.map:checkEntity(tx1, ty1, Map.TERRAIN, "block_move", self, false) and game.level.map:checkEntity(tx2, ty2, Map.TERRAIN, "block_move", self, false) then
+					self.zig_zag = util.dirNextZigZag(slide_dir.left, self.x, self.y)
+					return tx1, ty1
+				elseif game.level.map:checkEntity(tx1, ty1, Map.TERRAIN, "block_move", self, false) and not game.level.map:checkEntity(tx2, ty2, Map.TERRAIN, "block_move", self, false) then
+					self.zig_zag = util.dirNextZigZag(slide_dir.right, self.x, self.y)
+					return tx2, ty2
+				end
+			else
+				slide_dir = type(slide_dir) == "table" and (slide_dir.right or slide_dir.left) or slide_dir
+				tx, ty = util.coordAddDir(self.x, self.y, slide_dir)
+				if not game.level.map:checkEntity(tx, ty, Map.TERRAIN, "block_move", self, false) then
+					self.zig_zag = util.dirNextZigZag(slide_dir, self.x, self.y)
+					return tx, ty
+				end
+			end
+		end
+
+
+--[[
 		local ldir, rdir = dir_sides[dir].left, dir_sides[dir].right
 		local lx, ly = util.coordAddDir(self.x, self.y, ldir)
 		local rx, ry = util.coordAddDir(self.x, self.y, rdir)
@@ -38,6 +62,7 @@ function _M:tryPlayerSlide(x, y, force)
 		elseif game.level.map:checkEntity(lx, ly, Map.TERRAIN, "block_move", self, false) and not game.level.map:checkEntity(rx, ry, Map.TERRAIN, "block_move", self, false) then
 			x, y = rx, ry
 		end
+--]]
 	end
-	return x, y
+	return nil
 end
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index 0c353f0a5010a59a94368823ce1f4d388a211ba2..5c5cc62cd1ea5745fa1626a74c399bf61f34637b 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -166,6 +166,21 @@ function table.update(dst, src, deep)
 	end
 end
 
+--- Creates a read-only table
+function table.readonly(src)
+   for k, v in pairs(src) do
+	if type(v) == "table" then
+		src[k] = table.readonly(v)
+	end
+   end
+   return setmetatable(src, {
+     __newindex = function(src, key, value)
+                    error("Attempt to modify read-only table")
+                  end,
+     __metatable = false
+   });
+end
+
 function string.ordinal(number)
 	local suffix = "th"
 	number = tonumber(number)
@@ -882,8 +897,7 @@ setmetatable(tstring, {
 	end,
 })
 
-
-dir_to_angle = {
+local dir_to_angle = table.readonly{
 	[1] = 225,
 	[2] = 270,
 	[3] = 315,
@@ -894,7 +908,8 @@ dir_to_angle = {
 	[8] = 90,
 	[9] = 45,
 }
-dir_to_coord = {
+
+local dir_to_coord = table.readonly{
 	[1] = {-1, 1},
 	[2] = { 0, 1},
 	[3] = { 1, 1},
@@ -905,7 +920,8 @@ dir_to_coord = {
 	[8] = { 0,-1},
 	[9] = { 1,-1},
 }
-coord_to_dir = {
+
+local coord_to_dir = table.readonly{
 	[-1] = {
 		[-1] = 7,
 		[ 0] = 4,
@@ -923,20 +939,19 @@ coord_to_dir = {
 	},
 }
 
-dir_sides =
-{
-	[1] = {left=2, right=4},
-	[2] = {left=3, right=1},
-	[3] = {left=6, right=2},
-	[4] = {left=1, right=7},
-	[5] = {left=7, right=9}, -- To avoid problems
-	[6] = {left=9, right=3},
-	[7] = {left=4, right=8},
-	[8] = {left=7, right=9},
-	[9] = {left=8, right=6},
+local dir_sides = table.readonly{
+	[1] = {hard_left=3, left=2, right=4, hard_right=7},
+	[2] = {hard_left=6, left=3, right=1, hard_right=4},
+	[3] = {hard_left=9, left=6, right=2, hard_right=1},
+	[4] = {hard_left=2, left=1, right=7, hard_right=8},
+	[5] = {hard_left=4, left=7, right=9, hard_right=6}, -- To avoid problems
+	[6] = {hard_left=8, left=9, right=3, hard_right=2},
+	[7] = {hard_left=1, left=4, right=8, hard_right=9},
+	[8] = {hard_left=4, left=7, right=9, hard_right=6},
+	[9] = {hard_left=7, left=8, right=6, hard_right=3},
 }
 
-opposed_dir = {
+local opposed_dir = table.readonly{
 	[1] = 9,
 	[2] = 8,
 	[3] = 7,
@@ -948,17 +963,247 @@ opposed_dir = {
 	[9] = 1,
 }
 
+local hex_dir_to_angle = table.readonly{
+	[1] = 210,
+	[2] = 270,
+	[3] = 330,
+	[4] = 180,
+	[5] = 0,
+	[6] = 0,
+	[7] = 150,
+	[8] = 90,
+	[9] = 30,
+}
+
+local hex_dir_to_coord = table.readonly{
+	[0] = {
+		[1] = {-1, 0},
+		[2] = { 0, 1},
+		[3] = { 1, 0},
+		[4] = {-1, 0},
+		[5] = { 0, 0},
+		[6] = { 1, 0},
+		[7] = {-1,-1},
+		[8] = { 0,-1},
+		[9] = { 1,-1},
+	},
+	[1] = {
+		[1] = {-1, 1},
+		[2] = { 0, 1},
+		[3] = { 1, 1},
+		[4] = {-1, 0},
+		[5] = { 0, 0},
+		[6] = { 1, 0},
+		[7] = {-1, 0},
+		[8] = { 0,-1},
+		[9] = { 1, 0},
+	}
+}
+
+local hex_coord_to_dir = table.readonly{
+	[0] = {
+		[-1] = {
+			[-1] = 7,
+			[ 0] = 1, -- or 4
+			[ 1] = 1,
+		},
+		[ 0] = {
+			[-1] = 8,
+			[ 0] = 5,
+			[ 1] = 2,
+		},
+		[ 1] = {
+			[-1] = 9,
+			[ 0] = 3, -- or 6
+			[ 1] = 3,
+		},
+	},
+	[1] = {
+		[-1] = {
+			[-1] = 7,
+			[ 0] = 7, -- or 4
+			[ 1] = 1,
+		},
+		[ 0] = {
+			[-1] = 8,
+			[ 0] = 5,
+			[ 1] = 2,
+		},
+		[ 1] = {
+			[-1] = 9,
+			[ 0] = 9, -- or 6
+			[ 1] = 3,
+		},
+	}
+}
+
+local hex_dir_sides = table.readonly{
+	[0] = {
+		[1] = {hard_left=3, left=2, right=7, hard_right=8},
+		[2] = {hard_left=9, left=3, right=1, hard_right=7},
+		[3] = {hard_left=8, left=9, right=2, hard_right=1},
+		[4] = {hard_left=2, left=1, right=7, hard_right=8},
+		[5] = {hard_left=1, left=7, right=9, hard_right=3}, -- To avoid problems
+		[6] = {hard_left=8, left=9, right=3, hard_right=2},
+		[7] = {hard_left=2, left=1, right=8, hard_right=9},
+		[8] = {hard_left=1, left=7, right=9, hard_right=3},
+		[9] = {hard_left=7, left=8, right=3, hard_right=2},
+	},
+	[1] = {
+		[1] = {hard_left=3, left=2, right=7, hard_right=8},
+		[2] = {hard_left=9, left=3, right=1, hard_right=7},
+		[3] = {hard_left=8, left=9, right=2, hard_right=1},
+		[4] = {hard_left=2, left=1, right=7, hard_right=8},
+		[5] = {hard_left=1, left=7, right=9, hard_right=3}, -- To avoid problems
+		[6] = {hard_left=8, left=9, right=3, hard_right=2},
+		[7] = {hard_left=2, left=1, right=8, hard_right=9},
+		[8] = {hard_left=1, left=7, right=9, hard_right=3},
+		[9] = {hard_left=7, left=8, right=3, hard_right=2},
+	}
+}
+
+local hex_next_zig_zag = table.readonly{
+	[1] = "zig",
+	[2] = "zig",
+	[3] = "zig",
+	[7] = "zag",
+	[8] = "zag",
+	[9] = "zag",
+	zag = "zig",
+	zig = "zag",
+}
+
+local hex_zig_zag = table.readonly{
+	[4] = {
+		zig = 7,
+		zag = 1,
+	},
+	[6] = {
+		zig = 9,
+		zag = 3,
+	},
+}
+
+local hex_opposed_dir = table.readonly{
+	[0] = {
+		[1] = 9,
+		[2] = 8,
+		[3] = 7,
+		[4] = 3,
+		[5] = 5,
+		[6] = 1,
+		[7] = 3,
+		[8] = 2,
+		[9] = 1,
+	},
+	[1] = {
+		[1] = 9,
+		[2] = 8,
+		[3] = 7,
+		[4] = 9,
+		[5] = 5,
+		[6] = 7,
+		[7] = 3,
+		[8] = 2,
+		[9] = 1,
+	},
+}
+
 util = {}
 
+local is_hex = 0
+function util.hexOffset(x)
+	return 0.5 * (x % 2) * is_hex
+end
+
+function util.isHex()
+	return is_hex == 1
+end
+
+function util.dirToAngle(dir, sx, sy)
+	return is_hex == 0 and dir_to_angle[dir] or hex_dir_to_angle[dir]
+end
+
+function util.dirToCoord(dir, sx, sy)
+	return unpack(is_hex == 0 and dir_to_coord[dir] or hex_dir_to_coord[sx % 2][dir])
+end
+
+function util.coordToDir(dx, dy, sx, sy)
+	return is_hex == 0 and coord_to_dir[dx][dy] or hex_coord_to_dir[sx % 2][dx][dy]
+end
+
+function util.dirSides(dir, sx, sy)
+	return is_hex == 0 and dir_sides[dir] or hex_dir_sides[sx % 2][dir]
+end
+
+function util.dirZigZag(dir, sx, sy)
+	if is_hex == 0 then
+		return nil
+	else
+		return hex_zig_zag[dir]
+	end
+end
+
+function util.dirNextZigZag(dir, sx, sy)
+	if is_hex == 0 then
+		return nil
+	else
+		return hex_next_zig_zag[dir]
+	end
+end
+
+function util.opposedDir(dir, sx, sy)
+	return is_hex == 0 and opposed_dir[dir] or hex_opposed_dir[sx % 2][dir]
+end
+
 function util.getDir(x1, y1, x2, y2)
 	local xd, yd = x1 - x2, y1 - y2
 	if xd ~= 0 then xd = xd / math.abs(xd) end
 	if yd ~= 0 then yd = yd / math.abs(yd) end
-	return coord_to_dir[xd][yd], xd, yd
+	return util.coordToDir(xd, yd, x2, y2), xd, yd
+end
+
+function util.primaryDirs()
+	return is_hex == 0 and {2, 4, 6, 8} or {1, 2, 3, 7, 8, 9}
+end
+
+function util.adjacentDirs()
+	return is_hex == 0 and {1, 2, 3, 4, 6, 7, 8, 9} or {1, 2, 3, 7, 8, 9}
+end
+
+--- A list of adjacent coordinates depending on core.fov.set_algorithm.
+-- @param x x-coordinate of the source tile.
+-- @param y y-coordinate of the source tile.
+-- @param no_diagonals Boolean that restricts diagonal motion.
+-- @param no_cardinals Boolean that restricts cardinal motion.
+-- @return Array of {x, y} coordinate arrays indexed by direction from source.
+function util.adjacentCoords(x, y, no_diagonals, no_cardinals)
+	local coords = {}
+
+	if is_hex == 0 then
+		if not no_cardinals then
+			coords[6] = {x+1, y  }
+			coords[4] = {x-1, y  }
+			coords[2] = {x  , y+1}
+			coords[8] = {x  , y-1}
+		end
+		if not no_diagonals then
+			coords[3] = {x+1, y+1}
+			coords[9] = {x+1, y-1}
+			coords[1] = {x-1, y+1}
+			coords[7] = {x-1, y-1}
+		end
+	elseif not no_cardinals then
+		for _, dir in ipairs(util.primaryDirs()) do
+			coords[dir] = {util.coordAddDir(x, y, dir)}
+		end
+	end
+	return coords
 end
 
 function util.coordAddDir(x, y, dir)
-	return x + dir_to_coord[dir][1], y + dir_to_coord[dir][2]
+	local dx, dy = util.dirToCoord(dir, x, y)
+	return x + dx, y + dy
 end
 
 function util.boundWrap(i, min, max)
@@ -1100,13 +1345,15 @@ function core.fov.line(sx, sy, tx, ty, block, start_at_end)
 			return game.level.map:checkAllEntities(x, y, what)
 		end
 
-	local line = core.fov.line_base(sx, sy, tx, ty, game.level.map.w, game.level.map.h, start_at_end, block)
+	local line = is_hex == 0 and core.fov.line_base(sx, sy, tx, ty, game.level.map.w, game.level.map.h, start_at_end, block) or
+			core.fov.hex_line_base(sx, sy, tx, ty, game.level.map.w, game.level.map.h, start_at_end, block)
 	local l = {}
 	l.line = line
 	l.block = block
 	l.set_corner_block = core.fov.set_corner_block
 	local mt = {}
 	mt.__index = function(t, key, ...) if t.line[key] then return t.line[key] end end
+	mt.__call = function(t, ...) return t.line:step() end
 	setmetatable(l, mt)
 
 	return l
@@ -1120,7 +1367,7 @@ end
 -- ..r
 -- Default is "square"
 function core.fov.set_permissiveness(val)
-	val = type(val) == "string" and (string.lower(val) == "square" and 0.0 or
+	val = type(val) == "string" and ((string.lower(val) == "default" or string.lower(val) == "square") and 0.0 or
 						string.lower(val) == "diamond" and 0.5 or
 						string.lower(val) == "octagon" and 1 - math.sqrt(0.5) or   --0.29289321881345247560 or
 						string.lower(val) == "firstpeek" and 0.167) or
@@ -1139,13 +1386,14 @@ end
 -- Default is "circle_round"
 function core.fov.set_vision_shape(val)
 	sval = type(val) == "string" and string.lower(val)
-	val = sval and ((sval == "circle" or sval == "circle_round") and 0 or
+	val = sval and ((sval == "default" or sval == "circle" or sval == "circle_round") and 0 or
 				sval == "circle_floor" and 1 or
 				sval == "circle_ceil" and 2 or
 				sval == "circle_plus1" and 3 or
 				sval == "octagon" and 4 or
 				sval == "diamond" and 5 or
-				sval == "square" and 6) or
+				sval == "square" and 6 or
+				(sval == "hex" or sval == "hexagon") and 7) or
 			type(tonumber(val)) == "number" and tonumber(val)
 
 	if type(val) ~= "number" then return end
@@ -1153,6 +1401,31 @@ function core.fov.set_vision_shape(val)
 	return val
 end
 
+function core.fov.set_algorithm(val)
+	sval = type(val) == "string" and string.lower(val)
+	val = sval and ((sval == "default" or sval == "shadow" or sval == "shadowcasting") and 0 or
+				(sval == "hex" or sval == "hexagon") and 1) or
+			type(tonumber(val)) == "number" and tonumber(val)
+
+	if type(val) ~= "number" then return end
+	if val == 1 then
+		core.fov.set_vision_shape("hex")
+		is_hex = 1
+	else
+		core.fov.set_vision_shape("circle")
+		core.fov.set_permissiveness("square")
+		is_hex = 0
+	end
+--	core.fov.set_algorithm_base(val)
+	return val
+end
+
+--- create a basic bresenham line (or hex equivalent)
+line = {}
+function line.new(sx, sy, tx, ty)
+	return is_hex == 0 and bresenham.new(sx, sy, tx, ty) or core.fov.line(sx, sy, tx, ty, function() end, false)
+end
+
 --- Finds free grids around coords in a radius.
 -- This will return a random grid, the closest possible to the epicenter
 -- @param sx the epicenter coordinates
diff --git a/game/modules/tome/ai/sandworm_tunneler.lua b/game/modules/tome/ai/sandworm_tunneler.lua
index dd1e1d8918f1b2c1f8f649daa0a1fcfe93d48a90..16fcc3c0b823b2ac4e52b8aba8a140f7267ef74a 100644
--- a/game/modules/tome/ai/sandworm_tunneler.lua
+++ b/game/modules/tome/ai/sandworm_tunneler.lua
@@ -21,10 +21,9 @@ local Object = require "engine.Object"
 local DamageType = require "engine.DamageType"
 
 --- Random tunnel dir
-local function randDir()
-	local dirs = {4,6,8,2}
-	local d = dir_to_coord[dirs[rng.range(1, #dirs)]]
-	return d[1], d[2]
+local function randDir(sx, sy)
+	local dirs = util.primaryDirs() --{4,6,8,2}
+	return util.dirToCoord(dirs[rng.range(1, #dirs)], sx, sy)
 end
 
 --- Find the direction in which to tunnel
@@ -51,7 +50,7 @@ local function tunnel(self, x1, y1, x2, y2)
 	local startx, starty = x1, y1
 
 	if rng.percent(30) then
-		if rng.percent(10) then xdir, ydir = randDir()
+		if rng.percent(10) then xdir, ydir = randDir(x1, x2)
 		else xdir, ydir = tunnelDir(x1, y1, x2, y2)
 		end
 	end
@@ -65,7 +64,7 @@ local function tunnel(self, x1, y1, x2, y2)
 		end
 		nx, ny = x1 + xdir, y1 + ydir
 	end
---	print(feat, "try pos", nx, ny, "dir", coord_to_dir[xdir][ydir])
+--	print(feat, "try pos", nx, ny, "dir", util.coordToDir(xdir, ydir, nx, ny))
 
 	return nx, ny
 end
diff --git a/game/modules/tome/ai/tactical.lua b/game/modules/tome/ai/tactical.lua
index 1bb2239d84c00f128e31f9430508f7a25980ef89..1b1e93e2fbb84ecb54755cabd3b14f9394780c91 100644
--- a/game/modules/tome/ai/tactical.lua
+++ b/game/modules/tome/ai/tactical.lua
@@ -45,7 +45,7 @@ local canFleeDmapKeepLos = function(self)
 		local c = act:distanceMap(self.x, self.y)
 		if not c then return end
 		local dir
-		for i = 1, 9 do
+		for _, i in ipairs(util.adjacentDirs()) do
 			local sx, sy = util.coordAddDir(self.x, self.y, i)
 			-- Check LOS first
 			if checkLOS(sx, sy, act.x, act.y) then
@@ -55,7 +55,8 @@ local canFleeDmapKeepLos = function(self)
 			end
 		end
 		if dir then
-			return true, self.x+dir_to_coord[dir][1], self.y+dir_to_coord[dir][2]
+			local dx, dy = util.dirToCoord(dir, self.x, self.y)
+			return true, self.x + dx, self.y + dy
 		else
 			return false
 		end
diff --git a/game/modules/tome/class/Encounter.lua b/game/modules/tome/class/Encounter.lua
index 3cafcb5ec6cea3a1f1f494885854ac1bec581d62..213df2e0a581ef9aac7b2cf3fdc02183111126b6 100644
--- a/game/modules/tome/class/Encounter.lua
+++ b/game/modules/tome/class/Encounter.lua
@@ -70,11 +70,11 @@ end
 
 function _M:findSpotGeneric(who, fct)
 	local spots = {}
-	for i = -1, 1 do for j = -1, 1 do if i ~= 0 or j ~= 0 then
-		if fct(game.level.map, who.x + i, who.y + j) then
-			spots[#spots+1] = {who.x + i, who.y + j}
+	for _, coord in pairs(util.adjacentCoords(who.x, who.y)) do if game.level.map:isBound(coord[1], coord[2]) then
+		if fct(game.level.map, coord[1], coord[2]) then
+			spots[#spots+1] = {coord[1], coord[2]}
 		end
-	end end end
+	end end
 	if #spots > 0 then
 		local s = rng.table(spots)
 		return s[1], s[2]
@@ -84,11 +84,11 @@ end
 function _M:findSpot(who, what)
 	what = what or "block_move"
 	local spots = {}
-	for i = -1, 1 do for j = -1, 1 do if i ~= 0 or j ~= 0 then
-		if not game.level.map:checkAllEntities(who.x + i, who.y + j, what, who) and game.level.map:checkAllEntities(who.x + i, who.y + j, "can_encounter", who) and not game.level.map:checkAllEntities(who.x + i, who.y + j, "change_level") then
-			spots[#spots+1] = {who.x + i, who.y + j}
+	for _, coord in pairs(util.adjacentCoords(who.x, who.y)) do if game.level.map:isBound(coord[1], coord[2]) then
+		if not game.level.map:checkAllEntities(coord[1], coord[2], what, who) and game.level.map:checkAllEntities(coord[1], coord[2], "can_encounter", who) and not game.level.map:checkAllEntities(coord[1], coord[2], "change_level") then
+			spots[#spots+1] = {coord[1], coord[2]}
 		end
-	end end end
+	end end
 	if #spots > 0 then
 		local s = rng.table(spots)
 		return s[1], s[2]
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index 9d3ab131d48493108f4c94cd416761d85203d564..d6fb9f6558af9b96ada82361b5ca7551efeb99f6 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -193,6 +193,14 @@ end
 function _M:move(x, y, force)
 	local ox, oy = self.x, self.y
 	local moved = mod.class.Actor.move(self, x, y, force)
+
+	if not force and ox == self.x and oy == self.y and self.doPlayerSlide then
+		self.doPlayerSlide = nil
+		tx, ty = self:tryPlayerSlide(x, y, false)
+		if tx then moved = self:move(tx, ty, false) end
+	end
+	self.doPlayerSlide = nil
+
 	if moved then
 		game.level.map:moveViewSurround(self.x, self.y, config.settings.tome.scroll_dist, config.settings.tome.scroll_dist)
 		game.level.map.attrs(self.x, self.y, "walked", true)
diff --git a/game/modules/tome/class/interface/PlayerExplore.lua b/game/modules/tome/class/interface/PlayerExplore.lua
index 880f5cbf0c76b08e8bb01b7626fec1cde56c2608..e37467b517ababd49896f47710a3a101f178f0bc 100644
--- a/game/modules/tome/class/interface/PlayerExplore.lua
+++ b/game/modules/tome/class/interface/PlayerExplore.lua
@@ -37,8 +37,400 @@ local function toDouble(c)
 	return c - y * game.level.map.w, y
 end
 
+local listAdjacentTiles
+local getNextNodes
+local listAdjacentNodes
+local listSharedNodes
+local listSharedNodesPrevious
+local previousNode
+local checkAmbush
+
+local map_type
+
+local function generateNodeFunctions()
+	if util.isHex() == map_type then return end
+	map_type = util.isHex()
+
+	if util.isHex() then
+
 -- a flexible but slow function to list all adjacent tile
-local function listAdjacentTiles(tile, no_diagonal, no_cardinal)
+listAdjacentTiles = function(tile, no_diagonal, no_cardinal)
+	local tiles = {}
+	local x, y, c, val
+	if type(tile) == "table" then
+		x, y, c, val = unpack(tile)
+		val = val + 1
+	elseif type(tile) == "number" then
+		x, y = toDouble(tile)
+		c = tile
+		val = 1
+	else
+		return tiles
+	end
+
+	local left_okay = x > 0
+	local right_okay = x < game.level.map.w - 1
+	local lower_okay = y > 0
+	local upper_okay = y < game.level.map.h - 1
+
+	local p = x % 2
+	local r = 1 - p
+	if not no_cardinal then
+		if (upper_okay or p == 0) and left_okay  then tiles[1]        = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end
+		if  upper_okay                           then tiles[#tiles+1] = {x,     y + 1, c     +   game.level.map.w, val, 2 } end
+		if (upper_okay or p == 0) and right_okay then tiles[#tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 } end
+		if (lower_okay or r == 0) and left_okay  then tiles[#tiles+1] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end
+		if  lower_okay                           then tiles[#tiles+1] = {x,     y - 1, c     -   game.level.map.w, val, 8 } end
+		if (lower_okay or r == 0) and right_okay then tiles[#tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end
+	end
+	return tiles
+end
+
+-- Performing a flood-fill algorithm in lua with robust logic is going to be relatively slow, so we
+-- need to make things more efficient wherever we can.  "getNextNodes" below is an example of this.
+-- Every node knows from which direction it was explored, and it only explores adjacent tiles that
+-- may not have previously been explored.  Nodes that were explored from a cardinal direction only
+-- have three new adjacent tiles to iterate over, and diagonal directions have five new tiles.
+-- Therefore, we should favor cardinal direction tile propagation for speed whenever possible.
+--
+-- Note: if we want this to be faster such as using a floodfill for NPCs (better ai!), then we should
+-- perform the floodfill in C, where we could use more advanced tricks to make it blazingly fast.
+getNextNodes = {
+	-- Dir 1
+	function(node, cardinal_tiles, diagonal_tiles)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+		
+		if x > 0 then
+			                                           cardinal_tiles[#cardinal_tiles+1] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 }
+			if y < game.level.map.h - 1 or p == 0 then cardinal_tiles[#cardinal_tiles+1] = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end
+		end
+		if y < game.level.map.h - 1                   then diagonal_tiles[#diagonal_tiles+1] = {x,     y + 1, c     +   game.level.map.w, val, 2 } end
+	end,
+	--Dir 2
+	function(node, cardinal_tiles, diagonal_tiles)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+
+		if y < game.level.map.h - 1 or p == 0 then
+			if x > 0                    then cardinal_tiles[#cardinal_tiles+1] = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end
+			if x < game.level.map.w - 1 then cardinal_tiles[#cardinal_tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 } end
+		end
+		if y < game.level.map.h - 1         then diagonal_tiles[#diagonal_tiles+1] = {x,     y + 1, c     +   game.level.map.w, val, 2 } end
+	end,
+	-- Dir 3
+	function(node, cardinal_tiles, diagonal_tiles)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		if x < game.level.map.w - 1 then
+			                                           cardinal_tiles[#cardinal_tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 }
+			if y < game.level.map.h - 1 or p == 0 then cardinal_tiles[#cardinal_tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 } end
+		end
+		if y < game.level.map.h - 1                   then diagonal_tiles[#diagonal_tiles+1] = {x,     y + 1, c     +   game.level.map.w, val, 2 } end
+	end,
+	--Dir 4
+	function(node, cardinal_tiles, diagonal_tiles) end,
+	--Dir 5 (all adjacent, slow)
+	function(node, cardinal_tiles, diagonal_tiles)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		local left_okay = x > 0
+		local right_okay = x < game.level.map.w - 1
+		local lower_okay = y > 0
+		local upper_okay = y < game.level.map.h - 1
+
+		if (upper_okay or p == 0) and left_okay  then cardinal_tiles[#cardinal_tiles+1] = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end
+		if  upper_okay                           then diagonal_tiles[#diagonal_tiles+1] = {x,     y + 1, c     +   game.level.map.w, val, 2 } end
+		if (upper_okay or p == 0) and right_okay then cardinal_tiles[#cardinal_tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 } end
+		if (lower_okay or r == 0) and left_okay  then cardinal_tiles[#cardinal_tiles+1] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end
+		if  lower_okay                           then diagonal_tiles[#diagonal_tiles+1] = {x,     y - 1, c     -   game.level.map.w, val, 8 } end
+		if (lower_okay or r == 0) and right_okay then cardinal_tiles[#cardinal_tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end
+	end,
+	--Dir 6
+	function(node, cardinal_tiles, diagonal_tiles) end,
+	-- Dir 7
+	function(node, cardinal_tiles, diagonal_tiles)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		if x > 0 then
+			                        cardinal_tiles[#cardinal_tiles+1] = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 }
+			if y > 0 or r == 0 then cardinal_tiles[#cardinal_tiles+1] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end
+		end
+		if y > 0                   then diagonal_tiles[#diagonal_tiles+1] = {x,     y - 1, c     -   game.level.map.w, val, 8 } end
+	end,
+	--Dir 8
+	function(node, cardinal_tiles, diagonal_tiles)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local r = 1 - x % 2
+
+		if y > 0 or r == 0 then
+			if x > 0 then                    cardinal_tiles[#cardinal_tiles+1] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end
+			if x < game.level.map.w - 1 then cardinal_tiles[#cardinal_tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end
+		end
+		if y > 0 then                            diagonal_tiles[#diagonal_tiles+1] = {x,     y - 1, c     -   game.level.map.w, val, 8 } end
+	end,
+	-- Dir 9
+	function(node, cardinal_tiles, diagonal_tiles)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		if x < game.level.map.w - 1 then
+			                        cardinal_tiles[#cardinal_tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 }
+			if y > 0 or r == 0 then cardinal_tiles[#cardinal_tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end
+		end
+		if y > 0 then                   diagonal_tiles[#diagonal_tiles+1] = {x,     y - 1, c     -   game.level.map.w, val, 8 } end
+	end
+}
+
+-- Use directional information to list all adjacent tiles more efficiently
+listAdjacentNodes = {
+	-- Dir 1
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		local tiles = {{x,     y - 1, c     -   game.level.map.w, val, 8 },
+		               {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 }}
+
+		if y < game.level.map.h - 1 then
+			tiles[3] = {x,     y + 1, c     +   game.level.map.w, val, 2 }
+			tiles[4] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 }
+		end
+		if x > 0 then
+			                                           tiles[#tiles+1] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 }
+			if y < game.level.map.h - 1 or p == 0 then tiles[#tiles+1] = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end
+		end
+
+		return tiles
+	end,
+	-- Dir 2
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		local tiles = {{x,     y - 1, c     -   game.level.map.w, val, 8 }}
+
+		if x > 0                            then tiles[2]        = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end
+		if x < game.level.map.w - 1         then tiles[#tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end
+
+		if y < game.level.map.h - 1 or p == 0 then
+			if x > 0                    then tiles[#tiles+1] = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end
+			if x < game.level.map.w - 1 then tiles[#tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 } end
+		end
+		if y < game.level.map.h - 1         then tiles[#tiles+1] = {x,     y + 1, c     +   game.level.map.w, val, 2 } end
+
+
+		return tiles
+	end,
+	-- Dir 3
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		local tiles = {{x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 },
+		               {x,     y - 1, c     -   game.level.map.w, val, 8 }}
+
+		if y < game.level.map.h - 1 then
+			tiles[3] = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 }
+			tiles[4] = {x,     y + 1, c     +   game.level.map.w, val, 2 }
+		end
+		if x < game.level.map.w - 1 then
+			                                           tiles[#tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 }
+			if y < game.level.map.h - 1 or p == 0 then tiles[#tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 } end
+		end
+
+		return tiles
+	end,
+	-- Dir 4
+	function(node) end,
+	-- Dir 5
+	function(node)
+		local tiles = {}
+		getNextNodes[5](node, tiles, tiles)
+		return tiles
+	end,
+	-- Dir 6
+	function(node) end,
+	-- Dir 7
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		local tiles = {{x,     y + 1, c     +   game.level.map.w, val, 2 },
+		               {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 }}
+
+		if y > 0 then
+			tiles[3] = {x,     y - 1, c     -   game.level.map.w, val, 8 }
+			tiles[4] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 }
+		end
+		if x > 0 then
+			                        tiles[#tiles+1] = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 }
+			if y > 0 or r == 0 then tiles[#tiles+1] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end
+		end
+
+		return tiles
+	end,
+	-- Dir 8
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		local tiles = {{x,     y + 1, c     +   game.level.map.w, val, 2 }}
+
+		if x > 0                            then tiles[2]        = {x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end
+		if x < game.level.map.w - 1         then tiles[#tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 } end
+		if y > 0 or r == 0 then
+			if x > 0 then                    tiles[#tiles+1] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end
+			if x < game.level.map.w - 1 then tiles[#tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end
+		end
+		if y > 0 then                            tiles[#tiles+1] = {x,     y - 1, c     -   game.level.map.w, val, 8 } end
+
+		return tiles
+	end,
+	-- Dir 9
+	function(node)
+		local x, y, c, val = node[1], node[2], node[3], node[4]+1
+		local p = x % 2
+		local r = 1 - p
+
+		local tiles = {{x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 },
+		               {x,     y + 1, c     +   game.level.map.w, val, 2 }}
+
+		if y > 0 then
+			tiles[3] = {x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 }
+			tiles[4] = {x,     y - 1, c     -   game.level.map.w, val, 8 }
+		end
+
+		if x < game.level.map.w - 1 then
+			                        tiles[#tiles+1] = {x + 1, y + p, c + 1 + p*game.level.map.w, val, 3 }
+			if y > 0 or r == 0 then tiles[#tiles+1] = {x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end
+		end
+
+		return tiles
+	end
+}
+
+-- DON'T TRY TO INFER WALLS IN HEX MODE
+-- List tiles that are adjacent to both current tile and previous tile that the previous tile iterated over.
+-- Right now these (and "listSharedNodesPrevious") are used to infer what might be a wall, and may be useful later.
+-- c = current, p = previous     c*    *c*
+-- * = returned tile             *p    .p.
+listSharedNodes = {
+	-- Dir 1
+	function(node) return {} end,
+	-- Dir 2
+	function(node) return {} end,
+	-- Dir 3
+	function(node) return {} end,
+	-- Dir 4
+	function(node) return {} end,
+	-- Dir 5
+	function(node) return {} end,
+	-- Dir 6
+	function(node) return {} end,
+	-- Dir 7
+	function(node) return {} end,
+	-- Dir 8
+	function(node) return {} end,
+	-- Dir 9
+	function(node) return {} end
+}
+
+-- DON'T TRY TO INFER WALLS IN HEX MODE
+-- A partial complement to "listSharedNodes".  "listSharedNodes" and "listSharedNodesPrevious" allow us to easily
+-- check specific configurations, which will come in handy if/when I rewrite the "hack" for exploring large areas.
+-- c = current, p = previous     c.    .c.
+-- * = returned tile             .p    *p*
+listSharedNodesPrevious = {
+	-- Dir 1
+	function(node) return {} end,
+	-- Dir 2
+	function(node) return {} end,
+	-- Dir 3
+	function(node) return {} end,
+	-- Dir 4
+	function(node) return {} end,
+	-- Dir 5
+	function(node) return {} end,
+	-- Dir 6
+	function(node) return {} end,
+	-- Dir 7
+	function(node) return {} end,
+	-- Dir 8
+	function(node) return {} end,
+	-- Dir 9
+	function(node) return {} end
+}
+
+previousNode = {
+	-- Dir 1
+	function(node) local x, y, c, val = unpack(node) ; r = 1 - x%2 ; return {x + 1, y - r, c + 1 - r*game.level.map.w, val-1, 9 } end,
+	-- Dir 2
+	function(node) local x, y, c, val = unpack(node) ;               return {x,     y - 1, c     -   game.level.map.w, val-1, 8 } end,
+	-- Dir 3
+	function(node) local x, y, c, val = unpack(node) ; r = 1 - x%2 ; return {x - 1, y - r, c - 1 - r*game.level.map.w, val-1, 7 } end,
+	-- Dir 4
+	function(node) end,
+	-- Dir 5
+	function(node) local x, y, c, val = unpack(node) ;               return {x,     y,     c,                          val-1, 5 } end,
+	-- Dir 6
+	function(node) end,
+	-- Dir 7
+	function(node) local x, y, c, val = unpack(node) ; p = x % 2 ;   return {x + 1, y + p, c + 1 + p*game.level.map.w, val-1, 3 } end,
+	-- Dir 8
+	function(node) local x, y, c, val = unpack(node) ;               return {x,     y + 1, c     +   game.level.map.w, val-1, 2 } end,
+	-- Dir 9
+	function(node) local x, y, c, val = unpack(node) ; p = x % 2 ;   return {x - 1, y + p, c - 1 + p*game.level.map.w, val-1, 1 } end
+}
+
+-- One more kindness to the player: take advantage of asymmetric LoS in this one specific case.
+-- If an enemy is at '?', the player is able to prevent an ambush by moving to 'x' instead of 't'.
+-- This is the only sensibly preventable ambush (that I know of) in which the player can move
+-- in a way to see the would-be ambusher and the would-be ambusher can't see the player.
+-- However, don't do this if it will step onto a known trap
+--
+--   .tx      Moving onto 't' puts us adjacent to an unseen tile, '?'
+--   ?#@      --> Pick 'x' instead
+checkAmbush = function(self)
+	-- HEX TODO
+	if true then return nil end
+
+	if not self.running or not self.running.explore or not self.running.path or not self.running.path[self.running.cnt] then return end
+
+	local cx, cy = self.running.path[self.running.cnt].x, self.running.path[self.running.cnt].y
+	if math.abs(self.x - cx) == 1 and math.abs(self.y - cy) == 1 then
+		if game.level.map:checkAllEntities(self.x, cy, "block_move", self) and not game.level.map:checkAllEntities(cx, self.y, "block_move", self) and
+				game.level.map:isBound(self.x, 2*cy - self.y) and not game.level.map.has_seens(self.x, 2*cy - self.y) then
+			local trap = game.level.map(cx, self.y, Map.TRAP)
+			if not trap or not trap:knownBy(self) then
+				table.insert(self.running.path, self.running.cnt, {x=cx, y=self.y})
+			end
+		elseif game.level.map:checkAllEntities(cx, self.y, "block_move", self) and not game.level.map:checkAllEntities(self.x, cy, "block_move", self) and
+				game.level.map:isBound(2*cx - self.x, self.y) and not game.level.map.has_seens(2*cx - self.x, self.y) then
+			local trap = game.level.map(self.x, cy, Map.TRAP)
+			if not trap or not trap:knownBy(self) then
+				table.insert(self.running.path, self.running.cnt, {x=self.x, y=cy})
+			end
+		end
+	end
+end
+
+	else
+-- a flexible but slow function to list all adjacent tile
+listAdjacentTiles = function(tile, no_diagonal, no_cardinal)
 	local tiles = {}
 	local x, y, c, val
 	if type(tile) == "table" then
@@ -81,7 +473,7 @@ end
 --
 -- Note: if we want this to be faster such as using a floodfill for NPCs (better ai!), then we should
 -- perform the floodfill in C, where we could use more advanced tricks to make it blazingly fast.
-local getNextNodes = {
+getNextNodes = {
 	-- Dir 1
 	function(node, cardinal_tiles, diagonal_tiles)
 		local x, y, c, val = node[1], node[2], node[3], node[4]+1
@@ -208,7 +600,7 @@ local getNextNodes = {
 }
 
 -- Use directional information to list all adjacent tiles more efficiently
-local listAdjacentNodes = {
+listAdjacentNodes = {
 	-- Dir 1
 	function(node)
 		local x, y, c, val = node[1], node[2], node[3], node[4]+1
@@ -417,7 +809,7 @@ local listAdjacentNodes = {
 -- Right now these (and "listSharedNodesPrevious") are used to infer what might be a wall, and may be useful later.
 -- c = current, p = previous     c*    *c*
 -- * = returned tile             *p    .p.
-local listSharedNodes = {
+listSharedNodes = {
 	-- Dir 1
 	function(node)
 		local x, y, c, val = unpack(node)
@@ -486,7 +878,7 @@ local listSharedNodes = {
 -- check specific configurations, which will come in handy if/when I rewrite the "hack" for exploring large areas.
 -- c = current, p = previous     c.    .c.
 -- * = returned tile             .p    *p*
-local listSharedNodesPrevious = {
+listSharedNodesPrevious = {
 	-- Dir 1
 	function(node) return {} end,
 	-- Dir 2
@@ -535,7 +927,7 @@ local listSharedNodesPrevious = {
 	function(node) return {} end
 }
 
-local previousNode = {
+previousNode = {
 	-- Dir 1
 	function(node) local x, y, c, val = unpack(node) ; return {x + 1, y - 1, c + 1 - game.level.map.w, val-1, 9 } end,
 	-- Dir 2
@@ -564,7 +956,7 @@ local previousNode = {
 --
 --   .tx      Moving onto 't' puts us adjacent to an unseen tile, '?'
 --   ?#@      --> Pick 'x' instead
-local function checkAmbush(self)
+checkAmbush = function(self)
 	if not self.running or not self.running.explore or not self.running.path or not self.running.path[self.running.cnt] then return end
 
 	local cx, cy = self.running.path[self.running.cnt].x, self.running.path[self.running.cnt].y
@@ -585,9 +977,15 @@ local function checkAmbush(self)
 	end
 end
 
+end -- end else
+end -- end generateNodeFunctions
+
+
 -- If a target destination is found, then it creates (or updates) "self.running" and returns true.
 -- If no target, or if we shouldn't explore for some reason, then return false.
 function _M:autoExplore()
+	generateNodeFunctions()
+
 	-- levels that use "all_remembered" (like towns) don't set "has_seen" values to true for all grids,
 	-- so this lets us behave reasonably in these zones: go to objects and then exit
 	local do_unseen = not (game.level.all_remembered or game.zone and game.zone.all_remembered)
@@ -653,10 +1051,11 @@ function _M:autoExplore()
 							end
 						end
 
+						-- DON'T TRY TO INFER WALLS IN HEX MODE
 						-- look for tiles that are probably walls so we can hopefully explore more efficiently by preventing unnecessary return trips.
 						--               ?#       #?#
 						-- For example:  #.  and  ...  are probably walls (in most zones)
-						if not is_singlet then
+						if not is_singlet and not util.isHex() then
 							is_singlet = true
 							for _, anode in ipairs(listSharedNodes[dir](node)) do
 								if not game.level.map.has_seens(anode[1], anode[2]) or not game.level.map:checkEntity(anode[1], anode[2], Map.TERRAIN, "does_block_move") then
@@ -807,7 +1206,8 @@ function _M:autoExplore()
 		end
 
 		-- if we need to continue running but have no more tiles to iterate over, propagate from "slow_tiles" such as traps
-		if running and #current_tiles_next == 0 and #slow_tiles > 0 then
+		if #current_tiles_next == 0 and #slow_tiles > 0 and #unseen_tiles == 0 and #unseen_items == 0 and #unseen_doors == 0 and #exits == 0 and #portals == 0 then
+			running = true
 			current_tiles = slow_tiles
 			for _, node in ipairs(slow_tiles) do
 				local c, val = node[3], node[4]
diff --git a/game/modules/tome/data/rooms/space_tree_pod.lua b/game/modules/tome/data/rooms/space_tree_pod.lua
index 151fcef0224a6f9fd1a6634ca2a883f06eda55d8..5b9d04420a8aba2f672277016402d5464b0fda27 100644
--- a/game/modules/tome/data/rooms/space_tree_pod.lua
+++ b/game/modules/tome/data/rooms/space_tree_pod.lua
@@ -19,14 +19,18 @@
 
 local function check_borders(room_map, x, y, id)
 	local nb = 0
+	local possible_nb = 0
 	local own = false
-	for i = -1, 1 do for j = -1, 1 do
-		if room_map[x+i] and room_map[x+i][y+j] and room_map[x+i][y+j].room == id then
+	local coords = util.adjacentCoords(x, y)
+	coords[5] = {x, y}
+	for _, coord in pairs(coords) do
+		possible_nb = possible_nb + 1
+		if room_map[coord[1]] and room_map[coord[1]][coord[2]] and room_map[coord[1]][coord[2]].room == id then
 			nb = nb + 1
-			if i == 0 and j == 0 then own = true end
+			if coord[1] == x and coord[2] == y then own = true end
 		end
-	end end
-	return nb, own
+	end
+	return nb, possible_nb, own
 end
 
 return function(gen, id)
@@ -54,12 +58,12 @@ return function(gen, id)
 		local wormholes = {}
 		for i = x, x + mw do
 			for j = y, y + mh do
-				local nb, own = check_borders(gen.map.room_map, i, j, id)
+				local nb, possible_nb, own = check_borders(gen.map.room_map, i, j, id)
 				if nb > 0 then pod[#pod+1] = {x=i-x, y=j-y} end
-				if nb >= 9 and own and rng.percent(is_first and 10 or 40) then
+				if nb >= possible_nb and own and rng.percent(is_first and 10 or 40) then
 					gen.map(i, j, Map.TERRAIN, gen:resolve('T'))
 				end
-				if is_first and nb >= 9 and own then wormholes[#wormholes+1] = {i,j} end
+				if is_first and nb >= possible_nb and own then wormholes[#wormholes+1] = {i,j} end
 			end
 		end
 		if is_first and gen.level.level < gen.zone.max_level then
@@ -71,7 +75,7 @@ return function(gen, id)
 			end
 		end
 
-		gen.level.pods[#(gen.level.pods)+1] = {x1=x, x2=x+mw, y1=y, y2=y+mh, w=mw, h=mh, pod=pod, dir=rng.table{2,4,6,8}}
+		gen.level.pods[#(gen.level.pods)+1] = {x1=x, x2=x+mw, y1=y, y2=y+mh, w=mw, h=mh, pod=pod, dir=rng.table(util.primaryDirs())}
 		print(table.serialize(pod,nil,true))
 	end
 
diff --git a/game/modules/tome/data/talents/cunning/poisons.lua b/game/modules/tome/data/talents/cunning/poisons.lua
index 4d42c076b6ab1f1ac5c6d5bd973a8f705f997d73..cf5f6e5e04a0b2d5eec02f84470c170c9384d502 100644
--- a/game/modules/tome/data/talents/cunning/poisons.lua
+++ b/game/modules/tome/data/talents/cunning/poisons.lua
@@ -167,10 +167,12 @@ newTalent{
 	require = cuns_req_high4,
 	on_kill = function(self, t, target)
 		local possible = {}
-		for i = -1, 1 do for j = -1, 1 do if game.level.map:isBound(target.x + i, target.y + j) then if i ~= 0 or j ~= 0 then
-			local tgt = game.level.map(target.x + i, target.y + j, Map.ACTOR)
-			if tgt and not tgt.dead then possible[tgt] = true end
-		end end end end
+		for _, coord in pairs(util.adjacentCoords(target.x, target.y)) do
+			if game.level.map:isBound(coord[1], coord[2]) then
+				local tgt = game.level.map(coord[1], coord[2], Map.ACTOR)
+				if tgt and not tgt.dead then possible[tgt] = true end
+			end
+		end
 		possible[self] = nil
 		possible[target] = nil
 		possible = table.keys(possible)
diff --git a/game/modules/tome/data/talents/cursed/force-of-will.lua b/game/modules/tome/data/talents/cursed/force-of-will.lua
index 5064f8f1de0d0644e5389f310a8dfda6c3f38f3e..00bfdff1053ef4f853b8bd80d2eb3d2b2937ac06 100644
--- a/game/modules/tome/data/talents/cursed/force-of-will.lua
+++ b/game/modules/tome/data/talents/cursed/force-of-will.lua
@@ -33,9 +33,10 @@ local function forceHit(self, target, sourceX, sourceY, damage, knockback, knock
 	if not target.dead and knockback and knockback > 0 and target:canBe("knockback") and (target.never_move or 0) < 1 then
 		-- give direct hit a direction?
 		if sourceX == target.x and sourceY == target.y then
-			local newDirection = rng.range(1, 8)
-			sourceX = sourceX + dir_to_coord[newDirection][1]
-			sourceY = sourceY + dir_to_coord[newDirection][2]
+			local newDirection = rng.table(util.adjacentDirs())
+			local dx, dy = util.dirToCoord(newDirection, sourceX, sourceY)
+			sourceX = sourceX + dx
+			sourceY = sourceY + dy
 		end
 
 		local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", target) end
diff --git a/game/modules/tome/data/talents/cursed/slaughter.lua b/game/modules/tome/data/talents/cursed/slaughter.lua
index 418fba7b2674f1c2aa7fdf345c887cabae249cee..617840a37121f25b586c9acc82c32c0869139ce7 100644
--- a/game/modules/tome/data/talents/cursed/slaughter.lua
+++ b/game/modules/tome/data/talents/cursed/slaughter.lua
@@ -84,19 +84,21 @@ newTalent{
 		local level = math.max(3 * self:getTalentTypeMastery(t.type[1]) - 2, self:getTalentLevel(t) - 2)
 		return -self:rescaleDamage((math.sqrt(level) - 0.5) * 15 * ((100 + self:getStat("str")) / 200))
 	end,
+	range = 0,
+	radius = 1,
+        target = function(self, t)
+                return {type="ball", range=self:getTalentRange(t), selffire=false, radius=self:getTalentRadius(t)}
+        end,
 	action = function(self, t)
+		local tg = self:getTalentTarget(t)
+
 		local targets = {}
-		for i = -1, 1 do
-			for j = -1, 1 do
-				local x, y = self.x + i, self.y + j
-				if (self.x ~= x or self.y ~= y) and game.level.map:isBound(x, y) and game.level.map(x, y, Map.ACTOR) then
-					local target = game.level.map(x, y, Map.ACTOR)
-					if target and self:reactionToward(target) < 0 then
-						targets[#targets+1] = target
-					end
-				end
+		self:project(tg, self.x, self.y, function(px, py, tg, self)
+			local target = game.level.map(px, py, Map.ACTOR)
+			if target and self:reactionToward(target) < 0 then
+				targets[#targets+1] = target
 			end
-		end
+		end)
 
 		if #targets <= 0 then return nil end
 
diff --git a/game/modules/tome/data/talents/misc/inscriptions.lua b/game/modules/tome/data/talents/misc/inscriptions.lua
index 3ec8d32290a6d346e812bb25d417c77e8e2bae6f..825a66d8069b68bdaa071aca60c9b4a48cc2e038 100644
--- a/game/modules/tome/data/talents/misc/inscriptions.lua
+++ b/game/modules/tome/data/talents/misc/inscriptions.lua
@@ -492,10 +492,10 @@ newInscription{
 		self:magicMap(data.range, self.x, self.y, function(x, y)
 			local g = game.level.map(x, y, Map.TERRAIN)
 			if g and (g.always_remember or g:check("block_move")) then
-				for i = -1, 1 do for j = -1, 1 do
-					local g2 = game.level.map(x + i, y + j, Map.TERRAIN)
+				for _, coord in pairs(util.adjacentCoords(x, y)) do
+					local g2 = game.level.map(coord[1], coord[2], Map.TERRAIN)
 					if g2 and not g2:check("block_move") then return true end
-				end end
+				end
 			end
 		end)
 		self:setEffect(self.EFF_SENSE_HIDDEN, data.dur, {power=data.power + data.inc_stat})
diff --git a/game/modules/tome/data/talents/spells/earth.lua b/game/modules/tome/data/talents/spells/earth.lua
index 4357136b743e1f13f6941f9aca07e9d632b400bf..4766b8a21fb3886c8ae99ca3b32b2b0585ab5942 100644
--- a/game/modules/tome/data/talents/spells/earth.lua
+++ b/game/modules/tome/data/talents/spells/earth.lua
@@ -131,14 +131,14 @@ newTalent{
 			if not x or not y then return nil end
 		end
 
-		for i = -1, 1 do for j = -1, 1 do if game.level.map:isBound(x + i, y + j) then
-			if not game.level.map:checkAllEntities(x + i, y + j, "block_move") then
+		for _, coord in pairs(util.adjacentCoords(self.x, self.y)) do if game.level.map:isBound(coord[1], coord[2]) then
+			if not game.level.map:checkAllEntities(coord[1], coord[2], "block_move") then
 				-- Ok some explanation, we make a new *OBJECT* because objects can have energy and act
 				-- it stores the current terrain in "old_feat" and restores it when it expires
 				-- We CAN set an object as a terrain because they are all entities
 
 				local e = Object.new{
-					old_feat = game.level.map(x + i, y + j, Map.TERRAIN),
+					old_feat = game.level.map(coord[1], coord[2], Map.TERRAIN),
 					name = "summoned wall", image = "terrain/granite_wall1.png",
 					display = '#', color_r=255, color_g=255, color_b=255, back_color=colors.GREY,
 					always_remember = true,
@@ -146,7 +146,7 @@ newTalent{
 					block_move = true,
 					block_sight = true,
 					temporary = t.getDuration(self, t),
-					x = x + i, y = y + j,
+					x = coord[1], y = coord[2],
 					canAct = false,
 					act = function(self)
 						self:useEnergy()
@@ -166,9 +166,9 @@ newTalent{
 					summoner = self,
 				}
 				game.level:addEntity(e)
-				game.level.map(x + i, y + j, Map.TERRAIN, e)
+				game.level.map(coord[1], coord[2], Map.TERRAIN, e)
 			end
-		end end end
+		end end
 
 		game:playSoundNear(self, "talents/earth")
 		return true
diff --git a/game/modules/tome/data/talents/techniques/2hweapon.lua b/game/modules/tome/data/talents/techniques/2hweapon.lua
index 0d7468aa184ac02845b48348813b9143748fd7ef..451a8f4aa72d649b81850a3081d16b1ffe23c0ee 100644
--- a/game/modules/tome/data/talents/techniques/2hweapon.lua
+++ b/game/modules/tome/data/talents/techniques/2hweapon.lua
@@ -41,13 +41,13 @@ newTalent{
 			return nil
 		end
 
-		for i = -1, 1 do for j = -1, 1 do
-			local x, y = self.x + i, self.y + j
-			if (self.x ~= x or self.y ~= y) and game.level.map:isBound(x, y) and game.level.map(x, y, Map.ACTOR) then
-				local target = game.level.map(x, y, Map.ACTOR)
+		local tg = self:getTalentTarget(t)
+		self:project(tg, self.x, self.y, function(px, py, tg, self)
+			local target = game.level.map(px, py, Map.ACTOR)
+			if target and target ~= self then
 				self:attackTargetWith(target, weapon.combat, nil, self:combatTalentWeaponDamage(t, 1.4, 2.1))
 			end
-		end end
+		end)
 
 		return true
 	end,
diff --git a/game/modules/tome/data/talents/techniques/dualweapon.lua b/game/modules/tome/data/talents/techniques/dualweapon.lua
index c2b0ec60e8956c7f95273da97e4d406363181f38..93ce71c14144ea71a2517cfbc97459c60e0857d3 100644
--- a/game/modules/tome/data/talents/techniques/dualweapon.lua
+++ b/game/modules/tome/data/talents/techniques/dualweapon.lua
@@ -265,13 +265,13 @@ newTalent{
 			return nil
 		end
 
-		for i = -1, 1 do for j = -1, 1 do
-			local x, y = self.x + i, self.y + j
-			if (self.x ~= x or self.y ~= y) and game.level.map:isBound(x, y) and game.level.map(x, y, Map.ACTOR) then
-				local target = game.level.map(x, y, Map.ACTOR)
+		local tg = self:getTalentTarget(t)
+		self:project(tg, self.x, self.y, function(px, py, tg, self)
+			local target = game.level.map(px, py, Map.ACTOR)
+			if target and target ~= self then
 				self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 1.2, 1.9), true)
 			end
-		end end
+		end)
 
 		return true
 	end,
diff --git a/game/modules/tome/data/talents/techniques/weaponshield.lua b/game/modules/tome/data/talents/techniques/weaponshield.lua
index a84e1fe581a4df533943ad4b1ab4f1246738def6..cc3c426825fc6459e23aa0dbd51dceecdc31bcae 100644
--- a/game/modules/tome/data/talents/techniques/weaponshield.lua
+++ b/game/modules/tome/data/talents/techniques/weaponshield.lua
@@ -224,6 +224,11 @@ newTalent{
 	stamina = 30,
 	tactical = { ESCAPE = { knockback = 2 }, DEFEND = { knockback = 0.5 } },
 	on_pre_use = function(self, t, silent) if not self:hasShield() then if not silent then game.logPlayer(self, "You require a weapon and a shield to use this talent.") end return false end return true end,
+	range = 0,
+	radius = 1,
+	target = function(self, t)
+		return {type="ball", range=self:getTalentRange(t), selffire=false, radius=self:getTalentRadius(t)}
+	end,
 	action = function(self, t)
 		local shield = self:hasShield()
 		if not shield then
@@ -231,10 +236,10 @@ newTalent{
 			return nil
 		end
 
-		for i = -1, 1 do for j = -1, 1 do
-			local x, y = self.x + i, self.y + j
-			if (self.x ~= x or self.y ~= y) and game.level.map:isBound(x, y) and game.level.map(x, y, Map.ACTOR) then
-				local target = game.level.map(x, y, Map.ACTOR)
+		local tg = self:getTalentTarget(t)
+		self:project(tg, self.x, self.y, function(px, py, tg, self)
+			local target = game.level.map(x, y, Map.ACTOR)
+			if target then
 				if target:checkHit(self:combatAttack(shield.special_combat), target:combatPhysicalResist(), 0, 95, 5 - self:getTalentLevel(t) / 2) and target:canBe("knockback") then
 					target:knockback(self.x, self.y, 2 + self:getTalentLevel(t))
 					if target:canBe("stun") then target:setEffect(target.EFF_DAZED, 3 + self:getStr(8), {}) end
@@ -242,7 +247,7 @@ newTalent{
 					game.logSeen(target, "%s resists the knockback!", target.name:capitalize())
 				end
 			end
-		end end
+		end)
 
 		return true
 	end,
diff --git a/game/modules/tome/data/zones/infinite-dungeon/zone.lua b/game/modules/tome/data/zones/infinite-dungeon/zone.lua
index 4d7aca648fb4ca111121f7bc5127a5ac14f7a753..b586a4fc564570955997b9351e7506065950444c 100644
--- a/game/modules/tome/data/zones/infinite-dungeon/zone.lua
+++ b/game/modules/tome/data/zones/infinite-dungeon/zone.lua
@@ -72,13 +72,12 @@ return {
 		if level.level == 1 or level.level == 10 or level.level == 20 or level.level == 30 or level.level == 40 then
 			local l = game.zone:makeEntityByName(level, "terrain", "ID_HISTORY"..level.level)
 			if not l then return end
-			for i = -1, 1 do for j = -1, 1 do
-				local x, y = level.default_up.x + i, level.default_up.y + j
-				if game.level.map:isBound(x, y) and (i ~= 0 or j ~= 0) and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") then
-					game.zone:addEntity(level, l, "terrain", x, y)
+			for _, coord in pairs(util.adjacentCoords(level.default_up.x, level.default_up.y)) do
+				if game.level.map:isBound(coord[1], coord[2]) and (i ~= 0 or j ~= 0) and not game.level.map:checkEntity(coord[1], coord[2], engine.Map.TERRAIN, "block_move") then
+					game.zone:addEntity(level, l, "terrain", coord[1], coord[2])
 					return
 				end
-			end end
+			end
 		end
 
 		-- Provide some achievements
diff --git a/game/modules/tome/data/zones/maze/zone.lua b/game/modules/tome/data/zones/maze/zone.lua
index fb2efc1cb77039b5141b2924ce9d255f7f960a4d..b40442d54ce2fba92cde2311e06db92883d859d2 100644
--- a/game/modules/tome/data/zones/maze/zone.lua
+++ b/game/modules/tome/data/zones/maze/zone.lua
@@ -80,13 +80,12 @@ return {
 		if level.level == 1 and p:knowTalent(p.T_TRAP_MASTERY) then
 			local l = game.zone:makeEntityByName(level, "object", "NOTE_LEARN_TRAP")
 			if not l then return end
-			for i = -1, 1 do for j = -1, 1 do
-				local x, y = level.default_down.x + i, level.default_down.y + j
-				if game.level.map:isBound(x, y) and (i ~= 0 or j ~= 0) and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") then
-					game.zone:addEntity(level, l, "object", x, y)
+			for _, coord in pairs(util.adjacentCoords(level.default_down.x, level.default_down.y)) do
+				if game.level.map:isBound(coord[1], coord[2]) and (i ~= 0 or j ~= 0) and not game.level.map:checkEntity(coord[1], coord[2], engine.Map.TERRAIN, "block_move") then
+					game.zone:addEntity(level, l, "object", coord[1], coord[2])
 					return
 				end
-			end end
+			end
 		end
 	end,
 }
diff --git a/game/modules/tome/data/zones/reknor/zone.lua b/game/modules/tome/data/zones/reknor/zone.lua
index 2d8696efc1a60465143c4f0c263c3b9db002eb55..3bd9c8d7c36067bfb97949c78a6f459a25bb8c3d 100644
--- a/game/modules/tome/data/zones/reknor/zone.lua
+++ b/game/modules/tome/data/zones/reknor/zone.lua
@@ -84,13 +84,12 @@ return {
 		if level.level == 1 then
 			local l = game.zone:makeEntityByName(level, "terrain", "IRON_THRONE_EDICT")
 			if not l then return end
-			for i = -1, 1 do for j = -1, 1 do
-				local x, y = level.default_up.x + i, level.default_up.y + j
-				if game.level.map:isBound(x, y) and (i ~= 0 or j ~= 0) and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") then
-					game.zone:addEntity(level, l, "terrain", x, y)
+			for _, coord in pairs(util.adjacentCoords(level.default_up.x, level.default_up.y)) do
+				if game.level.map:isBound(coord[1], coord[2]) and (i ~= 0 or j ~= 0) and not game.level.map:checkEntity(coord[1], coord[2], engine.Map.TERRAIN, "block_move") then
+					game.zone:addEntity(level, l, "terrain", coord[1], coord[2])
 					return
 				end
-			end end
+			end
 		end
 	end,
 }
diff --git a/src/core_lua.c b/src/core_lua.c
index 22b430f9091889882289017088dc95b90700a3b6..6fcd08f5f2ce5bc04cad5fdc2cdbd258777f4f42 100644
--- a/src/core_lua.c
+++ b/src/core_lua.c
@@ -2830,7 +2830,7 @@ int luaopen_core(lua_State *L)
 	lua_settable(L, -3);
 
 	luaL_openlib(L, "rng", rnglib, 0);
-	luaL_openlib(L, "line", linelib, 0);
+	luaL_openlib(L, "bresenham", linelib, 0);
 
 	lua_settop(L, 0);
 	return 1;
diff --git a/src/fov.c b/src/fov.c
index 24a22f09de84feeaf5c3a1bf480f02d7b91f6f84..7e6827ec715143195bc24cec360ef1654344d7e7 100644
--- a/src/fov.c
+++ b/src/fov.c
@@ -36,6 +36,8 @@
 #include <math.h>
 #include <time.h>
 
+#define INV_SQRT_3_2 1.15470053837925152902
+
 /******************************************************************
  ******************************************************************
  *                              FOV                               *
@@ -68,6 +70,12 @@ typedef struct
 	fov_line_data line;
 } lua_fov_line;
 
+typedef struct
+{
+	struct lua_fov fov;
+	hex_fov_line_data line;
+} lua_hex_fov_line;
+
 static int lua_fov_set_permissiveness(lua_State *L)
 {
 	float val = luaL_checknumber(L, 1);
@@ -91,6 +99,7 @@ static int lua_fov_get_permissiveness(lua_State *L)
 		lua_settable(L, LUA_REGISTRYINDEX);
 		lua_pushnumber(L, 0.5f);
 	}
+	return 1;
 }
 
 static int lua_fov_set_vision_shape(lua_State *L)
@@ -113,12 +122,81 @@ static int lua_fov_get_vision_shape(lua_State *L)
 		lua_settable(L, LUA_REGISTRYINDEX);
 		lua_pushnumber(L, FOV_SHAPE_CIRCLE_ROUND);
 	}
+	return 1;
+}
+
+// this is kinda ugly, so I may come back to it and make it purty
+static int lua_fov_get_distance(lua_State *L, double x1, double y1, double x2, double y2, bool ret_float)
+{
+	lua_fov_get_vision_shape(L);
+	int shape = luaL_checknumber(L, -1);
+	if (shape == FOV_SHAPE_HEX) {
+		int dx1 = (x1 < 0.0f) ? -(int)(0.5f - x1) : (int)(0.5f + x1);
+		int dy1 = (y1 < 0.0f) ? -(int)(0.5f - y1) : (int)(0.5f + y1);
+		int dx2 = (x2 < 0.0f) ? -(int)(0.5f - x2) : (int)(0.5f + x2);
+		int dy2 = (y2 < 0.0f) ? -(int)(0.5f - y2) : (int)(0.5f + y2);
+
+		// there may be a better way to do this
+		int dist = abs(dx2 - dx1);
+		int dy = dy2 - dy1;
+		int ady = abs(dy);
+		ady -= (dist + (((dx1 & 1) + (dy < 0)) & 1))/2;
+		if (ady > 0) {
+			dist += ady;
+		}
+		lua_pushnumber(L, dist);
+		return 1;
+	} else {
+		double dx = fabs(x2 - x1);
+		double dy = fabs(y2 - y1);
+		double dist;
+
+		switch(shape) {
+		case FOV_SHAPE_CIRCLE_ROUND :
+			dist = sqrt(dx*dx + dy*dy) + 0.5;
+			break;
+		case FOV_SHAPE_CIRCLE_FLOOR :
+			dist = sqrt(dx*dx + dy*dy);
+			break;
+		case FOV_SHAPE_CIRCLE_CEIL :
+			if (ret_float)
+				dist = sqrt(dx*dx + dy*dy);
+			else
+				dist = ceil(sqrt(dx*dx + dy*dy));
+			break;
+		case FOV_SHAPE_CIRCLE_PLUS1 :
+			dist = sqrt(dx*dx + dy*dy);
+			if (dist > 0.5) dist = dist + 1 - 1.0/dist;
+			break;
+		case FOV_SHAPE_OCTAGON :
+			dist = (dx > dy) ? (dx + 0.5*dy) : (dy + 0.5*dx);
+			break;
+		case FOV_SHAPE_DIAMOND :
+			dist = dx + dy;
+			break;
+		case FOV_SHAPE_SQUARE :
+			dist = (dx > dy) ? dx : dy;
+			break;
+		default :
+			dist = sqrt(dx*dx + dy*dy) + 0.5;
+			break;
+		}
+	
+		if (ret_float)
+			lua_pushnumber(L, dist);
+		else
+			lua_pushnumber(L, (int)dist);
+		return 1;
+	}
 }
 
 static void map_seen(void *m, int x, int y, int dx, int dy, int radius, void *src)
 {
 	struct lua_fov *fov = (struct lua_fov *)m;
 	if (x < 0 || y < 0 || x >= fov->w || y >= fov->h) return;
+	lua_fov_get_distance(L, (float)(x-dx), (float)(y-dy), (float)x, (float)y, false);
+	int sqdist = luaL_checknumber(L, -1);
+	sqdist = sqdist*sqdist;
 
 	// circular view - can be changed if you like
 	lua_rawgeti(fov->L, LUA_REGISTRYINDEX, fov->apply_ref);
@@ -128,7 +206,7 @@ static void map_seen(void *m, int x, int y, int dx, int dy, int radius, void *sr
 	lua_pushnumber(fov->L, y);
 	lua_pushnumber(fov->L, dx);
 	lua_pushnumber(fov->L, dy);
-	lua_pushnumber(fov->L, dx*dx + dy*dy);
+	lua_pushnumber(fov->L, sqdist);
 	lua_call(fov->L, 6, 0);
 }
 
@@ -314,48 +392,8 @@ static int lua_distance(lua_State *L)
 	double x2 = luaL_checknumber(L, 3);
 	double y2 = luaL_checknumber(L, 4);
 	bool ret_float = lua_toboolean(L, 5);
-	double dx = fabs(x2 - x1);
-	double dy = fabs(y2 - y1);
-	double dist;
-	lua_fov_get_vision_shape(L);
-	int shape = luaL_checknumber(L, -1);
-
-	switch(shape) {
-	case FOV_SHAPE_CIRCLE_ROUND :
-		dist = sqrt(dx*dx + dy*dy) + 0.5;
-		break;
-	case FOV_SHAPE_CIRCLE_FLOOR :
-		dist = sqrt(dx*dx + dy*dy);
-		break;
-	case FOV_SHAPE_CIRCLE_CEIL :
-		if (ret_float)
-			dist = sqrt(dx*dx + dy*dy);
-		else
-			dist = ceil(sqrt(dx*dx + dy*dy));
-		break;
-	case FOV_SHAPE_CIRCLE_PLUS1 :
-		dist = sqrt(dx*dx + dy*dy);
-		if (dist > 0.5) dist = dist + 1 - 1.0/dist;
-		break;
-	case FOV_SHAPE_OCTAGON :
-		dist = (dx > dy) ? (dx + 0.5*dy) : (dy + 0.5*dx);
-		break;
-	case FOV_SHAPE_DIAMOND :
-		dist = dx + dy;
-		break;
-	case FOV_SHAPE_SQUARE :
-		dist = (dx > dy) ? dx : dy;
-		break;
-	default :
-		dist = sqrt(dx*dx + dy*dy) + 0.5;
-		break;
-	}
-
-	if (ret_float)
-		lua_pushnumber(L, dist);
-	else
-		lua_pushnumber(L, (int)dist);
 
+	lua_fov_get_distance(L, x1, y1, x2, y2, ret_float);
 	return 1;
 }
 
@@ -431,13 +469,14 @@ typedef struct {
 
 static void map_default_seen(void *m, int x, int y, int dx, int dy, int radius, void *src)
 {
-	// TODO: understand how this function uses distances and use "lua_distance" (i.e., core.fov.distance) if necessary
 	default_fov *def = (default_fov*)src;
 	struct lua_fov *fov = (struct lua_fov *)m;
-	float sqdist = dx*dx + dy*dy;
-	float dist = sqrtf(sqdist);
 	if (x < 0 || y < 0 || x >= fov->w || y >= fov->h) return;
 
+	lua_fov_get_distance(L, (float)(x-dx), (float)(y-dy), (float)x, (float)y, false);
+	int dist = luaL_checknumber(L, -1);
+	int sqdist = dist*dist;
+
 	// Distance Map
 	if (def->do_dmap)
 	{
@@ -938,6 +977,314 @@ static int lua_free_fov_line(lua_State *L)
 	return 1;
 }
 
+/****************************************************************
+ ** HEX_FOV line
+ ****************************************************************/
+
+static int lua_hex_fov_line_init(lua_State *L)
+{
+	int source_x = luaL_checknumber(L, 1);
+	int source_y = luaL_checknumber(L, 2);
+	int dest_x = luaL_checknumber(L, 3);
+	int dest_y = luaL_checknumber(L, 4);
+	int w = luaL_checknumber(L, 5);
+	int h = luaL_checknumber(L, 6);
+	bool start_at_end = lua_toboolean(L, 7);
+	int opaque_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+	lua_hex_fov_line *lua_line = (lua_hex_fov_line*)lua_newuserdata(L, sizeof(lua_hex_fov_line));
+	hex_fov_line_data *line = &(lua_line->line);
+	struct lua_fov *fov = &(lua_line->fov);
+	fov->cache_ref = LUA_NOREF;
+	fov->cache = NULL;
+	fov->L = L;
+	fov->opaque_ref = opaque_ref;
+	fov->w = w;
+	fov->h = h;
+	fov_settings_init(&(fov->fov_settings));
+	fov_settings_set_opacity_test_function(&(fov->fov_settings), map_opaque);
+
+	hex_fov_create_los_line(&(fov->fov_settings), fov, NULL, line, source_x, source_y, dest_x, dest_y, start_at_end);
+	luaL_unref(L, LUA_REGISTRYINDEX, fov->opaque_ref);
+
+	auxiliar_setclass(L, "core{hexfovline}", -1);
+	return 1;
+}
+
+static int lua_hex_fov_line_step(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+		lua_getfield(L, 1, "block");
+		lua_line->fov.opaque_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+		lua_line->fov.L = L;
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+		lua_line->fov.opaque_ref = LUA_NOREF;
+	}
+
+	hex_fov_line_data *line = &(lua_line->line);
+	bool dont_stop_at_end = lua_toboolean(L, 2);
+	if (!dont_stop_at_end && line->dest_t == line->t || line->dest_t == 0) return 0;
+
+	line->t += 1;
+	float fx = INV_SQRT_3_2 * (line->source_x + (float)line->t * line->step_x + line->eps_x);
+	int x = (int)fx - (fx < 0.0f);
+	float fy = line->source_y + (float)line->t * line->step_y + line->eps_y - 0.5f*(x & 1);
+	int y = (int)fy - (fy < 0.0f);
+
+	lua_pushnumber(L, x);
+	lua_pushnumber(L, y);
+	lua_pushnil(L);
+	lua_pushnil(L);
+	return 4;
+}
+
+// Hmm, this function was just added and may change in the near-future.  We probably want
+// to create a line at a specific angle, so let's simply make a function that does just that.
+static int lua_hex_fov_line_change_step(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+	}
+	hex_fov_line_data *line = &(lua_line->line);
+	float step_x = lua_tonumber(L, 2);
+	float step_y = lua_tonumber(L, 3);
+
+	line->step_x = step_x;
+	line->step_y = step_y;
+	return 0;
+}
+
+// use to "wiggle" away from boundary cases
+// Will this ever be needed for hexes?
+static int lua_hex_fov_line_wiggle(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+	}
+	hex_fov_line_data *line = &(lua_line->line);
+	bool wiggle_me_gently = lua_toboolean(L, 2);
+
+	if (fabs(line->step_x) < fabs(line->step_y)) {
+		if (wiggle_me_gently) {
+			line->step_y += 0.001f;
+		} else {
+			line->step_y -= 0.001f;
+		}
+	} else {
+		if (wiggle_me_gently) {
+			line->step_x += 0.001f;
+		} else {
+			line->step_x -= 0.001f;
+		}
+	}
+
+	return 0;
+}
+
+// The next three functions aren't used anywhere and can probably be deleted
+static int lua_hex_fov_line_blocked_xy(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+	}
+
+	hex_fov_line_data *line = &(lua_line->line);
+	bool dont_stop_at_end = lua_toboolean(L, 2);
+
+	if (!line->is_blocked) return 0;
+
+	float fx = INV_SQRT_3_2 * (line->source_x + (float)line->block_t * line->step_x + line->eps_x);
+	int x = (int)fx - (fx < 0.0f);
+	float fy = line->source_y + (float)line->block_t * line->step_y + line->eps_y - 0.5f*(x & 1);
+	int y = (int)fy - (fy < 0.0f);
+
+	lua_pushnumber(L, x);
+	lua_pushnumber(L, y);
+
+	return 2;
+}
+
+static int lua_hex_fov_line_last_open_xy(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+	}
+
+	hex_fov_line_data *line = &(lua_line->line);
+	bool dont_stop_at_end = lua_toboolean(L, 2);
+	int x, y;
+	float fx, fy;
+
+	if (line->is_blocked) {
+		fx = INV_SQRT_3_2 * (line->source_x + (float)(line->block_t - 1) * line->step_x + line->eps_x);
+		x = (int)fx - (fx < 0.0f);
+		fy = line->source_y + (float)(line->block_t - 1) * line->step_y + line->eps_y - 0.5f*(x & 1);
+		y = (int)fy - (fy < 0.0f);
+	}
+	else {
+		fx = INV_SQRT_3_2 * (line->source_x + (float)line->dest_t * line->step_x + line->eps_x);
+		x = (int)fx - (fx < 0.0f);
+		fy = line->source_y + (float)line->dest_t * line->step_y + line->eps_y - 0.5f*(x & 1);
+		y = (int)fy - (fy < 0.0f);
+	}
+	lua_pushnumber(L, line->source_x + x);
+	lua_pushnumber(L, line->source_y + y);
+	return 2;
+}
+
+static int lua_hex_fov_line_is_blocked(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+	}
+
+	lua_pushboolean(L, lua_line->line.is_blocked);
+	return 1;
+}
+
+static int lua_hex_fov_line_reset(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+	}
+
+	hex_fov_line_data *line = &(lua_line->line);
+	if (line->start_at_end) {
+		if (line->is_blocked) {
+			line->t = line->block_t;
+		} else {
+			line->t = line->dest_t;
+		}
+	} else {
+		line->t = 0;
+	}
+	return 0;
+}
+
+// export data so we may save it in lua
+static int lua_hex_fov_line_export(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+	}
+	hex_fov_line_data *line = &(lua_line->line);
+	lua_pushnumber(L, line->source_x);
+	lua_pushnumber(L, line->source_y);
+	lua_pushnumber(L, line->t);
+	lua_pushnumber(L, line->block_t);
+	lua_pushnumber(L, line->dest_t);
+	lua_pushnumber(L, line->step_x);
+	lua_pushnumber(L, line->step_y);
+	lua_pushnumber(L, line->eps_x);
+	lua_pushnumber(L, line->eps_y);
+	lua_pushboolean(L, line->is_blocked);
+	lua_pushboolean(L, line->start_at_end);
+
+	return 11;
+}
+
+// load previously exported data (or create a specific line of your choice)
+static int lua_hex_fov_line_import(lua_State *L)
+{
+	int w = luaL_checknumber(L, 1);
+	int h = luaL_checknumber(L, 2);
+	float source_x = luaL_checknumber(L, 3);
+	float source_y = luaL_checknumber(L, 4);
+	int t = luaL_checknumber(L, 5);
+	int block_t = luaL_checknumber(L, 6);
+	int dest_t = luaL_checknumber(L, 7);
+	float step_x = luaL_checknumber(L, 8);
+	float step_y = luaL_checknumber(L, 9);
+	float eps_x = luaL_checknumber(L, 10);
+	float eps_y = luaL_checknumber(L, 11);
+	bool is_blocked = lua_toboolean(L, 12);
+	bool start_at_end = lua_toboolean(L, 13);
+
+	lua_hex_fov_line *lua_line = (lua_hex_fov_line*)lua_newuserdata(L, sizeof(lua_hex_fov_line));
+	hex_fov_line_data *line = &(lua_line->line);
+	line->source_x = source_x;
+	line->source_y = source_y;
+	line->t = t;
+	line->block_t = block_t;
+	line->dest_t = dest_t;
+	line->step_x = step_x;
+	line->step_y = step_y;
+	line->eps_x = eps_x;
+	line->eps_y = eps_y;
+	line->is_blocked = is_blocked;
+	line->start_at_end = start_at_end;
+
+	struct lua_fov *fov = &(lua_line->fov);
+	fov->cache_ref = LUA_NOREF;
+	fov->cache = NULL;
+	fov->L = L;
+	fov->opaque_ref = LUA_NOREF;
+	fov->w = w;
+	fov->h = h;
+	fov_settings_init(&(fov->fov_settings));
+	fov_settings_set_opacity_test_function(&(fov->fov_settings), map_opaque);
+
+	auxiliar_setclass(L, "core{hexfovline}", -1);
+	return 1;
+}
+
+static int lua_free_hex_fov_line(lua_State *L)
+{
+	lua_hex_fov_line *lua_line;
+	if (lua_istable(L, 1)) {
+		lua_getfield(L, 1, "line");
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", -1);
+		lua_pop(L, 1);
+	} else {
+		lua_line = (lua_hex_fov_line*)auxiliar_checkclass(L, "core{hexfovline}", 1);
+	}
+
+	// Nothing should actually be allocated, but just in case...
+	fov_settings_free(&(lua_line->fov.fov_settings));
+	lua_pushnumber(L, 1);
+	return 1;
+}
+
 static const struct luaL_reg fovlib[] =
 {
 	{"newCache", lua_new_fovcache},
@@ -947,7 +1294,9 @@ static const struct luaL_reg fovlib[] =
 	{"calc_beam", lua_fov_calc_beam},
 	{"calc_beam_any_angle", lua_fov_calc_beam_any_angle},
 	{"line_base", lua_fov_line_init},
+	{"hex_line_base", lua_hex_fov_line_init},
 	{"line_import", lua_fov_line_import},
+	{"hex_line_import", lua_hex_fov_line_import},
 	{"set_permissiveness_base", lua_fov_set_permissiveness},
 	{"set_vision_shape_base", lua_fov_set_vision_shape},
 	{NULL, NULL},
@@ -975,10 +1324,26 @@ static const struct luaL_reg fovline_reg[] =
 	{NULL, NULL},
 };
 
+static const struct luaL_reg hexfovline_reg[] =
+{
+	{"__gc", lua_free_hex_fov_line},
+	{"__call", lua_hex_fov_line_step},
+	{"step", lua_hex_fov_line_step},
+	{"change_step", lua_hex_fov_line_change_step},
+	{"wiggle", lua_hex_fov_line_wiggle},
+	{"is_blocked", lua_hex_fov_line_is_blocked},
+	{"blocked_xy", lua_hex_fov_line_blocked_xy},
+	{"last_open_xy", lua_hex_fov_line_last_open_xy},
+	{"reset", lua_hex_fov_line_reset},
+	{"export", lua_hex_fov_line_export},
+	{NULL, NULL},
+};
+
 int luaopen_fov(lua_State *L)
 {
 	auxiliar_newclass(L, "fov{cache}", fovcache_reg);
 	auxiliar_newclass(L, "core{fovline}", fovline_reg);
+	auxiliar_newclass(L, "core{hexfovline}", hexfovline_reg);
 	luaL_openlib(L, "core.fov", fovlib, 0);
 	lua_pop(L, 1);
 	return 1;
diff --git a/src/fov/fov.c b/src/fov/fov.c
index 2476a3f3a3ae8d521e3a74558c990d35d3d1d60f..409d2150958494ba079e449bb6df251468b0f10c 100644
--- a/src/fov/fov.c
+++ b/src/fov/fov.c
@@ -17,6 +17,11 @@
 #define DtoR 1.74532925199432957692e-02
 #define RtoD 57.2957795130823208768
 
+#define INV_SQRT_3 0.577350269189625764509
+#define SQRT_3     1.73205080756887729353
+#define SQRT_3_2   0.866025403784438646764
+#define SQRT_3_4   0.433012701892219323382
+
 /*
 +---++---++---++---+
 |   ||   ||   ||   |
@@ -218,9 +223,7 @@ static float fov_slope(float dx, float dy) {
                                                                                                 \
         if (start_slope - end_slope > GRID_EPSILON) {                                           \
             return;                                                                             \
-        }                                                                                       \
-                                                                                                \
-        if (dx == 0) {                                                                          \
+        } else if (dx == 0) {                                                                   \
             fov_octant_##nx##ny##nf(data, dx+1, start_slope, end_slope, blocked_below, blocked_above, apply_edge, apply_diag); \
             return;                                                                             \
         } else if ((unsigned)dx > data->radius) {                                               \
@@ -345,6 +348,130 @@ FOV_DEFINE_OCTANT(-,-,x,y,m,m,n)
 FOV_DEFINE_OCTANT(-,-,y,x,m,m,y)
 
 
+#define HEX_FOV_DEFINE_SEXTANT(signx, signy, nx, ny, one)                                                                       \
+    static void hex_fov_sextant_##nx##ny(                                                                                       \
+                                        fov_private_data_type *data,                                                            \
+                                        int dy,                                                                                 \
+                                        float start_slope,                                                                      \
+                                        float end_slope,                                                                        \
+                                        bool apply_edge1,                                                                       \
+                                        bool apply_edge2) {                                                                     \
+        int x, y, x0, x1, p;                                                                                                    \
+        int prev_blocked = -1;                                                                                                  \
+        float fdy, end_slope_next;                                                                                              \
+        fov_settings_type *settings = data->settings;                                                                           \
+                                                                                                                                \
+        if (start_slope - end_slope > GRID_EPSILON) {                                                                           \
+            return;                                                                                                             \
+        } else if ((unsigned)dy > data->radius) {                                                                               \
+            return;                                                                                                             \
+        }                                                                                                                       \
+                                                                                                                                \
+        fdy = (float)dy;                                                                                                        \
+        x0 = (int)(0.5f + fdy*start_slope / (SQRT_3_2 + 0.5f*start_slope) + GRID_EPSILON);                                      \
+        x1 = (int)(0.5f + fdy*end_slope / (SQRT_3_2 + 0.5f*end_slope) - GRID_EPSILON);                                          \
+        if (x1 < x0) return;                                                                                                    \
+                                                                                                                                \
+        x = data->source_x signx x0;                                                                                            \
+        p = ((x & 1) + one) & 1;                                                                                                \
+        fdy += 0.25f;                                                                                                           \
+        y = data->source_y signy (dy - (x0 + 1 - p)/2);                                                                         \
+                                                                                                                                \
+        for (; x0 <= x1; ++x0) {                                                                                                \
+            if (settings->opaque(data->map, x, y)) {                                                                            \
+                if ((apply_edge1 || x0 > 0) && (apply_edge2 || x0 != dy)) {                                                     \
+                    settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source);       \
+                }                                                                                                               \
+                if (prev_blocked == 0) {                                                                                        \
+                    end_slope_next = (-SQRT_3_4 + SQRT_3_2*(float)x0) / (fdy - 0.5f*(float)x0);                                 \
+                    hex_fov_sextant_##nx##ny(data, dy+1, start_slope, end_slope_next, apply_edge1, apply_edge2);                \
+                }                                                                                                               \
+                prev_blocked = 1;                                                                                               \
+            } else {                                                                                                            \
+                if (prev_blocked == 1) {                                                                                        \
+                    start_slope = (-SQRT_3_4 + SQRT_3_2*(float)x0) / (fdy - 0.5f*(float)x0);                                    \
+                }                                                                                                               \
+                if ((apply_edge1 || x0 > 0) && (apply_edge2 || x0 != dy)) {                                                     \
+                    settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source);       \
+                }                                                                                                               \
+                prev_blocked = 0;                                                                                               \
+            }                                                                                                                   \
+            y = y signy (-p);                                                                                                   \
+            x = x signx 1;                                                                                                      \
+            p = !p;                                                                                                             \
+        }                                                                                                                       \
+                                                                                                                                \
+        if (prev_blocked == 0) {                                                                                                \
+            hex_fov_sextant_##nx##ny(data, dy+1, start_slope, end_slope, apply_edge1, apply_edge2);                             \
+        }                                                                                                                       \
+    }
+
+
+#define HEX_FOV_DEFINE_LR_SEXTANT(signx, nx)                                                                                    \
+    static void hex_fov_sextant_##nx(                                                                                           \
+                                        fov_private_data_type *data,                                                            \
+                                        int dx,                                                                                 \
+                                        float start_slope,                                                                      \
+                                        float end_slope,                                                                        \
+                                        bool apply_edge1,                                                                       \
+                                        bool apply_edge2) {                                                                     \
+        int x, y, y0, y1, p;                                                                                                    \
+        int prev_blocked = -1;                                                                                                  \
+        float fdx, fdy, end_slope_next;                                                                                         \
+        fov_settings_type *settings = data->settings;                                                                           \
+                                                                                                                                \
+        if (start_slope - end_slope > GRID_EPSILON) {                                                                           \
+            return;                                                                                                             \
+        } else if ((unsigned)dx > data->radius) {                                                                               \
+            return;                                                                                                             \
+        }                                                                                                                       \
+                                                                                                                                \
+        x = data->source_x signx dx;                                                                                            \
+        fdx = (float)dx * SQRT_3_2;                                                                                             \
+        fdy = -0.5f*(float)dx - 0.5f;                                                                                           \
+                                                                                                                                \
+        p = -dx / 2 - (dx & 1)*(x & 1);                                                                                         \
+        y0 = (int)(fdx*start_slope - fdy + GRID_EPSILON);                                                                       \
+        y1 = (int)(fdx*end_slope - fdy - GRID_EPSILON);                                                                         \
+        if (y1 < y0) return;                                                                                                    \
+                                                                                                                                \
+        y = data->source_y + y0 + p;                                                                                            \
+                                                                                                                                \
+        for (; y0 <= y1; ++y0) {                                                                                                \
+            if (settings->opaque(data->map, x, y)) {                                                                            \
+                if ((apply_edge1 || y0 > 0) && (apply_edge2 || y0 != dx)) {                                                     \
+                    settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source);       \
+                }                                                                                                               \
+                if (prev_blocked == 0) {                                                                                        \
+                    end_slope_next = ((float)y0 + fdy) / fdx;                                                                   \
+                    hex_fov_sextant_##nx(data, dx+1, start_slope, end_slope_next, apply_edge1, apply_edge2);                    \
+                }                                                                                                               \
+                prev_blocked = 1;                                                                                               \
+            } else {                                                                                                            \
+                if (prev_blocked == 1) {                                                                                        \
+                    start_slope = ((float)y0 + fdy) / fdx;                                                                      \
+                }                                                                                                               \
+                if ((apply_edge1 || y0 > 0) && (apply_edge2 || y0 != dx)) {                                                     \
+                    settings->apply(data->map, x, y, x - data->source_x, y - data->source_y, data->radius, data->source);       \
+                }                                                                                                               \
+                prev_blocked = 0;                                                                                               \
+            }                                                                                                                   \
+            ++y;                                                                                                                \
+        }                                                                                                                       \
+                                                                                                                                \
+        if (prev_blocked == 0) {                                                                                                \
+            hex_fov_sextant_##nx(data, dx+1, start_slope, end_slope, apply_edge1, apply_edge2);                                 \
+        }                                                                                                                       \
+    }
+
+HEX_FOV_DEFINE_SEXTANT(+,+,n,e,1)
+HEX_FOV_DEFINE_SEXTANT(-,+,n,w,1)
+HEX_FOV_DEFINE_SEXTANT(+,-,s,e,0)
+HEX_FOV_DEFINE_SEXTANT(-,-,s,w,0)
+HEX_FOV_DEFINE_LR_SEXTANT(+,e)
+HEX_FOV_DEFINE_LR_SEXTANT(-,w)
+
+
 /* Circle --------------------------------------------------------- */
 
 static void _fov_circle(fov_private_data_type *data) {
@@ -374,6 +501,26 @@ static void _fov_circle(fov_private_data_type *data) {
     fov_octant_pmn(data, 1, (float)0.0f, (float)1.0f, false, false, false, false);
 }
 
+static void _hex_fov_circle(fov_private_data_type *data) {
+/*
+  _            |            _
+   \___2   nw 1|1 ne   2___/
+       \___    |    ___/
+       2   \__ | __/   2
+     w      __>&<__      e
+       1___/   |   \___1
+    ___/       |       \___
+  _/   2   sw 1|1 se   2   \_
+               |
+*/
+    hex_fov_sextant_ne(data, 1, 0.0f, SQRT_3, true, true);
+    hex_fov_sextant_nw(data, 1, 0.0f, SQRT_3, false, true);
+    hex_fov_sextant_w(data, 1, -INV_SQRT_3, INV_SQRT_3, true, false);
+    hex_fov_sextant_sw(data, 1, 0.0f, SQRT_3, true, false);
+    hex_fov_sextant_se(data, 1, 0.0f, SQRT_3, false, true);
+    hex_fov_sextant_e(data, 1, -INV_SQRT_3, INV_SQRT_3, false, false);
+}
+
 void fov_circle(fov_settings_type *settings,
                 void *map,
                 void *source,
@@ -389,7 +536,10 @@ void fov_circle(fov_settings_type *settings,
     data.source_y = source_y;
     data.radius = radius;
 
-    _fov_circle(&data);
+    if (settings->shape == FOV_SHAPE_HEX)
+        _hex_fov_circle(&data);
+    else
+        _fov_circle(&data);
 }
 
 /**
@@ -588,10 +738,21 @@ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source,
     if (beam_angle <= 0.0f) {
         return;
     } else if (beam_angle >= 360.0f) {
-        _fov_circle(&data);
+        if (settings->shape == FOV_SHAPE_HEX)
+            _hex_fov_circle(&data);
+        else
+            _fov_circle(&data);
         return;
     }
 
+    if (settings->shape == FOV_SHAPE_HEX) {
+        /* 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));
+        dx *= SQRT_3_2;
+    }
+
     beam_angle = 0.5f * DtoR * beam_angle;
     x_start = cos(beam_angle)*dx + sin(beam_angle)*dy;
     y_start = cos(beam_angle)*dy - sin(beam_angle)*dx;
@@ -601,15 +762,13 @@ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source,
     if (y_start > 0.0f) {
         if (x_start > 0.0f) {                      /* octant 1 */               /* octant 2 */
             angle_begin = ( y_start <  x_start) ? (y_start / x_start)        : (2.0f - x_start / y_start);
-        }
-        else {                                     /* octant 3 */               /* octant 4 */
+        } else {                                   /* octant 3 */               /* octant 4 */
             angle_begin = (-x_start <  y_start) ? (2.0f - x_start / y_start) : (4.0f + y_start / x_start);
         }
     } else {
         if (x_start < 0.0f) {                      /* octant 5 */               /* octant 6 */
             angle_begin = (-y_start < -x_start) ? (4.0f + y_start / x_start) : (6.0f - x_start / y_start);
-        }
-        else {                                     /* octant 7 */               /* octant 8 */
+        } else {                                   /* octant 7 */               /* octant 8 */
             angle_begin = ( x_start < -y_start) ? (6.0f - x_start / y_start) : (8.0f + y_start / x_start);
         }
     }
@@ -617,15 +776,13 @@ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source,
     if (y_end > 0.0f) {
         if (x_end > 0.0f) {                  /* octant 1 */           /* octant 2 */
             angle_end = ( y_end <  x_end) ? (y_end / x_end)        : (2.0f - x_end / y_end);
-        }
-        else {                               /* octant 3 */           /* octant 4 */
+        } else {                             /* octant 3 */           /* octant 4 */
             angle_end = (-x_end <  y_end) ? (2.0f - x_end / y_end) : (4.0f + y_end / x_end);
         }
     } else {
         if (x_end < 0.0f) {                  /* octant 5 */           /* octant 6 */
             angle_end = (-y_end < -x_end) ? (4.0f + y_end / x_end) : (6.0f - x_end / y_end);
-        }
-        else {                               /* octant 7 */           /* octant 8 */
+        } else {                             /* octant 7 */           /* octant 8 */
             angle_end = ( x_end < -y_end) ? (6.0f - x_end / y_end) : (8.0f + y_end / x_end);
         }
     }
@@ -634,23 +791,486 @@ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source,
         angle_end += 8.0f;
     }
 
-    if (1.0f - angle_begin > FLT_EPSILON) {
-        BEAM_ANY_DIRECTION(0.0f, ppn, ppy, pmy, mpn, mmn, mmy, mpy, pmn);
-    } else if (2.0f - angle_begin > 2.0f * FLT_EPSILON) {
-        BEAM_ANY_DIRECTION_DIAG(1.0f, ppy, pmy, mpn, mmn, mmy, mpy, pmn, ppn);
-    } else if (3.0f - angle_begin > 3.0f * FLT_EPSILON) {
-        BEAM_ANY_DIRECTION(2.0f, pmy, mpn, mmn, mmy, mpy, pmn, ppn, ppy);
-    } else if (4.0f - angle_begin > 4.0f * FLT_EPSILON) {
-        BEAM_ANY_DIRECTION_DIAG(3.0f, mpn, mmn, mmy, mpy, pmn, ppn, ppy, pmy);
-    } else if (5.0f - angle_begin > 5.0f * FLT_EPSILON) {
-        BEAM_ANY_DIRECTION(4.0f, mmn, mmy, mpy, pmn, ppn, ppy, pmy, mpn);
-    } else if (6.0f - angle_begin > 6.0f * FLT_EPSILON) {
-        BEAM_ANY_DIRECTION_DIAG(5.0f, mmy, mpy, pmn, ppn, ppy, pmy, mpn, mmn);
-    } else if (7.0f - angle_begin > 7.0f * FLT_EPSILON) {
-        BEAM_ANY_DIRECTION(6.0f, mpy, pmn, ppn, ppy, pmy, mpn, mmn, mmy);
-    } else if (8.0f - angle_begin > 8.0f * FLT_EPSILON) {
-        BEAM_ANY_DIRECTION_DIAG(7.0f, pmn, ppn, ppy, pmy, mpn, mmn, mmy, mpy);
+    if (settings->shape == FOV_SHAPE_HEX) {
+        if (angle_begin > 8.0f - INV_SQRT_3) {
+            angle_begin -= 8.0f;
+            angle_end -= 8.0f;
+        }
+
+        if(angle_begin < INV_SQRT_3) {
+            //east
+            start_slope = angle_begin;
+            end_slope = betweenf(angle_end, -INV_SQRT_3, INV_SQRT_3);
+            hex_fov_sextant_e(&data, 1, start_slope, end_slope, true, true);
+
+            if (angle_end - INV_SQRT_3 > FLT_EPSILON) {
+                start_slope = betweenf(2.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                hex_fov_sextant_ne(&data, 1, start_slope, SQRT_3, true, false);
+
+                if (angle_end - 2.0f > 2.0f*FLT_EPSILON) {
+                    end_slope = betweenf(angle_end - 2.0f, 0.0f, 2.0f - INV_SQRT_3);
+                    if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                    hex_fov_sextant_nw(&data, 1, 0.0f, end_slope, false, true);
+
+                    if (angle_end - 4.0f + INV_SQRT_3 > 3.0f*FLT_EPSILON) {
+                        start_slope = betweenf(4.0f - angle_end, -INV_SQRT_3, INV_SQRT_3);
+                        hex_fov_sextant_w(&data, 1, start_slope, INV_SQRT_3, true, false);
+
+                        if (angle_end - 4.0f - INV_SQRT_3 > 5.0f*FLT_EPSILON) {
+                            start_slope = betweenf(6.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                            if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                            hex_fov_sextant_sw(&data, 1, start_slope, SQRT_3, true, false);
+
+                            if (angle_end - 6.0f > 6.0f*FLT_EPSILON) {
+                                end_slope = betweenf(angle_end - 6.0f, 0.0f, 2.0f - INV_SQRT_3);
+                                if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                                hex_fov_sextant_se(&data, 1, 0.0f, end_slope, false, true);
+
+                                if (angle_end - 8.0f + INV_SQRT_3 > 7.0f*FLT_EPSILON) {
+                                    end_slope = betweenf(angle_end - 8.0f, -INV_SQRT_3, INV_SQRT_3);
+                                    hex_fov_sextant_e(&data, 1, -INV_SQRT_3, end_slope, false, false);
+            }   }   }   }   }   }
+        } else if (angle_begin < 2.0f) {
+            //north-east
+            start_slope = betweenf(2.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+            if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+            end_slope = betweenf(2.0f - angle_begin, 0.0f, 2.0f - INV_SQRT_3);
+            if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+            hex_fov_sextant_ne(&data, 1, start_slope, end_slope, true, true);
+
+            if (angle_end - 2.0f > 2.0f*FLT_EPSILON) {
+                end_slope = betweenf(angle_end - 2.0f, 0.0f, 2.0f - INV_SQRT_3);
+                if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                hex_fov_sextant_nw(&data, 1, 0.0f, end_slope, false, true);
+
+                if (angle_end - 4.0f + INV_SQRT_3 > 3.0f*FLT_EPSILON) {
+                    start_slope = betweenf(4.0f - angle_end, -INV_SQRT_3, INV_SQRT_3);
+                    hex_fov_sextant_w(&data, 1, start_slope, INV_SQRT_3, true, false);
+
+                    if (angle_end - 4.0f - INV_SQRT_3 > 5.0f*FLT_EPSILON) {
+                        start_slope = betweenf(6.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                        if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                        hex_fov_sextant_sw(&data, 1, start_slope, SQRT_3, true, false);
+
+                        if (angle_end - 6.0f > 6.0f*FLT_EPSILON) {
+                            end_slope = betweenf(angle_end - 6.0f, 0.0f, 2.0f - INV_SQRT_3);
+                            if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                            hex_fov_sextant_se(&data, 1, 0.0f, end_slope, false, true);
+
+                            if (angle_end - 8.0f + INV_SQRT_3 > 7.0f*FLT_EPSILON) {
+                                end_slope = betweenf(angle_end - 8.0f, -INV_SQRT_3, INV_SQRT_3);
+                                hex_fov_sextant_e(&data, 1, -INV_SQRT_3, end_slope, false, true);
+
+                                if (angle_end - 8.0f - INV_SQRT_3 > 8.0f*FLT_EPSILON) {
+                                    start_slope = betweenf(10.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                                    if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                                    hex_fov_sextant_ne(&data, 1, start_slope, SQRT_3, false, false);
+            }   }   }   }   }   }
+        } else if (angle_begin < 4.0f - INV_SQRT_3) {
+            //north-west
+            start_slope = betweenf(angle_begin - 2.0f, 0.0f, 2.0f - INV_SQRT_3);
+            if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+            end_slope = betweenf(angle_end - 2.0f, 0.0f, 2.0f - INV_SQRT_3);
+            if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+            hex_fov_sextant_nw(&data, 1, start_slope, end_slope, true, true);
+
+            if (angle_end - 4.0f + INV_SQRT_3 > 3.0f*FLT_EPSILON) {
+                start_slope = betweenf(4.0f - angle_end, -INV_SQRT_3, INV_SQRT_3);
+                hex_fov_sextant_w(&data, 1, start_slope, INV_SQRT_3, true, false);
+
+                if (angle_end - 4.0f - INV_SQRT_3 > 5.0f*FLT_EPSILON) {
+                    start_slope = betweenf(6.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                    if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                    hex_fov_sextant_sw(&data, 1, start_slope, SQRT_3, true, false);
+
+                    if (angle_end - 6.0f > 6.0f*FLT_EPSILON) {
+                        end_slope = betweenf(angle_end - 6.0f, 0.0f, 2.0f - INV_SQRT_3);
+                        if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                        hex_fov_sextant_se(&data, 1, 0.0f, end_slope, false, true);
+
+                        if (angle_end - 8.0f + INV_SQRT_3 > 7.0f*FLT_EPSILON) {
+                            end_slope = betweenf(angle_end - 8.0f, -INV_SQRT_3, INV_SQRT_3);
+                            hex_fov_sextant_e(&data, 1, -INV_SQRT_3, end_slope, false, true);
+
+                            if (angle_end - 8.0f - INV_SQRT_3 > 8.0f*FLT_EPSILON) {
+                                start_slope = betweenf(10.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                                if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                                hex_fov_sextant_ne(&data, 1, start_slope, SQRT_3, true, false);
+
+                                if (angle_end - 10.0f > 10.0f*FLT_EPSILON) {
+                                    end_slope = betweenf(angle_end - 10.0f, 0.0f, 2.0f - INV_SQRT_3);
+                                    if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                                    hex_fov_sextant_nw(&data, 1, 0.0f, end_slope, false, false);
+            }   }   }   }   }   }
+        } else if (angle_begin < 4.0f + INV_SQRT_3) {
+            //west
+            start_slope = betweenf(4.0f - angle_end, -INV_SQRT_3, INV_SQRT_3);
+            end_slope = betweenf(4.0f - angle_begin, -INV_SQRT_3, INV_SQRT_3);
+            hex_fov_sextant_w(&data, 1, start_slope, end_slope, true, true);
+
+            if (angle_end - 4.0f - INV_SQRT_3 > 5.0f*FLT_EPSILON) {
+                start_slope = betweenf(6.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                hex_fov_sextant_sw(&data, 1, start_slope, SQRT_3, true, false);
+
+                if (angle_end - 6.0f > 6.0f*FLT_EPSILON) {
+                    end_slope = betweenf(angle_end - 6.0f, 0.0f, 2.0f - INV_SQRT_3);
+                    if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                    hex_fov_sextant_se(&data, 1, 0.0f, end_slope, false, true);
+
+                    if (angle_end - 8.0f + INV_SQRT_3 > 7.0f*FLT_EPSILON) {
+                        end_slope = betweenf(angle_end - 8.0f, -INV_SQRT_3, INV_SQRT_3);
+                        hex_fov_sextant_e(&data, 1, -INV_SQRT_3, end_slope, false, true);
+
+                        if (angle_end - 8.0f - INV_SQRT_3 > 8.0f*FLT_EPSILON) {
+                            start_slope = betweenf(10.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                            if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                            hex_fov_sextant_ne(&data, 1, start_slope, SQRT_3, true, false);
+
+                            if (angle_end - 10.0f > 10.0f*FLT_EPSILON) {
+                                end_slope = betweenf(angle_end - 10.0f, 0.0f, 2.0f - INV_SQRT_3);
+                                if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                                hex_fov_sextant_nw(&data, 1, 0.0f, end_slope, false, true);
+
+                                if (angle_end - 12.0f + INV_SQRT_3 > 11.0f*FLT_EPSILON) {
+                                    start_slope = betweenf(12.0f - angle_end, -INV_SQRT_3, INV_SQRT_3);
+                                    hex_fov_sextant_w(&data, 1, start_slope, INV_SQRT_3, false, false);
+            }   }   }   }   }   }
+        } else if (angle_begin < 6.0f) {
+            //south-west
+            start_slope = betweenf(6.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+            if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+            end_slope = betweenf(6.0f - angle_begin, 0.0f, 2.0f - INV_SQRT_3);
+            if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+            hex_fov_sextant_sw(&data, 1, start_slope, end_slope, true, true);
+
+            if (angle_end - 6.0f > 6.0f*FLT_EPSILON) {
+                end_slope = betweenf(angle_end - 6.0f, 0.0f, 2.0f - INV_SQRT_3);
+                if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                hex_fov_sextant_se(&data, 1, 0.0f, end_slope, false, true);
+
+                if (angle_end - 8.0f + INV_SQRT_3 > 7.0f*FLT_EPSILON) {
+                    end_slope = betweenf(angle_end - 8.0f, -INV_SQRT_3, INV_SQRT_3);
+                    hex_fov_sextant_e(&data, 1, -INV_SQRT_3, end_slope, false, true);
+
+                    if (angle_end - 8.0f - INV_SQRT_3 > 8.0f*FLT_EPSILON) {
+                        start_slope = betweenf(10.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                        if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                        hex_fov_sextant_ne(&data, 1, start_slope, SQRT_3, true, false);
+
+                        if (angle_end - 10.0f > 10.0f*FLT_EPSILON) {
+                            end_slope = betweenf(angle_end - 10.0f, 0.0f, 2.0f - INV_SQRT_3);
+                            if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                            hex_fov_sextant_nw(&data, 1, 0.0f, end_slope, false, true);
+
+                            if (angle_end - 12.0f + INV_SQRT_3 > 11.0f*FLT_EPSILON) {
+                                start_slope = betweenf(12.0f - angle_end, -INV_SQRT_3, INV_SQRT_3);
+                                hex_fov_sextant_w(&data, 1, start_slope, INV_SQRT_3, true, false);
+
+                                if (angle_end - 12.0f - INV_SQRT_3 > 12.0f*FLT_EPSILON) {
+                                    start_slope = betweenf(14.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                                    if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                                    hex_fov_sextant_sw(&data, 1, start_slope, SQRT_3, false, false);
+            }   }   }   }   }   }
+        } else if (angle_begin < 8.0f - INV_SQRT_3) {
+            //south-east
+            start_slope = betweenf(angle_begin - 6.0f, 0.0f, 2.0f - INV_SQRT_3);
+            if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+            end_slope = betweenf(angle_end - 6.0f, 0.0f, 2.0f - INV_SQRT_3);
+            if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+            hex_fov_sextant_se(&data, 1, start_slope, end_slope, true, true);
+
+            if (angle_end - 8.0f + INV_SQRT_3 > 7.0f*FLT_EPSILON) {
+                end_slope = betweenf(angle_end - 8.0f, -INV_SQRT_3, INV_SQRT_3);
+                hex_fov_sextant_e(&data, 1, -INV_SQRT_3, end_slope, false, true);
+
+                if (angle_end - 8.0f - INV_SQRT_3 > 8.0f*FLT_EPSILON) {
+                    start_slope = betweenf(10.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                    if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                    hex_fov_sextant_ne(&data, 1, start_slope, SQRT_3, true, false);
+
+                    if (angle_end - 10.0f > 10.0f*FLT_EPSILON) {
+                        end_slope = betweenf(angle_end - 10.0f, 0.0f, 2.0f - INV_SQRT_3);
+                        if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                        hex_fov_sextant_nw(&data, 1, 0.0f, end_slope, false, true);
+
+                        if (angle_end - 12.0f + INV_SQRT_3 > 11.0f*FLT_EPSILON) {
+                            start_slope = betweenf(12.0f - angle_end, -INV_SQRT_3, INV_SQRT_3);
+                            hex_fov_sextant_w(&data, 1, start_slope, INV_SQRT_3, true, false);
+
+                            if (angle_end - 12.0f - INV_SQRT_3 > 12.0f*FLT_EPSILON) {
+                                start_slope = betweenf(14.0f - angle_end, 0.0f, 2.0f - INV_SQRT_3);
+                                if (start_slope > 1.0f) start_slope = 1.0f / (2.0f - start_slope);
+                                hex_fov_sextant_sw(&data, 1, start_slope, SQRT_3, true, false);
+
+                                if (angle_end - 14.0f > 14.0f*FLT_EPSILON) {
+                                    end_slope = betweenf(angle_end - 14.0f, 0.0f, 2.0f - INV_SQRT_3);
+                                    if (end_slope > 1.0f) end_slope = 1.0f / (2.0f - end_slope);
+                                    hex_fov_sextant_se(&data, 1, 0.0f, end_slope, false, false);
+            }   }   }   }   }   }
+        }
+    } else  {
+        if (1.0f - angle_begin > FLT_EPSILON) {
+            BEAM_ANY_DIRECTION(0.0f, ppn, ppy, pmy, mpn, mmn, mmy, mpy, pmn);
+        } else if (2.0f - angle_begin > 2.0f * FLT_EPSILON) {
+            BEAM_ANY_DIRECTION_DIAG(1.0f, ppy, pmy, mpn, mmn, mmy, mpy, pmn, ppn);
+        } else if (3.0f - angle_begin > 3.0f * FLT_EPSILON) {
+            BEAM_ANY_DIRECTION(2.0f, pmy, mpn, mmn, mmy, mpy, pmn, ppn, ppy);
+        } else if (4.0f - angle_begin > 4.0f * FLT_EPSILON) {
+            BEAM_ANY_DIRECTION_DIAG(3.0f, mpn, mmn, mmy, mpy, pmn, ppn, ppy, pmy);
+        } else if (5.0f - angle_begin > 5.0f * FLT_EPSILON) {
+            BEAM_ANY_DIRECTION(4.0f, mmn, mmy, mpy, pmn, ppn, ppy, pmy, mpn);
+        } else if (6.0f - angle_begin > 6.0f * FLT_EPSILON) {
+            BEAM_ANY_DIRECTION_DIAG(5.0f, mmy, mpy, pmn, ppn, ppy, pmy, mpn, mmn);
+        } else if (7.0f - angle_begin > 7.0f * FLT_EPSILON) {
+            BEAM_ANY_DIRECTION(6.0f, mpy, pmn, ppn, ppy, pmy, mpn, mmn, mmy);
+        } else if (8.0f - angle_begin > 8.0f * FLT_EPSILON) {
+            BEAM_ANY_DIRECTION_DIAG(7.0f, pmn, ppn, ppy, pmy, mpn, mmn, mmy, mpy);
+        }
+    }
+}
+
+ // a work in progress
+#define HEX_LOS_DEFINE_SEXTANT(signx, signy, nx, ny, one)                                                                       \
+    static float hex_los_sextant_##nx##ny(                                                                                      \
+                                        fov_private_data_type *data,                                                            \
+                                        hex_fov_line_data *line,                                                                \
+                                        float start_slope,                                                                      \
+                                        float target_slope,                                                                     \
+                                        float end_slope) {                                                                      \
+        int x, y, x0, x1, p, prev_blocked;                                                                                      \
+        int dy = 1;                                                                                                             \
+        int delta = line->dest_t - 1;                                                                                           \
+        float fx0, fx1, fdx0, fdx1;                                                                                             \
+        float fdy = 1.0f;                                                                                                       \
+        fov_settings_type *settings = data->settings;                                                                           \
+                                                                                                                                \
+        fdx0 = start_slope / (SQRT_3_2 + 0.5f*start_slope);                                                                     \
+        fdx1 = end_slope / (SQRT_3_2 + 0.5f*end_slope);                                                                         \
+                                                                                                                                \
+        fx0 = 0.5f + fdx0 + GRID_EPSILON;                                                                                       \
+        fx1 = 0.5f + fdx1 - GRID_EPSILON;                                                                                       \
+        x0 = (int)fx0;                                                                                                          \
+        x1 = (int)fx1;                                                                                                          \
+        x = data->source_x signx x0;                                                                                            \
+        p = ((x & 1) + one) & 1;                                                                                                \
+        y = data->source_y signy (1 - (x0 + 1 - p)/2);                                                                          \
+                                                                                                                                \
+        for (;;) {                                                                                                              \
+            if (--delta < 0) {                                                                                                  \
+                line->step_x = signx target_slope / (INV_SQRT_3*target_slope + 1);                                              \
+                line->step_y = signy 1 / (INV_SQRT_3*target_slope + 1);                                                         \
+                return;                                                                                                         \
+            }                                                                                                                   \
+            prev_blocked = settings->opaque(data->map, x, y);                                                                   \
+            ++x0;                                                                                                               \
+            y = y signy (-p);                                                                                                   \
+            x = x signx 1;                                                                                                      \
+            if (x0 == x1) {                                                                                                     \
+                if (settings->opaque(data->map, x, y)) {                                                                        \
+                    if (prev_blocked == 0) {                                                                                    \
+                        end_slope = (-SQRT_3_4 + SQRT_3_2*(float)x0) / (fdy + 0.25 - 0.5f*(float)x0);                           \
+                        fdx1 = end_slope / (SQRT_3_2 + 0.5f*end_slope);                                                         \
+                        target_slope = end_slope;                                                                               \
+                        fx1 = 0.5f + fdy*fdx1 + GRID_EPSILON;                                                                   \
+                        line->eps_x = signx (-GRID_EPSILON);                                                                    \
+                        line->eps_y = signy GRID_EPSILON;                                                                       \
+                    } else if (prev_blocked == 1) {                                                                             \
+                        line->step_x = signx target_slope / (INV_SQRT_3*target_slope + 1);                                      \
+                        line->step_y = signy 1 / (INV_SQRT_3*target_slope + 1);                                                 \
+                        return;                                                                                                 \
+                    }                                                                                                           \
+                } else if (prev_blocked == 1) {                                                                                 \
+                    start_slope = (-SQRT_3_4 + SQRT_3_2*(float)x0) / (fdy + 0.25 - 0.5f*(float)x0);                             \
+                    fdx0 = start_slope / (SQRT_3_2 + 0.5f*start_slope);                                                         \
+                    target_slope = start_slope;                                                                                 \
+                    fx0 = 0.5f + fdy*fdx0 + GRID_EPSILON;                                                                       \
+                    line->eps_x = signx GRID_EPSILON;                                                                           \
+                    line->eps_y = signy (-GRID_EPSILON);                                                                        \
+                }                                                                                                               \
+            } else if (prev_blocked == 1) {                                                                                     \
+                line->step_x = signx target_slope / (INV_SQRT_3*target_slope + 1);                                              \
+                line->step_y = signy 1 / (INV_SQRT_3*target_slope + 1);                                                         \
+                return;                                                                                                         \
+            }                                                                                                                   \
+            fx0 += fdx0;                                                                                                        \
+            fx1 += fdx1;                                                                                                        \
+            x0 = (int)fx0;                                                                                                      \
+            x1 = (int)fx1;                                                                                                      \
+            x = data->source_x signx x0;                                                                                        \
+            ++dy;                                                                                                               \
+            fdy += 1.0f;                                                                                                        \
+            p = ((x & 1) + one) & 1;                                                                                            \
+            prev_blocked = -1;                                                                                                  \
+            y = data->source_y signy (dy - (x0 + 1 - p)/2);                                                                     \
+        }                                                                                                                       \
+    }
+
+#define HEX_LOS_DEFINE_LR_SEXTANT(signx, nx)                                                                                    \
+    static float hex_los_sextant_##nx(                                                                                          \
+                                        fov_private_data_type *data,                                                            \
+                                        hex_fov_line_data *line,                                                                \
+                                        float start_slope,                                                                      \
+                                        float target_slope,                                                                     \
+                                        float end_slope) {                                                                      \
+        int x, y, y0, y1, p, prev_blocked;                                                                                      \
+        int dx = 1;                                                                                                             \
+        int delta = line->dest_t - 1;                                                                                           \
+        float fy0, fy1;                                                                                                         \
+        float fdx = SQRT_3_2;                                                                                                   \
+        float fdy = -1.0f;                                                                                                      \
+        fov_settings_type *settings = data->settings;                                                                           \
+                                                                                                                                \
+        x = data->source_x signx 1;                                                                                             \
+        p = -(x & 1);                                                                                                           \
+        fy0 = SQRT_3_2 * start_slope + 1.0f + GRID_EPSILON;                                                                     \
+        fy1 = SQRT_3_2 * end_slope + 1.0f - GRID_EPSILON;                                                                       \
+        y0 = (int)fy0;                                                                                                          \
+        y1 = (int)fy1;                                                                                                          \
+        y = data->source_y + y0 + p;                                                                                            \
+                                                                                                                                \
+        for (;;) {                                                                                                              \
+            if (--delta < 0) {                                                                                                  \
+                line->step_y = SQRT_3_2 * target_slope;                                                                         \
+                return;                                                                                                         \
+            }                                                                                                                   \
+            prev_blocked = settings->opaque(data->map, x, y);                                                                   \
+            ++y0;                                                                                                               \
+            ++y;                                                                                                                \
+            if (y0 == y1) {                                                                                                     \
+                if (settings->opaque(data->map, x, y)) {                                                                        \
+                    if (prev_blocked == 0) {                                                                                    \
+                        end_slope = ((float)y0 + fdy) / fdx;                                                                    \
+                        fy1 = fdx*end_slope - fdy - GRID_EPSILON;                                                               \
+                        target_slope = end_slope;                                                                               \
+                        line->eps_y = -GRID_EPSILON;                                                                            \
+                    } else if (prev_blocked == 1) {                                                                             \
+                        line->step_y = SQRT_3_2 * target_slope;                                                                 \
+                        return;                                                                                                 \
+                    }                                                                                                           \
+                } else if (prev_blocked == 1) {                                                                                 \
+                    start_slope = ((float)y0 + fdy) / fdx;                                                                      \
+                    fy0 = fdx*start_slope - fdy + GRID_EPSILON;                                                                 \
+                    target_slope = start_slope;                                                                                 \
+                    line->eps_y = GRID_EPSILON;                                                                                 \
+                }                                                                                                               \
+            } else if (prev_blocked == 1) {                                                                                     \
+                line->step_y = SQRT_3_2 * target_slope;                                                                         \
+                return;                                                                                                         \
+            }                                                                                                                   \
+            x = x signx 1;                                                                                                      \
+            fdx += SQRT_3_2;                                                                                                    \
+            fdy -= 0.5f;                                                                                                        \
+            fy0 += SQRT_3_2*start_slope + 0.5f;                                                                                 \
+            fy1 += SQRT_3_2*end_slope + 0.5f;                                                                                   \
+            ++dx;                                                                                                               \
+            p = -dx / 2 - (dx & 1)*(x & 1);                                                                                     \
+            y0 = (int)fy0;                                                                                                      \
+            y1 = (int)fy1;                                                                                                      \
+            y = data->source_y + y0 + p;                                                                                        \
+        }                                                                                                                       \
+    }
+
+HEX_LOS_DEFINE_SEXTANT(+,+,n,e,1)
+HEX_LOS_DEFINE_SEXTANT(-,+,n,w,1)
+HEX_LOS_DEFINE_SEXTANT(+,-,s,e,0)
+HEX_LOS_DEFINE_SEXTANT(-,-,s,w,0)
+HEX_LOS_DEFINE_LR_SEXTANT(+,e)
+HEX_LOS_DEFINE_LR_SEXTANT(-,w)
+
+void hex_fov_create_los_line(fov_settings_type *settings, void *map, void *source, hex_fov_line_data *line,
+                         int source_x, int source_y,
+                         int dest_x, int dest_y,
+                         bool start_at_end) {
+
+    fov_private_data_type data;
+    data.settings = settings;
+    data.map = map;
+    data.source_x = source_x;
+    data.source_y = source_y;
+
+    line->t = 0;
+    line->is_blocked = false;
+    line->start_at_end = start_at_end;
+    line->source_x = SQRT_3_2 * (float)source_x + SQRT_3_4;
+    line->source_y = 0.5f + (float)source_y + 0.5f*(float)(source_x & 1);
+
+    float dx = SQRT_3_2 * (float)(dest_x - source_x);
+    float dy = (float)(dest_y - source_y) + (float)((dest_x - source_x) & 1) * (0.5f - (float)(source_x & 1));
+    float adx = fabs(dx);
+    float ady = fabs(dy);
+    float start_slope, target_slope, end_slope;
+
+    if (SQRT_3*ady - adx < GRID_EPSILON) {
+        line->eps_x = 0.0f;
+        start_slope = (dy - 0.5f) / adx;
+        target_slope = dy / adx;
+        end_slope = (dy + 0.5f) / adx;
+
+        if (dx > GRID_EPSILON) {
+            line->eps_y = GRID_EPSILON;
+            line->step_x = SQRT_3_2;
+            line->dest_t = dest_x - source_x;
+            hex_los_sextant_e(&data, line, start_slope, target_slope, end_slope);
+        } else {
+            line->eps_y = -GRID_EPSILON;
+            line->step_x = -SQRT_3_2;
+            line->dest_t = source_x - dest_x;
+            hex_los_sextant_w(&data, line, start_slope, target_slope, end_slope);
+        }
+    } else {
+        line->dest_t = (int)(ady + INV_SQRT_3 * adx + 0.25f);
+        start_slope = (adx - SQRT_3_4) / (ady + 0.25f);
+        target_slope = adx / ady;
+        end_slope = (adx + SQRT_3_4) / (ady - 0.25f);
+
+        if (dx > GRID_EPSILON) {
+            line->eps_y = GRID_EPSILON;
+            if (dy > 0.0f) {
+                line->eps_x = -GRID_EPSILON;
+                hex_los_sextant_ne(&data, line, start_slope, target_slope, end_slope);
+            } else {
+                line->eps_x = GRID_EPSILON;
+                hex_los_sextant_se(&data, line, start_slope, target_slope, end_slope);
+            }
+        } else {
+            line->eps_y = -GRID_EPSILON;
+            if (dy > 0.0f) {
+                line->eps_x = -GRID_EPSILON;
+                hex_los_sextant_nw(&data, line, start_slope, target_slope, end_slope);
+            } else {
+                line->eps_x = GRID_EPSILON;
+                hex_los_sextant_sw(&data, line, start_slope, target_slope, end_slope);
+            }
+
+        }
+    }
+
+/* // simple linex
+    if (SQRT_3*ady < adx) {
+        if (dest_x > source_x) {
+            line->step_x = SQRT_3_2;
+            line->dest_t = dest_x - source_x;
+            line->eps_y = GRID_EPSILON;
+        } else {
+            line->step_x = -SQRT_3_2;
+            line->dest_t = source_x - dest_x;
+            line->eps_y = -GRID_EPSILON;
+        }
+        line->eps_x = 0.0f;
+        line->step_y = dy * line->step_x / dx;
+    } else {
+        line->dest_t = (int)(ady + INV_SQRT_3 * adx + 0.25f);
+        line->step_x = dx / (float)line->dest_t;
+        line->step_y = dy / (float)line->dest_t;
+        line->eps_x = (dy < 0.0f) ? GRID_EPSILON : -GRID_EPSILON;
+        line->eps_y = (dx > 0.0f) ? GRID_EPSILON : -GRID_EPSILON;
+    }
+    if (start_at_end) {
+        line->t = line->dest_t;
     }
+*/
 }
 
 void fov_create_los_line(fov_settings_type *settings, void *map, void *source, fov_line_data *line,
@@ -804,23 +1424,23 @@ void fov_create_los_line(fov_settings_type *settings, void *map, void *source, f
                 }
 
                 /* check if upper_slope is blocked */
-		if (sx + x != tx) {
-	                if (settings->opaque(map, sx + x, sy + my1)) {
-	                    b1 = true;
-	                    mb1 = true;
-	                    upper_slope_prev = upper_slope;
-	                    upper_slope = ((float)my1 - gy*0.5f) / ((float)x + gx*settings->permissiveness);
-	                    blocked_above = true;
-	                }
-	                else if (y1 != my1 && settings->opaque(map, sx + x, sy + y1)) {
-	                    val = ((float)y1 - gy*0.5f) / ((float)x + gx*settings->permissiveness);
-	                    if (gabs * val < gabs * upper_slope) {
-	                        b1 = true;
-	                        upper_slope_prev = upper_slope;
-	                        upper_slope = val;
-	                        blocked_above = true;
-	                    }
-	                }
+                if (sx + x != tx) {
+                    if (settings->opaque(map, sx + x, sy + my1)) {
+                        b1 = true;
+                        mb1 = true;
+                        upper_slope_prev = upper_slope;
+                        upper_slope = ((float)my1 - gy*0.5f) / ((float)x + gx*settings->permissiveness);
+                        blocked_above = true;
+                    }
+                    else if (y1 != my1 && settings->opaque(map, sx + x, sy + y1)) {
+                        val = ((float)y1 - gy*0.5f) / ((float)x + gx*settings->permissiveness);
+                        if (gabs * val < gabs * upper_slope) {
+                            b1 = true;
+                            upper_slope_prev = upper_slope;
+                            upper_slope = val;
+                            blocked_above = true;
+                        }
+                    }
                 }
 
                 /* being "pinched" isn't blocked, because one can still look diagonally */
@@ -924,25 +1544,25 @@ void fov_create_los_line(fov_settings_type *settings, void *map, void *source, f
                 }
 
                 /* check if upper_slope is blocked */
-		if (sy + y != ty) {
-	                if (settings->opaque(map, sx + mx1, sy + y)) {
-	                    b1 = true;
-	                    mb1 = true;
-	                    upper_slope_prev = upper_slope;
-	                    upper_slope = ((float)mx1 - gx*0.5f) / ((float)y + gy*settings->permissiveness);
-	                    blocked_above = true;
-	                }
-	                else if (x1 != mx1 && settings->opaque(map, sx + x1, sy + y)) {
-	                    val = ((float)x1 - gx*0.5f) / ((float)y + gy*settings->permissiveness);
-	                    if (gabs * val < gabs * upper_slope) {
-	                        b1 = true;
-	                        upper_slope_prev = upper_slope;
-	                        upper_slope = val;
-	                        blocked_above = true;
-	                    }
-	                }
+                if (sy + y != ty) {
+                    if (settings->opaque(map, sx + mx1, sy + y)) {
+                        b1 = true;
+                        mb1 = true;
+                        upper_slope_prev = upper_slope;
+                        upper_slope = ((float)mx1 - gx*0.5f) / ((float)y + gy*settings->permissiveness);
+                        blocked_above = true;
+                    }
+                    else if (x1 != mx1 && settings->opaque(map, sx + x1, sy + y)) {
+                        val = ((float)x1 - gx*0.5f) / ((float)y + gy*settings->permissiveness);
+                        if (gabs * val < gabs * upper_slope) {
+                            b1 = true;
+                            upper_slope_prev = upper_slope;
+                            upper_slope = val;
+                            blocked_above = true;
+                        }
+                    }
                 }
-	
+
                 /* being "pinched" isn't blocked, because one can still look diagonally */
                 if (mb0 && b1 || b0 && mb1 ||
                         gabs * (lower_slope - upper_slope) > GRID_EPSILON ||
diff --git a/src/fov/fov.h b/src/fov/fov.h
index 67be6aeb4800be78cd2fb3f0a1c788e8cb991768..c074e4a970db94f7faa43514c15e408221fb3256 100644
--- a/src/fov/fov.h
+++ b/src/fov/fov.h
@@ -63,7 +63,8 @@ typedef enum {
     FOV_SHAPE_CIRCLE_PLUS1,             /* floor(d + 1 - 1.0/d)           sqrt(r^2 + 1 - x^2)     x^2 + y^2 <= r^2 + 1    */
     FOV_SHAPE_OCTAGON,                  /* max(x, y) + min(x, y)/2        2*(r - x) + 1                                   */
     FOV_SHAPE_DIAMOND,                  /* x + y                          r - x                                           */
-    FOV_SHAPE_SQUARE                    /* max(x, y)                      r                                               */
+    FOV_SHAPE_SQUARE,                   /* max(x, y)                      r                                               */
+    FOV_SHAPE_HEX
 } fov_shape_type;
 
 /* The following aren't implemented and, hence, aren't used. If the the original library implements it later, we may want to copy it */
@@ -149,6 +150,41 @@ typedef struct {
     bool start_at_end;
 } fov_line_data;
 
+/** struct of calculated data for field of vision lines */
+typedef struct {
+    /** Parametrization variable used to count the "steps" of the line */
+    int t;
+
+    /** Parametrization value of t for where line is blocked, if applicable */
+    int block_t;
+
+    /** Parametrization value of t for where line reaches destination tile */
+    int dest_t;
+
+    /** "real" x from which the line originates */
+    float source_x;
+
+    /** "real" y from which the line originates */
+    float source_y;
+
+    /** Size of single step in x direction, so for t'th step, delta_x = t*step_x */
+    float step_x;
+
+    /** Size of single step in y direction, so for t'th step, delta_y = t*step_y */
+    float step_y;
+
+    /** Epsilon used to round toward the correct chirality or based on adjacent obstructed grids */
+    float eps_x;
+
+    /** Epsilon used to round toward the correct chirality or based on adjacent obstructed grids */
+    float eps_y;
+
+    /** Whether or not the line is blocked */
+    bool is_blocked;
+
+    /** Whether the line should begin at the destination (and continue away from the source) */
+    bool start_at_end;
+} hex_fov_line_data;
 
 /** The opposite direction to that given. */
 #define fov_direction_opposite(direction) ((fov_direction_type)(((direction)+4)&0x7))
@@ -307,8 +343,8 @@ 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,
-						float dx, float dy, float beam_angle
+                        int source_x, int source_y, unsigned radius,
+                        float dx, float dy, float beam_angle
 );
 
 /**
@@ -327,10 +363,18 @@ void fov_beam_any_angle(fov_settings_type *settings, void *map, void *source,
  * \param dest_y y-axis coordinate from which to end.
  * \param start_at_end if true, the line will begin at the destination (x, y) and continue away from source (x, y)
  */
-void fov_create_los_line(fov_settings_type *settings, void *map, void *source, fov_line_data *line,
-                                                int source_x, int source_y,
-                                                int dest_x, int dest_y,
-                                                bool start_at_end
+void fov_create_los_line(fov_settings_type *settings, void *map, void *source,
+                         fov_line_data *line,
+                         int source_x, int source_y,
+                         int dest_x, int dest_y,
+                         bool start_at_end
+); 
+
+void hex_fov_create_los_line(fov_settings_type *settings, void *map, void *source,
+                         hex_fov_line_data *line,
+                         int source_x, int source_y,
+                         int dest_x, int dest_y,
+                         bool start_at_end
 ); 
 
 #ifdef __cplusplus
diff --git a/src/map.c b/src/map.c
index 22d41adde3945ed263d71f9a37cd7c9829b4f208..6da40953acb901751ea0392ff9002101b9e00fc7 100644
--- a/src/map.c
+++ b/src/map.c
@@ -31,6 +31,32 @@
 //#include "shaders.h"
 #include "useshader.h"
 
+static const char IS_HEX_KEY = 'k';
+
+/*
+static int lua_set_is_hex(lua_State *L)
+{
+	int val = luaL_checknumber(L, 1);
+	lua_pushlightuserdata(L, (void *)&IS_HEX_KEY); // push address as guaranteed unique key
+	lua_pushnumber(L, val);
+	lua_settable(L, LUA_REGISTRYINDEX);
+	return 0;
+}
+*/
+
+static int lua_is_hex(lua_State *L)
+{
+	lua_pushlightuserdata(L, (void *)&IS_HEX_KEY); // push address as guaranteed unique key
+	lua_gettable(L, LUA_REGISTRYINDEX);  /* retrieve value */
+	if (lua_isnil(L, -1)) {
+		lua_pop(L, 1); // remove nil
+		lua_pushlightuserdata(L, (void *)&IS_HEX_KEY); // push address as guaranteed unique key
+		lua_pushnumber(L, 0);
+		lua_settable(L, LUA_REGISTRYINDEX);
+		lua_pushnumber(L, 0);
+	}
+}
+
 static int map_object_new(lua_State *L)
 {
 	long uid = luaL_checknumber(L, 1);
@@ -238,11 +264,17 @@ static int map_object_reset_move_anim(lua_State *L)
 static int map_object_set_move_anim(lua_State *L)
 {
 	map_object *obj = (map_object*)auxiliar_checkclass(L, "core{mapobj}", 1);
-	// If at rest use satrting point
+
+	lua_is_hex(L);
+	int is_hex = luaL_checknumber(L, -1);
+
+	// If at rest use starting point
 	if (!obj->move_max)
 	{
-		obj->oldx = luaL_checknumber(L, 2);
-		obj->oldy = luaL_checknumber(L, 3);
+		int ox = luaL_checknumber(L, 2);
+		int oy = luaL_checknumber(L, 3);
+		obj->oldx = ox;
+		obj->oldy = oy + 0.5f*(ox & is_hex);
 	}
 	// If already moving, compute starting point
 	else
@@ -250,7 +282,7 @@ static int map_object_set_move_anim(lua_State *L)
 		int ox = luaL_checknumber(L, 2);
 		int oy = luaL_checknumber(L, 3);
 		obj->oldx = obj->animdx + ox;
-		obj->oldy = obj->animdy + oy;
+		obj->oldy = obj->animdy + oy + 0.5f*(ox & is_hex);
 	}
 	obj->move_step = 0;
 	obj->move_max = luaL_checknumber(L, 6);
@@ -299,14 +331,14 @@ static int map_object_is_valid(lua_State *L)
 
 static bool _CheckGL_Error(const char* GLcall, const char* file, const int line)
 {
-    GLenum errCode;
-    if((errCode = glGetError())!=GL_NO_ERROR)
-    {
+	GLenum errCode;
+	if((errCode = glGetError())!=GL_NO_ERROR)
+	{
 		printf("OPENGL ERROR #%i: (%s) in file %s on line %i\n",errCode,gluErrorString(errCode), file, line);
-        printf("OPENGL Call: %s\n",GLcall);
-        return FALSE;
-    }
-    return TRUE;
+		printf("OPENGL Call: %s\n",GLcall);
+		return FALSE;
+	}
+	return TRUE;
 }
 
 //#define _DEBUG
@@ -543,10 +575,11 @@ static void setup_seens_texture(map_type *map)
 	if (map->seens_texture) glDeleteTextures(1, &(map->seens_texture));
 	if (map->seens_map) free(map->seens_map);
 
+	int f = (map->is_hex & 1);
 	int realw=1;
-	while (realw < map->w) realw *= 2;
+	while (realw < f + (1+f)*map->w) realw *= 2;
 	int realh=1;
-	while (realh < map->h) realh *= 2;
+	while (realh < f + (1+f)*map->h) realh *= 2;
 	map->seens_map_w = realw;
 	map->seens_map_h = realh;
 
@@ -584,6 +617,7 @@ static int map_new(lua_State *L)
 	int tile_w = luaL_checknumber(L, 7);
 	int tile_h = luaL_checknumber(L, 8);
 	int zdepth = luaL_checknumber(L, 9);
+	int is_hex = luaL_checknumber(L, 10);
 	int i, j;
 
 	map_type *map = (map_type*)lua_newuserdata(L, sizeof(map_type));
@@ -600,6 +634,11 @@ static int map_new(lua_State *L)
 
 	map->minimap_gridsize = 4;
 
+	map->is_hex = (is_hex > 0);
+	lua_pushlightuserdata(L, (void *)&IS_HEX_KEY); // push address as guaranteed unique key
+	lua_pushnumber(L, map->is_hex);
+	lua_settable(L, LUA_REGISTRYINDEX);
+
 	map->vertices = calloc(2*4*QUADS_PER_BATCH, sizeof(GLfloat)); // 2 coords, 4 vertices per particles
 	map->colors = calloc(4*4*QUADS_PER_BATCH, sizeof(GLfloat)); // 4 color data, 4 vertices per particles
 	map->texcoords = calloc(2*4*QUADS_PER_BATCH, sizeof(GLfloat));
@@ -908,6 +947,7 @@ static void map_update_seen_texture(map_type *map)
 	int my = map->used_my;
 	GLubyte *seens = map->seens_map;
 	int ptr = 0;
+	int f = (map->is_hex & 1);
 	int ii, jj;
 	map->seensinfo_w = map->w;
 	map->seensinfo_h = map->h;
@@ -917,13 +957,31 @@ static void map_update_seen_texture(map_type *map)
 		for (ii = 0; ii < map->w; ii++)
 		{
 			int i = ii, j = jj;
+			ptr = (((1+f)*j + (i & f)) * map->seens_map_w + (1+f)*i) * 4;
 			if ((i < 0) || (j < 0) || (i >= map->w) || (j >= map->h))
 			{
 				seens[ptr] = 0;
 				seens[ptr+1] = 0;
 				seens[ptr+2] = 0;
 				seens[ptr+3] = 255;
-				ptr += 4;
+				if (f) {
+					ptr += 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255;
+					ptr += 4 * map->seens_map_w - 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255;
+					ptr += 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255;
+				}
+				//ptr += 4;
 				continue;
 			}
 			float v = map->grids_seens[j*map->w+i] * 255;
@@ -935,6 +993,23 @@ static void map_update_seen_texture(map_type *map)
 				seens[ptr+1] = (GLubyte)0;
 				seens[ptr+2] = (GLubyte)0;
 				seens[ptr+3] = (GLubyte)255-v;
+				if (f) {
+					ptr += 4;
+					seens[ptr] = (GLubyte)0;
+					seens[ptr+1] = (GLubyte)0;
+					seens[ptr+2] = (GLubyte)0;
+					seens[ptr+3] = (GLubyte)255-v;
+					ptr += 4 * map->seens_map_w - 4;
+					seens[ptr] = (GLubyte)0;
+					seens[ptr+1] = (GLubyte)0;
+					seens[ptr+2] = (GLubyte)0;
+					seens[ptr+3] = (GLubyte)255-v;
+					ptr += 4;
+					seens[ptr] = (GLubyte)0;
+					seens[ptr+1] = (GLubyte)0;
+					seens[ptr+2] = (GLubyte)0;
+					seens[ptr+3] = (GLubyte)255-v;
+				}
 			}
 			else if (map->grids_remembers[i][j])
 			{
@@ -942,6 +1017,23 @@ static void map_update_seen_texture(map_type *map)
 				seens[ptr+1] = 0;
 				seens[ptr+2] = 0;
 				seens[ptr+3] = 255 - map->obscure_a * 255;
+				if (f) {
+					ptr += 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255 - map->obscure_a * 255;
+					ptr += 4 * map->seens_map_w - 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255 - map->obscure_a * 255;
+					ptr += 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255 - map->obscure_a * 255;
+				}
 			}
 			else
 			{
@@ -949,11 +1041,28 @@ static void map_update_seen_texture(map_type *map)
 				seens[ptr+1] = 0;
 				seens[ptr+2] = 0;
 				seens[ptr+3] = 255;
+				if (f) {
+					ptr += 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255;
+					ptr += 4 * map->seens_map_w - 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255;
+					ptr += 4;
+					seens[ptr] = 0;
+					seens[ptr+1] = 0;
+					seens[ptr+2] = 0;
+					seens[ptr+3] = 255;
+				}
 			}
-			ptr += 4;
+			//ptr += 4;
 		}
 		// Skip the rest of the texture, silly GPUs not supporting NPOT textures!
-		ptr += (map->seens_map_w - map->w) * 4;
+		//ptr += (map->seens_map_w - map->w) * 4;
 	}
 	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, map->seens_map_w, map->seens_map_h, GL_BGRA, GL_UNSIGNED_BYTE, seens);
 }
@@ -986,11 +1095,12 @@ static int map_draw_seen_texture(lua_State *L)
 
 	tglBindTexture(GL_TEXTURE_2D, map->seens_texture);
 
+	int f = 1 + (map->is_hex & 1);
 	GLfloat texcoords[2*4] = {
 		0, 0,
-		0, 1,
-		1, 1,
-		1, 0,
+		0, f,
+		f, f,
+		f, 0,
 	};
 	GLfloat colors[4*4] = {
 		1,1,1,1,
@@ -1205,6 +1315,8 @@ void display_map_quad(GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *ma
 	 ********************************************************/
 	float animdx = 0, animdy = 0;
 	if (m->display_last == DL_NONE) m->move_max = 0;
+	lua_is_hex(L);
+	int is_hex = luaL_checknumber(L, -1);
 	if (m->move_max)
 	{
 		m->move_step += nb_keyframes;
@@ -1214,7 +1326,7 @@ void display_map_quad(GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *ma
 		if (m->move_max)
 		{
 			float adx = (float)i - m->oldx;
-			float ady = (float)j - m->oldy;
+			float ady = (float)j - m->oldy + 0.5f*(i & is_hex);
 
 			// Motion bluuuurr!
 			if (m->move_blur)
@@ -1343,7 +1455,7 @@ static int map_to_screen(lua_State *L)
 				if ((i < 0) || (j < 0) || (i >= map->w) || (j >= map->h)) continue;
 
 				int dx = x + i * map->tile_w;
-				int dy = y + j * map->tile_h;
+				int dy = y + j * map->tile_h + (i & map->is_hex) * map->tile_h / 2;
 				map_object *mo = map->grids[i][j][z];
 				if (!mo) continue;
 
@@ -1412,6 +1524,7 @@ static int minimap_to_screen(lua_State *L)
 	int col_idx = 0;
 	GLfloat r, g, b, a;
 
+	int f = (map->is_hex & 1);
 	// Create/recreate the minimap data if needed
 	if (map->mm_w != mdw || map->mm_h != mdh)
 	{
@@ -1422,7 +1535,7 @@ static int minimap_to_screen(lua_State *L)
 		int realw=1;
 		int realh=1;
 		while (realw < mdw) realw *= 2;
-		while (realh < mdh) realh *= 2;
+		while (realh < f + (1+f)*mdh) realh *= 2;
 
 		glGenTextures(1, &(map->mm_texture));
 		map->mm_w = mdw;
@@ -1443,7 +1556,7 @@ static int minimap_to_screen(lua_State *L)
 
 	int ptr;
 	GLubyte *mm = map->minimap;
-	memset(mm, 0, map->mm_rw * map->mm_rh * 4 * sizeof(GLubyte));
+	memset(mm, 0, map->mm_rh * map->mm_rw * 4 * sizeof(GLubyte));
 	for (z = 0; z < map->zdepth; z++)
 	{
 		for (i = mdx; i < mdx + mdw; i++)
@@ -1453,7 +1566,7 @@ static int minimap_to_screen(lua_State *L)
 				if ((i < 0) || (j < 0) || (i >= map->w) || (j >= map->h)) continue;
 				map_object *mo = map->grids[i][j][z];
 				if (!mo || mo->mm_r < 0) continue;
-				ptr = ((j-mdy) * map->mm_rw + (i-mdx)) * 4;
+				ptr = (((1+f)*(j-mdy) + (i & f)) * map->mm_rw + (i-mdx)) * 4;
 
 				if ((mo->on_seen && map->grids_seens[j*map->w+i]) || (mo->on_remember && map->grids_remembers[i][j]) || mo->on_unknown)
 				{
@@ -1469,6 +1582,13 @@ static int minimap_to_screen(lua_State *L)
 					mm[ptr+1] = g * 255;
 					mm[ptr+2] = r * 255;
 					mm[ptr+3] = a * 255;
+					if (f) {
+						ptr += 4 * map->mm_rw;
+						mm[ptr] = b * 255;
+						mm[ptr+1] = g * 255;
+						mm[ptr+2] = r * 255;
+						mm[ptr+3] = a * 255;
+					}
 				}
 			}
 		}
@@ -1478,8 +1598,8 @@ static int minimap_to_screen(lua_State *L)
 	// Display it
 	GLfloat texcoords[2*4] = {
 		0, 0,
-		0, (float)mdh/(float)map->mm_rh,
-		(float)mdw/(float)map->mm_rw, (float)mdh/(float)map->mm_rh,
+		0, (float)((1+f)*mdh)/(float)map->mm_rh,
+		(float)mdw/(float)map->mm_rw, (float)((1+f)*mdh)/(float)map->mm_rh,
 		(float)mdw/(float)map->mm_rw, 0,
 	};
 	GLfloat colors[4*4] = {
diff --git a/src/map.h b/src/map.h
index acfd690bcea06ef01e1f8439190ca02e288af936..210f16ad6e1753d7d76e8fe6552f0dc07e40a8b3 100644
--- a/src/map.h
+++ b/src/map.h
@@ -81,6 +81,8 @@ typedef struct {
 
 	int minimap_gridsize;
 
+	int is_hex;
+
 	// Map parameters
 	float obscure_r, obscure_g, obscure_b, obscure_a;
 	float shown_r, shown_g, shown_b, shown_a;