View difference between Paste ID: MZEPY5vN and dULznkqY
SHOW: | | - or go back to the newest paste.
1
----------------------------------------------------------------------
2
--                              Module API                          --
3
----------------------------------------------------------------------
4-
4+
5-
-- Version 0.1a
5+
-- Version 0.1(481): 1.481-compatible
6-
6+
7
-- Module is an API that allows to interact with master and perform next functions:
8
--    Communication to the master.
9
--    Navigation to the said point.
10
--    Request tasks and execute functions using a response table.
11
-- Usage:
12
-- 1) Set up a task table using the module.addTasks(taskTable) function
13
--    taskTable is a table with tasks as keys (tasks are recived from Master)
14
--    and functions as values. Function should accept a response as an argument,
15
--    so master can pass some additional data to module.
16
--    Those functions are executed when module recieved an associated task after module has navigated to
17
--    the position sent in response (if any)
18
-- 2) Set the type of your module using the setType (newType) function
19
--    Type is used by Master to determine what can this module do.
20
-- 3) Run the module.init () function
21
-- 4) Run the module.operate() function to start
22
-- 5) If you move the module in your tasks, please use module.move(direction) or module.moveEx (f) to move it
23
--    and use turn(side) or turnTo(f) to turn it, because those functions are keep tracking the module's position
24
--    or you can find its new position by yourself and use module.setPosition (x,y,z,f) to set it
25
 
26
 
27
--[[
28
*********************************************************************************************
29
*                                    Navigation Part                                        *
30
*********************************************************************************************
31
]]--
32
 
33
-- Location is a global table containing all the coordinates and facing direction of the turtle
34
-- And here is the function to set the position of the turtle and another one to get it.
35
location = {x = 0, y = 0, z = 0, f = 0}
36
 
37
function setPosition (x, y, z, f)
38
    location.x = x
39
    location.y = y
40
    location.z = z
41
    location.f = f
42
end
43
 
44
function getPosition ()
45
    return { x = location.x,
46
             y = location.y,
47
             z = location.z,
48
             f = location.f, } -- return a copy of the location table.
49
end
50
 
51
-- And a small function to print the position. For debug purposes
52
function printPosition ()
53
    print ("Currrent turtle position:")
54
    print ("    x = "..location.x)
55
    print ("    y = "..location.y)
56
    print ("    z = "..location.z)
57
    print ("    f = "..location.f)
58
end
59
 
60
-- A turning function
61
function turn (direction)
62
    if direction == "left" then
63
        turtle.turnLeft()
64
        location.f = location.f - 1
65
    elseif direction == "right" then
66
        turtle.turnRight()
67
        location.f = location.f + 1
68
    elseif direction == "around" then
69
        turtle.turnLeft()
70
        turtle.turnLeft()
71
        location.f = location.f - 2
72
    else
73
        print ("No such direction: "..direction)
74
        return false
75
    end
76
    location.f = (location.f + 4) % 4 -- This is used to handle the f < 0 and f > 3 situations
77
    return true
78
end
79
 
80
-- This one is used to turn the turtle to specified f direction.
81
-- A little bit of pony magic is used to calculate how should it turn
82
function turnTo (face)
83
    local diff = face - location.f
84
    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
85
        return turn ("around")
86
    elseif math.fmod(diff + 4, 4) ==  1 then    --this is true if the difference between f and face is 1
87
            return turn ("right")                   --f = 0 and face = 3 is also satisfies the condition (-3 + 4 == 1)
88
    elseif math.fmod(diff - 4, 4) == -1 then    --this is true if the difference between f and face is -1
89
            return turn ("left")                    --f = 3 and face = 1 is also satisfies the condition ( 3 - 4 == 1)
90
    end
91
    return false    --returned if turtle is already faced the specified direction
92
end
93
 
94
-- This function is used to return actual x and z shifts
95
-- These shifts are the numbers which you should add to the coordinates when turtle moves forward.
96
-- I use a table to decode it since it is more compact and easier to read than lots of conditions
97
local tShifts = {
98
    [0] = { 1,  0},
99
    [1] = { 0,  1},
100
    [2] = {-1,  0},
101
    [3] = { 0, -1},
102
}
103
function fDirection ()
104
    return unpack (tShifts[location.f])
105
end
106
 
107
-- This function is used to update the turtle location when it has moved
108
function updateLocation (direction)
109
    if direction == "up" then
110
        location.y = location.y + 1
111
    elseif direction == "down" then
112
        location.y = location.y - 1        
