1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
------------------------------------------------------------------------------
-- v_usage.lua:
--
-- Usage functions. Manipulates our internal feature grid.
------------------------------------------------------------------------------
hyper.usage = {}
function hyper.usage.new_usage(width, height, initialiser)
usage_grid = { eligibles = { open = {}, closed = {} }, anchors = {}, width = width, height = height }
if initialiser == nil then
initialiser = function()
return { feature = "space", solid = true, space = true, carvable = true, vault = false, anchors = {} }
end
end
for y = 0, (height-1), 1 do
usage_grid[y] = { }
for x = 0, (width-1), 1 do
usage_grid[y][x] = initialiser(x,y)
end
end
return usage_grid
end
-- Get a square from the usage grid. The value returned is a table which can contain the following values:
-- * vault - indicates this square is part of an existing vault placement and should not generally be overwritten, although
-- it might be connectable to if it's an open square. Might have been placed by an ORIENT primary vault, or
-- could have placed during hyper build by a "tagged" generator.
-- * feature - the feature name at this square. Will be applied to the dungeon grid after build if it's not already applied
-- by a vault. TODO: I was considering converting to glyphs to possibly optimise things, make everything
-- conceptually simpler for people coming from vault design backgrounds, and to support a few new scenarios (e.g.
-- current lack of support for KMONS and a bunch of other mapdef features). But at this stage I don't think it's even feasible (review).
-- * space - Empty space, nothing is applied to this square. Typically from map-defined vaults where there are space
-- characters around the map edges.
-- * open - Open space where rooms and features can be placed.
-- * anchors - Describes any spots that can be anchored to this cell for connection purposes. (Replacing normal and normal_inverse...)
-- * normal - For eligible walls that can have further rooms attached to them; this indicates the direction in which such a
-- room should be oriented in order to connect to this square.
-- * normal_inverse - Indicates that rooms can be attached in the opposite orientation as well; e.g. a long 1-line-thick wall
-- that can be attached to on either side.
-- * room - If this square was created as part of a room placement this will be the associated room. (The square could either
-- be part of the room's internal features or part of the walls/doors/etc.) TODO: It might be handy to store both
-- rooms for a partitioning wall, in fact up to four rooms should be possible on a corner.
-- * solid
-- * wall
-- * open_area
function hyper.usage.get_usage(usage_grid,x,y)
-- Handle out of bounds
if usage_grid[y] == nil then
return nil
end
if usage_grid[y][x] == nil then
return nil
end
return usage_grid[y][x]
end
function hyper.usage.set_usage(usage_grid,x,y,usage)
if usage_grid[y] == nil or usage_grid[y][x] == nil then return false end
-- Check existing usage, remove it from eligibles if it's there
local current = usage_grid[y][x]
if current.eligibles_index ~= nil then
table.remove(usage_grid.eligibles[current.eligibles_which],current.eligibles_index)
end
if current ~= usage and current.anchors ~= nil then
for i,anchor in ipairs(current.anchors) do
-- TODO: util.remove will be pretty slow on large lists (which is why for eligibles we cache the index)
-- ...this shouldn't be a huge problem for anchors since there aren't typically tons of them but it's something to watch out for...
util.remove(usage_grid.anchors,anchor)
end
end
-- Add to the eligibles list if it's eligible. For now never carve vaults (although we could have vaults with a special tag allowing walls to be carved)
if not usage.vault and (usage.carvable or not usage.solid) then
usage.spot = { x = x, y = y } -- Store x,y in the usage object otherwise when we look it up in the list we don't know where it came from!
local which_table = usage.solid and "closed" or "open"
table.insert(usage_grid.eligibles[which_table],usage)
usage.eligibles_which = which_table
usage.eligibles_index = #(usage_grid.eligibles[which_table]) -- Store index of the new item so we can remove it when it's overwritten
end
if usage.anchors ~= nil then
for i,anchor in ipairs(usage.anchors) do
table.insert(usage_grid.anchors,anchor)
end
end
-- Store usage in grid
usage_grid[y][x] = usage
end
-- Filter a usage grid
function hyper.usage.filter_usage(usage_grid,filter,transform,region)
-- Determine region to be filtered
local x1,y1,x2,y2 = 0,0,usage_grid.width-1,usage_grid.height-1
if region ~= nil then
x1,y1,x2,y2 = region.x1,region.y1,region.x2,region.y2
end
-- Determine method of filtering if we don't have a callback
local filter_func = filter
-- If a filter table is provided then all values have to match
-- If any more complex filter logic is needed, provide a callback
if type(filter) == "table" then
filter_func = function(usage)
for k,val in pairs(filter) do
if usage[k] ~= val then return false end
end
return true
end
end
-- Determine method of transforming the usage when filter matches.
-- If a transform table is provided then we'll replace each value
-- from the current usage with the value from transform.
local transform_func = transform
if type(transform) == "table" then
transform_func = function(usage)
-- TODO: Maybe we should take a clone of the usage. Currently it shouldn't be a problem but if the object
-- reference is ever duplicated this could alter usage on unexpected squares.
for k,val in pairs(transform) do
usage[k] = val
end
return usage
end
end
for x = x1,x2,1 do
for y = y1,y2,1 do
local current = hyper.usage.get_usage(usage_grid,x,y)
-- Test callback
if filter_func(current) then
-- Perform transform
-- Even though we might just be altering the existing usage object, calling set_usage ensures that
-- eligibility lists are updated correctly.
hyper.usage.set_usage(usage_grid, x,y, transform_func(current))
end
end
end
end
function hyper.usage.from_feature_name(feature_name)
return hyper.usage.from_feature(dgn.feature(feature_num))
end
-- Initialises a usage grid coord by inspecting the
-- existing dungeon grid
function hyper.usage.grid_initialiser(x,y)
local feature = dgn.grid(x,y)
local mask = dgn.in_vault(x,y)
return { feature = feature,
vault = mask,
anchors = {},
space = false,
carvable = false,
solid = not (feat.has_solid_floor(feature) or feat.is_door(feature)),
wall = feat.is_wall(feature)
}
end
-- Scan a grid and determine potential usage of features.
-- Features on the edge of the map can be flagged for external connection
-- - this allows this grid to be joined onto existing features on the map.
-- Features inside the map can be flagged for internal connection
-- - e.g. placing rooms within rooms, or carving into a rock area inside the room.
function hyper.usage.analyse_grid_usage(usage_grid,options)
local get = hyper.usage.get_usage
local set = hyper.usage.set_usage
-- TODO: An optimised zonify would do this job pretty well and
-- possibly get more useful information.
for x = 0, usage_grid.width-1, 1 do
for y = 0, usage_grid.height-1, 1 do
local usage = get(usage_grid,x,y)
-- Ignore vaults
if not usage.vault then
-- Check walls for carvability
if usage.wall then
for i,normal in ipairs(vector.normals) do
local near = get(usage_grid,x+normal.x,y+normal.y)
if near ~= nil and not near.solid then
usage.carvable = true
table.insert(usage.anchors,{ normal = vector.normals[(normal.dir+2)%4 + 1], pos = { x=0,y=0 }, grid_pos = { x=x,y=y }})
end
end
-- Check open spaces for bordering walls
elseif not usage.solid then
local no_wall = true
for i,normal in ipairs(vector.directions) do
local near = hyper.usage.get_usage(usage_grid,x+normal.x,y+normal.y)
if near ~= nil and near.solid then usage.buffer = true end
end
end
-- Reapply the usage - this will update the
-- global anchors list
set(usage_grid,x,y,usage)
end
end
end
end
|