Advertisement
vedalken254

Untitled

Apr 16th, 2014
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.42 KB | None | 0 0
  1. -- master API by PonyKuu (Revision by vedalken254 for CC 1.6 with help from CC Forums)
  2. -- Version 0.2.1Alpha
  3.  
  4. -- A little change to standard assert function
  5. _G.assert = function(condition, errMsg, level)
  6.   if not condition then
  7.         error(errMsg, (tonumber(level) or 1) + 1)
  8.   end
  9.   return condition
  10. end
  11.  
  12. --[[
  13. *********************************************************************************************
  14. *                                    Communication Part                                     *
  15. *********************************************************************************************
  16. ]]--
  17.  
  18. -- MasterID is the unique identifier of master. Default is 100
  19. local MasterID = 100
  20. -- Channel is the channel Master listens.
  21. local channel
  22. -- A modem.
  23. local modem = peripheral.wrap ("right")
  24.  
  25. -- The first function is used to parse a message received on modem
  26. -- It determines whether the message is valid or not
  27. -- Message should be a table with fields
  28. --  1) Protocol - must be equal to "SwarmNet"
  29. --  2) ID - that's a sender ID
  30. --  3) Master - that must be equal to MasterID variable.
  31. --  4) Type - the type of the module. Used to know how to handle its task requests
  32. --  Some other fields
  33. local function isValid (message)
  34.     return  message ~= nil and
  35.             type(message) == "table" and
  36.             message.Protocol == "SwarmNet" and
  37.             message.ID ~= nil and
  38.             message.Master == MasterID
  39. end
  40.  
  41. -- The function that listens for a valid message and returns it
  42. function listen ()
  43.     local msg
  44.     while not isValid(msg) do
  45.         local _, _, _, _, text_message = os.pullEvent("modem_message")
  46.         msg = textutils.unserialize (text_message)
  47.     end
  48.     rednet.broadcast(msg, "RemotePC")
  49.     return msg
  50. end
  51.  
  52. -- And a function to send a response
  53. function response (ID, chan, message)
  54.     assert (type(ID) == "number", "Bad module ID: Number required, got "..type(ID), 2)
  55.     assert (type(chan) == "number", "Bad channel: Number required, got "..type(chan), 2)
  56.     assert (type(message) == "table", "Bad message: Table required, got "..type(message), 2)
  57.     message.Protocol = "SwarmNet"
  58.     message.ID = ID
  59.     message.Master = MasterID
  60.     modem.transmit (chan+1, chan, textutils.serialize(message))
  61. end
  62.  
  63.  
  64. --[[
  65. *********************************************************************************************
  66. *                                   Module Placement Part                                   *
  67. *********************************************************************************************
  68. ]]--
  69. -- moduleCount is the number of active modules
  70. -- needModules is the number of modules required
  71. local moduleCount = 0
  72. local needModules = 0
  73.  
  74. -- Equipment is a table that contains information about slots with chests and turtles.
  75. -- Fuel is the fuel chest, Stuff is the stuff chest and Turtle is the wireless mining turtle.
  76. -- If Stuff or Fuel is nil, it is not used, otherwize it is the slot where it lies.
  77. -- Make sure that if Master sucks items from Module and then breaks it, the equipment table will still be correct.
  78. local equipment = {Fuel = 1, Stuff = 2, Turtle = 3}
  79. function setEquipment (newEquipment)
  80.     assert (type(newEquipment)=="table", "Bad equipment configuration: Table required.", 2)
  81.     assert (newEquipment.Turtle, "\"Turtle\" field is required in equipment configuration", 2)
  82.     for k, v in pairs(newEquipment) do
  83.         assert (type(v) == "number" and v > 0 and v < 17, "Bad equipment field "..k.." : Not a slot number", 2)
  84.     end
  85.     equipment = newEquipment
  86. end
  87.  
  88. -- Function to place a new module
  89. local function addModule ()
  90.     turtle.select (equipment.Turtle)
  91.     -- put it down
  92.     if turtle.place () then
  93.         -- Add a fuel chest if it is set
  94.         if equipment.Fuel ~= nil then
  95.             turtle.select (equipment.Fuel)
  96.             turtle.drop (1)
  97.         end
  98.         -- And a stuff chest
  99.         if equipment.Stuff ~= nil then
  100.             turtle.select (equipment.Stuff)
  101.             turtle.drop (1)
  102.         end
  103.         -- select the first slot to make things look good :)
  104.         turtle.select (1)
  105.         -- Turn on the module
  106.         peripheral.call ("front", "turnOn")
  107.     end
  108. end
  109.  
  110.  
  111. --[[
  112. *********************************************************************************************
  113. *                                       Operation Part                                      *
  114. *********************************************************************************************
  115. ]]--
  116.  
  117. -- tStates is a table that contains all the states of the modules.
  118. -- Indexes of that table are ID's and values are tables with following fields
  119. --  1) Type - type of the module. Each Type has it's own task table
  120. --  2) State - the state of the module. List of states is unique for each module type, but there are some common states: "Waiting" and "Returning"
  121. tStates = {}
  122.  
  123. -- There is a function that searches for a free ID. Let's limit the maximun number of IDs to 1024
  124. local function freeID ()
  125.     for i = 1, 64 do
  126.         if tStates [i] == nil then
  127.             return i
  128.         end
  129.     end
  130. end
  131.  
  132. -- Tasks table. I'll explain it a bit later
  133. tTasks = {}
  134.  
  135. -- Here is the table used to handle the requests.
  136. tRequests = {
  137.     -- "Master" is the request sent by new modules. We should assign a new ID to it and remember that ID in tStates table
  138.     Master = function (request)
  139.         local ID = freeID ()
  140.         response (request.ID, channel, {NewID = ID})
  141.         tStates[ID] = {State = "Waiting", Type = request.Type}
  142.         moduleCount = moduleCount + 1
  143.     end,
  144.     -- Task is the request used to reques the next thing module should do
  145.     -- It uses the tTasks table. There is a function for each module type which returns a response
  146.     -- Response may contain coordinate of the next place and id should contain "Task" field
  147.     -- This function may do something else, change the state of module and so on.
  148.     -- To associate it with the module, sender ID is passed to that function
  149.     -- tTasks table should be filled by user of this API
  150.     Task = function (request)
  151.         response (request.ID, channel, tTasks[request.Type](request.ID))
  152.     end,
  153.     -- "Returned" is the request sent by module that has returned to Master and waiting there until Master collects it
  154.     Returned = function (request)
  155.         while turtle.suck () do end -- Get all the items that module had
  156.         turtle.dig ()               -- And get the module back
  157.         tStates[request.ID] = nil -- delete that module from our state table
  158.         moduleCount = moduleCount - 1 -- and decrease the counter
  159.     end
  160. }
  161.  
  162. -- This is the variable used to determine whether the master should place modules
  163. local isPlacing = false
  164.  
  165. -- This is the function used to place modules if there are any available
  166. -- It automatically stops placing modules when there is enough modules
  167. local function moduleManager ()
  168.     -- A function which checks each slot from the Equipment table and returns is there at least one item in each slot
  169.     local function freeEquipment ()
  170.         for key, value in pairs(equipment) do
  171.             if turtle.getItemCount (value) == 0 then
  172.                 return false
  173.             end
  174.         end
  175.         return true
  176.     end
  177.     while true do
  178.         if isPlacing then
  179.             if moduleCount == needModules or not freeEquipment () then
  180.                 isPlacing = false
  181.             else
  182.                 addModule ()
  183.             end
  184.         end
  185.         sleep (6)
  186.     end
  187. end
  188.  
  189. -- The main operation function
  190. -- stateFunction is the function used to determine when master should stop.
  191. -- Master stops when there are no active modules and stateFunction returns false.
  192. -- State function can do other things, such as reinitialization to a new module script and so on, but it shouldn't take too much time to execute
  193. function operate (stateFunction)
  194.     assert (type(stateFunction) == "function", "Bad state function: Function required, got "..type(stateFunction), 2)
  195.     local server = function ()
  196.         -- run is used to determine whether master should run.
  197.         local run = stateFunction ()
  198.         while run or moduleCount > 0 do
  199.             -- check our state function
  200.             run = stateFunction()
  201.             -- If state function returns false, then stop placing modules
  202.             if isPlacing and not run then
  203.                 isPlacing = false
  204.             end
  205.             local request = listen ()
  206.             tRequests[request.Request](request) -- Just execute the request handler.
  207.         end
  208.     end
  209.     parallel.waitForAny (server, moduleManager)
  210.     modem.closeAll ()
  211. end
  212.  
  213. --[[
  214. *********************************************************************************************
  215. *                                    Initialization Part                                    *
  216. *********************************************************************************************
  217. ]]--
  218.  -- Position is just the position of the Master.
  219. local Position = {x = 0, y = 0, z = 0, f = 0}
  220. -- New placed modules will request "Position" to know where they are.
  221. local modPosition = {x = 1, y = 0, z = 0, f = 0}
  222.  
  223. -- naviData is used by modules to navigate and not interlock themselves
  224. -- by default they use highway navigation method, which requires prameters x, z and height
  225. local naviData = {x = 0, z = 0, height = 0}
  226.  
  227. function setNavigation (newNaviData)
  228.     assert (type(newNaviData) == "table", "Bad navigation data: Table required.", 2)
  229.     naviData = newNaviData
  230. end
  231.  
  232. -- Some basic movement and refueling
  233. -- Refuel temporary uses 16 slot to get the fuel out of fuel chest
  234. local function refuel (amount)
  235.     local fuel =  turtle.getFuelLevel ()
  236.     if fuel == "unlimited" or fuel > amount then
  237.         return true
  238.     else
  239.         assert (equipment.Fuel, "Fuel chest is not set while fuel is finite.", 2)
  240.         turtle.select (equipment.Fuel)
  241.         turtle.placeUp ()
  242.         turtle.select (16)
  243.         turtle.suckUp ()
  244.         while true do
  245.             if not turtle.refuel (1) then
  246.                 turtle.suckUp ()
  247.             end
  248.             if turtle.getFuelLevel() >= amount then
  249.                 turtle.dropUp ()
  250.                 turtle.select (equipment.Fuel)
  251.                 turtle.digUp ()
  252.                 return true
  253.             end
  254.         end
  255.     end
  256. end
  257.  
  258. local tMoves = {
  259.     forward = turtle.forward,
  260.     back = turtle.back
  261. }
  262. local function forceMove (direction)
  263.     refuel (1)
  264.     while not tMoves[direction]() do
  265.         print "Movement obstructed. Waiting"
  266.         sleep (1)
  267.     end
  268. end
  269.  
  270. -- The function to set the "task" function for the said module type
  271. function setType (Type, taskFunction)
  272.     assert (type(Type) == "string", "Bad module type: String required, got "..type(Type), 2)
  273.     assert (type(taskFunction) == "function", "Bad task function: Function required, got "..type(taskFunction), 2)
  274.     tTasks[Type] = taskFunction
  275. end
  276.  
  277.  
  278. -- This function is used to make all the files required by module to run.
  279. -- Scriptname is the name of the main module script
  280. local function prepareFiles (scriptname)
  281.     -- Copy all the required files on the disk
  282.     if fs.exists ("/disk/module") then
  283.         fs.delete ("/disk/module")
  284.     end
  285.     fs.copy ("module", "/disk/module")
  286.     if fs.exists ("/disk/"..scriptname) then
  287.         fs.delete ("/disk/"..scriptname)
  288.     end
  289.     fs.copy (scriptname, "/disk/"..scriptname)
  290.     -- Make a startup file for modules
  291.     local file = fs.open ("/disk/startup", "w")
  292.     file.writeLine ("shell.run(\"copy\", \"/disk/module\", \"/module\")")
  293.     file.writeLine ("shell.run(\"copy\", \"/disk/"..scriptname.."\", \""..scriptname.."\")")
  294.     file.writeLine ("shell.run(\""..scriptname.."\")")
  295.     file.close()
  296.    
  297.     -- Now, make files with all the data modules need
  298.     file = fs.open ("/disk/comminitdata", "w")
  299.     positionFile = fs.open ("/disk/posdata", "w")
  300.     naviFile = fs.open ("/disk/navidata", "w")
  301.    
  302.     -- Communication data: master ID and communication channel
  303.     file.writeLine (string.gsub (textutils.serialize ({MasterID = MasterID, channel = channel}), "\n%s", ""))
  304.     file.close()
  305.     positionFile.writeLine (string.gsub (textutils.serialize (modPosition), "\n%s", ""))
  306.     positionFile.close()
  307.     naviFile.writeLine (string.gsub (textutils.serialize (naviData), "\n%s", ""))
  308.     naviFile.close()
  309. end
  310.  
  311. -- The initialization function of the master.
  312. -- Filename is the name of module's script. It will be copied on the disk drive
  313. function init (filename, ID, mainChannel, moduleCount)
  314.     assert (fs.exists(filename), "Module script \""..filename.."\" does not exist.", 2)
  315.     assert (fs.exists("module"), "Module API is required.", 2)
  316.     assert (type(ID) == "string" or type(ID) == "number" , "Bad Master ID: String or number required, got "..type(ID), 2)
  317.     assert (type(mainChannel) == "number", "Bad channel: Number required, got "..type(mainChannel), 2)
  318.     assert (type(moduleCount) == "number" and moduleCount > 0, "Bad module count: Positive number required.", 2)
  319.    
  320.     -- Set the ID of the Master
  321.     MasterID = ID
  322.     -- Set the main channel
  323.     channel = mainChannel
  324.  
  325.     -- Next, we need to know the position of the master.
  326.     -- If gps is not found, use relative coordinates
  327.     modem.open(gps.CHANNEL_GPS)
  328.     local gpsPresent = true
  329.     local x, y, z = gps.locate(5)
  330.     if x == nil then
  331.         x, y, z = 0, 0, 0
  332.         print "No gps found. Using relative coordinates."
  333.         gpsPresent = false
  334.     end
  335.    
  336.     -- Now we need to move forward to copy files on disk and determine our f
  337.     forceMove ("forward")
  338.     local newX, newZ = 1, 0 -- if there is no gps, assume that master is facing positive x
  339.     if gpsPresent then
  340.         newX, __, newZ = gps.locate (5)
  341.     end
  342.     if channel ~= gps.CHANNEL_GPS then
  343.         modem.close(gps.CHANNEL_GPS)
  344.     end
  345.     -- Determine f by the difference of coordinates.
  346.     local xDiff = newX - x
  347.     local zDiff = newZ - z
  348.     if xDiff ~= 0 then
  349.         if xDiff > 0 then
  350.             f = 0     -- Positive x
  351.         else
  352.             f = 2     -- Negative x
  353.         end
  354.     else
  355.         if zDiff > 0 then
  356.             f = 1     -- Positive z
  357.         else
  358.             f = 3     -- Negative z
  359.         end
  360.     end
  361.     -- And set the position and modPosition variables.
  362.     Position = {x = x, y = y, z = z, f = f}
  363.     modPosition = {x = newX, y = y, z = newZ, f = f}
  364.    
  365.     -- Make all the file required
  366.     prepareFiles (filename)
  367.     -- And go back to initial location
  368.     forceMove ("back")
  369.    
  370.     -- Set the amount of modules needed. I use "+" because one can run init again to add a different type of module to the operation
  371.     needModules = needModules + moduleCount
  372.     -- Open the channel to listen for requests
  373.     modem.open (channel)
  374.     -- And start placing modules
  375.     isPlacing = true
  376. end
  377.  
  378. --[[
  379. *********************************************************************************************
  380. *                                       Utility Part                                        *
  381. *********************************************************************************************
  382. ]]--
  383.  
  384. -- This is just a return response added for convinience, because every task function should return the module eventually.
  385. function makeReturnTask ()
  386.     -- return position is two block away from Master. So, let's calculate it.
  387.     local tShifts = {
  388.         [0] = { 1,  0},
  389.         [1] = { 0,  1},
  390.         [2] = {-1,  0},
  391.         [3] = { 0, -1},
  392.     }
  393.     local xShift, zShift = unpack (tShifts[modPosition.f])
  394.     returnX = modPosition.x + xShift
  395.     returnZ = modPosition.z + zShift
  396.     return {   Task = "Return",
  397.         x = returnX,
  398.         y = modPosition.y,
  399.         z = returnZ,
  400.         f = (modPosition.f+2)%4, -- basically, reverse direction
  401.     }
  402. end
  403.  
  404. -- And a function to make any other task
  405. -- additionalData is optional.
  406. function makeTask (taskName, coordinates, additionalData)
  407.     assert (type(taskName) == "string", "Bad task name: String required, got "..type(taskName), 2)
  408.     assert (type(coordinates) == "table", "Bad coordinates: Table required, got "..type(coordinates), 2)
  409.     local tCoordinates = {"x", "y", "z", "f"}
  410.     -- Check if the coordinates are actually numbers
  411.     for index, value in ipairs(tCoordinates) do
  412.         assert (type(coordinates[value]) == "number", "Bad "..value.." coordinate: Number required, got"..type(coordinates[value]), 2)
  413.     end
  414.    
  415.     local newTask = {Task = taskName}
  416.     for key, value in pairs (coordinates) do  
  417.         newTask[key] = value
  418.     end
  419.     if type(additionalData) == "table" then
  420.         for key, value in pairs (additionalData) do  
  421.             newTask[key] = value
  422.         end
  423.     end
  424.     return newTask
  425. end
  426.  
  427. -- These functions are used to get or set the state of the module with specified ID
  428. function getState (ID)
  429.     return tStates[ID].State
  430. end
  431. function setState (ID, newState)
  432.     assert (tStates[ID], "No module with such ID: "..ID, 2)
  433.     assert (type(newState) == "string", "Bad task name: String required, got "..type(newState), 2)
  434.     tStates[ID].State = newState
  435. end
  436.  
  437. -- reinit function is just a simplified init, that doesn't update channel, MasterID and Master's position. Use it to change the modules script
  438. function reinit (filename, moduleCount)
  439.     assert (fs.exists(filename), "Module script \""..filename.."\" does not exist.", 2)
  440.     assert (type(moduleCount) == "number" and moduleCount > 0, "Bad module count: Positive number required.", 2)
  441.     -- Prepare the files on disk
  442.     forceMove ("forward")
  443.     prepareFiles (filename)
  444.     forceMove ("back")
  445.     -- Set the amount of modules needed.
  446.     needModules = needModules + moduleCount
  447.     -- Open the channel to listen for requests
  448.     modem.open (channel)
  449.     -- And start placing modules
  450.     isPlacing = true
  451. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement