View difference between Paste ID: 9gGbEj7X and r32RjcK0
SHOW: | | - or go back to the newest paste.
1
-- voice version 1.1
2
-- list of args
3
local startupArgs = { ... }
4
if #startupArgs < 1 or #startupArgs > 2 then
5
  print "Usage: voice <name> [mute]"
6
  return
7
end
8
9
-- whether we should mute turtle output
10
local mute = "partial"
11
if startupArgs[2] == "false" then
12
  mute = false
13
elseif startupArgs[2] == "true" then
14
  mute = true
15
end
16
17
-- loading config data and defaults
18
local config = {}
19
if fs.exists( "voice.cfg" ) then
20
  local configFile = fs.open( "voice.cfg", "r" )
21
  config = textutils.unserialise( configFile.readAll() ) or {}
22
  configFile.close()
23
end
24
25
-- commands to disallow
26
local blacklist = config.blacklist or {
27
  equipLeft = true, -- use "equip"
28
  equipRight = true, -- don't lose the chatbox, we need that
29
  wrap = true, -- useless
30
  find = true, -- useless
31
}
32
-- and programs
33
local progBlacklist = config.progBlacklist or {
34
  -- will cause errors
35
  startup = true,
36
  voice = true,
37
  equip = true, -- use equip command
38
  
39
  -- locks the controller out
40
  edit = true,
41
  exit = true,
42
  lua = true,
43
  shell = true,
44
  shutdown = true,  
45
}
46
local scriptPath = config.scriptPath or "scripts/"
47
48
-- backward/forward compatibility
49
local unpack = table.unpack or unpack
50
51
-- only take input from a predetermined player
52
local name = startupArgs[1]
53
local label = os.getComputerLabel() or "Turtle"
54
55
-- chat commands
56
local chatbox = peripheral.wrap( "right" )
57
local say, tell
58
59
-- define data based on which chatbox we are using, or if we are using a modem
60
local mode, eventType, opened
61
local messages = {}
62
local pType = peripheral.getType( "right" )
63
64
if pType == "chatbox" then
65
  mode = "moar"
66
  eventType = "chatbox_command"
67
  chatbox.setLabel( label )
68
  function say( message )
69
    chatbox.say( message )
70
    sleep( 0.6 )
71
  end
72
  function tell( message )
73
    chatbox.tell( name, message )
74
    sleep( 0.6 )
75
  end
76
77
elseif pType == "chatBox" then
78
  mode = "plus"
79
  eventType = "command"
80
  function say( message )
81
    chatbox.say( message, 60000000, true, label )
82
    sleep( 0.6 )
83
  end
84
  function tell( message )
85
    chatbox.tell( name, message, 60000000, true, label )
86
    sleep( 0.6 )
87
  end
88
  
89
elseif pType == "modem" then
90
  name = tonumber( name )
91
  mode = "modem"
92
  eventType = "rednet_message"
93
  if not rednet.isOpen( "right" ) then
94
    opened = true
95
    rednet.open( "right" )
96
  end
97
  function tell( message )
98
    table.insert( messages, message )
99
  end
100
  
101
else
102
  print "Unable to find chatbox"
103
  return
