1------------------------------------------------------------------------------ 2-- Iterationeering functions. 3-- The positional iterators are all dlua only and iterate over regions in 4-- dungeon grid coordinates. 5-- @module iter 6 7-- Example usage of adjacent_iterator: 8-- 9-- function iter.rats() 10-- local i 11-- for i in iter.adjacent_iterator() do 12-- dgn.create_monster(i.x, i.y, "rat") 13-- end 14-- end 15-- 16-- The above function will surround the player with rats. 17 18-- dgn.point iterators. 19-- All functions expect dgn.point parameters, and return dgn.points. 20iter = {} 21 22-- generic filters for use with iterators 23-- these always need to be used with 'rvi', and any filter that acts on them 24-- is assumed to as well (but only return a point) 25 26function iter.monster_filter (filter) 27 local function filter_fn (point) 28 if filter ~= nil then 29 if filter(point) == nil then 30 return nil 31 else 32 point = filter(point) 33 end 34 end 35 36 if not dgn.in_bounds(point.x, point.y) then 37 return nil 38 end 39 40 return dgn.mons_at(point.x, point.y) 41 end 42 return filter_fn 43end 44 45-- rectangle_iterator 46iter.rectangle_iterator = {} 47 48function iter.rectangle_iterator:_new () 49 local m = {} 50 setmetatable(m, self) 51 self.__index = self 52 return m 53end 54 55function iter.rectangle_iterator:new (corner1, corner2, filter, rvi) 56 if corner1 == nil or corner2 == nil then 57 error("need two corners to a rectangle") 58 end 59 60 if corner2.x < corner1.x then 61 error("corner2's x less than corner1's; did you swap them?") 62 elseif corner2.y < corner1.y then 63 error("corner2's y less than corner1's; did you swap them?") 64 end 65 66 local mt = iter.rectangle_iterator:_new() 67 mt.min_x = corner1.x 68 mt.min_y = corner1.y 69 mt.max_x = corner2.x 70 mt.max_y = corner2.y 71 mt.cur_x = corner1.x - 1 72 mt.cur_y = corner1.y 73 mt.filter = filter or nil 74 mt.rvi = rvi or false 75 76 return mt:iter() 77end 78 79function iter.rectangle_iterator:next() 80 local point = nil 81 repeat 82 if self.cur_x >= self.max_x then 83 self.cur_y = self.cur_y + 1 84 self.cur_x = self.min_x 85 if self.cur_y > self.max_y then 86 point = -1 87 else 88 point = self:check_filter(dgn.point(self.cur_x, self.cur_y)) 89 if point == nil then 90 point = -2 91 end 92 end 93 else 94 self.cur_x = self.cur_x + 1 95 point = self:check_filter(dgn.point(self.cur_x, self.cur_y)) 96 if point == nil then 97 point = -2 98 end 99 end 100 if point == -1 then 101 return nil 102 elseif point == -2 then 103 point = nil 104 end 105 until point 106 107 return point 108end 109 110function iter.rectangle_iterator:check_filter(point) 111 if self.filter ~= nil then 112 if self.filter(point) then 113 if self.rvi then 114 return self.filter(point) 115 else 116 return point 117 end 118 else 119 return nil 120 end 121 else 122 return point 123 end 124end 125 126function iter.rectangle_iterator:iter () 127 return function() return self:next() end, nil, nil 128end 129 130function iter.rect_iterator(top_corner, bottom_corner, filter, rvi) 131 return iter.rectangle_iterator:new(top_corner, bottom_corner, filter, rvi) 132end 133 134function iter.rect_size_iterator(top_corner, size, filter, rvi) 135 return iter.rect_iterator(top_corner, top_corner + size - dgn.point(1, 1), 136 filter, rvi) 137end 138 139function iter.mons_rect_iterator (top_corner, bottom_corner, filter) 140 return iter.rect_iterator(top_corner, bottom_corner, iter.monster_filter(filter), true) 141end 142 143function iter.border_iterator(top_corner, bottom_corner, rvi) 144 local function check_inner(point) 145 if point.x == top_corner.x or point.x == bottom_corner.x or point.y == top_corner.y or point.y == bottom_corner.y then 146 return point 147 end 148 return nil 149 end 150 return iter.rectangle_iterator:new(top_corner, bottom_corner, check_inner, rvi) 151end 152 153-- los_iterator 154function iter.los_iterator (ic, filter, center, rvi) 155 local y_x, y_y = you.pos() 156 157 if center ~= nil then 158 y_x, y_y = center:xy() 159 end 160 161 local include_center = ic 162 local top_corner = dgn.point(y_x - 8, y_y - 8) 163 local bottom_corner = dgn.point(y_x + 8, y_y + 8) 164 165 local function check_los (point) 166 local _x, _y = point:xy() 167 local npoint = true 168 169 if filter ~= nil then 170 if rvi then 171 npoint = filter(point) 172 end 173 174 if not filter(point) then 175 return nil 176 end 177 end 178 179 if y_x == _x and y_y == _y then 180 if rvi and include_center then 181 return npoint 182 else 183 return include_center 184 end 185 end 186 187 if not you.see_cell(_x, _y) then 188 return nil 189 end 190 191 if rvi then 192 return npoint 193 else 194 return true 195 end 196 end 197 198 return iter.rect_iterator(top_corner, bottom_corner, check_los, rvi) 199end 200 201function iter.mons_los_iterator (ic, filter, center) 202 return iter.los_iterator(ic, iter.monster_filter(filter), center, true) 203end 204 205-- adjacent_iterator 206function iter.adjacent_iterator (ic, filter, center, rvi) 207 local y_x, y_y = you.pos() 208 209 if center ~= nil then 210 y_x, y_y = center:xy() 211 end 212 213 local top_corner = dgn.point(y_x - 1, y_y - 1) 214 local bottom_corner = dgn.point(y_x + 1, y_y + 1) 215 local include_center = ic 216 217 local function check_adj (point) 218 local _x, _y = point:xy() 219 local npoint = point 220 221 if filter ~= nil then 222 if rvi then 223 npoint = filter(point) 224 end 225 226 if not filter(point) then 227 return nil 228 end 229 end 230 231 if y_x == _x and y_y == _y then 232 if rvi and include_center then 233 return npoint 234 else 235 return include_center 236 end 237 end 238 239 if rvi then 240 return npoint 241 else 242 return true 243 end 244 end 245 246 return iter.rect_iterator(top_corner, bottom_corner, check_adj, rvi) 247end 248 249function iter.mons_adjacent_iterator (ic, filter, center) 250 return iter.adjacent_iterator(ic, iter.monster_filter(filter), center, true) 251end 252 253function iter.adjacent_iterator_to(center, include_center, filter) 254 return iter.adjacent_iterator(include_center, filter, center, true) 255end 256 257-- circle_iterator 258function iter.circle_iterator (radius, ic, filter, center, rvi) 259 if radius == nil then 260 error("circle_iterator needs a radius") 261 end 262 263 local y_x, y_y = you.pos() 264 265 if center ~= nil then 266 y_x, y_y = center:xy() 267 end 268 269 local include_center = ic 270 local top_corner = dgn.point(y_x - radius, y_y - radius) 271 local bottom_corner = dgn.point(y_x + radius, y_y + radius) 272 273 local function check_dist (point) 274 local _x, _y = point:xy() 275 local dist = dgn.distance(y_x, y_y, _x, _y) 276 local npoint = nil 277 278 if filter ~= nil then 279 if rvi then 280 npoint = filter(point) 281 end 282 283 if not filter(point) then 284 return nil 285 end 286 end 287 288 if y_x == _x and y_y == _y then 289 if rvi and include_center then 290 return npoint 291 else 292 return include_center 293 end 294 end 295 296 if dist >= (radius * radius) + 1 then 297 return nil 298 end 299 300 if rvi then 301 return npoint 302 else 303 return true 304 end 305 end 306 307 return iter.rect_iterator(top_corner, bottom_corner, check_dist, rvi) 308end 309 310function iter.mons_circle_iterator (radius, ic, filter, center) 311 return iter.circle_iterator(radius, ic, iter.monster_filter(filter), center, true) 312end 313 314-- item stack-related functions 315-- not necessarily iterators 316function iter.stack_search (coord, term, extra) 317 local _x, _y 318 if extra ~= nil then 319 _x = coord 320 _y = term 321 term = extra 322 elseif coord == nil then 323 error("stack_search requires a location") 324 else 325 _x, _y = coord:xy() 326 end 327 328 if term == nil then 329 error("stack_search requires a search term") 330 end 331 332 local stack = dgn.items_at(_x, _y) 333 if #stack == 0 then 334 return nil 335 end 336 337 for _, item in ipairs(stack) do 338 if string.find(item.name(), (term)) then 339 return item 340 end 341 end 342 343 return nil 344end 345 346function iter.stack_destroy(coord, extra) 347 local _x, _y 348 if extra ~= nil then 349 _x = coord 350 _y = extra 351 elseif coord == nil then 352 error("stack_search requires a location") 353 else 354 _x, _y = coord:xy() 355 end 356 357 local stack = dgn.items_at(_x, _y) 358 359 while #stack ~= 0 do 360 local name = stack[1].name() 361 if stack[1].destroy() then 362 if #stack <= #dgn.items_at(_x, _y) then 363 error("destroyed an item ('" .. name .. "'), but the " 364 .. "stack size is the same") 365 return 366 end 367 stack = dgn.items_at(_x, _y) 368 else 369 error("couldn't destroy item '" .. name .. "'") 370 return false 371 end 372 end 373end 374 375-- subvault_iterator 376-- Iterates through all map locations in a subvault that will get written 377-- back to a parent. 378function iter.subvault_iterator (e, filter) 379 380 if e == nil then 381 error("subvault_iterator requires the env to be passed, e.g. _G") 382 end 383 384 local top_corner = dgn.point(0, 0) 385 local bottom_corner 386 387 local w,h = e.subvault_size() 388 if w == 0 or h == 0 then 389 -- Construct a valid rectangle. subvault_cell_valid will always fail. 390 bottom_corner = top_corner 391 else 392 bottom_corner = dgn.point(w-1, h-1) 393 end 394 395 396 local function check_mask (point) 397 if filter ~= nil then 398 if not filter(point) then 399 return false 400 end 401 end 402 403 return e.subvault_cell_valid(point.x, point.y) 404 end 405 406 return iter.rect_iterator(top_corner, bottom_corner, check_mask) 407end 408 409-- Marker iterators 410-- firstly, a point iterator. It's really just a fancy ipairs(), but stateful 411-- and with the ability to filter points. 412 413iter.point_iterator = {} 414 415function iter.point_iterator:_new () 416 local m = {} 417 setmetatable(m, self) 418 self.__index = self 419 return m 420end 421 422function iter.point_iterator:new (ptable, filter, rv_instead) 423 if ptable == nil then 424 error("ptable cannot be nil for point_iterator") 425 end 426 427 local mt = iter.point_iterator:_new() 428 mt.cur_p = 0 429 mt.table = ptable 430 mt.rvi = rv_instead or false 431 mt.filter = filter or nil 432 433 return mt:iter() 434end 435 436function iter.point_iterator:next() 437 local point = nil 438 local q = 0 439 repeat 440 q = q + 1 441 self.cur_p = self.cur_p + 1 442 point = self:check_filter(self.table[self.cur_p]) 443 until point or q == 10 444 445 return point 446end 447 448function iter.point_iterator:check_filter(point) 449 if self.filter ~= nil then 450 if self.filter(point) then 451 if self.rvi then 452 return self.filter(point) 453 else 454 return point 455 end 456 else 457 return nil 458 end 459 else 460 return point 461 end 462end 463 464function iter.point_iterator:iter () 465 return function() return self:next() end, nil, nil 466end 467 468-- An easier and more posh way of interfacing with find_marker_positions_by_prop. 469function iter.slave_iterator (prop, value) 470 local ptable = dgn.find_marker_positions_by_prop(prop, value) 471 if #ptable == 0 then 472 error("Didn't find any props for " .. prop .. "=" .. value) 473 else 474 return iter.point_iterator:new(ptable) 475 end 476end 477 478--- Inventory iterator. 479-- Iterates over tables of @{items.Item} objects, possibly filtering them. 480-- @usage `for item in iter.invent_iterator() 481-- if item.is_useless then 482-- item:drop() 483-- end 484-- end` 485-- @type iter.invent_iterator 486 487iter.invent_iterator = {} 488 489function iter.invent_iterator:_new () 490 local m = {} 491 setmetatable(m, self) 492 self.__index = self 493 return m 494end 495 496--- Create a new inventory iterator 497-- @param itable the inventory table 498-- @param[opt] filter filter function 499-- @param[optchain=false] rv_instead return the rv of the filter instead 500function iter.invent_iterator:new (itable, filter, rv_instead) 501 if itable == nil then 502 error("itable cannot be nil for invent_iterator") 503 end 504 505 local mt = iter.invent_iterator:_new() 506 mt.cur_p = 0 507 mt.table = itable 508 mt.rvi = rv_instead or false 509 mt.filter = filter or nil 510 511 return mt:iter() 512end 513 514--- Advance the iterator 515function iter.invent_iterator:next() 516 local point = nil 517 local q = 0 518 repeat 519 q = q + 1 520 self.cur_p = self.cur_p + 1 521 point = self:check_filter(self.table[self.cur_p]) 522 until point or q == 10 523 524 return point 525end 526 527--- Check an item agains the iterator's filter 528-- @param item 529-- @return either the item or filter(item) if rv is asked for 530function iter.invent_iterator:check_filter(item) 531 if self.filter ~= nil then 532 if self.filter(item) then 533 if self.rvi then 534 return self.filter(item) 535 else 536 return item 537 end 538 else 539 return nil 540 end 541 else 542 return item 543 end 544end 545 546function iter.invent_iterator:iter () 547 return function() return self:next() end, nil, nil 548end 549 550--- An easier and more posh way of interfacing with inventory. 551function iter.inventory_iterator () 552 return iter.invent_iterator:new(items.inventory()) 553end 554 555function iter.stash_iterator (point, y) 556 if y == nil then 557 return iter.invent_iterator:new(dgn.items_at(point.x, point.y)) 558 else 559 return iter.invent_iterator:new(dgn.items_at(point, y)) 560 end 561end 562