113
    elseif direction == "forward" then
114
        local xShift, zShift = fDirection ()
115
        location.x = location.x + xShift        
116
        location.z = location.z + zShift
117
    elseif direction == "back" then
118
        local xShift, yShift = fDirection ()
119
        location.x = location.x - xShift
120
        location.z = location.z - zShift
121
    end
122
end
123
 
124
-- This function helps us with our EnderChests if there is something that obstructs placement
125
-- It checks what's going on on top of it, and if there is a turtle, it waits before digging.
126
function forcePlaceUp ()
127-
	while not turtle.placeUp() do
127+
        while not turtle.placeUp() do
128-
		if turtle.detectUp() then
128+
                if turtle.detectUp() then
129-
			if peripheral.isPresent ("top") and peripheral.getType ("top") == "turtle" then
129+
                        if peripheral.isPresent ("top") and peripheral.getType ("top") == "turtle" then
130-
				print "Other turtle detected. Waiting..."
130+
                                print "Other turtle detected. Waiting..."
131-
				while peripheral.isPresent ("top") do
131+
                                while peripheral.isPresent ("top") do
132-
					sleep (5)
132+
                                        sleep (5)
133-
				end
133+
                                end
134-
			end
134+
                        end
135-
			turtle.digUp()
135+
                        turtle.digUp()
136-
		elseif turtle.attackUp() then
136+
                elseif turtle.attackUp() then
137-
			print "Attacking obstructing entity"
137+
                        print "Attacking obstructing entity"
138-
		end
138+
139-
	end
139+
140
end
141
-- This function tries to refuel the turtle to make it have an <amount> of fuel
142
-- It assumes that turtle has a refuel ender chest in slot 1
143
function refuel (amount)
144
    if turtle.getFuelLevel () > amount then
145
            return true
146
    else
147
        turtle.select (1)
148
        forcePlaceUp ()
149
        turtle.suckUp ()
150
        while turtle.refuel (1) do
151
            if turtle.getFuelLevel() >= amount then
152
                turtle.dropUp ()
153
                turtle.digUp ()
154
                return true
155
            end
156
            if turtle.getItemCount (1) == 0 then
157
                turtle.suckUp ()
158
            end
159
        end
160
    end
161-
	turtle.dropUp ()
161+
        turtle.dropUp ()
162
    turtle.digUp ()
163
    return false
164
end
165
 
166
-- This function checks if there are required amount of fuel,
167
-- and if there's not, it tries to refuel it,
168
-- if it fails, it hangs until refueling sucseeds
169
function checkFuel (amount)
170
    if turtle.getFuelLevel () > amount then
171
        return true
172
    else
173
        if not refuel (amount) then
174
            print "No fuel found! Please, add more fuel!"
175
            while not refuel (amount) do
176
                sleep (2)
177
            end
178
        end
179
        return true
180
    end
181
end
182
 
183
-- This function used to empty the turtle's inventory into the ender chest in the second slot
184
-- Warning! Required a good sorting system able to empty the chest as quickly as possible!
185
-- It will hang if chest is full, but it's not recommended
186
function dumpStuff ()
187
    turtle.select (2)
188
    forcePlaceUp ()
189
    for i = 3,16 do
190
        while turtle.getItemCount (i) > 0 do
191
            turtle.select (i)
192
            turtle.dropUp ()
193
            end
194
    end
195
    turtle.select (2)
196
    turtle.digUp ()
197
    turtle.select (1)
198
end
199
 
200
function checkSpace ()
201
    if turtle.getItemCount (16) > 0 then
202
        dumpStuff ()
203
    end
204
end
205
 
206
-- Advanced movement function.
207
-- Again, I use a set of tables with all the movements and other "directional" turtle actions
208
-- 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.
209
-- If it waits too long (60 seconds), however, it just eats that turtle
210
tMove = {
211
    forward = turtle.forward,
212
    up = turtle.up,
213
    down = turtle.down
214
}
215
tDetect = {
216
    forward = turtle.detect,
217
    up = turtle.detectUp,
218
    down = turtle.detectDown
219
}
220
tDig = {
221
    forward = turtle.dig,
222
    up = turtle.digUp,
223
    down = turtle.digDown
224
}
225
tAttack = {
226
    forward = turtle.attack,
227
    up = turtle.attackUp,
228
    down = turtle.attackDown
229
}
230
tIsTurtle = {
231
    forward = function ()
232
        if peripheral.isPresent ("front") and peripheral.getType ("front") == "turtle" then
233
            return true
234
        end
235
        return false
236
    end,
237
    up = function ()
238
        if peripheral.isPresent ("top") and peripheral.getType ("top") == "turtle" then
239
            return true
240
        end
241
        return false
242
    end,
243
    down = function ()
244
        if peripheral.isPresent ("bottom") and peripheral.getType ("bottom") == "turtle" then
245
            return true
246
        end
247
        return false
248
    end
249
}
250
function move (direction)
251
    if not checkFuel (1) then
