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;