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 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
|
------------------------------------------------------------------------------
-- v_paint.lua:
--
-- Functions for painting the Vaults layouts and managing
-- walls and areas that rooms can be applied to.
--
-- We will (in the simplest case) paint rectangles of floor onto the dungeon
-- and then sweep across the whole grid to determine which squares can be
-- used for what.
--
-- Whilst placing rooms we'll update the usage grid as areas become available
-- or new walls become eligible (but that's a much more predictable case than
-- initial level generation).
------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- Initialize and empty usage grid
local function new_layout(width, height)
layout_grid = { width = width, height = height }
for y = 0, (height - 1), 1 do
layout_grid[y] = { }
for x = 0, (width - 1), 1 do
layout_grid[y][x] = { solid = true } -- 1 means wall
end
end
return layout_grid
end
function vaults_new_layout(width, height)
return new_layout(width,height)
end
local function new_usage(width, height)
usage_grid = { eligibles = { }, width = width, height = height }
for y = 0, (height-1), 1 do
usage_grid[y] = { }
for x = 0, (width-1), 1 do
usage_grid[y][x] = { usage = "none" }
end
end
return usage_grid
end
local function 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
-- Global version
function vaults_get_usage(usage_grid,x,y)
return get_usage(usage_grid,x,y)
end
local function 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_index)
end
-- Add to the eligibles list if it's eligible
if usage.usage == "eligible" or usage.usage == "eligible_open" or usage.usage == "open" 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!
table.insert(usage_grid.eligibles,usage)
usage.eligibles_index = #(usage_grid.eligibles) -- Store index of the new item so we can remove it when it's overwritten
end
-- Store usage in grid
usage_grid[y][x] = usage
end
-- Global version
function vaults_set_usage(usage_grid,x,y,usage)
return set_usage(usage_grid,x,y,usage)
end
local function get_layout(layout_grid,x,y)
-- Handle out of bounds
if layout_grid[y] == nil then
return { space = true, solid = true, exit = false, feature = "permarock_wall" }
end
if layout_grid[y][x] == nil then
return { space = true, solid = true, exit = false, feature = "permarock_wall" }
end
return layout_grid[y][x]
end
function vaults_get_layout(layout_grid,x,y)
return get_layout(layout_grid,x,y)
end
local function set_layout(layout_grid,x,y,value)
if layout_grid[y] == nil or layout_grid[y][x] == nil then
else
layout_grid[y][x] = value
end
end
function vaults_set_layout(layout_grid,x,y,value)
return set_layout(layout_grid,x,y,value)
end
local function determine_usage_from_layout(layout_grid,options)
usage_restricted_count = 0
usage_open_count = 0
usage_eligible_count = 0
usage_none_count = 0
local gxm, gym = layout_grid.width,layout_grid.height
local usage_grid = new_usage(gxm,gym)
local function gridsum(grid,list)
local sum = 0
for i,pos in ipairs(list) do
if grid[pos.y][pos.x].solid then sum = sum + 1 end
end
return sum
end
local local_grid = { }
for yl = -options.min_distance_from_wall, options.min_distance_from_wall, 1 do
local_grid[yl] = { }
end
local adjacent_vectors = { {x=-1,y=0},{x=1,y=0},{x=0,y=-1},{x=0,y=1} }
local diagonal_vectors = { {x=-1,y=-1},{x=1,y=-1},{x=-1,y=1},{x=1,y=1} }
for x = 0, gxm-1, 1 do
for y = 0, gym-1, 1 do
-- We need to know the local layout grid around this square
-- This flag will track if there is only floor in the area
local only_floor = true
for yl = -options.min_distance_from_wall, options.min_distance_from_wall, 1 do
for xl = -options.min_distance_from_wall, options.min_distance_from_wall, 1 do
local cell = get_layout(layout_grid,x + xl,y + yl)
local_grid[yl][xl] = cell
if cell.solid then only_floor = false end
end
end
-- Completely open floor so we could place a room here
if only_floor == true then
set_usage(usage_grid,x,y, { usage = "open" })
else
-- Are we dealing with floor or wall?
if local_grid[0][0].solid then -- Wall
-- A wall can either be usage "none" meaning parts of a room could later be built over it;
-- or it can be "eligible" meaning it can be used as a connecting wall/door to a room;
-- or it can be "restricted" meaning its geometry is not suited for a connecting wall or it has already been used
-- We don't need to cover all cases of complex geometry since for now the layouts will be
-- making simple large blocks. If complex geometry ever gets used it doesn't matter if we flag some squares
-- as eligible when they don't really work because room placement will still vetoe if it can't find a clear area of
-- open or none. It's more important to find all squares that *could* be eligible.
-- Sum the adjacent squares
local adjacent_sum = gridsum(local_grid, adjacent_vectors)
-- Eligible squares have floor on only one side
-- This ignores diagonals (which is where complex geometry will produce some eligible squares that aren't
-- really eligible). But it's complicated because we're after diagonals only on the side where the floor is.
-- What we need to know in the usage grid is the normal, i.e. the direction in which the user will be entering
-- the room.
if adjacent_sum == 3 then
-- Floor to the north
if not local_grid[-1][0].solid then
set_usage(usage_grid,x,y, { usage = "eligible", normal = adjacent_vectors[4], depth = 1})
end
-- Floor to the south
if not local_grid[1][0].solid then
set_usage(usage_grid,x,y, { usage = "eligible", normal = adjacent_vectors[3], depth = 1})
end
-- Floor to the west
if not local_grid[0][-1].solid then
set_usage(usage_grid,x,y, { usage = "eligible", normal = adjacent_vectors[2], depth = 1})
end
-- Floor to the east
if not local_grid[0][1].solid then
set_usage(usage_grid,x,y, { usage = "eligible", normal = adjacent_vectors[1], depth = 1})
end
else
-- Wall all around?
if adjacent_sum == 4 then
local diagonal_sum = gridsum(local_grid, diagonal_vectors)
-- Wall mostly all around? (We allow one missing corner otherwise rooms can't overlap corners
-- and logically it's fine for any wall to be placed there, other missing holes will fail the placement
-- anyway)
if diagonal_sum >= 3 then
-- Should have been set this way at initialization but let's check anyway
set_usage(usage_grid,x,y, { usage = "none" })
else
-- There are some diagonal holes so we can't use this square
set_usage(usage_grid,x,y, { usage = "restricted" })
end
end
end
else -- Floor
-- We already know there is a wall nearby, so this square is restricted
set_usage(usage_grid,x,y, { usage = "restricted", reason = "border" })
end
end
end
end
return usage_grid
end
-- Determine if a point is inside an oval
-- TODO: There's something wrong with this equation, ovals are coming out with flattened sides and sometimes completely square.
-- Needs somehow adjusting for the fact that we're dealing with grid squares, if that's indeed the problem ...
local function inside_oval(x,y,item)
-- Radii
local rx = (item.corner2.x - item.corner1.x)/2
local ry = (item.corner2.y - item.corner1.y)/2
-- Center point
local h = (item.corner2.x + item.corner1.x)/2
local k = (item.corner2.y + item.corner1.y)/2
-- Test
return ((math.pow(x-h,2)/math.pow(rx,2) + math.pow(y-k,2)/math.pow(ry,2))<=1)
end
local function inside_trapese(x,y,item)
local width1 = item.width1
local width2 = item.width2
if width1 == nil then width1 = 0 end
if width2 == nil then width2 = 1 end
return true
end
function paint_grid(paint, options, grid)
for i,item in ipairs(paint) do
local feature
if item.type == "floor" then
feature = options.layout_floor_type
elseif item.type == "wall" then
feature = options.layout_wall_type
elseif item.type == "space" then
feature = "space"
elseif item.feature ~= nil then
feature = item.feature
end
-- Check which shape to paint
local shape_type = "quad"
if item.shape ~= nil then shape_type = item.shape end
-- Discover whether the feature type is solid
local space = (feature == "space")
local solid = not space and not feat.has_solid_floor(feature)
local empty = (item.empty ~= nil and item.empty)
-- Paint features onto grid
if shape_type == "quad" or shape_type == "ellipse" or shape_type == "trapese" then
-- TODO: shape types are begging to be modularised
-- Set layout details in the painted area
for x = item.corner1.x, item.corner2.x, 1 do
for y = item.corner1.y, item.corner2.y, 1 do
if shape_type == "quad" or (shape_type == "ellipse" and inside_oval(x,y,item)) or (shape_type == "trapese" and inside_trapese(x,y,item)) then
local ax,ay = x,y
if item.wrap then
ax = x % grid.width
ay = y % grid.height
end
set_layout(layout_grid,ax,ay, { solid = solid, feature = feature, space = space, empty = empty })
end
end
end
elseif shape_type == "plot" then
if item.points ~= nil then
for i,pos in ipairs(item.points) do
set_layout(layout_grid, pos.x, pos.y, { solid = solid, feature = feature, empty = empty })
end
else
set_layout(layout_grid, item.x, item.y, { solid = solid, feature = feature, empty = empty })
end
end
end
end
function paint_vaults_layout(paint, options, layout_grid)
-- Default options
if options == nil then options = vaults_default_options() end
-- Pick wall type from spread in config
local wall_type = "stone_wall"
if options.wall_type ~= nil then wall_type = options.wall_type end
if options.layout_wall_weights ~= nil then
local chosen = util.random_weighted_from("weight", options.layout_wall_weights)
wall_type = chosen.feature
end
-- Store it in options so it can be used for room surrounds also
options.layout_wall_type = wall_type
local gxm, gym = dgn.max_bounds()
layout_grid = new_layout(gxm,gym) -- Will contain data about how each square is used and therefore how rooms can be applied
paint_grid(paint,options,layout_grid) -- Paint fills onto the layout grid
if _VAULTS_DEBUG then dump_layout_grid(layout_grid) end
local usage_grid = determine_usage_from_layout(layout_grid,options) -- Analyse the layout to determine usage
-- Apply to the actual dungeon grid
for x = 0, gxm-1, 1 do
for y = 0, gym-1, 1 do
local cell = get_layout(layout_grid,x,y)
if cell.feature ~= nil and cell.feature ~= "space" then
dgn.grid(x,y,cell.feature)
elseif cell.feature == nil then
-- Make sure we set the right type of wall in unpainted grids
dgn.grid(x,y,wall_type)
end
end
end
return usage_grid
end
|