Description
This is a mini-library for dynamically adding and removing special areas around your character. I do not provide an actual persistent, but in practice you would likely call the adding function when you either stop luring, or acquire a target and then you would call the removing function when you either restart luring or kill your target or hasn't had a target for X milliseconds or something like that. The fact that there are many different ways to do it, and they are all suitable under different circumstances, is why I left out the persistent part and only provide the init block.
API
- addDynamicAreas(int range, int minNeighbours, boolean removeNarrowPassages)
Adds special areas in a rectangle at range distance away from your character. Also adds areas in the four corners of said rectangle. It also adds special areas on all squares inside said rectangle that has less than minNeighbours walkable neighbours. Squares with special areas are not considerable walkable, but squares with creatures or moveable furniture is considered walkable. If removeNarrowPassages is truthy then squares whose both horizontal or vertical neighbours are unwalkable will also be removed.- removeDynamicAreas()
Removes all special areas that have been added since the first call to addDynamicAreas after the last call to removeSpecialAreas (if any). If you didn't understand that, then just make sure that you don't add any new areas while there are dynamic areas active, or those will later be removed when removeDynamicAreas is called. If you call addDynamicAreas multiple times then all those areas will be removed when you call removeDynamicAreas.
Examples
This is the basic shape of the dynamic special areas. In this picture the range parameter is set to 5. In practice you might want it to be higher than that, but I wanted everything to fit on the screen for the sake of the screenshot.
The script also removes squares with too few walkable neighbours. Here I've set the minNeighbours parameter to 3. Note that unwalkable squares don't necessarily get special areas on them (since those areas does nthing) which explains the shape of the two lefternmost pictures.
The script is also able to take previously existing special areas into account when it considers which squares that should be removed. Here you can also see what the removeNarrowPassages parameter does. See that square in the bottom right quadrant in the left screenshot that's pinned between a long horizontal area to the left, and a single square area to the right? It has 6 walkable neighbours so it should not have been removed based on the minNeighbours parameter (which was still set to 3). However, that square had an unwalkable square both to the right of it, and to the left so the script considered it a "narrow passage" and removed it. If removeNarrowPassages had not been truthy it would not have been removed.
Let me end with a zoomed out picture with range set to 9, minNeighbours to 4 and removeNarrowPassages set to true, which are settings that I would use in practice in open areas:
init start
local safelist
function addDynamicAreas(range, minNeighbours, removeNarrowPassages)
local range = range or 5
local minNeighbours = minNeighbours or 3
if not safelist then
safelist = {}
foreach settingsentry e "Cavebot/SpecialAreas" do
safelist[get(e, "Name")] = true
end
end
-- Add a basic rectangle. We're using 1 square areas
-- since the library api doesn't support a width or height parameter.
for x = $posx - range, $posx + range do
addspecialarea("Targeting", x, $posy - range, $posz)
addspecialarea("Targeting", x, $posy + range, $posz)
end
for y = $posy - range, $posy + range do
addspecialarea("Targeting", $posx - range, y, $posz)
addspecialarea("Targeting", $posx + range, y, $posz)
end
-- First we need some helpers to keep track of which squares that have
-- been removed with areas.
local function posToNum(x, y)
return 100000*x+y
end
local walkables = {}
local candidates = {}
for x = $posx - range + 1, $posx + range - 1 do
for y = $posy - range + 1, $posy + range - 1 do
candidates[posToNum(x, y)] = {x = x, y = y}
local top = iteminfo(topitem(x, y, $posz).id)
if tilewalkable(x, y, $posz) and not (top.isunpass and top.isunmove) then
walkables[posToNum(x, y)] = true
end
end
end
-- Add creature squares to the walkable list. Even if we cant walk on them they might move so we should
-- not purge squares based on their current position
foreach creature c 's' do
walkables[posToNum(c.posx, c.posy)] = true
end
-- Remove all squares already inside a special area from the walkable set
foreach settingsentry e "Cavebot/SpecialAreas" do
if get(e, "Policy"):find("Targeting") then
local x, y, z = get(e, "Coordinates"):match(REGEX_COORDS)
local w, h = get(e, "Size"):match(REGEX_RANGE)
for ax = x, x + w - 1 do
for ay = y, y + h - 1 do
walkables[posToNum(ax, ay)] = nil
end
end
end
end
local function addArea(x, y)
addspecialarea("Targeting", x, y, $posz)
walkables[posToNum(x, y)] = nil
end
-- Remove the corners because we know that those are bad!
addArea($posx - range + 1, $posy - range + 1)
addArea($posx - range + 1, $posy + range - 1)
addArea($posx + range - 1, $posy - range + 1)
addArea($posx + range - 1, $posy + range - 1)
function wneighbours(x, y)
local t = {}
local n = 0
for nx = x - 1, x + 1 do
for ny = y - 1, y + 1 do
if walkables[posToNum(nx, ny)] and (nx ~= x or ny ~= y) then
t[posToNum(nx, ny)] = {x = nx, y = ny}
n = n + 1
end
end
end
return t, n
end
-- Now we want to iteratively add areas on all squares with less than minNeighbours
-- walkable neighbours.
while candidates do
local newCandidates
for key, pos in pairs(candidates) do
local neighbours, nneighbours = wneighbours(pos.x, pos.y)
local narrowCond = removeNarrowPassages and
((not (neighbours[posToNum(pos.x - 1, pos.y)] or neighbours[posToNum(pos.x + 1, pos.y)])) or
(not (neighbours[posToNum(pos.x, pos.y - 1)] or neighbours[posToNum(pos.x, pos.y + 1)])))
-- Purge squares with less than minNeighbours walkable neighbours, and possibly also narrow passages!
if nneighbours < minNeighbours or narrowCond then
addArea(pos.x, pos.y)
newCandidates = newCandidates or {}
for key, npos in pairs(neighbours) do
newCandidates[key] = npos
end
end
end
candidates = newCandidates
end
end
function removeDynamicAreas()
if not safelist then return end
-- removespecialarea is bugged and does not always succeed so we
-- loop until everything is gone.
local changed = true
while changed do
changed = false
foreach settingsentry e "Cavebot/SpecialAreas" do
local name = get(e, "Name")
if not safelist[name] then
removespecialarea(name)
changed = true
end
end
end
safelist = nil
end
init end