Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ----------------------------------------------------------------------
- -- Module API --
- ----------------------------------------------------------------------
- -- Version 0.1a
- -- Module is an API that allows to interact with master and perform next functions:
- -- Communication to the master.
- -- Navigation to the said point.
- -- Request tasks and execute functions using a response table.
- -- Usage:
- -- 1) Set up a task table using the module.addTasks(taskTable) function
- -- taskTable is a table with tasks as keys (tasks are recived from Master)
- -- and functions as values. Function should accept a response as an argument,
- -- so master can pass some additional data to module.
- -- Those functions are executed when module recieved an associated task after module has navigated to
- -- the position sent in response (if any)
- -- 2) Set the type of your module using the setType (newType) function
- -- Type is used by Master to determine what can this module do.
- -- 3) Run the module.init () function
- -- 4) Run the module.operate() function to start
- -- 5) If you move the module in your tasks, please use module.move(direction) or module.moveEx (f) to move it
- -- and use turn(side) or turnTo(f) to turn it, because those functions are keep tracking the module's position
- -- or you can find its new position by yourself and use module.setPosition (x,y,z,f) to set it
- --[[
- *********************************************************************************************
- * Navigation Part *
- *********************************************************************************************
- ]]--
- -- Location is a global table containing all the coordinates and facing direction of the turtle
- -- And here is the function to set the position of the turtle and another one to get it.
- location = {x = 0, y = 0, z = 0, f = 0}
- function setPosition (x, y, z, f)
- location.x = x
- location.y = y
- location.z = z
- location.f = f
- end
- function getPosition ()
- return { x = location.x,
- y = location.y,
- z = location.z,
- f = location.f, } -- return a copy of the location table.
- end
- -- And a small function to print the position. For debug purposes
- function printPosition ()
- print ("Currrent turtle position:")
- print (" x = "..location.x)
- print (" y = "..location.y)
- print (" z = "..location.z)
- print (" f = "..location.f)
- end
- -- A turning function
- function turn (direction)
- if direction == "left" then
- turtle.turnLeft()
- location.f = location.f - 1
- elseif direction == "right" then
- turtle.turnRight()
- location.f = location.f + 1
- elseif direction == "around" then
- turtle.turnLeft()
- turtle.turnLeft()
- location.f = location.f - 2
- else
- print ("No such direction: "..direction)
- return false
- end
- location.f = (location.f + 4) % 4 -- This is used to handle the f < 0 and f > 3 situations
- return true
- end
- -- This one is used to turn the turtle to specified f direction.
- -- A little bit of pony magic is used to calculate how should it turn
- function turnTo (face)
- local diff = face - location.f
- if math.abs(diff) == 2 then --this is true if the difference between f and face is 2 or -2, so it should turn around
- return turn ("around")
- elseif math.fmod(diff + 4, 4) == 1 then --this is true if the difference between f and face is 1
- return turn ("right") --f = 0 and face = 3 is also satisfies the condition (-3 + 4 == 1)
- elseif math.fmod(diff - 4, 4) == -1 then --this is true if the difference between f and face is -1
- return turn ("left") --f = 3 and face = 1 is also satisfies the condition ( 3 - 4 == 1)
- end
- return false --returned if turtle is already faced the specified direction
- end
- -- This function is used to return actual x and z shifts
- -- These shifts are the numbers which you should add to the coordinates when turtle moves forward.
- -- I use a table to decode it since it is more compact and easier to read than lots of conditions
- local tShifts = {
- [0] = { 1, 0},
- [1] = { 0, 1},
- [2] = {-1, 0},
- [3] = { 0, -1},
- }
- function fDirection ()
- return unpack (tShifts[location.f])
- end
- -- This function is used to update the turtle location when it has moved
- function updateLocation (direction)
- if direction == "up" then
- location.y = location.y + 1
- elseif direction == "down" then
- location.y = location.y - 1
- elseif direction == "forward" then
- local xShift, zShift = fDirection ()
- location.x = location.x + xShift
- location.z = location.z + zShift
- elseif direction == "back" then
- local xShift, yShift = fDirection ()
- location.x = location.x - xShift
- location.z = location.z - zShift
- end
- end
- -- This function helps us with our EnderChests if there is something that obstructs placement
- -- It checks what's going on on top of it, and if there is a turtle, it waits before digging.
- function forcePlaceUp ()
- while not turtle.placeUp() do
- if turtle.detectUp() then
- if peripheral.isPresent ("top") and peripheral.getType ("top") == "turtle" then
- print "Other turtle detected. Waiting..."
- while peripheral.isPresent ("top") do
- sleep (5)
- end
- end
- turtle.digUp()
- elseif turtle.attackUp() then
- print "Attacking obstructing entity"
- end
- end
- end
- -- This function tries to refuel the turtle to make it have an <amount> of fuel
- -- It assumes that turtle has a refuel ender chest in slot 1
- function refuel (amount)
- if turtle.getFuelLevel () > amount then
- return true
- else
- turtle.select (1)
- forcePlaceUp ()
- turtle.suckUp ()
- while turtle.refuel (1) do
- if turtle.getFuelLevel() >= amount then
- turtle.dropUp ()
- turtle.digUp ()
- return true
- end
- if turtle.getItemCount (1) == 0 then
- turtle.suckUp ()
- end
- end
- end
- turtle.dropUp ()
- turtle.digUp ()
- return false
- end
- -- This function checks if there are required amount of fuel,
- -- and if there's not, it tries to refuel it,
- -- if it fails, it hangs until refueling sucseeds
- function checkFuel (amount)
- if turtle.getFuelLevel () > amount then
- return true
- else
- if not refuel (amount) then
- print "No fuel found! Please, add more fuel!"
- while not refuel (amount) do
- sleep (2)
- end
- end
- return true
- end
- end
- -- This function used to empty the turtle's inventory into the ender chest in the second slot
- -- Warning! Required a good sorting system able to empty the chest as quickly as possible!
- -- It will hang if chest is full, but it's not recommended
- function dumpStuff ()
- turtle.select (2)
- forcePlaceUp ()
- for i = 3,16 do
- while turtle.getItemCount (i) > 0 do
- turtle.select (i)
- turtle.dropUp ()
- end
- end
- turtle.select (2)
- turtle.digUp ()
- turtle.select (1)
- end
- function checkSpace ()
- if turtle.getItemCount (16) > 0 then
- dumpStuff ()
- end
- end
- -- Advanced movement function.
- -- Again, I use a set of tables with all the movements and other "directional" turtle actions
- -- The fuction detects if there is a turtle in the direction it wants to move, and if there is one, it waits while it moves away.
- -- If it waits too long (60 seconds), however, it just eats that turtle
- tMove = {
- forward = turtle.forward,
- up = turtle.up,
- down = turtle.down
- }
- tDetect = {
- forward = turtle.detect,
- up = turtle.detectUp,
- down = turtle.detectDown
- }
- tDig = {
- forward = turtle.dig,
- up = turtle.digUp,
- down = turtle.digDown
- }
- tAttack = {
- forward = turtle.attack,
- up = turtle.attackUp,
- down = turtle.attackDown
- }
- tIsTurtle = {
- forward = function ()
- if peripheral.isPresent ("front") and peripheral.getType ("front") == "turtle" then
- return true
- end
- return false
- end,
- up = function ()
- if peripheral.isPresent ("top") and peripheral.getType ("top") == "turtle" then
- return true
- end
- return false
- end,
- down = function ()
- if peripheral.isPresent ("bottom") and peripheral.getType ("bottom") == "turtle" then
- return true
- end
- return false
- end
- }
- function move (direction)
- if not checkFuel (1) then
- return false
- end
- while not tMove[direction]() do
- if tDetect[direction]() then
- if tIsTurtle[direction]() then
- print "Other turtle detected. Waiting..."
- while tIsTurtle[direction]() do
- sleep (5)
- end
- else
- if tDig[direction]() then
- checkSpace ()
- else
- print "Can't pass the obstruction!"
- return false
- end
- end
- elseif tAttack[direction]() then
- checkSpace ()
- end
- end
- updateLocation (direction)
- return true
- end
- -- This one is used to move turtle to specified f direction OR up/down <count times>
- function moveEx (direction, count)
- if type(direction) == "number" then
- turnTo (direction)
- for i = 1, count do
- move ("forward")
- end
- else
- for i = 1, count do
- move (direction)
- end
- end
- end
- -- A height function. For convinience
- function height (value)
- local diff = value - location.y
- refuel(math.abs(diff))
- if diff > 0 then
- moveEx ("up", diff)
- elseif diff < 0 then
- moveEx ("down", -diff)
- end
- end
- -- Small goto function. It moves turtle in one axis.
- -- Example: module.gotoOne ("x", 10) moves the turtle to x = 10
- function gotoOne (coordinate, value)
- if coordinate == "y" then
- height (value)
- elseif coordinate == "x" then
- local diff = value - location.x
- refuel(math.abs(diff))
- if diff > 0 then
- moveEx (0, diff)
- elseif diff < 0 then
- moveEx (2, -diff)
- end
- elseif coordinate == "z" then
- local diff = value - location.z
- refuel(math.abs(diff))
- if diff > 0 then
- moveEx (1, diff)
- elseif diff < 0 then
- moveEx (3, -diff)
- end
- end
- end
- -- Let's organize our movement a bit to avoid movement deadlocks. Thanks to Doyle3694 for the idea
- -- It is 2D navigation, so height is not taken into account. It will allow turtles to relocate themselves to their mining positions
- -- without stucking.
- -- I'll divide all the 2D area to four zones. In each zone turtle can move only in two directions.
- -- Zones are determinated by the pair of coordinates. Turtles will move clockwise relative to the point with that coordinates
- -- height is the heighat at which all navigation is done
- -- There is a function to set the zones
- local naviZones = {x = 0, z = 0, height = 0}
- function setZones (x, z, height)
- naviZones.x = x
- naviZones.z = z
- naviZones.height = height
- end
- -- There is a table used to determine to which directions turtle is allowed to move.
- -- Direction "first" is the direction turtle will move first ^_^ This one can't lead out of the zone.
- -- Direction "second" is leading to the next zone.
- -- Directions are the tables of coordinate and direction (+1 - positive, -1 - negative)
- local tZones = {
- z1 = {first = {"x", 1}, second = {"z", 1}},
- z2 = {first = {"z", 1}, second = {"x", -1}},
- z3 = {first = {"x", -1}, second = {"z", -1}},
- z4 = {first = {"z", -1}, second = {"x", 1}}
- }
- -- Here is a function to determine in which zone turtle is
- function currentZone ()
- if location.x > naviZones.x then
- if location.z > naviZones.z then
- return "z2"
- else
- return "z1"
- end
- else
- if location.z > naviZones.z then
- return "z3"
- else
- return "z4"
- end
- end
- end
- -- And there is one used to navigate turtle, taking into account zones.
- -- All the navigation is done at zones.height and then turtle changes it's height
- function navigateTo (destination)
- -- go to zones.height only if we need to move
- if destination.x ~= location.x or destination.z ~= location.z then
- height (naviZones.height)
- end
- while location.x ~= destination.x or location.z ~= destination.z do -- navigate between zones until we are in the place
- local zone = currentZone ()
- -- Let's unpack all the coordinates
- local coordinateFirst, directionFirst = unpack (tZones[zone].first)
- local coordinateSecond, directionSecond = unpack (tZones[zone].second)
- -- And check some things
- -- if we are allowed to move at both directions - just do it!
- if (destination[coordinateFirst]-location[coordinateFirst]) * directionFirst >= 0
- and (destination[coordinateSecond]-location[coordinateSecond]) * directionSecond >= 0 then
- -- BTW, we just check there that coordinate difference and destination has the same sign
- -- Otherwise turtle that is already went to "second" coordinate won't be able to change it's "first" coordinate
- -- Then just use gotoOne to go to the desired location
- gotoOne (coordinateFirst, destination[coordinateFirst])
- gotoOne (coordinateSecond, destination[coordinateSecond])
- else
- -- If we are not allowed to move by any of those coordinates in current zone
- -- Just head up to the second zone
- local nextPosition = naviZones[coordinateSecond]
- -- We need to do that for actually leave z1 and z4 since they are one block bigger
- if zone == "z4" or zone == "z1" then
- nextPosition = nextPosition + 1
- end
- -- go to the next zone!
- gotoOne (coordinateSecond, nextPosition)
- end
- end
- -- if y is set - move to y.
- if destination.y ~= nil then
- height(destination.y)
- end
- -- same for f
- if destination.f ~= nil then
- turnTo(destination.f)
- end
- end
- --[[
- *********************************************************************************************
- * Communication Part *
- *********************************************************************************************
- ]]--
- -- There are some variables used for communication
- -- ID is the turtle's unique identifier
- -- Master is the ID of the master
- -- Channel is the channel used to send messages to Master
- -- Master should respond on channel+1
- -- Type is the type of the module. Used by Master to determine what it can do
- local ID = 0
- local Master = 0
- local channel = 100
- local Type = "default"
- function setType (newType)
- Type = newType
- end
- -- Wee need a modem to communicate.
- local modem = peripheral.wrap ("right")
- -- The first function is used to parse a message received on modem
- -- It determines whether the message is valid or not
- -- Message should be a table with fields
- -- 1) Protocol - must be equal to "KuuNet"
- -- 2) ID - that must be equal to turtle's ID
- -- 3) Master - that must be equal to Master variable.
- -- Some other fiels
- function isValid (message)
- if message ~= nil then
- if message.Protocol ~= "KuuNet" then
- return false
- end
- if message.ID ~= ID then
- return false
- end
- if message.Master ~= Master then
- return false
- end
- return true
- else
- return false
- end
- end
- -- This function sends a request to Master and waits for a response.
- -- Request is just a simple string
- -- It returns response as message or nil
- function request (message, timeout)
- -- open a channel to listen for response
- modem.open (channel+1)
- local request = {
- Protocol = "KuuNet",
- ID = ID,
- Type = Type,
- Master = Master,
- Request = message
- }
- modem.transmit (channel, channel+1, textutils.serialize(request))
- local response = nil
- parallel.waitForAny (
- function ()
- sleep (timeout)
- end,
- function ()
- while true do
- local event, modemSide, sndChan, rplyChan, text_msg, senderDistance = os.pullEvent("modem_message")
- local msg = textutils.unserialize (text_msg)
- if isValid(msg) then
- response = msg
- break
- end
- end
- end
- )
- modem.close (channel+1)
- return response
- end
- --[[
- *********************************************************************************************
- * Operation part *
- *********************************************************************************************
- ]]--
- -- Fallback location is the point where module goes if it can't get a task from Master
- local fallbackLocation = {}
- -- We need to read all the data we need to move around - turtle's position and navigation zones
- -- "initdata" is the file created by Master and containing all the data that's similar for each module placed by that Master
- function init ()
- local file = fs.open ("/disk/initdata", "r")
- local communicationData = textutils.unserialize (file.readLine ())
- Master = communicationData.MasterID
- channel = communicationData.channel
- -- set module's location
- location = textutils.unserialize (file.readLine ())
- -- set navigation data
- naviZones = textutils.unserialize (file.readLine ())
- -- set fallbackLocation to the point where we are, but at navigation height.
- fallbackLocation = {x = location.x, y = naviZones.height, z = location.z, f = location.f}
- -- Then send a request to Master, to get our ID and to let Master remember us in its state table for modules
- local response = request ("Master", 5)
- ID = response.NewID
- -- move forward to be ready to go down. Because of the placement rules, master should place modules on some block, so the block under the module won't be empty.
- move("forward")
- end
- -- Here is the task table. It is the table, that contains all the task module can get form Master and associated functions
- -- The table should be filled by user of this API ^_~
- -- Module operation is
- -- 1) Requesting a task from master
- -- 2) Moving to the location specified in response (if present)
- -- 3) Executing the associated function
- -- There is always a default task "return" which returns module to Master.
- -- response from Master is always passed to the function, so, if it contains additional data, you can use it
- -- Return is a task to return. Master should pass a xet of coordinates as well as "f" direction to make module face the Master.
- tTasks = {
- Return = function (response)
- move ("forward")
- request ("Returned", 3)
- sleep (30)
- end
- }
- -- Function to add tasks to the task table
- function addTasks (taskTable)
- for key, value in pairs (taskTable) do
- tTasks[key] = value
- end
- end
- -- And one to remove a task ^_^
- function rmTask (task)
- tTasks [task] = nil
- end
- -- And a main operation function
- function operate ()
- while true do
- local task
- -- failedRequests indicates how many times Master didn't answered to request
- local failedRequests = 0
- while task == nil do
- task = request ("Task", 5)
- if task == nil then
- failedRequests = failedRequests + 1
- -- if there are 5 requests failed, then change position to the point of last communication
- if failedRequests >= 6 then
- print "No response from Master after 6 attempts. Moving to fallback position..."
- navigateTo (fallbackLocation)
- end
- else
- lastCommunication = getPosition()
- end
- end
- if task.x ~= nil and task.z ~= nil then
- navigateTo (task)
- end
- tTasks [task.Task](task) -- Task. tttTask. Tasktask. taskataskatask. HerpDerp.
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment