Advertisement
Larvix

blockMap

Jan 19th, 2024 (edited)
1,352
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.93 KB | None | 0 0
  1. -- Data collection functions
  2. local scan = {}
  3.  
  4. scan.clean = function(rawScan, posX, posY, posZ)
  5.     local myPos = vector.new(0,0,0)
  6.     if type(posX) == "number" then
  7.         myPos.x = posX
  8.     end
  9.     if type(posY) == "number" then
  10.         myPos.y = posY
  11.     end
  12.     if type(posZ) == "number" then
  13.         myPos.z = posZ
  14.     end
  15.     local cleanedScan = {}
  16.     for i = 1,#rawScan do
  17.         local name = rawScan[i].name or ""
  18.         if name:find(":") then name = name:sub(name:find(":")+1,#name) end
  19.         rawScan[i].name = name
  20.         if rawScan[i].name == "air" then
  21.             rawScan[i].name = ""
  22.         elseif rawScan[i].name == "concrete" or rawScan[i].name == "concrete_powder" or rawScan[i].name == "wool" or rawScan[i].name == "planks" or rawScan[i].name == "log" then
  23.             if rawScan[i].state and rawScan[i].state.color then
  24.                 rawScan[i].name = rawScan[i].name .. "_" .. rawScan[i].state.color
  25.             elseif rawScan[i].state and rawScan[i].state.variant then
  26.                 rawScan[i].name = rawScan[i].name .. "_" .. rawScan[i].state.variant
  27.             end
  28.         end
  29.         local pos = vector.new(rawScan[i].x,rawScan[i].y,rawScan[i].z)
  30.         pos = pos + myPos
  31.         cleanedScan[pos.y] = cleanedScan[pos.y] or {}
  32.         cleanedScan[pos.y][pos.x] = cleanedScan[pos.y][pos.x] or {}
  33.         cleanedScan[pos.y][pos.x][pos.z] = rawScan[i].name
  34.     end
  35.     return cleanedScan
  36. end
  37.  
  38. scan.scanner = function(posX, posY, posZ)
  39.     local rawScan = {}
  40.     local scanner = peripheral.find("plethora:scanner")
  41.     if not scanner then local modular = peripheral.find("manipulator") or peripheral.find("neuralInterface") end
  42.     if modular and modular.hasModule("plethora:scanner") then scanner = modular end
  43.     if not scanner then
  44.         error("'.scanner()' requires Plethora Block Scanner. Try using '.inspect()' if unavailable.")
  45.     else
  46.         rawScan = scanner.scan()
  47.     end
  48.     local cleanedScan = scan.clean(rawScan, posX, posY, posZ)
  49.     return cleanedScan
  50. end
  51.  
  52. -- Use a turtle to scan blocks around it
  53. -- Returns absolute position if turtle co-ordinates are given, or relative if not
  54. -- Requires direction turtle is facing
  55. -- 'inspectAll' boolean determines whether the turtle will spin to get the remaining 3 blocks.
  56. scan.inspect = function(facing, inspectAll, posX, posY, posZ)
  57.     local rawScan = {}
  58.     if not turtle then
  59.         error("Mode 'inspect' requires device to be a turtle.")
  60.     elseif facing ~= "north" and facing ~= "east" and facing ~= "south" and facing ~= "west" then
  61.         error("Mode 'inspect' requires direction turtle is currently facing (e.g. 'north')")
  62.     elseif type(inspectAll) ~= "nil" and type(inspectAll) ~= "boolean" then
  63.         error("Mode 'inspect' Argument #2 expects boolean, for whether to spin and get all surrounding blocks.")
  64.     else
  65.         local blockPos = {
  66.             ["north"] = vector.new( 0, 0,-1),
  67.             ["east"]  = vector.new( 1, 0, 0),
  68.             ["south"] = vector.new( 0, 0, 1),
  69.             ["west"]  = vector.new(-1, 0, 0),
  70.         }
  71.  
  72.         rawScan[1] = {
  73.             x = 0,
  74.             y = 0,
  75.             z = 0,
  76.             name = "turtle"
  77.         }
  78.  
  79.         rawScan[2] = {turtle.inspectUp()}
  80.         if not rawScan[2][1] then rawScan[2][2] = {} end
  81.         rawScan[2] = rawScan[2][2]
  82.         rawScan[2].x = 0
  83.         rawScan[2].y = 1
  84.         rawScan[2].z = 0
  85.  
  86.         rawScan[3] = {turtle.inspectDown()}
  87.         if not rawScan[3][1] then rawScan[3][2] = {} end
  88.         rawScan[3] = rawScan[3][2]
  89.         rawScan[3].x = 0
  90.         rawScan[3].y = -1
  91.         rawScan[3].z = 0
  92.  
  93.         rawScan[4] = {turtle.inspect()}
  94.         if not rawScan[4][1] then rawScan[4][2] = {} end
  95.         rawScan[4] = rawScan[4][2]
  96.         rawScan[4].x = blockPos[facing].x
  97.         rawScan[4].y = blockPos[facing].y
  98.         rawScan[4].z = blockPos[facing].z
  99.         if inspectAll == true then
  100.             for i = 5,7 do
  101.                 turtle.turnRight()
  102.                 if facing == "north" then facing = "east"
  103.                 elseif facing == "east" then facing = "south"
  104.                 elseif facing == "south" then facing = "west"
  105.                 elseif facing == "west" then facing = "north" end
  106.                 rawScan[i] = {turtle.inspect()}
  107.                 if not rawScan[i][1] then rawScan[i][2] = {} end
  108.                 rawScan[i] = rawScan[i][2]
  109.                 rawScan[i].x = blockPos[facing].x
  110.                 rawScan[i].y = blockPos[facing].y
  111.                 rawScan[i].z = blockPos[facing].z
  112.             end
  113.             turtle.turnRight()
  114.         end
  115.     end
  116.     local cleanedScan = scan.clean(rawScan, posX, posY, posZ)
  117.     return cleanedScan
  118. end
  119.  
  120. -- Map utility functions
  121. local map = {}
  122.  
  123. -- Convert raw map data into two tables: Blocks and air cluster
  124. map.compress = function(mapData)
  125.     -- Sort the co-ordinates by size
  126.     local coordinates = {}
  127.     for y, xzTbl in pairs(mapData) do
  128.         for x, zTbl in pairs(mapData[y]) do
  129.             for z, _ in pairs(mapData[y][x]) do
  130.                 table.insert(coordinates, { y = y, x = x, z = z })
  131.             end
  132.         end
  133.     end
  134.     table.sort(coordinates, function(a, b)
  135.         if a.y ~= b.y then
  136.             return a.y < b.y
  137.         elseif a.x ~= b.x then
  138.             return a.x < b.x
  139.         else
  140.             return a.z < b.z
  141.         end
  142.     end)
  143.  
  144.     -- Split the sorted map into maps for air and blocks
  145.     local airClusterMap = {}
  146.     local discoveredMap = {}
  147.     local currentAirCluster = nil
  148.     local last = {y = nil, x = nil, z = nil}
  149.  
  150.     for _, coord in ipairs(coordinates) do
  151.         local y, x, z = coord.y, coord.x, coord.z
  152.         if currentAirCluster and y == last.y and x == last.x and z == last.z + 1 and mapData[y][x][z] == "" then
  153.             currentAirCluster.e = z
  154.         elseif currentAirCluster then
  155.             airClusterMap[last.y] = airClusterMap[last.y] or {}
  156.             airClusterMap[last.y][last.x] = airClusterMap[last.y][last.x] or {}
  157.             currentAirCluster.e = last.z       
  158.             table.insert(airClusterMap[last.y][last.x], currentAirCluster)
  159.             currentAirCluster = nil
  160.         end
  161.         if not currentAirCluster and mapData[y][x][z] == "" then   
  162.             currentAirCluster = { s = z }
  163.         elseif mapData[y][x][z] ~= "" then
  164.             discoveredMap[y] = discoveredMap[y] or {}
  165.             discoveredMap[y][x] = discoveredMap[y][x] or {}
  166.             discoveredMap[y][x][z] = mapData[y][x][z]
  167.         end
  168.         last = {y = y, x = x, z = z}
  169.     end
  170.     if currentAirCluster then
  171.         airClusterMap[last.y] = airClusterMap[last.y] or {}
  172.         airClusterMap[last.y][last.x] = airClusterMap[last.y][last.x] or {}
  173.         currentAirCluster.e = last.z       
  174.         table.insert(airClusterMap[last.y][last.x], currentAirCluster)
  175.     end
  176.     local deflatedMap = {airClusterMap, discoveredMap}
  177.     return deflatedMap
  178. end
  179.  
  180. -- Convert a compressed map back into the raw format
  181. map.decompress = function(mapData)
  182.     local inflatedMap = {}
  183.     if #mapData == 2 then
  184.         local airClusterMap, discoveredMap = unpack(mapData)
  185.         if airClusterMap and discoveredMap then
  186.             for y, clusterMapX in pairs(airClusterMap) do
  187.                 inflatedMap[y] = inflatedMap[y] or {}
  188.                 for x, clusterList in pairs(airClusterMap[y]) do
  189.                     inflatedMap[y][x] = inflatedMap[y][x] or {}
  190.                     for clusters, cluster in pairs(airClusterMap[y][x]) do
  191.                         for z = cluster.s, cluster.e do
  192.                             inflatedMap[y][x][z] = ""
  193.                         end
  194.                     end
  195.                 end
  196.             end
  197.             for y, discoveredXZ in pairs(discoveredMap) do
  198.                 inflatedMap[y] = inflatedMap[y] or {}
  199.                 for x, discoveredZ in pairs(discoveredMap[y]) do
  200.                     inflatedMap[y][x] = inflatedMap[y][x] or {}
  201.                     for z, blockName in pairs(discoveredMap[y][x]) do
  202.                         inflatedMap[y][x][z] = blockName
  203.                     end
  204.                 end
  205.             end
  206.         end
  207.     end
  208.     return inflatedMap
  209. end
  210.  
  211. -- Combine two maps, overlaps treat old map as outdated
  212. map.update = function(oldMap, newMap)
  213.     local updatedMap = nil
  214.     if type(oldMap) == "table" and type(newMap) == "table" then
  215.         for y,xzTbl in pairs(newMap) do
  216.             if oldMap[y] and type(newMap[y]) == "table" then
  217.                 for x,row in pairs(newMap[y]) do
  218.                     if oldMap[y][x] and type(newMap[y][x]) == "table" then
  219.                         for z,block in pairs(newMap[y][x]) do
  220.                             oldMap[y][x][z] = block
  221.                         end
  222.                     else
  223.                         oldMap[y][x] = row
  224.                     end
  225.                 end
  226.             else
  227.                 oldMap[y] = xzTbl
  228.             end
  229.         end
  230.         updatedMap = oldMap
  231.     end
  232.     return updatedMap
  233. end
  234.  
  235. -- Return a table of map data trimmed for easy map drawing
  236. map.draw = function(mapTable, tileset, radius, midY, midX, midZ)
  237.     if not mapTable then mapTable = {} end
  238.     if not tileset then tileset = {} end
  239.     if radius and type(radius) == "number" then
  240.         radiusX = math.abs(radius)
  241.         radiusZ = math.abs(radius)
  242.     end
  243.     local maxX,maxZ = term.current().getSize()
  244.     if not radiusX or (radiusX*2)+1 > maxX then
  245.         radiusX = (maxX / 2)
  246.         if radiusX > math.floor(radiusX) then radiusX = math.floor(radiusX)
  247.         else radiusX = radiusX - 1 end
  248.     end
  249.     if not radiusZ or (radiusZ*2)+1 > maxZ then
  250.         radiusZ = (maxZ / 2)
  251.         if radiusZ > math.floor(radiusZ) then radiusZ = math.floor(radiusZ)
  252.         else radiusZ = radiusZ - 1 end
  253.     end
  254.     local mid = vector.new(0,0,0)
  255.     if midY and type(midY) == "number" then
  256.         mid.y = midY
  257.     end
  258.     if midX and type(midX) == "number" then
  259.         mid.x = midX
  260.     end
  261.     if midZ and type(midZ) == "number" then
  262.         mid.z = midZ
  263.     end
  264.     local drawnMap = {}
  265.     for x = 1, 1+(2*radiusX) do
  266.         if not drawnMap[x] then drawnMap[x] = {} end
  267.         for z = 1, 1+(2*radiusZ) do
  268.             if not drawnMap[x][z] then drawnMap[x][z] = {} end
  269.             drawnMap[x][z].x = mid.x + x - 1 - radiusX
  270.             drawnMap[x][z].y = mid.y
  271.             drawnMap[x][z].z = mid.z + z - 1 - radiusZ
  272.             if mapTable[mid.y] and mapTable[mid.y][drawnMap[x][z].x] and mapTable[mid.y][drawnMap[x][z].x][drawnMap[x][z].z] then
  273.                 drawnMap[x][z].name = mapTable[mid.y][drawnMap[x][z].x][drawnMap[x][z].z]
  274.             else
  275.                 drawnMap[x][z].name = "unknown"
  276.             end
  277.             if drawnMap[x][z].name == "" then drawnMap[x][z].name = "air" end
  278.             if tileset[drawnMap[x][z].name] then
  279.                 if tileset[drawnMap[x][z].name].tile then
  280.                     drawnMap[x][z].tile = tileset[drawnMap[x][z].name].tile
  281.                 end
  282.                 if tileset[drawnMap[x][z].name].fg then
  283.                     drawnMap[x][z].fg = tileset[drawnMap[x][z].name].fg
  284.                 end
  285.                 if tileset[drawnMap[x][z].name].bg then
  286.                     drawnMap[x][z].bg = tileset[drawnMap[x][z].name].bg
  287.                 end
  288.             end
  289.         end
  290.     end
  291.     return drawnMap
  292. end
  293.  
  294. local isDead = function(node)
  295.     for _,dead in pairs(deadNodes) do
  296.         if node.y == dead.y and node.x == dead.x and node.z == dead.z then
  297.             return true
  298.         end
  299.     end
  300.     return false
  301. end
  302.  
  303. local isVisited = function(node)
  304.     for _,existing in pairs(path) do
  305.         if node.y == existing.y and node.x == existing.x and node.z == existing.z then
  306.             return true
  307.         end
  308.     end
  309.     return false
  310. end
  311.  
  312. local heuristic = function(node, goal)
  313.     return math.abs(goal.x - node.x) + math.abs(goal.y - node.y) + math.abs(goal.z - node.z)
  314. end
  315.  
  316. local findNext = function(current, goal, previous)
  317.     local pNeighbours = {
  318.         {x = current.x + 1, y = current.y, z = current.z},
  319.         {x = current.x - 1, y = current.y, z = current.z},
  320.         {x = current.x, y = current.y, z = current.z + 1},
  321.         {x = current.x, y = current.y, z = current.z - 1},
  322.         {x = current.x, y = current.y + 1, z = current.z},
  323.         {x = current.x, y = current.y - 1, z = current.z},
  324.     }
  325.     local neighbours = {}
  326.  
  327.     for k, v in pairs(pNeighbours) do
  328.         if mappedBlocks[v.y] and mappedBlocks[v.y][v.x] and mappedBlocks[v.y][v.x][v.z] == "" then
  329.             local cost = 1 -- Assuming each movement has a cost of 1 for simplicity
  330.             table.insert(neighbours, {x = v.x, y = v.y, z = v.z, cost = cost})
  331.         end
  332.     end
  333.  
  334.     table.sort(neighbours, function(a, b)
  335.         return (a.cost + heuristic(a, goal)) < (b.cost + heuristic(b, goal))
  336.     end)
  337.  
  338.     if #neighbours == 0 then
  339.         table.insert(deadNodes, current)
  340.     end
  341.  
  342.     return neighbours
  343. end
  344.  
  345. local isValid = function(start,goal)
  346.     if mappedBlocks[goal.y] and mappedBlocks[goal.y][goal.x] and mappedBlocks[goal.y][goal.x][goal.z] == "" then
  347.         if #findNext(goal,start) > 0 and #findNext(start,goal) > 0 then
  348.             --textutils.slowPrint(textutils.serialise(findNext(start,goal)))
  349.             return true
  350.         end
  351.     end
  352.     return false
  353. end
  354.  
  355. local findPath = function(start, goal, timeout)
  356.     local time = os.clock() + timeout
  357.     local found = false
  358.     local exploredNodesCount = 0
  359.     local maxStaleIterations = 10 -- Adjust this value based on your needs
  360.  
  361.     if isValid(start, goal) then
  362.         while not isDead(start) and not found and os.clock() < time do
  363.             if (path[#path].x == goal.x and path[#path].y == goal.y and path[#path].z == goal.z) then
  364.                 found = true
  365.                 break
  366.             end
  367.  
  368.             os.queueEvent("yield")
  369.             os.pullEvent("yield")
  370.             local next = findNext(path[#path], goal, path[#path - 1])
  371.  
  372.             if #next == 0 then
  373.                 table.remove(path, #path)
  374.             else
  375.                 local newNode = false
  376.  
  377.                 for k, v in pairs(next) do
  378.                     if not isVisited(v) then
  379.                         newNode = true
  380.                         table.insert(path, v)
  381.                         exploredNodesCount = exploredNodesCount + 1
  382.                         break
  383.                     end
  384.                 end
  385.  
  386.                 if not newNode then
  387.                     table.insert(deadNodes, path[#path])
  388.                     table.remove(path, #path)
  389.                 end
  390.             end
  391.  
  392.             -- Check for staleness and break out of the loop if no progress is made
  393.             if exploredNodesCount > 0 then
  394.                 exploredNodesCount = 0
  395.             else
  396.                 maxStaleIterations = maxStaleIterations - 1
  397.                 if maxStaleIterations <= 0 then
  398.                     break
  399.                 end
  400.             end
  401.         end
  402.     end
  403.  
  404.     return found
  405. end
  406.  
  407. local function isNeighbor(node1, node2)
  408.     local dx = math.abs(node2.x - node1.x)
  409.     local dy = math.abs(node2.y - node1.y)
  410.     local dz = math.abs(node2.z - node1.z)
  411.  
  412.     return (dx == 1 and dy == 0 and dz == 0) or
  413.            (dx == 0 and dy == 1 and dz == 0) or
  414.            (dx == 0 and dy == 0 and dz == 1)
  415. end
  416.  
  417. local function smoothPath(path)
  418.     local smoothPath = {path[1]}
  419.     local i = 1
  420.  
  421.     while i < #path - 1 do
  422.         local j = i + 1
  423.         local straightLine = isNeighbor(path[i], path[j])
  424.  
  425.         while j <= #path do
  426.             if not mappedBlocks[path[j].y] or not mappedBlocks[path[j].y][path[j].x] or not mappedBlocks[path[j].y][path[j].x][path[j].z] == "" then
  427.                 break
  428.             end
  429.  
  430.             local canSee = true
  431.  
  432.             for k = i + 1, j - 1 do
  433.                 if not isDead(path[k]) then
  434.                     canSee = false
  435.                     break
  436.                 end
  437.             end
  438.  
  439.             if canSee then
  440.                 if not straightLine or isNeighbor(path[j - 1], path[j]) then
  441.                     smoothPath[#smoothPath + 1] = path[j]
  442.                 end
  443.                 i = j - 1
  444.                 break
  445.             end
  446.  
  447.             j = j + 1
  448.             straightLine = isNeighbor(path[i], path[j])
  449.         end
  450.  
  451.         i = i + 1
  452.     end
  453.  
  454.     -- Ensure the smoothed path includes the end point
  455.     smoothPath[#smoothPath + 1] = path[#path]
  456.  
  457.     return smoothPath
  458. end
  459.  
  460. local function removeDetours(path)
  461.     local i = 1
  462.  
  463.     while i <= #path - 2 do
  464.         local j = #path
  465.  
  466.         while j > i + 1 do
  467.             if isNeighbor(path[i], path[j]) then
  468.                 -- Nodes i and j are neighbors, remove the intermediate nodes
  469.                 for k = i + 1, j - 1 do
  470.                     table.remove(path, i + 1)
  471.                 end
  472.                 j = i + 2
  473.             else
  474.                 j = j - 1
  475.             end
  476.         end
  477.  
  478.         i = i + 1
  479.     end
  480.  
  481.     return path
  482. end
  483.  
  484. -- Find a route between two points using a given map
  485. mappedBlocks = {}
  486. map.pathfind = function(mapData, startX, startY, startZ, endX, endY, endZ, timeout)
  487.     mappedBlocks = mapData
  488.     local start = {x = tonumber(startX), y = tonumber(startY), z = tonumber(startZ)}
  489.     local goal = {x = tonumber(endX), y = tonumber(endY), z = tonumber(endZ)}
  490.     local timer = tonumber(timeout) or 5
  491.     path = {start}
  492.     deadNodes = {}
  493.     local hasPath = findPath(start,goal,timer)
  494.     if hasPath then
  495.         findPath(goal,start,timer)
  496.         path = removeDetours(path)
  497.         path = smoothPath(path)
  498.         foundPath = ""
  499.         for i = 2,#path do
  500.             local x = path[i].x - path[i-1].x
  501.             local y = path[i].y - path[i-1].y
  502.             local z = path[i].z - path[i-1].z
  503.             if x > 0 then foundPath = foundPath.."e"
  504.             elseif x < 0 then foundPath = foundPath.."w"
  505.             elseif z > 0 then foundPath = foundPath.."s"
  506.             elseif z < 0 then foundPath = foundPath.."n"
  507.             elseif y > 0 then foundPath = foundPath.."u"
  508.             elseif y < 0 then foundPath = foundPath.."d" end
  509.         end
  510.         return foundPath
  511.     end
  512.     return hasPath
  513. end
  514.  
  515. map.query = function(mapData,blockName)
  516.     local qResult = {}
  517.     for y,_ in pairs(mapData) do
  518.         for x,_ in pairs(mapData[y]) do
  519.             for z,block in pairs(mapData[y][x]) do
  520.                 if block == blockName then
  521.                     table.insert(qResult, {x = x, y = y, z = z})
  522.                 end
  523.             end
  524.         end
  525.     end
  526.     return qResult
  527. end
  528.  
  529. return {
  530. inspect = scan.inspect,
  531. scanner = scan.scanner,
  532. compress = map.compress,
  533. decompress = map.decompress,
  534. update = map.update,
  535. create = map.draw,
  536. path = map.pathfind,
  537. query = map.query,
  538. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement