File: hyper_usage.lua

package info (click to toggle)
crawl 2%3A0.33.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 95,264 kB
  • sloc: cpp: 358,145; ansic: 27,203; javascript: 9,491; python: 8,359; perl: 3,327; java: 2,667; xml: 2,191; makefile: 1,830; sh: 611; objc: 250; cs: 15; sed: 9; lisp: 3
file content (207 lines) | stat: -rw-r--r-- 8,959 bytes parent folder | download | duplicates (7)
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