104
end
105
106
-- list of mute overrides, or commands that return even when muted
107
-- basically any command that just returns a result
108
local unmuted = config.unmuted or {
109
  --turtle
110
  compare = true,
111
  compareDown = true,
112
  compareUp = true,
113
  compareTo = true,
114
  detect = true,
115
  detectDown = true,
116
  detectUp = true,
117
  equip = true,
118
  getFuelLevel = true,
119
  getFuelLimit = true,
120
  getItemCount = true,
121
  getItemDetail = true,
122
  getItemSpace = true,
123
  getSelectedSlot = true,
124
  inspect = true,
125
  inspectDown = true,
126
  inspectUp = true,
127
  
128
  -- redstone
129
  getSides = true,
130
  getInput = true,
131
  getOutput = true,
132
  getAnalogInput = true,
133
  getAnalogOutput = true,
134
  getBndledInput = true,
135
  getBunledOutput = true,
136
  testBundledOutput = true,
137
  
138
  -- peripheral
139
  isPresent = true,
140
  getType = true,
141
  getMethods = true,
142
  getNames = true,
143
  
144
  -- MoarPeripherals
145
  getDensity = true, -- Density Scanning Turtle
146
  getNaturalLightLevel = true, -- Outdoorsy Turtle
147
  getFacing = true, -- Dizzy Turtle/Peripherals++ Navigational Turtle
148
  --chatbox
149
  getLabel = true,
150
  getReadRange = true,
151
  getMaxReadRange = true,
152
  getSayRange = true,
153
  getMaxSayRange = true,
154
  getTellRange = true,
155
  getMaxTellRange = true,
156
  
157
  -- Peripherals++
158
  getEntity = true, --Ridable Turtle
159
  getLiquid = true, --Thirsty Turtle
160
  getXP = true, --XP Turtle
161
  --Barrel Turtle
162
  getUnlocalizedName = true,
163
  getLocalizedName = true,
164
  getItemID = true,
165
  getAmount = true,
166
  getOreDictEntries = true,
167
  --Environment Scanner
168
  isRaining = true,
169
  getBiome = true,
170
  getTemperature = true,
171
  isSnow = true,
172
  --Equivalence Checking Turtle
173
  getEntries = true,
174
  doItemsMatch = true,
175
  --Gardening Turtle
176
  getGrowth = true,
177
  getGrowthUp = true,
178
  getGrowthDown = true,
179
  --Player Sensor
180
  getNearbyPlayers = true,
181
  getAllPlayers = true,
182
  --Sign Reading Turtle
183
  read = true,
184
  readUp = true,
185
  readDown = true,
186
  
187
  -- chat commands
188
  script = true,
189
  delete = true,
190
  program = true,
191
  
192
  -- error reporting
193
  Usage = true,
194
  ERROR = true
195
}
196
197
-- override print
198
local consolePrint = print
199
print = function( ... )
200
  if not mute then
201
    local strings = { ... }
202
    for _, v in ipairs( strings ) do
203
      tell( v )
204
    end
205
  end
206
  return consolePrint( ... )
207
end
208
209
-- print a bunch of messages
210
function resultsPrint( com, results )
211
  local oldMute = mute
212
  if oldMute == "partial" and unmuted[com] then
213
    mute = false
214
  end
215
  if type( results ) == "table" then
216
    for i, v in ipairs( results ) do
217
      if v ~= nil then
