Advertisement
Evil_Bengt

Team Turtles @ Latest

Apr 28th, 2024 (edited)
816
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 24.43 KB | Source Code | 0 0
  1. -- ### Evil_Bengt, 2024-05-01 00:33:07
  2.  
  3.  
  4. -- ### MODULE: constants
  5. -- ### PATH: ./src/constants.lua
  6.  
  7.  
  8. WorkingSide = {
  9.     left = "left",
  10.     right = "right"
  11. }
  12.  
  13. Direction = {
  14.     facingCorridor = "the corridor",
  15.     awayFromCorridor = "away from the corridor",
  16.     outbound = "outbound",
  17.     inbound = "inbound"
  18. }
  19.  
  20. Communication = {}
  21.  
  22. Communication.protocol = {
  23.     request = "EVB_TeamTurtles_Request",
  24.     response = "EVB_TeamTurtles_Response"
  25. }
  26. Communication.messages = {
  27.     requestLayer = "requestLayer",
  28.     getProject = "getProject"
  29. }
  30.  
  31. Filenames = {
  32.     project = "tt_project",
  33.     state = "tt_state",
  34.     turnFile = "tt_turns",
  35.     filter = "tt_filter",
  36.     serverState = "tt_server_state"
  37. }
  38.  
  39. TurtleBlockTag = "computercraft:turtle"
  40.  
  41. RefuelPosition = {
  42.     home = 1,
  43.     spawn = 2
  44. }
  45.  
  46. MinimumNeededFuel = 200
  47.  
  48. ProjectIdLength = 10
  49.  
  50.  
  51.  
  52. -- ### MODULE: core
  53. -- ### PATH: ./src/core.lua
  54.  
  55.  
  56. -- MINING, MAIN FUNCTIONS --
  57.  
  58.  
  59. function Mine(nextPhaseIfFullFunction, inspectFunction, digFunction)
  60.     local any, data = inspectFunction()
  61.  
  62.     if any and IsInteresting(data) then
  63.         if CheckInventoryIsFull() then
  64.             return nextPhaseIfFullFunction()
  65.         end
  66.         digFunction()
  67.     end
  68. end
  69.  
  70.  
  71. function MineInfront(nextPhaseIfFullFunction)
  72.     return function ()
  73.         return Mine(nextPhaseIfFullFunction, turtle.inspect, turtle.dig)
  74.     end
  75. end
  76.  
  77. function MineAbove(nextPhaseIfFullFunction)
  78.     return function ()
  79.         return Mine(nextPhaseIfFullFunction, turtle.inspectUp, turtle.digUp)
  80.     end
  81. end
  82.  
  83. function MineBelow(nextPhaseIfFullFunction)
  84.     return function ()
  85.         return Mine(nextPhaseIfFullFunction, turtle.inspectDown, turtle.digDown)
  86.     end
  87. end
  88.  
  89.  
  90.  
  91. -- MINING, HELPERS --
  92.  
  93.  
  94. function CheckInventoryIsFull()
  95.     return turtle.getItemCount(16) > 0
  96. end
  97.  
  98.  
  99. function IsInteresting(blockData)
  100.     local result = true
  101.     local opcode = nil
  102.     local filterText = nil
  103.     local subResult = nil
  104.  
  105.     for _, line in pairs(Project.filters) do
  106.         opcode = string.sub(line, 1, 2)
  107.         filterText = string.sub(line, 4, -1)
  108.         subResult = FilterFunctions[opcode](filterText, blockData)
  109.         if subResult ~= nil then
  110.             result = subResult
  111.         end
  112.     end
  113.  
  114.     return result
  115. end
  116.  
  117. FilterFunctions = {
  118.     ["++"] = function (filter, blockData) return true end,
  119.     ["--"] = function (filter, blockData) return false end,
  120.     ["+n"] = function (filter, blockData)
  121.         if blockData.name == filter then
  122.             return true
  123.         end
  124.         return nil
  125.     end,
  126.     ["-n"] = function (filter, blockData)
  127.         if blockData.name == filter then
  128.             return false
  129.         end
  130.         return nil
  131.     end,
  132.     ["+t"] = function (filter, blockData)
  133.         if blockData.tags[filter] == true then
  134.             return true
  135.         end
  136.         return nil
  137.     end,
  138.     ["-t"] = function (filter, blockData)
  139.         if blockData.tags[filter] == true then
  140.             return false
  141.         end
  142.         return nil
  143.     end
  144. }
  145.  
  146.  
  147.  
  148. -- REFUELING --
  149.  
  150.  
  151. function Refuel(refuelPosition)
  152.     local function suckFuelInfront()
  153.         turtle.suck()
  154.         return turtle.getItemCount() > 0
  155.     end
  156.     local function suckFuelBelow()
  157.         turtle.suckDown()
  158.         return turtle.getItemCount() > 0
  159.     end
  160.  
  161.     local neededFuel = 0
  162.     neededFuel = neededFuel + AssignedLayer * 2
  163.     neededFuel = neededFuel + Project.width * Project.height / 3
  164.     neededFuel = neededFuel + Project.height * 2
  165.     neededFuel = neededFuel + 10
  166.     neededFuel = neededFuel + (refuelPosition == RefuelPosition.spawn and 1 or 0)
  167.     neededFuel = math.max(neededFuel, MinimumNeededFuel)
  168.  
  169.     turtle.select(1)
  170.     while turtle.getFuelLevel() < neededFuel do
  171.         Ensure(refuelPosition == RefuelPosition.home and suckFuelInfront or suckFuelBelow,
  172.             true, "Cannot refuel.", "Got fuel.")
  173.  
  174.         if not turtle.refuel() then
  175.             if refuelPosition == RefuelPosition.spawn then
  176.                 error("Cannot refuel, non-fuel items in fuel chest!")
  177.             end
  178.             Ensure(turtle.dropDown, true, "Cannot empty.", "Emptied successfully.")
  179.         end
  180.     end
  181. end
  182.  
  183.  
  184.  
  185. -- COMMUNICATION --
  186.  
  187.  
  188. function RequestLayer()
  189.     local payload = {
  190.         message = Communication.messages.requestLayer,
  191.         previousLayer = AssignedLayer,
  192.         projectId = Project.projectId
  193.     }
  194.     local responseRaw = SendRequest(Project.serverAddress, textutils.serialize(payload))
  195.     local response = textutils.unserialize(responseRaw)
  196.  
  197.     if not response.layer then
  198.         BroadcastFatalError("Decomissioned.")
  199.     end
  200.  
  201.     AssignedLayer = response.layer
  202.     print("AssignedLayer = " .. AssignedLayer)
  203. end
  204.  
  205. function FetchProject(serverAddress)
  206.     local payload = {
  207.         message = Communication.messages.getProject
  208.     }
  209.     local response = SendRequest(serverAddress, textutils.serialize(payload))
  210.     Project = textutils.unserialize(response)
  211.     print("Project = " .. textutils.serialize(Project))
  212. end
  213.  
  214.  
  215. function SendRequest(id, msg)
  216.     while true do
  217.         rednet.send(id, msg, Communication.protocol.request)
  218.         local responseId, responseMsg = rednet.receive(Communication.protocol.response, 10)
  219.  
  220.         if responseId == id then
  221.             return responseMsg
  222.         end
  223.  
  224.         BroadcastError("Cannot reach '" .. id .. "'.")
  225.     end
  226. end
  227.  
  228.  
  229.  
  230. -- ### MODULE: globalState
  231. -- ### PATH: ./src/globalState.lua
  232.  
  233.  
  234. -- Persisted
  235. Project = {
  236.     serverAddress = nil, -- integer
  237.     projectId = nil, -- string
  238.     width = nil, -- integer
  239.     height = nil, -- integer
  240.     workingSide = nil, -- WorkingSide,
  241.     filters = nil, -- table
  242. }
  243.  
  244. -- Persisted
  245.  
  246. AssignedLayer = nil
  247. InitialFuel = nil
  248. ActivePhase = nil
  249. PhaseArgs = nil
  250.  
  251. -- Not persisted
  252.  
  253. CompletedSteps = 0
  254. TurnFile = nil
  255.  
  256.  
  257.  
  258. -- ### MODULE: misc
  259. -- ### PATH: ./src/misc.lua
  260.  
  261.  
  262. -- MISC FUNCTIONS --
  263.  
  264.  
  265. function Me()
  266. ---@diagnostic disable-next-line: undefined-field
  267.     return "TT_" .. os.getComputerID() .. "@" .. (Project.serverAddress or "_")
  268. end
  269.  
  270. function BroadcastError(msg, printError)
  271.     if printError ~= false then
  272.         print("!! " .. msg)
  273.     end
  274.     rednet.broadcast("!! " .. Me() .. ": " .. msg)
  275. end
  276.  
  277. function BroadcastSuccess(msg)
  278.     rednet.broadcast("## " .. Me() .. ": " .. msg)
  279. end
  280.  
  281. function Ensure(fun, expect, errorMessage, resolveMessage)
  282.     local announced = false
  283.  
  284.     if not not fun() == expect then
  285.         return
  286.     end
  287.  
  288.     while true do
  289.         for _ = 1, 10 do
  290.             sleep(1)
  291.             if not not fun() == expect then
  292.                 if announced and resolveMessage then
  293.                     BroadcastSuccess(resolveMessage)
  294.                 end
  295.                 return
  296.             end
  297.         end
  298.         BroadcastError(errorMessage)
  299.         announced = true
  300.     end
  301. end
  302.  
  303. function BroadcastFatalError(msg, printError)
  304.     for i = 1, 10 do
  305.         BroadcastError(msg, printError)
  306.         sleep(60)
  307.     end
  308.     error(msg)
  309. end
  310.  
  311.  
  312.  
  313. -- ### MODULE: movement
  314. -- ### PATH: ./src/movement.lua
  315.  
  316.  
  317. -- MOVEMENT --
  318.  
  319.  
  320. function Forward()
  321.     YieldForTurtle(TurtleInfront)
  322.     while not turtle.forward() do
  323.         turtle.attack()
  324.         turtle.dig()
  325.     end
  326.     ResetTurnFile()
  327. end
  328.  
  329. function Up()
  330.     YieldForTurtle(TurtleAbove)
  331.     while not turtle.up() do
  332.         turtle.attackUp()
  333.         turtle.digUp()
  334.     end
  335.     ResetTurnFile()
  336. end
  337.  
  338. function Down()
  339.     YieldForTurtle(TurtleBelow)
  340.     while not turtle.down() do
  341.         turtle.attackDown()
  342.         turtle.digDown()
  343.     end
  344.     ResetTurnFile()
  345. end
  346.  
  347. function Right()
  348.     StartTurn()
  349.     turtle.turnRight()
  350.     EndTurn()
  351. end
  352.  
  353. function Left()
  354.     StartTurn()
  355.     turtle.turnLeft()
  356.     EndTurn()
  357. end
  358.  
  359.  
  360.  
  361. -- RECORD-KEEPING --
  362.  
  363.  
  364. function ResetTurnFile()
  365.     if TurnFile then
  366.         TurnFile.close()
  367.         TurnFile = nil
  368.     end
  369.     if fs.exists(Filenames.turnFile) then
  370.         fs.delete(Filenames.turnFile)
  371.     end
  372. end
  373.  
  374. function StartTurn()
  375.     if not TurnFile then
  376.         if fs.exists(Filenames.turnFile) then
  377.             fs.delete(Filenames.turnFile)
  378.         end
  379.         TurnFile = fs.open(Filenames.turnFile, "w")
  380.     end
  381.     local any, data = turtle.inspect()
  382.     TurnFile.writeLine(textutils.serialize({
  383.         blockInfront = any and data.name or nil
  384.     }))
  385. end
  386.  
  387. function EndTurn()
  388.     if not TurnFile then
  389.         if fs.exists(Filenames.turnFile) then
  390.             fs.delete(Filenames.turnFile)
  391.         end
  392.         TurnFile = fs.open(Filenames.turnFile, "w")
  393.     end
  394.     TurnFile.writeLine("ok")
  395. end
  396.  
  397.  
  398. -- ANTI-COLLISION --
  399.  
  400.  
  401. function TurtleInfront()
  402.     local any, data = turtle.inspect()
  403.     return any and data.tags[TurtleBlockTag]
  404. end
  405.  
  406. function TurtleAbove()
  407.     local any, data = turtle.inspectUp()
  408.     return any and data.tags[TurtleBlockTag]
  409. end
  410.  
  411. function TurtleBelow()
  412.     local any, data = turtle.inspectDown()
  413.     return any and data.tags[TurtleBlockTag]
  414. end
  415.  
  416.  
  417. function YieldForTurtle(checkFunction)
  418.     Ensure(checkFunction, false, "Blocked by other turtle.", "Unblocked.")
  419. end
  420.  
  421.  
  422.  
  423. -- ### MODULE: persistence
  424. -- ### PATH: ./src/persistence.lua
  425.  
  426.  
  427. -- FUNCTIONS --
  428.  
  429.  
  430. function PersistProject()
  431.     local fileHandle = fs.open(Filenames.project, "w")
  432.  
  433.     fileHandle.write(textutils.serialize(Project))
  434.     fileHandle.close()
  435. end
  436.  
  437. function LoadProject()
  438.     if not fs.exists(Filenames.project) then
  439.         BroadcastFatalError("Cannot load project file.")
  440.     end
  441.     local fileHandle = fs.open(Filenames.project, "r")
  442.  
  443.     Project = textutils.unserialize(fileHandle.readAll())
  444.     fileHandle.close()
  445. end
  446.  
  447.  
  448. function PersistState()
  449.     local fileHandle = fs.open(Filenames.state, "w")
  450.     local state = {
  451.         assignedLayer = AssignedLayer,
  452.         initialFuel = InitialFuel,
  453.         activePhase = ActivePhase.name,
  454.         phaseArgs = PhaseArgs
  455.     }
  456.     local serializedState = textutils.serialize(state)
  457.  
  458.     fileHandle.write(serializedState)
  459.     fileHandle.close()
  460. end
  461.  
  462. function LoadState()
  463.     if not fs.exists(Filenames.state) then
  464.         BroadcastFatalError("Cannot load state.")
  465.     end
  466.     local fileHandle = fs.open(Filenames.state, "r")
  467.     local serializedState = fileHandle.readAll()
  468.     fileHandle.close()
  469.  
  470.     local state = textutils.unserialize(serializedState)
  471.  
  472.     AssignedLayer = state.assignedLayer
  473.     InitialFuel = state.initialFuel
  474.     ActivePhase = Phase[state.activePhase]
  475.     PhaseArgs = state.phaseArgs
  476. end
  477.  
  478.  
  479. function LoadTurnFile()
  480.     local unfinished = nil
  481.     local turns = 0
  482.     if fs.exists(Filenames.turnFile) then
  483.         local handle = fs.open(Filenames.turnFile, "r")
  484.         local line = nil
  485.         repeat
  486.             line = handle.readLine()
  487.             if line == "ok" then
  488.                 turns = turns + 1
  489.                 unfinished = nil
  490.             elseif line ~= nil then
  491.                 unfinished = line
  492.             end
  493.         until line == nil
  494.         handle.close()
  495.     end
  496.     return unfinished and textutils.unserialize(unfinished) or turns
  497. end
  498.  
  499.  
  500. function LoadFilters()
  501.     local filters = {}
  502.     if not fs.exists(Filenames.filter) then
  503.         return filters
  504.     end
  505.     local handle = fs.open(Filenames.filter, "r")
  506.     local line = nil
  507.     repeat
  508.         line = handle.readLine()
  509.         if line then
  510.             table.insert(filters, line)
  511.         end
  512.     until not line
  513.     handle.close()
  514.     return filters
  515. end
  516.  
  517.  
  518.  
  519. -- ### MODULE: phases
  520. -- ### PATH: ./src/phases.lua
  521.  
  522.  
  523. Phase = {
  524.     inbound = { name = "inbound" },
  525.     outbound = { name = "outbound" },
  526.     working = { name = "working" },
  527.     backtracking = { name = "backtracking" },
  528.     emptyAndRefuel = { name = "emptyAndRefuel" }
  529. }
  530.  
  531.  
  532. -- GLOBAL FUNCTIONS
  533.  
  534.  
  535. function Resume(steps)
  536.     local moves = InitialFuel - turtle.getFuelLevel()
  537.     local turns = LoadTurnFile()
  538.     local isBlockInfront, blockInfront = turtle.inspect()
  539.  
  540.     if type(turns) == "table" and isBlockInfront and blockInfront == turns.blockInfront then
  541.         print("I got lost while turning. Please:")
  542.         print("- Place me at the spawn")
  543.         print("- Terminate the program")
  544.         print("- Rejoin the project (server id: " .. Project.serverAddress .. ")")
  545.         local location = "basecamp"
  546.         if ActivePhase.name == Phase.working.name or ActivePhase.name == Phase.backtracking.name then
  547.             location = "layer " .. AssignedLayer
  548.         elseif ActivePhase.name == Phase.inbound or ActivePhase.name == Phase.outbound.name then
  549.             location = "corridor"
  550.         end
  551.         BroadcastFatalError("Lost at " .. location .. ".", false)
  552.     end
  553.  
  554.     local step = 0
  555.  
  556.     local foundMoves = 0
  557.     while foundMoves < moves do
  558.         step = step + 1
  559.         if IsMove(steps[step]) then
  560.             foundMoves = foundMoves + 1
  561.         elseif steps[step] == nil then
  562.             BroadcastFatalError("Cannot resume from saved state. Turtle has consumed more fuel than expected.")
  563.         end
  564.     end
  565.  
  566.     local foundTurns = 0
  567.     while foundTurns < turns do
  568.         step = step + 1
  569.         if IsTurn(steps[step]) then
  570.             foundTurns = foundTurns + 1
  571.         elseif steps[step] == nil or IsMove(steps[step]) then
  572.             BroadcastFatalError("Cannot resume from saved state. Saved state is invalid.")
  573.         end
  574.     end
  575.  
  576.     return step
  577. end
  578.  
  579. function InitPhase(phase, args)
  580.     ActivePhase = phase
  581.     PhaseArgs = args
  582.     InitialFuel = turtle.getFuelLevel()
  583.  
  584.     ResetTurnFile()
  585.     PersistState()
  586. end
  587.  
  588.  
  589.  
  590. -- HELPERS --
  591.  
  592.  
  593. function IsMove(func)
  594.     return (func == Forward) or (func == Up) or (func == Down)
  595. end
  596.  
  597. function IsTurn(func)
  598.     return (func == Left) or (func == Right)
  599. end
  600.  
  601.  
  602.  
  603. -- OUTBOUND --
  604.  
  605.  
  606. function Phase.outbound.generateSteps(args)
  607.     local steps = {}
  608.  
  609.     if args.from == 0 then
  610.         table.insert(steps, Right)
  611.         table.insert(steps, Right)
  612.         table.insert(steps, Up)
  613.     end
  614.  
  615.     for _ = args.from + 1, AssignedLayer do
  616.         table.insert(steps, Forward)
  617.     end
  618.  
  619.     table.insert(steps, Project.workingSide == WorkingSide.right and Right or Left)
  620.     table.insert(steps, Forward)
  621.     table.insert(steps, Down)
  622.  
  623.     table.insert(steps, function ()
  624.         return Phase.working
  625.     end)
  626.  
  627.     return steps
  628. end
  629.  
  630.  
  631.  
  632. -- WORKING --
  633.  
  634.  
  635. function Phase.working.generateSteps(_)
  636.     local function backtrackToHome()
  637.         return Phase.backtracking, {
  638.             nDoneSteps = CompletedSteps,
  639.             goHome = true
  640.         }
  641.     end
  642.  
  643.  
  644.     local steps = {}
  645.  
  646.     for y = 1, Project.height / 3 do
  647.         for _ = 1, Project.width - 3 do
  648.             table.insert(steps, MineAbove(backtrackToHome))
  649.             table.insert(steps, MineBelow(backtrackToHome))
  650.             table.insert(steps, MineInfront(backtrackToHome))
  651.             table.insert(steps, Forward)
  652.         end
  653.  
  654.         table.insert(steps, MineAbove(backtrackToHome))
  655.         table.insert(steps, MineInfront(backtrackToHome))
  656.  
  657.         if y < Project.height / 3 then
  658.             for _ = 1, 3 do
  659.                 table.insert(steps, MineBelow(backtrackToHome))
  660.                 table.insert(steps, Down)
  661.                 table.insert(steps, MineInfront(backtrackToHome))
  662.             end
  663.             table.insert(steps, Left)
  664.             table.insert(steps, Left)
  665.         else
  666.             table.insert(steps, MineBelow(backtrackToHome))
  667.         end
  668.     end
  669.  
  670.     table.insert(steps, function ()
  671.         return Phase.backtracking, {
  672.             nDoneSteps = CompletedSteps,
  673.             goHome = false
  674.         }
  675.     end)
  676.  
  677.     return steps
  678. end
  679.  
  680.  
  681.  
  682. -- BACKTRACKING --
  683.  
  684.  
  685. function Phase.backtracking.generateSteps(args)
  686.     local steps = {}
  687.     local doneSteps = {}
  688.  
  689.     for i, step in ipairs(Phase.working.generateSteps()) do
  690.         if i > args.nDoneSteps then
  691.             break
  692.         end
  693.  
  694.         table.insert(doneSteps, step)
  695.     end
  696.  
  697.     table.insert(steps, Left)
  698.     table.insert(steps, Left)
  699.  
  700.     for i = args.nDoneSteps, 1, -1 do
  701.         local step = doneSteps[i]
  702.  
  703.         if step == Forward then
  704.             table.insert(steps, Forward)
  705.         elseif step == Up then
  706.             table.insert(steps, Down)
  707.         elseif step == Down then
  708.             table.insert(steps, Up)
  709.         elseif step == Left then
  710.             table.insert(steps, Right)
  711.         elseif step == Right then
  712.             table.insert(steps, Left)
  713.         end
  714.     end
  715.  
  716.     if args.goHome then
  717.         table.insert(steps, Forward)
  718.         table.insert(steps, Project.workingSide == WorkingSide.right and Left or Right)
  719.  
  720.         table.insert(steps, function ()
  721.             return Phase.inbound
  722.         end)
  723.     else
  724.         local prevLayer = AssignedLayer
  725.  
  726.         table.insert(steps, function ()
  727.             RequestLayer()
  728.             PersistState()
  729.         end)
  730.         table.insert(steps, Up)
  731.         table.insert(steps, Forward)
  732.         table.insert(steps, Project.workingSide == WorkingSide.right and Right or Left)
  733.  
  734.         table.insert(steps, function ()
  735.             return Phase.outbound, { from = prevLayer }
  736.         end)
  737.     end
  738.  
  739.     return steps
  740. end
  741.  
  742.  
  743.  
  744. -- INBOUND --
  745.  
  746.  
  747. function Phase.inbound.generateSteps(_)
  748.     local steps = {}
  749.  
  750.     for i = 1, AssignedLayer do
  751.         steps[i] = Forward
  752.     end
  753.  
  754.     table.insert(steps, function ()
  755.         return Phase.emptyAndRefuel
  756.     end)
  757.  
  758.     return steps
  759. end
  760.  
  761.  
  762.  
  763. -- EMPTY_AND_REFUEL --
  764.  
  765.  
  766. function Phase.emptyAndRefuel.generateSteps(_)
  767.     local refuelStep = function ()
  768.         for i = 1, 16 do
  769.             turtle.select(i)
  770.             Ensure(function ()
  771.                 return turtle.getItemCount(i) == 0 or turtle.dropDown()
  772.             end, true, "Cannot empty.", "Emptied successfully.")
  773.         end
  774.  
  775.         Refuel(RefuelPosition.home)
  776.  
  777.         return Phase.outbound, {
  778.             from = 0
  779.         }
  780.     end
  781.  
  782.     return { refuelStep }
  783. end
  784.  
  785.  
  786.  
  787. -- ### MODULE: server
  788. -- ### PATH: ./src/server.lua
  789.  
  790.  
  791.  
  792.  
  793.  
  794.  
  795. -- STATE --
  796.  
  797.  
  798. ServerState = nil
  799.  
  800.  
  801.  
  802. -- FUNCTIONS --
  803.  
  804.  
  805. function RunServer(args)
  806.     local loadedState = nil
  807.  
  808.     if fs.exists(Filenames.serverState) then
  809.         local fileHandle = fs.open(Filenames.serverState, "r")
  810.         loadedState = textutils.unserialize(fileHandle.readAll())
  811.         fileHandle.close()
  812.     end
  813.  
  814.     if args then
  815.         local width = tonumber(args[1])
  816.         local height = tonumber(args[2])
  817.         local side = args[3]
  818.  
  819.         if not width or width <= 0 or not height or height <= 0 or (side ~= WorkingSide.right and side ~= WorkingSide.left) then
  820.             error("Usage: <width> <height> <right|left>")
  821.             return false
  822.         end
  823.         if height % 3 ~= 0 then
  824.             error("Height must be divisible by 3!")
  825.             return false
  826.         end
  827.  
  828.         ServerState = {
  829.             layers = {},
  830.             turtles = {},
  831.             project = {}
  832.         }
  833.  
  834.         ServerState.project.serverAddress = os.getComputerID()
  835.         ServerState.project.projectId = os.epoch()
  836.         ServerState.project.width = width
  837.         ServerState.project.height = height
  838.         ServerState.project.workingSide = side
  839.     elseif loadedState then
  840.         ServerState = loadedState
  841.     else
  842.         print("No server session to resume.")
  843.         return false
  844.     end
  845.  
  846.    
  847.     ServerState.project.filters = LoadFilters()
  848.     SaveServerState()
  849.  
  850.     while true do
  851.         local id, msg = rednet.receive(Communication.protocol.request)
  852.  
  853.         local payload = textutils.unserialize(msg)
  854.         local response = MessageHandlers[payload.message](payload, id)
  855.  
  856.         sleep(0.1)
  857.         rednet.send(id, textutils.serialize(response), Communication.protocol.response)
  858.     end
  859. end
  860.  
  861. function ClearServer()
  862.     if fs.exists(Filenames.serverState) then
  863.         fs.delete(Filenames.serverState)
  864.     end
  865. end
  866.  
  867. function SaveServerState()
  868.     local fileHandle = fs.open(Filenames.serverState, "w")
  869.     fileHandle.write(textutils.serialize(ServerState))
  870.     fileHandle.close()
  871. end
  872.  
  873.  
  874.  
  875. -- MESSAGE HANDLERS --
  876.  
  877.  
  878. MessageHandlers = {
  879.     [Communication.messages.getProject] = function (payload, id)
  880.         return ServerState.project
  881.     end,
  882.  
  883.     [Communication.messages.requestLayer] = function (payload, id)
  884.         if payload.projectId ~= ServerState.project.projectId then
  885.             return {
  886.                 layer = nil
  887.             }
  888.         end
  889.  
  890.         if payload.previousLayer ~= ServerState.turtles[id] or (not payload.previousLayer and ServerState.turtles[id]) then
  891.             return {
  892.                 layer = ServerState.turtles[id]
  893.             }
  894.         end
  895.  
  896.         local newLayer = #ServerState.layers + 1
  897.         ServerState.layers[newLayer] = id
  898.         if payload.previousLayer then
  899.             ServerState.layers[payload.previousLayer] = false
  900.         end
  901.         ServerState.turtles[id] = newLayer
  902.         SaveServerState()
  903.  
  904.         return {
  905.             layer = newLayer
  906.         }
  907.     end
  908. }
  909.  
  910.  
  911.  
  912. -- ### MODULE: turtle
  913. -- ### PATH: ./src/turtle.lua
  914.  
  915.  
  916.  
  917.  
  918.  
  919.  
  920. -- STATE --
  921.  
  922.  
  923. Resuming = false
  924.  
  925.  
  926.  
  927. -- FUNCTIONS --
  928.  
  929.  
  930. function RunTurtle(arg)
  931.     if not arg then
  932.         LoadProject()
  933.         LoadState()
  934.         CompletedSteps = Resume(ActivePhase.generateSteps(PhaseArgs))
  935.         Resuming = true
  936.     else
  937.         local serverId = tonumber(arg)
  938.         if not serverId then
  939.             return false
  940.         end
  941.         FetchProject(serverId)
  942.         PersistProject()
  943.         RequestLayer()
  944.         Refuel(RefuelPosition.spawn)
  945.         InitPhase(Phase.outbound, { from = -1 })
  946.     end
  947.  
  948.     while true do
  949.         local steps = ActivePhase.generateSteps(PhaseArgs)
  950.         local nSteps = #steps
  951.  
  952.         if not Resuming then
  953.             CompletedSteps = 0
  954.         else
  955.             Resuming = false
  956.         end
  957.  
  958.         while CompletedSteps < nSteps do
  959.             local newPhase, newPhaseArgs = steps[CompletedSteps + 1]()
  960.  
  961.             if newPhase then
  962.                 InitPhase(newPhase, newPhaseArgs)
  963.                 print(newPhase.name)
  964.                 break
  965.             end
  966.  
  967.             CompletedSteps = CompletedSteps + 1
  968.         end
  969.     end
  970. end
  971.  
  972. function ClearTurtle()
  973.     local files = {
  974.         Filenames.project,
  975.         Filenames.state,
  976.         Filenames.turnFile
  977.     }
  978.     for _, filename in pairs(files) do
  979.         if fs.exists(filename) then
  980.             fs.delete(filename)
  981.         end
  982.     end
  983. end
  984.  
  985.  
  986.  
  987. -- ### MAIN MODULE
  988. -- ### main.lua
  989.  
  990.  
  991.  
  992.  
  993. function Install()
  994.     local programName = shell.getRunningProgram()
  995.     local startupScript = "shell.run(\"" .. programName .. "\")"
  996.     local oppositeNames = {
  997.         ["startup"] = "startup.lua",
  998.         ["startup.lua"] = "startup"
  999.     }
  1000.  
  1001.     if programName == "startup" or programName == "startup.lua" then
  1002.         if fs.exists(oppositeNames[programName]) then
  1003.             fs.move(oppositeNames[programName], os.epoch() .. "_" .. oppositeNames[programName])
  1004.         end
  1005.         return
  1006.     end
  1007.  
  1008.     if fs.exists("startup.lua") then
  1009.         fs.move("startup.lua", os.epoch() .. "_startup.lua")
  1010.     end
  1011.  
  1012.     if fs.exists("startup") then
  1013.         local readHandle = fs.open("startup", "r")
  1014.         local contents = readHandle.readAll()
  1015.         readHandle.close()
  1016.  
  1017.         if contents == startupScript then
  1018.             return
  1019.         end
  1020.  
  1021.         fs.move("startup", os.epoch() .. "_startup")
  1022.     end
  1023.  
  1024.     local writeHandle = fs.open("startup", "w")
  1025.     writeHandle.write(startupScript)
  1026.     writeHandle.close()
  1027. end
  1028.  
  1029.  
  1030. Install()
  1031.  
  1032.  
  1033. for _, side in pairs({
  1034.     "top",
  1035.     "bottom",
  1036.     "left",
  1037.     "right",
  1038.     "front",
  1039.     "back"
  1040. }) do
  1041.     if peripheral.hasType(side, "modem") then
  1042.         rednet.close(side)
  1043.         rednet.open(side)
  1044.         break
  1045.     end
  1046. end
  1047.  
  1048.  
  1049. Args = { ... }
  1050.  
  1051. if #Args == 0 then
  1052.     print("Turtle resuming in 5 seconds...")
  1053.     sleep(5)
  1054.     parallel.waitForAll(function ()
  1055.         RunTurtle(nil)
  1056.     end, function ()
  1057.         RunServer(nil)
  1058.     end)
  1059. elseif #Args == 1 then
  1060.     ClearTurtle()
  1061.     if tonumber(Args[1]) == os.getComputerID() then
  1062.         parallel.waitForAll(function ()
  1063.             RunTurtle(os.getComputerID())
  1064.         end, function ()
  1065.             RunServer(nil)
  1066.         end)
  1067.     else
  1068.         ClearServer()
  1069.         RunTurtle(Args[1])
  1070.     end
  1071. elseif #Args == 3 then
  1072.     ClearTurtle()
  1073.     ClearServer()
  1074.     parallel.waitForAny(function ()
  1075.         RunTurtle(os.getComputerID())
  1076.     end, function ()
  1077.         RunServer(Args)
  1078.     end)
  1079. end
  1080.  
  1081. -- TODO: clear previous state if starting new session, but preserve project id (or generate random each time if possible? math.random?)
  1082.  
Advertisement
Comments
Add Comment
Please, Sign In to add comment
Advertisement