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 |