218
        local comText = com .. ( #results == 1 and "" or " " .. i ) .. ": "
219
        if type( v ) == "table" then
220
          for k, w in pairs( v ) do
221
            print( comText .. k .. " = '" .. tostring(w) .. "'" )
222
          end
223
        else
224
          print( comText .. tostring(v) )
225
        end
226
      end
227
    end
228
  else
229
    print( com .. ": " .. tostring(results) )
230
  end
231
  mute = oldMute
232
end
233
234
-- write an error
235
function resultsError( text )
236
  return resultsPrint( "ERROR", text )
237
end
238
239
-- split the input string into space separate parameters, for a few override functions
240
function processArgs( input )
241
  local out = {}
242
  for match in string.gmatch( input, '%S+') do
243
    table.insert( out, match )
244
  end
245
  return out
246
end
247
248
-- process the input string into the command table
249
-- format: { { { command, { arg, ... } }, ... }, number }
250
function processCommand( input )
251
  local out = {}
252
  for match in string.gmatch( input, '%S+') do
253
    
254
    -- first, split out the number
255
    -- format - command:number
256
    local command, number
257
    if match:find( ":" ) then
258
      command = match:gsub( ":.*$", "" )
259
      number = tonumber(( match:gsub( "^.*:", "" ) ))
260
    else
261
      command = match
262
      number = 1
263
    end
264
    
265
    -- then, split apart our commands
266
    -- format - command+command2
267
    local commands = {}
268
    for match2 in command:gmatch( "([^+]+)" ) do
269
      table.insert( commands, match2 )
270
    end
271
    
272
    -- next, find the args
273
    -- format - command(arg,arg2)
274
    local results = {}
275
    for _, v in ipairs( commands ) do
276
      if v:find( "%(" ) then
277
        local command = v:gsub( "%(.*%)$", "" )
278
        local arg = v:gsub( "^.*%((.*)%)$", "%1" )
279
        local argList = {}
280
        for match in arg:gmatch( "([^,]+)" ) do
281
          -- numbers
282
          if tonumber( match ) then
283
            table.insert( argList, tonumber( match ) )
284
          -- boolean true
285
          elseif match == "true" then
286
            table.insert( argList, true )
287
          -- boolean false
288
          elseif match == "false" then
289
            table.insert( argList, false )
290
          -- string
291
          else
292
            -- character escapes for strings
293
            match = match
294
              :gsub( "%%%%", "%%X" )  -- %%: percent sign escape
295
              :gsub( "([^%%])%-", "%1 " ) -- -: space
296
              :gsub( "%%%-", "-" )    -- %-: underscore
297
              :gsub( "%%P", "+" )     -- %P: plus sign
298
              :gsub( "%%C", "," )     -- %C: comma
299
              :gsub( "%%L", "(" )     -- %L: left bracket
300
              :gsub( "%%R", ")" )     -- %R: right bracket
301
              :gsub( "%%N", ":" )     -- %N: colon
302
              :gsub( "%%T", "true" )  -- %T: standalone "true"
303
              :gsub( "%%F", "false" ) -- %F: standalone "false"
304
              :gsub( "%%X", "%%" )    -- %X: alternate percent sign escape
305
            table.insert( argList, match )
306
          end
307
        end
308
        table.insert( results, { command, argList } )
309
      else
310
        table.insert( results, { v, {} } )
311
      end
312
    end
313
    table.insert( out, { results, number } )
314
  end
315
  return out
316
end
317
318
-- peripheral commands
319
local tool = peripheral.wrap( "left" )
320
321
-- prevent function errors from crashing the program
322
function callFunction( com, func, ... )
323
  local results = { pcall( func, ... ) }
324
  if results[1] then
325
    table.remove( results, 1 )
326
    resultsPrint( com, results )
327
  else
328
    resultsError( com .. ": " .. results[2]:gsub( "^pcall: ", "" ) )
329
  end
330
end
331
332
-- function to run commands, used for running scripts as well
333
local runCommand -- recursion of sorts, this function can call itself
334
local usedScripts = {} -- to prevent a script from running itself
335
function runCommand( command )
336
  local commands = processCommand( command )    
337
  for _, v in ipairs( commands ) do
338
    local num = v[2]
339
    if type( v[1] ) == "table" and type( num ) == "number" then
340
      for i = 1, v[2] do
341
        for _, comData in ipairs( v[1] ) do
342
          local com = comData[1]
343
          local args = comData[2]
344
          if blacklist[com] then
345
            resultsError( "Function '" .. com .. "' is blacklisted" )
346
          
347
          -- mute control
348
          elseif com == "mute" then
349
            if args[1] == true then
350
              mute = true
351
            elseif args[1] == false then
352
              mute = false
353
            else
354
              mute = "partial"
355
            end
356
          
357
          -- shorthand for mute(false)
358
          elseif com == "unmute" then
359
            mute = false
360
          
361
          -- turtle scripts, TODO: script writing command
362
          elseif com == "script" then
363
            if args[1] then
364
              local name = scriptPath .. args[1] .. ".tz"
365
              if fs.exists( name ) then
366
                if usedScripts[args[1]] then
367
                  resultsError( "script: A script cannot run itself" )
368
                else
369
                  usedScripts[args[1]] = true
370
                  local file = fs.open( name, "r" )
371
                  local script = file.readAll()
372
                  file.close()
373
              
374
                  -- don't allow using the script command in scripts, as a script should not eteranlly loop
375
                  resultsPrint( "script", "Running script '" .. args[1] .. "'" )
376
                  runCommand( script, true )
377
                  usedScripts[args[1]] = nil
378
                end
379
              else
380
                resultsError( "script: Script does not exist" )
381
              end
382
            else
383
              resultsError( "script: Must specifiy a script" )
384
            end
385
            
386
          -- run a program from the turtle
387
          elseif com == "program" then
388
            if args[1] then
389
              if progBlacklist[args[1]] then
390
                resultsError( "program: Program is blacklisted" )
391
              else
392
                resultsPrint( "program", "Running program '" .. args[1] .. "'" )
393
                shell.run( table.concat( args, " " ) )
394
              end
395
            else
396
              resultsError( "program: Must specifiy a program" )
397
            end
398
            
399
          -- delay
400
          elseif com == "sleep" then
401
            if type( args[1] ) == "number" then
402
              resultsPrint( com, { sleep( args[1] ) } )
403
            else
404
              resultsError( "sleep: Number expected, got " .. type( args[1] ) )
405
            end
406
            
407
          -- swap tools and reload peripheral
408
          elseif com == "equip" then
409
            turtle.equipLeft()
410
            local pType = peripheral.getType( "left" ) or "tool/empty"
411
            resultsPrint( com, { pType } )
412
            tool = peripheral.wrap( "left" )
413
            
414
          -- and lastly chatbox commands
415
          elseif com == "say" and mode ~= "modem" then
416
            if args[1] then
417
              say( args[1] )
418
            end
419
            
420
          -- call a peripheral, either a turtle one or a block
421
          elseif peripheral[com] then
422
            callFunction( com, peripheral[com], unpack( args ) )
423
            
424
          -- if none of the others, first try a turtle command
425
          elseif turtle[com] then
426
            callFunction( com, turtle[com], unpack( args ) )
427
            
428
          -- then a redstone command
429
          elseif redstone[com] then
430
            callFunction( com, redstone[com], unpack( args ) )
431
          
432
          -- then a peripheral command
433
          elseif tool and tool[com] then
434
            callFunction( com, tool[com], unpack( args ) )
435
            
436
          else
437
            resultsError( "No such command '" .. com .. "'" )
438
          end
439
        end
440
      end
441
      
442
    else
443
      resultsError( "Broken command syntax" )
444
    end
445
  end
446
end
447
448
consolePrint( "Running 'voice' by KnightMiner" )
449
consolePrint( "Press [end] to exit" )
450
451
-- main loop
452
while true do
453
  local event = { os.pullEvent() }
454
  if event[1] == "key" and event[2] == keys["end"] then
455
    break
456
  elseif event[1] == eventType then
457
    -- make sure we have the right protocol for a turtle
458
    local protocol = true
459
    if mode == "modem" then
460
      protocol = ( event[4] == "voice-command" )
461
    -- dump the side data when using a MoarPeripherals chatbox, Peripherals++ does not have that
462
    elseif mode == "moar" then
463
      table.remove( event, 2 )
464
    end
465
    
466
    -- only run on the predetermined player
467
    if event[2] == name and protocol then
468
      -- Peripherals++ separates commands by a space automatically, but does not trim the space near the \
469
      -- so just rejoin and manually split it later
470
      local command = event[3]
471
      if mode == "plus" then
472
        command = table.concat( command, ' ' ):gsub( '^\\ *', '' )
473
      end
474
      
475
      -- split the command into space separated stuff
476
      local args = processArgs( command )
477
      
478
      if args[1] == "script" then
479
        if args[2] then
480
          local name = scriptPath .. args[2] .. ".tz"
481
          if args[3] then
482
            if fs.exists( name ) then
483
              resultsError( "Script '" .. args[2] .. "' already exists" )
484
            else
485
              local file = fs.open( name, "w" )
486
              file.write( table.concat( args, "\n", 3 ) )
487
              file.close()
488
              resultsPrint( "script", "Saved script as '" .. args[2] .. "'" )
489
            end
490
          else
491
            if fs.exists( name ) then
492
              local file = fs.open( name, "r" )
493
              local script = file.readAll()
494
              file.close()
495
              resultsPrint( "script", script:gsub( "%s+", " " ) )
496
            else
497
              resultsError( "Script '" .. args[2] .. "' does not exist" )
498
            end
499
          end
500
        else
501
          resultsPrint( "Usage", "script <name> <command> ..." )
502
        end
503
          
504
      elseif args[1] == "delete" then
505
        if args[2] then
506
          local name = scriptPath .. args[2] .. ".tz"
507
          if fs.exists( name ) then
508
            fs.delete( name )
509
            resultsPrint( "delete", "Deleted script '" .. args[2] .. "'" )
510
          else
511
            resultsError( "Script '" .. args[2] .. "' does not exist" )
512
          end
513
        else
514
          resultsPrint( "Usage", "delete <name>" )
515
        end        
516
      else
517
        runCommand( command )
518
      end
519
      
520
      -- send the messages to the control 
521
      if mode == "modem" then
522
        sleep( 0.1 ) -- just in case it executes instantly
523
        rednet.send( name, messages, "voice-messages" )
524
        messages = {}
525
      end 
526
    end  
527
  end
528
end
529
530
-- shutdown tasks
531
if opened then
532
  rednet.close( "right" )
533
end
534
print = consolePrint