252
        return false
253
    end
254
    while not tMove[direction]() do
255
        if tDetect[direction]() then
256
            if tIsTurtle[direction]() then
257
                print "Other turtle detected. Waiting..."
258
                while tIsTurtle[direction]() do
259
                    sleep (5)
260
                end
261
            else
262
                if tDig[direction]() then
263
                    checkSpace ()
264
                else
265
                    print "Can't pass the obstruction!"
266
                    return false
267
                end
268
            end
269
        elseif tAttack[direction]() then
270
            checkSpace ()
271
        end
272
    end
273
    updateLocation (direction)
274
    return true
275
end
276
 
277
-- This one is used to move turtle to specified f direction OR up/down <count times>
278
function moveEx (direction, count)
279
    if type(direction) == "number" then
280
        turnTo (direction)
281
        for i = 1, count do
282
            move ("forward")
283
        end
284
    else
285
        for i = 1, count do
286
            move (direction)
287
        end
288
    end
289
end
290
 
291
-- A height function. For convinience
292
function height (value)
293
    local diff = value - location.y
294
    refuel(math.abs(diff))
295
    if diff > 0 then
296
        moveEx ("up", diff)
297
    elseif diff < 0 then
298
        moveEx ("down", -diff)
299
    end
300
end
301
 
302
-- Small goto function. It moves turtle in one axis.
303
-- Example: module.gotoOne ("x", 10) moves the turtle to x = 10
304
function gotoOne (coordinate, value)
305
    if coordinate == "y" then
306
        height (value)
307
    elseif coordinate == "x" then
308
        local diff = value - location.x
309
        refuel(math.abs(diff))
310
        if diff > 0 then
311
            moveEx (0, diff)
312
        elseif diff < 0 then
313
            moveEx (2, -diff)
314
        end
315
    elseif coordinate == "z" then
316
        local diff = value - location.z
317
        refuel(math.abs(diff))
318
        if diff > 0 then
319
            moveEx (1, diff)
320
        elseif diff < 0 then
321
            moveEx (3, -diff)
322
        end
323
    end
324
end
325
 
326
-- Let's organize our movement a bit to avoid movement deadlocks. Thanks to Doyle3694 for the idea
327
-- It is 2D navigation, so height is not taken into account. It will allow turtles to relocate themselves to their mining positions
328
-- without stucking.
329
-- I'll divide all the 2D area to four zones. In each zone turtle can move only in two directions.
330
-- Zones are determinated by the pair of coordinates. Turtles will move clockwise relative to the point with that coordinates
331
-- height is the heighat at which all navigation is done
332
-- There is a function to set the zones
333
local naviZones = {x = 0, z = 0, height = 0}
334
function setZones (x, z, height)
335
    naviZones.x = x
336
    naviZones.z = z
337
    naviZones.height = height
338
end
339
 
340
-- There is a table used to determine to which directions turtle is allowed to move.
341
-- Direction "first" is the direction turtle will move first ^_^ This one can't lead out of the zone.
342
-- Direction "second" is leading to the next zone.
343
-- Directions are the tables of coordinate and direction (+1 - positive, -1 - negative)
344
local tZones = {
345
    z1 = {first = {"x",  1}, second = {"z",  1}},
346
    z2 = {first = {"z",  1}, second = {"x", -1}},
347
    z3 = {first = {"x", -1}, second = {"z", -1}},
348
    z4 = {first = {"z", -1}, second = {"x",  1}}
349
}
350
 
351
-- Here is a function to determine in which zone turtle is
352
function currentZone ()
353
    if location.x > naviZones.x then
354
        if location.z > naviZones.z then
355
            return "z2"
356
        else
357
            return "z1"
358
        end
359
    else
360
        if location.z > naviZones.z then
361
            return "z3"
362
        else
363
            return "z4"
364
        end
365
    end
366
end
367
 
368
-- And there is one used to navigate turtle, taking into account zones.
369
-- All the navigation is done at zones.height and then turtle changes it's height
370
function navigateTo (destination)
371
    -- go to zones.height only if we need to move
