From ac8f99c8962f0829da6474fabe77deb0d5fbf2a7 Mon Sep 17 00:00:00 2001 From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54> Date: Tue, 14 Aug 2012 23:12:33 +0000 Subject: [PATCH] Faster auto-explore git-svn-id: http://svn.net-core.org/repos/t-engine4@5460 51575b47-30f0-44d4-a5cc-537603b46e54 --- .../tome/class/interface/PlayerExplore.lua | 2724 +++++++++++------ game/modules/tome/dialogs/debug/DebugMain.lua | 9 + 2 files changed, 1789 insertions(+), 944 deletions(-) diff --git a/game/modules/tome/class/interface/PlayerExplore.lua b/game/modules/tome/class/interface/PlayerExplore.lua index 298a18ba69..836203b8fa 100644 --- a/game/modules/tome/class/interface/PlayerExplore.lua +++ b/game/modules/tome/class/interface/PlayerExplore.lua @@ -26,6 +26,9 @@ require "engine.class" local Map = require "engine.Map" local Dialog = require "engine.ui.Dialog" +-- Man, if only I had known about the ffi library when I first wrote auto-explore, I probably would have structured things differently +local is_ffi, ffi = pcall(require, "ffi") -- check if ffi is available (it should be) + module(..., package.seeall, class.make) local function toSingle(x, y) @@ -37,947 +40,1779 @@ local function toDouble(c) return c - y * game.level.map.w, y end -local listAdjacentTiles -local getNextNodes +-- Using structs may be better, but I was able to easily search/replace by using arrays. +-- FFI makes this code 2-3x faster for me. I think the GC may still cause occasional hiccups of slowness. +if is_ffi then + ffi.cdef[[ + typedef int cnode[5]; + typedef int ctile[2]; + ]] +end + local listAdjacentNodes -local listSharedNodes -local listSharedNodesPrevious -local previousNode +local getNextNodes +local listAdjacentTiles +local listSharedTiles +local listSharedTilesPrevious +local previousTile local checkAmbush local map_type +-- Heh, this somehow turned into 1850 lines. Trust me, this didn't take nearly as long to write as you might expect. +-- Ample use of search/replace and copy/paste was used at every step. Heh, got kind of out of control though :-P 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 -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}) + if util.isHex() and is_ffi then + -- a flexible but slow function to list all adjacent tile + listAdjacentNodes = function(tile, no_diagonal, no_cardinal) + local tiles = {} + local x, y, c + if type(tile) == "number" then + x, y = toDouble(tile) + c = tile + val = 1 + elseif tile[0] then + x, y, c, val = tile[0], tile[1], tile[2], tile[3]+1 + else + return tiles 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}) + 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] = ffi.new("cnode", { x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 }) end + if upper_okay then tiles[#tiles+1] = ffi.new("cnode", { x, y + 1, c + game.level.map.w, val, 2 }) end + if (upper_okay or p == 0) and right_okay then tiles[#tiles+1] = ffi.new("cnode", { 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] = ffi.new("cnode", { x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 }) end + if lower_okay then tiles[#tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) end + if (lower_okay or r == 0) and right_okay then tiles[#tiles+1] = ffi.new("cnode", { x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 }) end end + return tiles 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 - 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 - - if not no_cardinal then - if upper_okay then tiles[1] = {x, y + 1, c + game.level.map.w, val, 2 } end - if left_okay then tiles[#tiles+1] = {x - 1, y, c - 1, val, 4 } end - if right_okay then tiles[#tiles+1] = {x + 1, y, c + 1, val, 6 } end - if lower_okay then tiles[#tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } end - end - if not no_diagonal then - if left_okay and upper_okay then tiles[#tiles+1] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } end - if right_okay and upper_okay then tiles[#tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } end - if left_okay and lower_okay then tiles[#tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end - if right_okay and lower_okay then tiles[#tiles+1] = {x + 1, y - 1, c + 1 - 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 - - if y < game.level.map.h - 1 then - cardinal_tiles[#cardinal_tiles+1] = {x, y + 1, c + game.level.map.w, val, 2 } - diagonal_tiles[#diagonal_tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - if x > 0 then - diagonal_tiles[#diagonal_tiles+1] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - cardinal_tiles[#cardinal_tiles+1] = {x - 1, y, c - 1, val, 4 } - diagonal_tiles[#diagonal_tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - end - elseif x > 0 then - cardinal_tiles[#cardinal_tiles+1] = {x - 1, y, c - 1, val, 4 } - diagonal_tiles[#diagonal_tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - end - end, - --Dir 2 - function(node, cardinal_tiles, diagonal_tiles) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - if y > game.level.map.h - 2 then return end - - if x > 0 then diagonal_tiles[#diagonal_tiles+1] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } end - cardinal_tiles[#cardinal_tiles+1] = {x, y + 1, c + game.level.map.w, val, 2 } - if x < game.level.map.w - 1 then diagonal_tiles[#diagonal_tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } end - end, - -- Dir 3 - function(node, cardinal_tiles, diagonal_tiles) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - - if y < game.level.map.h - 1 then - diagonal_tiles[#diagonal_tiles+1] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - cardinal_tiles[#cardinal_tiles+1] = {x, y + 1, c + game.level.map.w, val, 2 } - if x < game.level.map.w - 1 then - diagonal_tiles[#diagonal_tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - cardinal_tiles[#cardinal_tiles+1] = {x + 1, y, c + 1, val, 6 } - diagonal_tiles[#diagonal_tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } - end - elseif x < game.level.map.w - 1 then - cardinal_tiles[#cardinal_tiles+1] = {x + 1, y, c + 1, val, 6 } - diagonal_tiles[#diagonal_tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } - end - end, - --Dir 4 - function(node, cardinal_tiles, diagonal_tiles) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - if x < 1 then return end - - if y < game.level.map.h - 1 then diagonal_tiles[#diagonal_tiles+1] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } end - cardinal_tiles[#cardinal_tiles+1] = {x - 1, y, c - 1, val, 4 } - if y > 0 then diagonal_tiles[#diagonal_tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end - 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 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 then cardinal_tiles[#cardinal_tiles+1] = {x, y + 1, c + game.level.map.w, val, 2 } end - if left_okay then cardinal_tiles[#cardinal_tiles+1] = {x - 1, y, c - 1, val, 4 } end - if right_okay then cardinal_tiles[#cardinal_tiles+1] = {x + 1, y, c + 1, val, 6 } end - if lower_okay then cardinal_tiles[#cardinal_tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } end - - if left_okay and upper_okay then diagonal_tiles[#diagonal_tiles+1] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } end - if right_okay and upper_okay then diagonal_tiles[#diagonal_tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } end - if left_okay and lower_okay then diagonal_tiles[#diagonal_tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end - if right_okay and lower_okay then diagonal_tiles[#diagonal_tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } end - end, - --Dir 6 - function(node, cardinal_tiles, diagonal_tiles) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - if x > game.level.map.w - 2 then return end - - if y < game.level.map.h - 1 then diagonal_tiles[#diagonal_tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } end - cardinal_tiles[#cardinal_tiles+1] = {x + 1, y, c + 1, val, 6 } - if y > 0 then diagonal_tiles[#diagonal_tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } end - end, - -- Dir 7 - function(node, cardinal_tiles, diagonal_tiles) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - - if x > 0 then - diagonal_tiles[#diagonal_tiles+1] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - cardinal_tiles[#cardinal_tiles+1] = {x - 1, y, c - 1, val, 4 } - if y > 0 then - diagonal_tiles[#diagonal_tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - cardinal_tiles[#cardinal_tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } - diagonal_tiles[#diagonal_tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + -- 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[0], node[1], node[2], node[3]+1 + local p = x % 2 + local r = 1 - p + if x > 0 then + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { 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] = ffi.new("cnode", { 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] = ffi.new("cnode", { 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[0], node[1], node[2], node[3]+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] = ffi.new("cnode", { 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] = ffi.new("cnode", { 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] = ffi.new("cnode", { 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[0], node[1], node[2], node[3]+1 + local p = x % 2 + local r = 1 - p + if x < game.level.map.w - 1 then + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { 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] = ffi.new("cnode", { 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] = ffi.new("cnode", { 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[0], node[1], node[2], node[3]+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] = ffi.new("cnode", { x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 }) end + if upper_okay then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { 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] = ffi.new("cnode", { 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] = ffi.new("cnode", { x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 }) end + if lower_okay then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { 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] = ffi.new("cnode", { 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[0], node[1], node[2], node[3]+1 + local p = x % 2 + local r = 1 - p + if x > 0 then + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { 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] = ffi.new("cnode", { x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 }) end + end + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { 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[0], node[1], node[2], node[3]+1 + local r = 1 - x % 2 + if y > 0 or r == 0 then + if x > 0 then cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { 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] = ffi.new("cnode", { x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 }) end + end + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { 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[0], node[1], node[2], node[3]+1 + local p = x % 2 + local r = 1 - p + if x < game.level.map.w - 1 then + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { 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] = ffi.new("cnode", { x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 }) end + end + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) end end - elseif y > 0 then - cardinal_tiles[#cardinal_tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } - diagonal_tiles[#diagonal_tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } - end - end, - --Dir 8 - function(node, cardinal_tiles, diagonal_tiles) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - if y < 1 then return end - - if x > 0 then diagonal_tiles[#diagonal_tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end - cardinal_tiles[#cardinal_tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } - if x < game.level.map.w - 1 then diagonal_tiles[#diagonal_tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } end - end, - -- Dir 9 - function(node, cardinal_tiles, diagonal_tiles) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - - if x < game.level.map.w - 1 then - diagonal_tiles[#diagonal_tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - cardinal_tiles[#cardinal_tiles+1] = {x + 1, y, c + 1, val, 6 } - if y > 0 then - diagonal_tiles[#diagonal_tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - cardinal_tiles[#cardinal_tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } - diagonal_tiles[#diagonal_tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + } + + -- Use directional information to list all adjacent tiles more efficiently + listAdjacentTiles = { + -- Dir 1 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {ffi.new("ctile", { x, y - 1 }), + ffi.new("ctile", { x + 1, y - r })} + if y < game.level.map.h - 1 then + tiles[3] = ffi.new("ctile", { x, y + 1 }) + tiles[4] = ffi.new("ctile", { x + 1, y + p }) + end + if x > 0 then + tiles[#tiles+1] = ffi.new("ctile", { x - 1, y - r }) + if y < game.level.map.h - 1 or p == 0 then tiles[#tiles+1] = ffi.new("ctile", { x - 1, y + p }) end + end + return tiles + end, + -- Dir 2 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {ffi.new("ctile", { x, y - 1 })} + if x > 0 then tiles[2] = ffi.new("ctile", { x - 1, y - r }) end + if x < game.level.map.w - 1 then tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - r }) end + if y < game.level.map.h - 1 or p == 0 then + if x > 0 then tiles[#tiles+1] = ffi.new("ctile", { x - 1, y + p }) end + if x < game.level.map.w - 1 then tiles[#tiles+1] = ffi.new("ctile", { x + 1, y + p }) end + end + if y < game.level.map.h - 1 then tiles[#tiles+1] = ffi.new("ctile", { x, y + 1 }) end + return tiles + end, + -- Dir 3 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {ffi.new("ctile", { x - 1, y - r }), + ffi.new("ctile", { x, y - 1 })} + if y < game.level.map.h - 1 then + tiles[3] = ffi.new("ctile", { x - 1, y + p }) + tiles[4] = ffi.new("ctile", { x, y + 1 }) + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - r }) + if y < game.level.map.h - 1 or p == 0 then tiles[#tiles+1] = ffi.new("ctile", { x + 1, y + p }) 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 = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {ffi.new("ctile", { x, y + 1 }), + ffi.new("ctile", { x + 1, y + p })} + if y > 0 then + tiles[3] = ffi.new("ctile", { x, y - 1 }) + tiles[4] = ffi.new("ctile", { x + 1, y - r }) + end + if x > 0 then + tiles[#tiles+1] = ffi.new("ctile", { x - 1, y + p }) + if y > 0 or r == 0 then tiles[#tiles+1] = ffi.new("ctile", { x - 1, y - r }) end + end + return tiles + end, + -- Dir 8 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {ffi.new("ctile", { x, y + 1 })} + if x > 0 then tiles[2] = ffi.new("ctile", { x - 1, y + p }) end + if x < game.level.map.w - 1 then tiles[#tiles+1] = ffi.new("ctile", { x + 1, y + p }) end + if y > 0 or r == 0 then + if x > 0 then tiles[#tiles+1] = ffi.new("ctile", { x - 1, y - r }) end + if x < game.level.map.w - 1 then tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - r }) end + end + if y > 0 then tiles[#tiles+1] = ffi.new("ctile", { x, y - 1 }) end + return tiles + end, + -- Dir 9 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {ffi.new("ctile", { x - 1, y + p }), + ffi.new("ctile", { x, y + 1 })} + if y > 0 then + tiles[3] = ffi.new("ctile", { x - 1, y - r }) + tiles[4] = ffi.new("ctile", { x, y - 1 }) + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y + p }) + if y > 0 or r == 0 then tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - r }) end + end + return tiles end - elseif y > 0 then - diagonal_tiles[#diagonal_tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - cardinal_tiles[#cardinal_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 tiles = {{x + 1, y, c + 1, val, 6 }, - {x, y - 1, c - game.level.map.w, val, 8 }, - {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }} - if y < game.level.map.h - 1 then - tiles[4] = {x, y + 1, c + game.level.map.w, val, 2 } - tiles[5] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - if x > 0 then - tiles[6] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[7] = {x - 1, y, c - 1, val, 4 } - tiles[8] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } + } + + -- 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 "listSharedTilesPrevious") are used to infer what might be a wall, and may be useful later. + -- c = current, p = previous c* *c* + -- * = returned tile *p .p. + listSharedTiles = { + -- 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 "listSharedTiles". "listSharedTiles" and "listSharedTilesPrevious" 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* + listSharedTilesPrevious = { + -- 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 + } + + previousTile = { + -- Dir 1 + function(node) return ffi.new("ctile", { node[0] + 1, node[1] - 1 + node[0]%2 }) end, + -- Dir 2 + function(node) return ffi.new("ctile", { node[0], node[1] - 1 }) end, + -- Dir 3 + function(node) return ffi.new("ctile", { node[0] - 1, node[1] - 1 + node[0]%2 }) end, + -- Dir 4 + function(node) end, + -- Dir 5 + function(node) return ffi.new("ctile", { node[0], node[1] }) end, + -- Dir 6 + function(node) end, + -- Dir 7 + function(node) return ffi.new("ctile", { node[0] + 1, node[1] + node[0]%2 }) end, + -- Dir 8 + function(node) return ffi.new("ctile", { node[0], node[1] + 1 }) end, + -- Dir 9 + function(node) return ffi.new("ctile", { node[0] - 1, node[1] + node[0]%2 }) 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 - elseif x > 0 then - tiles[4] = {x - 1, y, c - 1, val, 4 } - tiles[5] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end - return tiles - end, - -- Dir 2 - function(node) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - local tiles = {{x, y - 1, c - game.level.map.w, val, 8 }} - - if y < game.level.map.h - 1 then - tiles[2] = {x, y + 1, c + game.level.map.w, val, 2 } - if x > 0 then - tiles[3] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[4] = {x - 1, y, c - 1, val, 4 } - tiles[5] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - end - if x < game.level.map.w - 1 then - tiles[#tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - tiles[#tiles+1] = {x + 1, y, c + 1, val, 6 } - tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } - end - else - if x > 0 then - tiles[2] = {x - 1, y, c - 1, val, 4 } - tiles[3] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } + + elseif is_ffi then + -- a flexible but slow function to list all adjacent tile + listAdjacentNodes = function(tile, no_diagonal, no_cardinal) + local tiles = {} + local x, y, c + if type(tile) == "number" then + x, y = toDouble(tile) + c = tile + val = 1 + elseif tile[0] then + x, y, c, val = tile[0], tile[1], tile[2], tile[3]+1 + else + return tiles end - if x < game.level.map.w - 1 then - tiles[#tiles+1] = {x + 1, y, c + 1, val, 6 } - tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + 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 not no_cardinal then + if upper_okay then tiles[1] = ffi.new("cnode", { x, y + 1, c + game.level.map.w, val, 2 }) end + if left_okay then tiles[#tiles+1] = ffi.new("cnode", { x - 1, y, c - 1, val, 4 }) end + if right_okay then tiles[#tiles+1] = ffi.new("cnode", { x + 1, y, c + 1, val, 6 }) end + if lower_okay then tiles[#tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) end end - end - return tiles - end, - -- Dir 3 - function(node) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - local tiles = {{x - 1, y, c - 1, val, 4 }, - {x - 1, y - 1, c - 1 - 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[4] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[5] = {x, y + 1, c + game.level.map.w, val, 2 } - if x < game.level.map.w - 1 then - tiles[6] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - tiles[7] = {x + 1, y, c + 1, val, 6 } - tiles[8] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + if not no_diagonal then + if left_okay and upper_okay then tiles[#tiles+1] = ffi.new("cnode", { x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }) end + if right_okay and upper_okay then tiles[#tiles+1] = ffi.new("cnode", { x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }) end + if left_okay and lower_okay then tiles[#tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) end + if right_okay and lower_okay then tiles[#tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) end end - elseif x < game.level.map.w - 1 then - tiles[4] = {x + 1, y, c + 1, val, 6 } - tiles[5] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + return tiles end - return tiles - end, - -- Dir 4 - function(node) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - local tiles = {{x + 1, y, c + 1, val, 6 }} - - if x > 0 then - tiles[2] = {x - 1, y, c - 1, val, 4 } - if y < game.level.map.h - 1 then - tiles[3] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[4] = {x, y + 1, c + game.level.map.w, val, 2 } - tiles[5] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } + + -- 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[0], node[1], node[2], node[3]+1 + if y < game.level.map.h - 1 then + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y + 1, c + game.level.map.w, val, 2 }) + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }) + if x > 0 then + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }) + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x - 1, y, c - 1, val, 4 }) + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) + end + elseif x > 0 then + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x - 1, y, c - 1, val, 4 }) + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) + end + end, + --Dir 2 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if y > game.level.map.h - 2 then return end + if x > 0 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }) end + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y + 1, c + game.level.map.w, val, 2 }) + if x < game.level.map.w - 1 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }) end + end, + -- Dir 3 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if y < game.level.map.h - 1 then + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }) + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y + 1, c + game.level.map.w, val, 2 }) + if x < game.level.map.w - 1 then + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }) + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x + 1, y, c + 1, val, 6 }) + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) + end + elseif x < game.level.map.w - 1 then + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x + 1, y, c + 1, val, 6 }) + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) + end + end, + --Dir 4 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if x < 1 then return end + if y < game.level.map.h - 1 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }) end + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x - 1, y, c - 1, val, 4 }) + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) end + end, + --Dir 5 (all adjacent, slow) + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + 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 then cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y + 1, c + game.level.map.w, val, 2 }) end + if left_okay then cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x - 1, y, c - 1, val, 4 }) end + if right_okay then cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x + 1, y, c + 1, val, 6 }) end + if lower_okay then cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) end + if left_okay and upper_okay then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }) end + if right_okay and upper_okay then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }) end + if left_okay and lower_okay then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) end + if right_okay and lower_okay then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) end + end, + --Dir 6 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if x > game.level.map.w - 2 then return end + if y < game.level.map.h - 1 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }) end + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x + 1, y, c + 1, val, 6 }) + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) end + end, + -- Dir 7 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if x > 0 then + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }) + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x - 1, y, c - 1, val, 4 }) + if y > 0 then + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) + end + elseif y > 0 then + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) + end + end, + --Dir 8 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if y < 1 then return end + if x > 0 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) end + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) + if x < game.level.map.w - 1 then diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) end + end, + -- Dir 9 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if x < game.level.map.w - 1 then + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }) + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x + 1, y, c + 1, val, 6 }) + if y > 0 then + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }) + end + elseif y > 0 then + diagonal_tiles[#diagonal_tiles+1] = ffi.new("cnode", { x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }) + cardinal_tiles[#cardinal_tiles+1] = ffi.new("cnode", { x, y - 1, c - game.level.map.w, val, 8 }) + end end - if y > 0 then - tiles[#tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - tiles[#tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } - tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + } + + -- Use directional information to list all adjacent tiles more efficiently + listAdjacentTiles = { + -- Dir 1 + function(node) + local x, y = node[0], node[1] + local tiles = {ffi.new("ctile", { x + 1, y }), + ffi.new("ctile", { x, y - 1 }), + ffi.new("ctile", { x + 1, y - 1 })} + if y < game.level.map.h - 1 then + tiles[4] = ffi.new("ctile", { x, y + 1 }) + tiles[5] = ffi.new("ctile", { x + 1, y + 1 }) + if x > 0 then + tiles[6] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[7] = ffi.new("ctile", { x - 1, y }) + tiles[8] = ffi.new("ctile", { x - 1, y - 1 }) + end + elseif x > 0 then + tiles[4] = ffi.new("ctile", { x - 1, y }) + tiles[5] = ffi.new("ctile", { x - 1, y - 1 }) + end + return tiles + end, + -- Dir 2 + function(node) + local x, y = node[0], node[1] + local tiles = {ffi.new("ctile", { x, y - 1 })} + if y < game.level.map.h - 1 then + tiles[2] = ffi.new("ctile", { x, y + 1 }) + if x > 0 then + tiles[3] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[4] = ffi.new("ctile", { x - 1, y }) + tiles[5] = ffi.new("ctile", { x - 1, y - 1 }) + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y + 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - 1 }) + end + else + if x > 0 then + tiles[2] = ffi.new("ctile", { x - 1, y }) + tiles[3] = ffi.new("ctile", { x - 1, y - 1 }) + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - 1 }) + end + end + return tiles + end, + -- Dir 3 + function(node) + local x, y = node[0], node[1] + local tiles = {ffi.new("ctile", { x - 1, y }), + ffi.new("ctile", { x - 1, y - 1 }), + ffi.new("ctile", { x, y - 1 })} + if y < game.level.map.h - 1 then + tiles[4] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[5] = ffi.new("ctile", { x, y + 1 }) + if x < game.level.map.w - 1 then + tiles[6] = ffi.new("ctile", { x + 1, y + 1 }) + tiles[7] = ffi.new("ctile", { x + 1, y }) + tiles[8] = ffi.new("ctile", { x + 1, y - 1 }) + end + elseif x < game.level.map.w - 1 then + tiles[4] = ffi.new("ctile", { x + 1, y }) + tiles[5] = ffi.new("ctile", { x + 1, y - 1 }) + end + return tiles + end, + -- Dir 4 + function(node) + local x, y = node[0], node[1] + local tiles = {ffi.new("ctile", { x + 1, y })} + if x > 0 then + tiles[2] = ffi.new("ctile", { x - 1, y }) + if y < game.level.map.h - 1 then + tiles[3] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[4] = ffi.new("ctile", { x, y + 1 }) + tiles[5] = ffi.new("ctile", { x + 1, y + 1 }) + end + if y > 0 then + tiles[#tiles+1] = ffi.new("ctile", { x - 1, y - 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x, y - 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - 1 }) + end + else + if y < game.level.map.h - 1 then + tiles[2] = ffi.new("ctile", { x, y + 1 }) + tiles[3] = ffi.new("ctile", { x + 1, y + 1 }) + end + if y > 0 then + tiles[#tiles+1] = ffi.new("ctile", { x, y - 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - 1 }) + end + end + return tiles + end, + -- Dir 5 + function(node) + local tiles = {} + getNextNodes[5](node, tiles, tiles) + return tiles + end, + -- Dir 6 + function(node) + local x, y = node[0], node[1] + local tiles = {ffi.new("ctile", { x - 1, y })} + if x < game.level.map.w - 1 then + tiles[2] = ffi.new("ctile", { x + 1, y }) + if y < game.level.map.h - 1 then + tiles[3] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[4] = ffi.new("ctile", { x, y + 1 }) + tiles[5] = ffi.new("ctile", { x + 1, y + 1 }) + end + if y > 0 then + tiles[#tiles+1] = ffi.new("ctile", { x - 1, y - 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x, y - 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - 1 }) + end + else + if y < game.level.map.h - 1 then + tiles[2] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[3] = ffi.new("ctile", { x, y + 1 }) + end + if y > 0 then + tiles[#tiles+1] = ffi.new("ctile", { x - 1, y - 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x, y - 1 }) + end + end + return tiles + end, + -- Dir 7 + function(node) + local x, y = node[0], node[1] + local tiles = {ffi.new("ctile", { x, y + 1 }), + ffi.new("ctile", { x + 1, y + 1 }), + ffi.new("ctile", { x + 1, y })} + if x > 0 then + tiles[4] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[5] = ffi.new("ctile", { x - 1, y }) + if y > 0 then + tiles[6] = ffi.new("ctile", { x - 1, y - 1 }) + tiles[7] = ffi.new("ctile", { x, y - 1 }) + tiles[8] = ffi.new("ctile", { x + 1, y - 1 }) + end + elseif y > 0 then + tiles[4] = ffi.new("ctile", { x, y - 1 }) + tiles[5] = ffi.new("ctile", { x + 1, y - 1 }) + end + return tiles + end, + -- Dir 8 + function(node) + local x, y = node[0], node[1] + local tiles = {ffi.new("ctile", { x, y + 1 })} + if y > 0 then + tiles[2] = ffi.new("ctile", { x, y - 1 }) + if x > 0 then + tiles[3] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[4] = ffi.new("ctile", { x - 1, y }) + tiles[5] = ffi.new("ctile", { x - 1, y - 1 }) + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y + 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y - 1 }) + end + else + if x > 0 then + tiles[2] = ffi.new("ctile", { x - 1, y + 1 }) + tiles[3] = ffi.new("ctile", { x - 1, y }) + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y + 1 }) + tiles[#tiles+1] = ffi.new("ctile", { x + 1, y }) + end + end + return tiles + end, + -- Dir 9 + function(node) + local x, y = node[0], node[1] + local tiles = {ffi.new("ctile", { x - 1, y + 1 }), + ffi.new("ctile", { x, y + 1 }), + ffi.new("ctile", { x - 1, y })} + if x < game.level.map.w - 1 then + tiles[4] = ffi.new("ctile", { x + 1, y + 1 }) + tiles[5] = ffi.new("ctile", { x + 1, y }) + if y > 0 then + tiles[6] = ffi.new("ctile", { x - 1, y - 1 }) + tiles[7] = ffi.new("ctile", { x, y - 1 }) + tiles[8] = ffi.new("ctile", { x + 1, y - 1 }) + end + elseif y > 0 then + tiles[4] = ffi.new("ctile", { x - 1, y - 1 }) + tiles[5] = ffi.new("ctile", { x, y - 1 }) + end + return tiles end - else - if y < game.level.map.h - 1 then - tiles[2] = {x, y + 1, c + game.level.map.w, val, 2 } - tiles[3] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } + } + + -- List tiles that are adjacent to both current tile and previous tile that the previous tile iterated over. + -- Right now these (and "listSharedTilesPrevious") are used to infer what might be a wall, and may be useful later. + -- c = current, p = previous c* *c* + -- * = returned tile *p .p. + listSharedTiles = { + -- Dir 1 + function(node) + local x, y = node[0], node[1] + return {ffi.new("ctile", { x + 1, y }), + ffi.new("ctile", { x, y - 1 })} + end, + -- Dir 2 + function(node) + local x, y = node[0], node[1] + if x < 1 then return {ffi.new("ctile", { x + 1, y })} + elseif x > game.level.map.w - 2 then return {ffi.new("ctile", { x - 1, y })} + else return {ffi.new("ctile", { x - 1, y }), + ffi.new("ctile", { x + 1, y })} + end + end, + -- Dir 3 + function(node) + local x, y = node[0], node[1] + return {ffi.new("ctile", { x - 1, y }), + ffi.new("ctile", { x, y - 1 })} + end, + -- Dir 4 + function(node) + local x, y = node[0], node[1] + if y < 1 then return {ffi.new("ctile", { x, y + 1 })} + elseif y > game.level.map.h - 2 then return {ffi.new("ctile", { x, y - 1 })} + else return {ffi.new("ctile", { x, y + 1 }), + ffi.new("ctile", { x, y - 1 })} + end + end, + -- Dir 5 + function(node) return {} end, + -- Dir 6 + function(node) + local x, y = node[0], node[1] + if y < 1 then return {ffi.new("ctile", { x, y + 1 })} + elseif y > game.level.map.h - 2 then return {ffi.new("ctile", { x, y - 1 })} + else return {ffi.new("ctile", { x, y + 1 }), + ffi.new("ctile", { x, y - 1 })} + end + end, + -- Dir 7 + function(node) + local x, y = node[0], node[1] + return {ffi.new("ctile", { x, y + 1 }), + ffi.new("ctile", { x + 1, y })} + end, + -- Dir 8 + function(node) + local x, y = node[0], node[1] + if x < 1 then return {ffi.new("ctile", { x + 1, y })} + elseif x > game.level.map.w - 2 then return {ffi.new("ctile", { x - 1, y })} + else return {ffi.new("ctile", { x - 1, y }), + ffi.new("ctile", { x + 1, y })} + end + end, + -- Dir 9 + function(node) + local x, y = node[0], node[1] + return {ffi.new("ctile", { x, y + 1 }), + ffi.new("ctile", { x - 1, y })} end - if y > 0 then - tiles[#tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } - tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + } + + -- A partial complement to "listSharedTiles". "listSharedTiles" and "listSharedTilesPrevious" 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* + listSharedTilesPrevious = { + -- Dir 1 + function(node) return {} end, + -- Dir 2 + function(node) + local x, y = node[0], node[1] + if x < 1 then return {ffi.new("ctile", { x + 1, y - 1 })} + elseif x > game.level.map.w - 2 then return {ffi.new("ctile", { x - 1, y - 1 })} + else return {ffi.new("ctile", { x + 1, y - 1 }), + ffi.new("ctile", { x - 1, y - 1 })} + end + end, + -- Dir 3 + function(node) return {} end, + -- Dir 4 + function(node) + local x, y = node[0], node[1] + if y < 1 then return {ffi.new("ctile", { x + 1, y + 1 })} + elseif y > game.level.map.h - 2 then return {ffi.new("ctile", { x + 1, y - 1 })} + else return {ffi.new("ctile", { x + 1, y + 1 }), + ffi.new("ctile", { x + 1, y - 1 })} + end + end, + -- Dir 5 + function(node) return {} end, + -- Dir 6 + function(node) + local x, y = node[0], node[1] + if y < 1 then return {ffi.new("ctile", { x - 1, y + 1 })} + elseif y > game.level.map.h - 2 then return {ffi.new("ctile", { x - 1, y - 1 })} + else return {ffi.new("ctile", { x - 1, y + 1 }), + ffi.new("ctile", { x - 1, y - 1 })} + end + end, + -- Dir 7 + function(node) return {} end, + -- Dir 8 + function(node) + local x, y = node[0], node[1] + if x < 1 then return {ffi.new("ctile", { x + 1, y + 1 })} + elseif x > game.level.map.w - 2 then return {ffi.new("ctile", { x - 1, y + 1 })} + else return {ffi.new("ctile", { x + 1, y + 1 }), + ffi.new("ctile", { x - 1, y + 1 })} + end + end, + -- Dir 9 + function(node) return {} end + } + + previousTile = { + -- Dir 1 + function(node) return ffi.new("ctile", { node[0] + 1, node[1] - 1 }) end, + -- Dir 2 + function(node) return ffi.new("ctile", { node[0], node[1] - 1 }) end, + -- Dir 3 + function(node) return ffi.new("ctile", { node[0] - 1, node[1] - 1 }) end, + -- Dir 4 + function(node) return ffi.new("ctile", { node[0] + 1, node[1] }) end, + -- Dir 5 + function(node) return ffi.new("ctile", { node[0], node[1] }) end, + -- Dir 6 + function(node) return ffi.new("ctile", { node[0] - 1, node[1] }) end, + -- Dir 7 + function(node) return ffi.new("ctile", { node[0] + 1, node[1] + 1 }) end, + -- Dir 8 + function(node) return ffi.new("ctile", { node[0], node[1] + 1 }) end, + -- Dir 9 + function(node) return ffi.new("ctile", { node[0] - 1, node[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) + 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 - return tiles - end, - -- Dir 5 - function(node) - local tiles = {} - getNextNodes[5](node, tiles, tiles) - return tiles - end, - -- Dir 6 - function(node) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - local tiles = {{x - 1, y, c - 1, val, 4 }} - - if x < game.level.map.w - 1 then - tiles[2] = {x + 1, y, c + 1, val, 6 } - if y < game.level.map.h - 1 then - tiles[3] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[4] = {x, y + 1, c + game.level.map.w, val, 2 } - tiles[5] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } + + elseif util.isHex() then + -- a flexible but slow function to list all adjacent tile + listAdjacentNodes = function(tile, no_diagonal, no_cardinal) + local tiles = {} + local x, y, c + if type(tile) == "number" then + x, y = toDouble(tile) + c = tile + val = 1 + elseif tile[0] then + x, y, c, val = tile[0], tile[1], tile[2], tile[3]+1 + else + return tiles end - if y > 0 then - tiles[#tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - tiles[#tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } - tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + 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] = { [0] = x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end + if upper_okay then tiles[#tiles+1] = { [0] = x, y + 1, c + game.level.map.w, val, 2 } end + if (upper_okay or p == 0) and right_okay then tiles[#tiles+1] = { [0] = 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] = { [0] = x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end + if lower_okay then tiles[#tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } end + if (lower_okay or r == 0) and right_okay then tiles[#tiles+1] = { [0] = x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end end - else - if y < game.level.map.h - 1 then - tiles[2] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[3] = {x, y + 1, c + game.level.map.w, val, 2 } + 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[0], node[1], node[2], node[3]+1 + local p = x % 2 + local r = 1 - p + if x > 0 then + cardinal_tiles[#cardinal_tiles+1] = { [0] = 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] = { [0] = 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] = { [0] = 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[0], node[1], node[2], node[3]+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] = { [0] = 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] = { [0] = 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] = { [0] = 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[0], node[1], node[2], node[3]+1 + local p = x % 2 + local r = 1 - p + if x < game.level.map.w - 1 then + cardinal_tiles[#cardinal_tiles+1] = { [0] = 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] = { [0] = 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] = { [0] = 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[0], node[1], node[2], node[3]+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] = { [0] = x - 1, y + p, c - 1 + p*game.level.map.w, val, 1 } end + if upper_okay then diagonal_tiles[#diagonal_tiles+1] = { [0] = 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] = { [0] = 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] = { [0] = x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end + if lower_okay then diagonal_tiles[#diagonal_tiles+1] = { [0] = 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] = { [0] = 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[0], node[1], node[2], node[3]+1 + local p = x % 2 + local r = 1 - p + if x > 0 then + cardinal_tiles[#cardinal_tiles+1] = { [0] = 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] = { [0] = x - 1, y - r, c - 1 - r*game.level.map.w, val, 7 } end + end + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = { [0] = 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[0], node[1], node[2], node[3]+1 + local r = 1 - x % 2 + if y > 0 or r == 0 then + if x > 0 then cardinal_tiles[#cardinal_tiles+1] = { [0] = 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] = { [0] = x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end + end + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = { [0] = 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[0], node[1], node[2], node[3]+1 + local p = x % 2 + local r = 1 - p + if x < game.level.map.w - 1 then + cardinal_tiles[#cardinal_tiles+1] = { [0] = 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] = { [0] = x + 1, y - r, c + 1 - r*game.level.map.w, val, 9 } end + end + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } end end - if y > 0 then - tiles[#tiles+1] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - tiles[#tiles+1] = {x, y - 1, c - game.level.map.w, val, 8 } + } + + -- Use directional information to list all adjacent tiles more efficiently + listAdjacentTiles = { + -- Dir 1 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {{ [0] = x, y - 1 }, + { [0] = x + 1, y - r }} + if y < game.level.map.h - 1 then + tiles[3] = { [0] = x, y + 1 } + tiles[4] = { [0] = x + 1, y + p } + end + if x > 0 then + tiles[#tiles+1] = { [0] = x - 1, y - r } + if y < game.level.map.h - 1 or p == 0 then tiles[#tiles+1] = { [0] = x - 1, y + p } end + end + return tiles + end, + -- Dir 2 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {{ [0] = x, y - 1 }} + if x > 0 then tiles[2] = { [0] = x - 1, y - r } end + if x < game.level.map.w - 1 then tiles[#tiles+1] = { [0] = x + 1, y - r } end + if y < game.level.map.h - 1 or p == 0 then + if x > 0 then tiles[#tiles+1] = { [0] = x - 1, y + p } end + if x < game.level.map.w - 1 then tiles[#tiles+1] = { [0] = x + 1, y + p } end + end + if y < game.level.map.h - 1 then tiles[#tiles+1] = { [0] = x, y + 1 } end + return tiles + end, + -- Dir 3 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {{ [0] = x - 1, y - r }, + { [0] = x, y - 1 }} + if y < game.level.map.h - 1 then + tiles[3] = { [0] = x - 1, y + p } + tiles[4] = { [0] = x, y + 1 } + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = { [0] = x + 1, y - r } + if y < game.level.map.h - 1 or p == 0 then tiles[#tiles+1] = { [0] = x + 1, y + p } 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 = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {{ [0] = x, y + 1 }, + { [0] = x + 1, y + p }} + if y > 0 then + tiles[3] = { [0] = x, y - 1 } + tiles[4] = { [0] = x + 1, y - r } + end + if x > 0 then + tiles[#tiles+1] = { [0] = x - 1, y + p } + if y > 0 or r == 0 then tiles[#tiles+1] = { [0] = x - 1, y - r } end + end + return tiles + end, + -- Dir 8 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {{ [0] = x, y + 1 }} + if x > 0 then tiles[2] = { [0] = x - 1, y + p } end + if x < game.level.map.w - 1 then tiles[#tiles+1] = { [0] = x + 1, y + p } end + if y > 0 or r == 0 then + if x > 0 then tiles[#tiles+1] = { [0] = x - 1, y - r } end + if x < game.level.map.w - 1 then tiles[#tiles+1] = { [0] = x + 1, y - r } end + end + if y > 0 then tiles[#tiles+1] = { [0] = x, y - 1 } end + return tiles + end, + -- Dir 9 + function(node) + local x, y = node[0], node[1] + local p = x % 2 + local r = 1 - p + local tiles = {{ [0] = x - 1, y + p }, + { [0] = x, y + 1 }} + if y > 0 then + tiles[3] = { [0] = x - 1, y - r } + tiles[4] = { [0] = x, y - 1 } + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = { [0] = x + 1, y + p } + if y > 0 or r == 0 then tiles[#tiles+1] = { [0] = x + 1, y - r } end + end + return tiles end - end - return tiles - end, - -- Dir 7 - function(node) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - local tiles = {{x, y + 1, c + game.level.map.w, val, 2 }, - {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }, - {x + 1, y, c + 1, val, 6 }} - if x > 0 then - tiles[4] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[5] = {x - 1, y, c - 1, val, 4 } - if y > 0 then - tiles[6] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - tiles[7] = {x, y - 1, c - game.level.map.w, val, 8 } - tiles[8] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + } + + -- 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 "listSharedTilesPrevious") are used to infer what might be a wall, and may be useful later. + -- c = current, p = previous c* *c* + -- * = returned tile *p .p. + listSharedTiles = { + -- 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 "listSharedTiles". "listSharedTiles" and "listSharedTilesPrevious" 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* + listSharedTilesPrevious = { + -- 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 + } + + previousTile = { + -- Dir 1 + function(node) return { [0] = node[0] + 1, node[1] - 1 + node[0]%2 } end, + -- Dir 2 + function(node) return { [0] = node[0], node[1] - 1 } end, + -- Dir 3 + function(node) return { [0] = node[0] - 1, node[1] - 1 + node[0]%2 } end, + -- Dir 4 + function(node) end, + -- Dir 5 + function(node) return { [0] = node[0], node[1] } end, + -- Dir 6 + function(node) end, + -- Dir 7 + function(node) return { [0] = node[0] + 1, node[1] + node[0]%2 } end, + -- Dir 8 + function(node) return { [0] = node[0], node[1] + 1 } end, + -- Dir 9 + function(node) return { [0] = node[0] - 1, node[1] + node[0]%2 } 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 - elseif y > 0 then - tiles[4] = {x, y - 1, c - game.level.map.w, val, 8 } - tiles[5] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } end - return tiles - end, - -- Dir 8 - function(node) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - local tiles = {{x, y + 1, c + game.level.map.w, val, 2 }} - - if y > 0 then - tiles[2] = {x, y - 1, c - game.level.map.w, val, 8 } - if x > 0 then - tiles[3] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[4] = {x - 1, y, c - 1, val, 4 } - tiles[5] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - end - if x < game.level.map.w - 1 then - tiles[#tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - tiles[#tiles+1] = {x + 1, y, c + 1, val, 6 } - tiles[#tiles+1] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + + else + -- a flexible but slow function to list all adjacent tile + listAdjacentNodes = function(tile, no_diagonal, no_cardinal) + local tiles = {} + local x, y, c + if type(tile) == "number" then + x, y = toDouble(tile) + c = tile + val = 1 + elseif tile[0] then + x, y, c, val = tile[0], tile[1], tile[2], tile[3]+1 + else + return tiles end - else - if x > 0 then - tiles[2] = {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } - tiles[3] = {x - 1, y, c - 1, val, 4 } + 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 not no_cardinal then + if upper_okay then tiles[1] = { [0] = x, y + 1, c + game.level.map.w, val, 2 } end + if left_okay then tiles[#tiles+1] = { [0] = x - 1, y, c - 1, val, 4 } end + if right_okay then tiles[#tiles+1] = { [0] = x + 1, y, c + 1, val, 6 } end + if lower_okay then tiles[#tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } end end - if x < game.level.map.w - 1 then - tiles[#tiles+1] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - tiles[#tiles+1] = {x + 1, y, c + 1, val, 6 } + if not no_diagonal then + if left_okay and upper_okay then tiles[#tiles+1] = { [0] = x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } end + if right_okay and upper_okay then tiles[#tiles+1] = { [0] = x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } end + if left_okay and lower_okay then tiles[#tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end + if right_okay and lower_okay then tiles[#tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } end end + return tiles end - return tiles - end, - -- Dir 9 - function(node) - local x, y, c, val = node[1], node[2], node[3], node[4]+1 - local tiles = {{x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }, - {x, y + 1, c + game.level.map.w, val, 2 }, - {x - 1, y, c - 1, val, 4 }} - if x < game.level.map.w - 1 then - tiles[4] = {x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } - tiles[5] = {x + 1, y, c + 1, val, 6 } - if y > 0 then - tiles[6] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - tiles[7] = {x, y - 1, c - game.level.map.w, val, 8 } - tiles[8] = {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + + -- 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[0], node[1], node[2], node[3]+1 + if y < game.level.map.h - 1 then + cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y + 1, c + game.level.map.w, val, 2 } + diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } + if x > 0 then + diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } + cardinal_tiles[#cardinal_tiles+1] = { [0] = x - 1, y, c - 1, val, 4 } + diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } + end + elseif x > 0 then + cardinal_tiles[#cardinal_tiles+1] = { [0] = x - 1, y, c - 1, val, 4 } + diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } + end + end, + --Dir 2 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if y > game.level.map.h - 2 then return end + if x > 0 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } end + cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y + 1, c + game.level.map.w, val, 2 } + if x < game.level.map.w - 1 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } end + end, + -- Dir 3 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if y < game.level.map.h - 1 then + diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } + cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y + 1, c + game.level.map.w, val, 2 } + if x < game.level.map.w - 1 then + diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } + cardinal_tiles[#cardinal_tiles+1] = { [0] = x + 1, y, c + 1, val, 6 } + diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + end + elseif x < game.level.map.w - 1 then + cardinal_tiles[#cardinal_tiles+1] = { [0] = x + 1, y, c + 1, val, 6 } + diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + end + end, + --Dir 4 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if x < 1 then return end + if y < game.level.map.h - 1 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } end + cardinal_tiles[#cardinal_tiles+1] = { [0] = x - 1, y, c - 1, val, 4 } + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end + end, + --Dir 5 (all adjacent, slow) + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + 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 then cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y + 1, c + game.level.map.w, val, 2 } end + if left_okay then cardinal_tiles[#cardinal_tiles+1] = { [0] = x - 1, y, c - 1, val, 4 } end + if right_okay then cardinal_tiles[#cardinal_tiles+1] = { [0] = x + 1, y, c + 1, val, 6 } end + if lower_okay then cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } end + if left_okay and upper_okay then diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } end + if right_okay and upper_okay then diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } end + if left_okay and lower_okay then diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end + if right_okay and lower_okay then diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } end + end, + --Dir 6 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if x > game.level.map.w - 2 then return end + if y < game.level.map.h - 1 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } end + cardinal_tiles[#cardinal_tiles+1] = { [0] = x + 1, y, c + 1, val, 6 } + if y > 0 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } end + end, + -- Dir 7 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if x > 0 then + diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y + 1, c - 1 + game.level.map.w, val, 1 } + cardinal_tiles[#cardinal_tiles+1] = { [0] = x - 1, y, c - 1, val, 4 } + if y > 0 then + diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } + cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } + diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + end + elseif y > 0 then + cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } + diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + end + end, + --Dir 8 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if y < 1 then return end + if x > 0 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } end + cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } + if x < game.level.map.w - 1 then diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } end + end, + -- Dir 9 + function(node, cardinal_tiles, diagonal_tiles) + local x, y, c, val = node[0], node[1], node[2], node[3]+1 + if x < game.level.map.w - 1 then + diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y + 1, c + 1 + game.level.map.w, val, 3 } + cardinal_tiles[#cardinal_tiles+1] = { [0] = x + 1, y, c + 1, val, 6 } + if y > 0 then + diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } + cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } + diagonal_tiles[#diagonal_tiles+1] = { [0] = x + 1, y - 1, c + 1 - game.level.map.w, val, 9 } + end + elseif y > 0 then + diagonal_tiles[#diagonal_tiles+1] = { [0] = x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } + cardinal_tiles[#cardinal_tiles+1] = { [0] = x, y - 1, c - game.level.map.w, val, 8 } + end end - elseif y > 0 then - tiles[4] = {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 } - tiles[5] = {x, y - 1, c - game.level.map.w, val, 8 } - end - return tiles - end -} - --- 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) - local x, y, c, val = unpack(node) - return {{x + 1, y, c + 1, val, 6 }, - {x, y - 1, c - game.level.map.w, val, 8 }} - end, - -- Dir 2 - function(node) - local x, y, c, val = unpack(node) - if x < 1 then return {{x + 1, y, c + 1, val, 6 }} - elseif x > game.level.map.w - 2 then return {{x - 1, y, c - 1, val, 4 }} - else return {{x - 1, y, c - 1, val, 4 }, - {x + 1, y, c + 1, val, 6 }} - end - end, - -- Dir 3 - function(node) - local x, y, c, val = unpack(node) - return {{x - 1, y, c - 1, val, 4 }, - {x, y - 1, c - game.level.map.w, val, 8 }} - end, - -- Dir 4 - function(node) - local x, y, c, val = unpack(node) - if y < 1 then return {{x, y + 1, c + game.level.map.w, val, 2 }} - elseif y > game.level.map.h - 2 then return {{x, y - 1, c - game.level.map.w, val, 8 }} - else return {{x, y + 1, c + game.level.map.w, val, 2 }, - {x, y - 1, c - game.level.map.w, val, 8 }} - end - end, - -- Dir 5 - function(node) return {} end, - -- Dir 6 - function(node) - local x, y, c, val = unpack(node) - if y < 1 then return {{x, y + 1, c + game.level.map.w, val, 2 }} - elseif y > game.level.map.h - 2 then return {{x, y - 1, c - game.level.map.w, val, 8 }} - else return {{x, y + 1, c + game.level.map.w, val, 2 }, - {x, y - 1, c - game.level.map.w, val, 8 }} - end - end, - -- Dir 7 - function(node) - local x, y, c, val = unpack(node) - return {{x, y + 1, c + game.level.map.w, val, 2 }, - {x + 1, y, c + 1, val, 6 }} - end, - -- Dir 8 - function(node) - local x, y, c, val = unpack(node) - if x < 1 then return {{x + 1, y, c + 1, val, 6 }} - elseif x > game.level.map.w - 2 then return {{x - 1, y, c - 1, val, 4 }} - else return {{x - 1, y, c - 1, val, 4 }, - {x + 1, y, c + 1, val, 6 }} - end - end, - -- Dir 9 - function(node) - local x, y, c, val = unpack(node) - return {{x, y + 1, c + game.level.map.w, val, 2 }, - {x - 1, y, c - 1, val, 4 }} - end -} - --- 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) - local x, y, c, val = unpack(node) - if x < 1 then return {{x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }} - elseif x > game.level.map.w - 2 then return {{x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }} - else return {{x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }, - {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }} - end - end, - -- Dir 3 - function(node) return {} end, - -- Dir 4 - function(node) - local x, y, c, val = unpack(node) - if y < 1 then return {{x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }} - elseif y > game.level.map.h - 2 then return {{x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }} - else return {{x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }, - {x + 1, y - 1, c + 1 - game.level.map.w, val, 9 }} - end - end, - -- Dir 5 - function(node) return {} end, - -- Dir 6 - function(node) - local x, y, c, val = unpack(node) - if y < 1 then return {{x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }} - elseif y > game.level.map.h - 2 then return {{x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }} - else return {{x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }, - {x - 1, y - 1, c - 1 - game.level.map.w, val, 7 }} - end - end, - -- Dir 7 - function(node) return {} end, - -- Dir 8 - function(node) - local x, y, c, val = unpack(node) - if x < 1 then return {{x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }} - elseif x > game.level.map.w - 2 then return {{x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }} - else return {{x + 1, y + 1, c + 1 + game.level.map.w, val, 3 }, - {x - 1, y + 1, c - 1 + game.level.map.w, val, 1 }} - end - end, - -- Dir 9 - function(node) return {} end -} - -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 - 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) ; return {x - 1, y - 1, c - 1 - game.level.map.w, val-1, 7 } end, - -- Dir 4 - function(node) local x, y, c, val = unpack(node) ; return {x + 1, y, c + 1, val-1, 6 } end, - -- Dir 5 - function(node) local x, y, c, val = unpack(node) ; return {x, y, c, val-1, 5 } end, - -- Dir 6 - function(node) local x, y, c, val = unpack(node) ; return {x - 1, y, c - 1, val-1, 4 } end, - -- Dir 7 - function(node) local x, y, c, val = unpack(node) ; return {x + 1, y + 1, c + 1 + 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) ; return {x - 1, y + 1, c - 1 + 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) - 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}) + } + + -- Use directional information to list all adjacent tiles more efficiently + listAdjacentTiles = { + -- Dir 1 + function(node) + local x, y = node[0], node[1] + local tiles = {{ [0] = x + 1, y }, + { [0] = x, y - 1 }, + { [0] = x + 1, y - 1 }} + if y < game.level.map.h - 1 then + tiles[4] = { [0] = x, y + 1 } + tiles[5] = { [0] = x + 1, y + 1 } + if x > 0 then + tiles[6] = { [0] = x - 1, y + 1 } + tiles[7] = { [0] = x - 1, y } + tiles[8] = { [0] = x - 1, y - 1 } + end + elseif x > 0 then + tiles[4] = { [0] = x - 1, y } + tiles[5] = { [0] = x - 1, y - 1 } + end + return tiles + end, + -- Dir 2 + function(node) + local x, y = node[0], node[1] + local tiles = {{ [0] = x, y - 1 }} + if y < game.level.map.h - 1 then + tiles[2] = { [0] = x, y + 1 } + if x > 0 then + tiles[3] = { [0] = x - 1, y + 1 } + tiles[4] = { [0] = x - 1, y } + tiles[5] = { [0] = x - 1, y - 1 } + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = { [0] = x + 1, y + 1 } + tiles[#tiles+1] = { [0] = x + 1, y } + tiles[#tiles+1] = { [0] = x + 1, y - 1 } + end + else + if x > 0 then + tiles[2] = { [0] = x - 1, y } + tiles[3] = { [0] = x - 1, y - 1 } + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = { [0] = x + 1, y } + tiles[#tiles+1] = { [0] = x + 1, y - 1 } + end + end + return tiles + end, + -- Dir 3 + function(node) + local x, y = node[0], node[1] + local tiles = {{ [0] = x - 1, y }, + { [0] = x - 1, y - 1 }, + { [0] = x, y - 1 }} + if y < game.level.map.h - 1 then + tiles[4] = { [0] = x - 1, y + 1 } + tiles[5] = { [0] = x, y + 1 } + if x < game.level.map.w - 1 then + tiles[6] = { [0] = x + 1, y + 1 } + tiles[7] = { [0] = x + 1, y } + tiles[8] = { [0] = x + 1, y - 1 } + end + elseif x < game.level.map.w - 1 then + tiles[4] = { [0] = x + 1, y } + tiles[5] = { [0] = x + 1, y - 1 } + end + return tiles + end, + -- Dir 4 + function(node) + local x, y = node[0], node[1] + local tiles = {{ [0] = x + 1, y }} + if x > 0 then + tiles[2] = { [0] = x - 1, y } + if y < game.level.map.h - 1 then + tiles[3] = { [0] = x - 1, y + 1 } + tiles[4] = { [0] = x, y + 1 } + tiles[5] = { [0] = x + 1, y + 1 } + end + if y > 0 then + tiles[#tiles+1] = { [0] = x - 1, y - 1 } + tiles[#tiles+1] = { [0] = x, y - 1 } + tiles[#tiles+1] = { [0] = x + 1, y - 1 } + end + else + if y < game.level.map.h - 1 then + tiles[2] = { [0] = x, y + 1 } + tiles[3] = { [0] = x + 1, y + 1 } + end + if y > 0 then + tiles[#tiles+1] = { [0] = x, y - 1 } + tiles[#tiles+1] = { [0] = x + 1, y - 1 } + end + end + return tiles + end, + -- Dir 5 + function(node) + local tiles = {} + getNextNodes[5](node, tiles, tiles) + return tiles + end, + -- Dir 6 + function(node) + local x, y = node[0], node[1] + local tiles = {{ [0] = x - 1, y }} + if x < game.level.map.w - 1 then + tiles[2] = { [0] = x + 1, y } + if y < game.level.map.h - 1 then + tiles[3] = { [0] = x - 1, y + 1 } + tiles[4] = { [0] = x, y + 1 } + tiles[5] = { [0] = x + 1, y + 1 } + end + if y > 0 then + tiles[#tiles+1] = { [0] = x - 1, y - 1 } + tiles[#tiles+1] = { [0] = x, y - 1 } + tiles[#tiles+1] = { [0] = x + 1, y - 1 } + end + else + if y < game.level.map.h - 1 then + tiles[2] = { [0] = x - 1, y + 1 } + tiles[3] = { [0] = x, y + 1 } + end + if y > 0 then + tiles[#tiles+1] = { [0] = x - 1, y - 1 } + tiles[#tiles+1] = { [0] = x, y - 1 } + end + end + return tiles + end, + -- Dir 7 + function(node) + local x, y = node[0], node[1] + local tiles = {{ [0] = x, y + 1 }, + { [0] = x + 1, y + 1 }, + { [0] = x + 1, y }} + if x > 0 then + tiles[4] = { [0] = x - 1, y + 1 } + tiles[5] = { [0] = x - 1, y } + if y > 0 then + tiles[6] = { [0] = x - 1, y - 1 } + tiles[7] = { [0] = x, y - 1 } + tiles[8] = { [0] = x + 1, y - 1 } + end + elseif y > 0 then + tiles[4] = { [0] = x, y - 1 } + tiles[5] = { [0] = x + 1, y - 1 } + end + return tiles + end, + -- Dir 8 + function(node) + local x, y = node[0], node[1] + local tiles = {{ [0] = x, y + 1 }} + if y > 0 then + tiles[2] = { [0] = x, y - 1 } + if x > 0 then + tiles[3] = { [0] = x - 1, y + 1 } + tiles[4] = { [0] = x - 1, y } + tiles[5] = { [0] = x - 1, y - 1 } + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = { [0] = x + 1, y + 1 } + tiles[#tiles+1] = { [0] = x + 1, y } + tiles[#tiles+1] = { [0] = x + 1, y - 1 } + end + else + if x > 0 then + tiles[2] = { [0] = x - 1, y + 1 } + tiles[3] = { [0] = x - 1, y } + end + if x < game.level.map.w - 1 then + tiles[#tiles+1] = { [0] = x + 1, y + 1 } + tiles[#tiles+1] = { [0] = x + 1, y } + end + end + return tiles + end, + -- Dir 9 + function(node) + local x, y = node[0], node[1] + local tiles = {{ [0] = x - 1, y + 1 }, + { [0] = x, y + 1 }, + { [0] = x - 1, y }} + if x < game.level.map.w - 1 then + tiles[4] = { [0] = x + 1, y + 1 } + tiles[5] = { [0] = x + 1, y } + if y > 0 then + tiles[6] = { [0] = x - 1, y - 1 } + tiles[7] = { [0] = x, y - 1 } + tiles[8] = { [0] = x + 1, y - 1 } + end + elseif y > 0 then + tiles[4] = { [0] = x - 1, y - 1 } + tiles[5] = { [0] = x, y - 1 } + end + return tiles 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}) + } + + -- List tiles that are adjacent to both current tile and previous tile that the previous tile iterated over. + -- Right now these (and "listSharedTilesPrevious") are used to infer what might be a wall, and may be useful later. + -- c = current, p = previous c* *c* + -- * = returned tile *p .p. + listSharedTiles = { + -- Dir 1 + function(node) + local x, y = node[0], node[1] + return {{ [0] = x + 1, y }, + { [0] = x, y - 1 }} + end, + -- Dir 2 + function(node) + local x, y = node[0], node[1] + if x < 1 then return {{ [0] = x + 1, y }} + elseif x > game.level.map.w - 2 then return {{ [0] = x - 1, y }} + else return {{ [0] = x - 1, y }, + { [0] = x + 1, y }} + end + end, + -- Dir 3 + function(node) + local x, y = node[0], node[1] + return {{ [0] = x - 1, y }, + { [0] = x, y - 1 }} + end, + -- Dir 4 + function(node) + local x, y = node[0], node[1] + if y < 1 then return {{ [0] = x, y + 1 }} + elseif y > game.level.map.h - 2 then return {{ [0] = x, y - 1 }} + else return {{ [0] = x, y + 1 }, + { [0] = x, y - 1 }} + end + end, + -- Dir 5 + function(node) return {} end, + -- Dir 6 + function(node) + local x, y = node[0], node[1] + if y < 1 then return {{ [0] = x, y + 1 }} + elseif y > game.level.map.h - 2 then return {{ [0] = x, y - 1 }} + else return {{ [0] = x, y + 1 }, + { [0] = x, y - 1 }} + end + end, + -- Dir 7 + function(node) + local x, y = node[0], node[1] + return {{ [0] = x, y + 1 }, + { [0] = x + 1, y }} + end, + -- Dir 8 + function(node) + local x, y = node[0], node[1] + if x < 1 then return {{ [0] = x + 1, y }} + elseif x > game.level.map.w - 2 then return {{ [0] = x - 1, y }} + else return {{ [0] = x - 1, y }, + { [0] = x + 1, y }} + end + end, + -- Dir 9 + function(node) + local x, y = node[0], node[1] + return {{ [0] = x, y + 1 }, + { [0] = x - 1, y }} + end + } + + -- A partial complement to "listSharedTiles". "listSharedTiles" and "listSharedTilesPrevious" 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* + listSharedTilesPrevious = { + -- Dir 1 + function(node) return {} end, + -- Dir 2 + function(node) + local x, y = node[0], node[1] + if x < 1 then return {{ [0] = x + 1, y - 1 }} + elseif x > game.level.map.w - 2 then return {{ [0] = x - 1, y - 1 }} + else return {{ [0] = x + 1, y - 1 }, + { [0] = x - 1, y - 1 }} + end + end, + -- Dir 3 + function(node) return {} end, + -- Dir 4 + function(node) + local x, y = node[0], node[1] + if y < 1 then return {{ [0] = x + 1, y + 1 }} + elseif y > game.level.map.h - 2 then return {{ [0] = x + 1, y - 1 }} + else return {{ [0] = x + 1, y + 1 }, + { [0] = x + 1, y - 1 }} + end + end, + -- Dir 5 + function(node) return {} end, + -- Dir 6 + function(node) + local x, y = node[0], node[1] + if y < 1 then return {{ [0] = x - 1, y + 1 }} + elseif y > game.level.map.h - 2 then return {{ [0] = x - 1, y - 1 }} + else return {{ [0] = x - 1, y + 1 }, + { [0] = x - 1, y - 1 }} + end + end, + -- Dir 7 + function(node) return {} end, + -- Dir 8 + function(node) + local x, y = node[0], node[1] + if x < 1 then return {{ [0] = x + 1, y + 1 }} + elseif x > game.level.map.w - 2 then return {{ [0] = x - 1, y + 1 }} + else return {{ [0] = x + 1, y + 1 }, + { [0] = x - 1, y + 1 }} + end + end, + -- Dir 9 + function(node) return {} end + } + + previousTile = { + -- Dir 1 + function(node) return { [0] = node[0] + 1, node[1] - 1 } end, + -- Dir 2 + function(node) return { [0] = node[0], node[1] - 1 } end, + -- Dir 3 + function(node) return { [0] = node[0] - 1, node[1] - 1 } end, + -- Dir 4 + function(node) return { [0] = node[0] + 1, node[1] } end, + -- Dir 5 + function(node) return { [0] = node[0], node[1] } end, + -- Dir 6 + function(node) return { [0] = node[0] - 1, node[1] } end, + -- Dir 7 + function(node) return { [0] = node[0] + 1, node[1] + 1 } end, + -- Dir 8 + function(node) return { [0] = node[0], node[1] + 1 } end, + -- Dir 9 + function(node) return { [0] = node[0] - 1, node[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) + 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 - end -end - -end -- end else + end -- end else end -- end generateNodeFunctions @@ -991,9 +1826,9 @@ function _M:autoExplore() local do_unseen = not (game.level.all_remembered or game.zone and game.zone.all_remembered) -- if we changed levels, then remove previous auto-explore information - if self.running_prev and self.running_prev.level ~= game.level then self.running_prev = nil end + if self.running_prev and self.running_prev.levelstring ~= tostring(game.level) then self.running_prev = nil end - local node = { self.x, self.y, toSingle(self.x, self.y), 0, 5 } + local node = is_ffi and ffi.new("cnode", {self.x, self.y, toSingle(self.x, self.y), 0, 5 }) or { [0] = self.x, self.y, toSingle(self.x, self.y), 0, 5 } local current_tiles = { node } local unseen_tiles = {} local unseen_singlets = {} @@ -1002,7 +1837,7 @@ function _M:autoExplore() local exits = {} local portals = {} local values = {} - values[node[3]] = 0 + values[node[2]] = 0 local safe_doors = {} local door_values = {} local slow_values = {} @@ -1026,14 +1861,15 @@ function _M:autoExplore() local cardinal_tiles = {} local diagonal_tiles = {} -- Nearly half the time is spent here. This could be implemented in C if desired, but I think it's fast enough + -- I wonder how much time is spent here now that nodes are using ffi data for _, node in ipairs(current_tiles) do - getNextNodes[node[5]](node, cardinal_tiles, diagonal_tiles) + getNextNodes[node[4]](node, cardinal_tiles, diagonal_tiles) end -- The other half of the time is spent in this loop for _, tile_list in ipairs({cardinal_tiles, diagonal_tiles}) do for _, node in ipairs(tile_list) do - local x, y, c, move_cost, dir = unpack(node) + local x, y, c, move_cost, dir = node[0], node[1], node[2], node[3], node[4] if not game.level.map.has_seens(x, y) and do_unseen then if not values[c] or values[c] > move_cost then @@ -1044,8 +1880,8 @@ function _M:autoExplore() end -- Try to not abandon lone unseen tiles local is_singlet = true - for _, anode in ipairs(listAdjacentNodes[dir](node)) do - if not game.level.map.has_seens(anode[1], anode[2]) then + for _, anode in ipairs(listAdjacentTiles[dir](node)) do + if not game.level.map.has_seens(anode[0], anode[1]) then is_singlet = false break end @@ -1057,19 +1893,19 @@ function _M:autoExplore() -- For example: #. and ... are probably walls (in most zones) 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 + for _, anode in ipairs(listSharedTiles[dir](node)) do + if not game.level.map.has_seens(anode[0], anode[1]) or not game.level.map:checkEntity(anode[0], anode[1], Map.TERRAIN, "does_block_move") then is_singlet = false -- if we propagated diagonally, then check if this might be a wall side, not corner -- c = current, 1 = supposed wall #c1 -- p = previous, 2 = supposed floor p.2 elseif dir % 2 == 1 then - local x1 = 2*x - anode[1] - local y1 = 2*y - anode[2] + local x1 = 2*x - anode[0] + local y1 = 2*y - anode[1] if game.level.map.has_seens(x1, y1) and game.level.map:checkEntity(x1, y1, Map.TERRAIN, "does_block_move") then - local pnode = previousNode[dir](node) - x1 = x1 + pnode[1] - anode[1] - y1 = y1 + pnode[2] - anode[2] + local pnode = previousTile[dir](node) + x1 = x1 + pnode[0] - anode[0] + y1 = y1 + pnode[1] - anode[1] if game.level.map.has_seens(x1, y1) and not game.level.map:checkEntity(x1, y1, Map.TERRAIN, "does_block_move") then is_singlet = true break @@ -1081,8 +1917,8 @@ function _M:autoExplore() -- c = current, ? = supposed floor #c# -- p = previous, ?p? if is_singlet then - for _, anode in ipairs(listSharedNodesPrevious[dir](node)) do - if not game.level.map.has_seens(anode[1], anode[2]) or game.level.map:checkEntity(anode[1], anode[2], Map.TERRAIN, "does_block_move") then + for _, anode in ipairs(listSharedTilesPrevious[dir](node)) do + if not game.level.map.has_seens(anode[0], anode[1]) or game.level.map:checkEntity(anode[0], anode[1], Map.TERRAIN, "does_block_move") then is_singlet = false break end @@ -1120,7 +1956,7 @@ function _M:autoExplore() -- (and they can always interrupt running if something terrible happens) if not (terrain.does_block_move or terrain.door_opened) then if is_slow then - node[4] = move_cost + node[3] = move_cost slow_values[c] = move_cost slow_tiles[#slow_tiles + 1] = node else @@ -1140,8 +1976,8 @@ function _M:autoExplore() -- only go to closed doors with unseen grids behind them. We can go through "safe" doors elseif terrain.door_opened and do_unseen then local is_unexplored = false - for _, anode in ipairs(listAdjacentNodes[dir](node)) do - if not game.level.map.has_seens(anode[1], anode[2]) then + for _, anode in ipairs(listAdjacentTiles[dir](node)) do + if not game.level.map.has_seens(anode[0], anode[1]) then is_unexplored = true break end @@ -1152,7 +1988,7 @@ function _M:autoExplore() door_values[c] = move_cost end else -- door is safe to move through - node[4] = move_cost + 1 + node[3] = move_cost + 1 values[c] = move_cost + 1 current_tiles_next[#current_tiles_next + 1] = node safe_doors[c] = true @@ -1164,8 +2000,8 @@ function _M:autoExplore() elseif terrain.orb_portal then local is_portal_center = true local is_small_portal = true - for _, anode in ipairs(listAdjacentNodes[dir](node)) do - if not game.level.map:checkEntity(anode[1], anode[2], Map.TERRAIN, "orb_portal") then + for _, anode in ipairs(listAdjacentTiles[dir](node)) do + if not game.level.map:checkEntity(anode[0], anode[1], Map.TERRAIN, "orb_portal") then is_portal_center = false else is_small_portal = false @@ -1210,7 +2046,7 @@ function _M:autoExplore() running = true current_tiles = slow_tiles for _, node in ipairs(slow_tiles) do - local c, val = node[3], node[4] + local c, val = node[2], node[3] if not values[c] or val < values[c] then values[c] = val end @@ -1358,21 +2194,21 @@ function _M:autoExplore() local door_val = door_values[c] local min_diagonal = door_val local min_cardinal = door_val - for _, node in ipairs(listAdjacentTiles(c, true)) do - if values[node[3]] and values[node[3]] < min_cardinal then - min_cardinal = values[node[3]] + for _, node in ipairs(listAdjacentNodes(c, true)) do + if values[node[2]] and values[node[2]] < min_cardinal then + min_cardinal = values[node[2]] end end - for _, node in ipairs(listAdjacentTiles(c, false, true)) do - if values[node[3]] and values[node[3]] < min_diagonal then - min_diagonal = values[node[3]] + for _, node in ipairs(listAdjacentNodes(c, false, true)) do + if values[node[2]] and values[node[2]] < min_diagonal then + min_diagonal = values[node[2]] end end local plus_one = 0 if min_cardinal > min_diagonal then - for _, node in ipairs(listAdjacentTiles(c, false, true)) do - if values[node[3]] then - add_values[node[3]] = values[node[3]] + 1 + for _, node in ipairs(listAdjacentNodes(c, false, true)) do + if values[node[2]] then + add_values[node[2]] = values[node[2]] + 1 plus_one = 1 end end @@ -1497,8 +2333,8 @@ function _M:autoExplore() -- perform a greedy minimization that prefers cardinal directions local cardinals = {} local min_cardinal = current_val - for _, node in ipairs(listAdjacentTiles(target, true)) do - local c = node[3] + for _, node in ipairs(listAdjacentNodes(target, true)) do + local c = node[2] if values[c] and values[c] <= min_cardinal then min_cardinal = values[c] cardinals[#cardinals + 1] = node @@ -1506,8 +2342,8 @@ function _M:autoExplore() end local diagonals = {} local min_diagonal = current_val - for _, node in ipairs(listAdjacentTiles(target, false, true)) do - local c = node[3] + for _, node in ipairs(listAdjacentNodes(target, false, true)) do + local c = node[2] if values[c] and values[c] < min_diagonal then min_diagonal = values[c] diagonals[#diagonals + 1] = node @@ -1523,18 +2359,18 @@ function _M:autoExplore() if #cardinals == 0 or min_diagonal < min_cardinal and not (min_cardinal < min_diagonal + 2 and (safe_doors[c] or door_values[c])) then current_val = min_diagonal for _, node in ipairs(diagonals) do - if values[node[3]] == min_diagonal then - path[#path + 1] = {x=node[1], y=node[2]} - target = node[3] + if values[node[2]] == min_diagonal then + path[#path + 1] = {x=node[0], y=node[1]} + target = node[2] break end end else current_val = min_cardinal for _, node in ipairs(cardinals) do - if values[node[3]] == min_cardinal then - path[#path + 1] = {x=node[1], y=node[2]} - target = node[3] + if values[node[2]] == min_cardinal then + path[#path + 1] = {x=node[0], y=node[1]} + target = node[2] break end end @@ -1604,7 +2440,7 @@ function _M:autoExplore() end, false, true), explore = target_type, target = {x=target_x, y=target_y}, - level = game.level + levelstring = tostring(game.level) } -- hack! if self.running_prev then diff --git a/game/modules/tome/dialogs/debug/DebugMain.lua b/game/modules/tome/dialogs/debug/DebugMain.lua index 15d22ba9d3..bc032a6a8c 100644 --- a/game/modules/tome/dialogs/debug/DebugMain.lua +++ b/game/modules/tome/dialogs/debug/DebugMain.lua @@ -111,6 +111,14 @@ function _M:use(item) end end end end + elseif act == "remove-all" then + local l = {} + for uid, e in pairs(game.level.entities) do + if not game.party:hasMember(e) then l[#l+1] = e end + end + for i, e in ipairs(l) do + game.level:removeEntity(e) + end end end @@ -128,6 +136,7 @@ function _M:generateList() list[#list+1] = {name="Alter Faction", dialog="AlterFaction"} list[#list+1] = {name="Give Sher'tul fortress energy", action="shertul-energy"} list[#list+1] = {name="Create Trap", dialog="CreateTrap"} + list[#list+1] = {name="Remove all creatures", action="remove-all"} local chars = {} for i, v in ipairs(list) do -- GitLab