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 |