372
    if destination.x ~= location.x or destination.z ~= location.z then
373
        height (naviZones.height)
374
    end
375
    while location.x ~= destination.x or location.z ~= destination.z do -- navigate between zones until we are in the place
376-
		local zone = currentZone ()
376+
                local zone = currentZone ()
377
        -- Let's unpack all the coordinates
378
        local coordinateFirst, directionFirst = unpack (tZones[zone].first)  
379
        local coordinateSecond, directionSecond = unpack (tZones[zone].second)
380
        -- And check some things
381
        -- if we are allowed to move at both directions - just do it!
382
        if (destination[coordinateFirst]-location[coordinateFirst]) * directionFirst >= 0
383
            and (destination[coordinateSecond]-location[coordinateSecond]) * directionSecond >= 0 then
384
            -- BTW, we just check there that coordinate difference and destination has the same sign
385
            -- Otherwise turtle that is already went to "second" coordinate won't be able to change it's "first" coordinate
386
            -- Then just use gotoOne to go to the desired location
387
            gotoOne (coordinateFirst, destination[coordinateFirst])            
388
            gotoOne (coordinateSecond, destination[coordinateSecond])            
389
        else
390
            -- If we are not allowed to move by any of those coordinates in current zone
391
            -- Just head up to the second zone
392
            local nextPosition = naviZones[coordinateSecond]
393
            -- We need to do that for actually leave z1 and z4 since they are one block bigger
394
            if zone == "z4" or zone == "z1" then
395
                nextPosition = nextPosition + 1
396
            end
397
            -- go to the next zone!                
398
            gotoOne (coordinateSecond, nextPosition)
399
        end        
400
    end
401-
	-- if y is set - move to y.
401+
        -- if y is set - move to y.
402-
	if destination.y ~= nil then
402+
        if destination.y ~= nil then
403-
		height(destination.y)
403+
                height(destination.y)
404-
	end
404+
405
    -- same for f
406-
	if destination.f ~= nil then
406+
        if destination.f ~= nil then
407-
		turnTo(destination.f)
407+
                turnTo(destination.f)
408-
	end
408+
409
end
410
 
411
--[[
412
*********************************************************************************************
413
*                                    Communication Part                                     *
414
*********************************************************************************************
415
]]--
416
-- There are some variables used for communication
417
-- ID is the turtle's unique identifier
418
-- Master is the ID of the master
419
-- Channel is the channel used to send messages to Master
420
-- Master should respond on channel+1
421
-- Type is the type of the module. Used by Master to determine what it can do
422
local ID = 0
423
local Master = 0
424-
local channel = 100
424+
425
function setType (newType)
426
        Type = newType
427-
	Type = newType
427+
428
 
429
-- The first function is used to parse a message received on modem
430-
-- Wee need a modem to communicate.
430+
431-
local modem = peripheral.wrap ("right")
431+
432
--  1) Protocol - must be equal to "KuuNet"
433
--  2) ID - that must be equal to turtle's ID
434
--  3) Master - that must be equal to Master variable.
435
--  Some other fiels
436
function isValid (message)
437
        if message ~= nil then
438
                if message.Protocol ~= "KuuNet" then
439
                        return false
440
                end
441-
	if message ~= nil then
441+
                if message.ID ~= ID then
442-
		if message.Protocol ~= "KuuNet" then
442+
                        return false
443-
			return false
443+
444-
		end
444+
                if message.Master ~= Master then
445-
		if message.ID ~= ID then
445+
                        return false
446-
			return false
446+
447-
		end
447+
448-
		if message.Master ~= Master then
448+
449-
			return false
449+
                return false
450-
		end
450+
451-
		return true
451+
452-
	else
452+
453-
		return false
453+
454-
	end
454+
455
-- It returns response as message or nil
456
function request (message, timeout)
457
        -- open a channel to listen for response
458
        rednet.open ("right")
459
        local request = {
460
                Protocol = "KuuNet",
461-
	-- open a channel to listen for response
461+
                ID = ID,
462-
	modem.open (channel+1)
462+
                Type = Type,
463-
	local request = {
463+
                Master = Master,
464-
		Protocol = "KuuNet",
464+
                Request = message
465-
		ID = ID,
465+
        }
466-
		Type = Type,
466+
        rednet.broadcast (textutils.serialize(request))
467-
		Master = Master,
467+
        local response = nil
468-
		Request = message
468+
        parallel.waitForAny (
469-
	}
469+
                function ()
470-
	modem.transmit (channel, channel+1, textutils.serialize(request))
470+
                        sleep (timeout)
471-
	local response = nil
471+
                end,
472-
	parallel.waitForAny (
472+
                function ()
473-
		function ()
473+
                        while true do
474-
			sleep (timeout)
474+
                                local event, sndID, text_msg, senderDistance = os.pullEvent("rednet_message")
475-
		end,
475+
                                local msg = textutils.unserialize (text_msg)
476-
		function ()
476+
                                if isValid(msg) then
477-
			while true do
477+
                                        response = msg
478-
				local event, modemSide, sndChan, rplyChan, text_msg, senderDistance = os.pullEvent("modem_message")
478+
                                        break
479-
				local msg = textutils.unserialize (text_msg)
479+
                                end
480-
				if isValid(msg) then
480+
                        end
481-
					response = msg
481+
482-
					break
482+
        )
483-
				end
483+
        rednet.close ("right")
484-
			end
484+
        return response
485-
		end
485+
486-
	)
486+
487-
	modem.close (channel+1)
487+
488-
	return response
488+
489
*                                     Operation part                                        *
490
*********************************************************************************************
491
]]--
492
-- Fallback location is the point where module goes if it can't get a task from Master
493
local fallbackLocation = {}
494
 
495
-- We need to read all the data we need to move around - turtle's position and navigation zones
496
-- "initdata" is the file created by Master and containing all the data that's similar for each module placed by that Master
497
function init ()
498
    local file = fs.open ("/disk/initdata", "r")
499
    local communicationData = textutils.unserialize (file.readLine ())
500
    Master = communicationData.MasterID
501
    -- set module's location
502
        location = textutils.unserialize (file.readLine ())
503
    -- set navigation data
504
        naviZones = textutils.unserialize (file.readLine ())
505-
    channel = communicationData.channel
505+
506
    -- set fallbackLocation to the point where we are, but at navigation height.
507-
	location = textutils.unserialize (file.readLine ())
507+
508
   
509-
	naviZones = textutils.unserialize (file.readLine ())
509+
510-
    
510+
        local response = request ("Master", 5)
511
        ID = response.NewID
512
   
513-
    
513+
        -- 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.
514
        move("forward")
515-
 	local response = request ("Master", 5)
515+
516-
	ID = response.NewID
516+
517
-- Here is the task table. It is the table, that contains all the task module can get form Master and associated functions
518-
	-- 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.
518+
519-
	move("forward")
519+
520
--  1) Requesting a task from master
521
--  2) Moving to the location specified in response (if present)
522
--  3) Executing the associated function
523
-- There is always a default task "return" which returns module to Master.
524
-- response from Master is always passed to the function, so, if it contains additional data, you can use it
525
-- Return is a task to return. Master should pass a xet of coordinates as well as "f" direction to make module face the Master.
526
tTasks = {
527
        Return = function (response)
528
                move ("forward")
529
                request ("Returned", 3)
530
                sleep (30)
531
        end
532-
	Return = function (response)
532+
533-
		move ("forward")
533+
534-
		request ("Returned", 3)
534+
535-
		sleep (30)
535+
536-
	end
536+
        for key, value in pairs (taskTable) do
537
                tTasks[key] = value
538
        end
539
end
540
 
541-
	for key, value in pairs (taskTable) do
541+
542-
		tTasks[key] = value
542+
543-
	end
543+
        tTasks [task] = nil
544
end
545
 
546
-- And a main operation function
547
function operate ()
548-
	tTasks [task] = nil
548+
        while true do
549
                local task
550
        -- failedRequests indicates how many times Master didn't answered to request
551
        local failedRequests = 0
552
                while task == nil do
553-
	while true do
553+
                        task = request ("Task", 5)
554-
		local task
554+
555
                failedRequests = failedRequests + 1
556
                -- if there are 5 requests failed, then change position to the point of last communication
557-
		while task == nil do
557+
558-
			task = request ("Task", 5)
558+
559
                    navigateTo (fallbackLocation)
560
                end
561
            else
562
                lastCommunication = getPosition()
563
            end
564
                end
565
                if task.x ~= nil and task.z ~= nil then
566
                        navigateTo (task)
567
                end
568
                tTasks [task.Task](task) -- Task. tttTask. Tasktask. taskataskatask. HerpDerp.
569-
		end
569+
570-
		if task.x ~= nil and task.z ~= nil then
570+