Server is a Big Deal
We recently released LiveCode Community Server. Why should you care?
Well, because its a big deal. LiveCode is orders of magnitude easier to use and understand than tradtional web programming languages. Using the same language for web and desktop can be game changing. Releasing all this power for free, for anyone to use, could ultimately lead to LiveCode being the server programming tool of choice - at least that's the plan! Why not try it out yourself, see what you can do with it? I've asked David Williams to provide you with a hands on, real world example of server at work, take a look, work through it, have some fun!
Firstly, let’s take a look at what we’re planning to achieve:
You can see here that as I join the server, the bot gives me a welcome message. I can then execute some commands (prefaced by !!), and the server responds. You can see that the !!test command is getting the result of “the millisecs” from the livecode script. So, we type in a command, the script parses the command, and then gives us the appropriate result.
So, the two most pertinent questions are “how does the server script know what we typed in” and then “how does the server script execute the relevant minecraft server commands”. This diagram lays out the structure of the system I have set up here. Don’t worry if it’s not entirely clear immediately what is going on, we’ll run through that in more detail now:
We have the davebot.lc server side script, and the minecraft server java process. They are both running in unix screens (more on that in a moment). The core of the mechanism is this: the minecraft server constantly writes events to the minecraft log file - every time almost anything happens (a player connects, says something, et cetera), a new line is appended to this file. This log file is a perfect copy of the output that is constantly generated by the minecraft server process. The davebot.lc script constantly reads from the log file (at intervals of 5 seconds). The davebot.lc script might then write some data to the minecraft server unix screen, which is then immediately written to the minecraft server process itself.
What are unix screens? A unix screen is a process that can be thought of in this context as a ‘wrapper’ process for another process (program/script/etc). It means that we can run a script or program inside a screen, and then close our terminal session without terminating the process, as the process is handled by the screen instead of our terminal session. You can also attach to the screen to see what it’s outputting, and type commands into it, if the process expects to take input that way. Importantly, it also provides a very easy way to write data to a process, as we can also use the screen command to say ‘write data x to screen named y’, as though someone had typed that data into the screen. This is how the davebot.lc script writes data to the minecraft server process - it executes a shell command to write to the screen, and the screen passes that write on to the actual process.
Note that we also run the davebot.lc script in a screen, but this is simply so that it remains alive once we close our terminal session.
Let’s take a look at an image from the minecraft server’s unix screen that is taken at the same time as the first screenshot displayed above:
Click to zoom image
We can see here that when my player joins the server, this fact is reported by a few lines, one of them being of format
(TIMESTAMP) [INFO] [STDOUT] Loading Player: (PLAYERNAME)
The davebot.lc script parses for these lines, extracts the player name from them, and then uses the ‘tell’ command to send some messages to that player as you can see in the several lines starting with ‘tell’. Now, you can also see that whenever a player says something, this fact is reported by a line of format:
(TIMESTAMP) [INFO] [MINECRAFT] <(PLAYERNAME)> (MESSAGE)
So, we can also parse for these lines, and extract the player name and message. We then parse the message part to see if it starts with !! (our marker for commands) and then to try and execute that command if so. We executed two commands - !!help, which gets davebot to tell the player a predefined list of commands, and !!test, which gets it to say “UNIX TIME IS (the milliseconds)”.
So, let’s take a look at the actual davebot.lc file itself, to see how it works. Here’s runDaveBot, the first handler that the script enters:
## this is the main loop - this will loop forever every x # millisecs (x is defined in config file) command runDaveBot invokeCmd "startup" ## say's a startup message ## load in config file updateConf put shell("tail -n" && sConfig["TAIL_SIZE"] && quote & \ "ForgeModLoader-server-0.log" & quote) \ into sProcessedCommands repeat ## read in a chunk of (default 10) lines at the end of the # log file for parsing ## note that in a standard minecraft server environment, the # log file name is server.log NOT ForgeModLoader-server-0.log. put shell("tail -n" && sConfig["TAIL_SIZE"] && quote & \ "ForgeModLoader-server-0.log" & quote) into tParseText ## loop through and parse each line for something to do ## itemdel is ">" as we are using this to parse for player # names set the itemDel to ">" repeat for each line tLine in tParseText if (sProcessedCommands <> empty) and \ (tLine is among the \ lines of sProcessedCommands) then ## if we've already processed this line, then skip it, # we've already acted on it. next repeat end if ## if a player logs in, then... if "loading player" is in tLine then put tLine into line (the number of lines in \ sProcessedCommands + 1) of sProcessedCommands ## extract their name from tLine set the itemDel to ":" put item -1 of tLine into tName ## invoke the "info" command, which sends that # player the welcome message invokeCmd "info", tName end if ## if a player says a command then... if "!!" is in tLine and ">" is in tLine then put tLine into line (the number of lines in \ sProcessedCommands + 1) of sProcessedCommands ## extract their name from the line and # place it in tName put item 1 of tLine into tName set the itemDel to "<" put item 2 of tName into tName set the itemDel to "!" put item 3 of tLine into tCmd ## strip the <> characters from it replace ">" with empty in tName replace "<" with empty in tName ## if it's a valid command (currently always returns yes) # then do that comman if recognizedCmd(tCmd) then invokeCmd tCmd, tName end if end if end repeat ## wait a number of milliseconds before the loop loops around # again wait sConfig["REFRESH_TIME"] millisecs with messages end repeat end runDaveBot
So what’s happening here, is that when we enter this handler, we do some initial setup and then we enter into an infinitely repeating loop. The loop has a wait (with messages) at the end which means this will only run every few seconds (otherwise it would consume a huge amount of CPU cycles and probably cause the server to grind to a halt).
In every iteration of the loop, we take a chunk of lines off the end of the server log file. We use a shell command to do this, as using the system’s ‘tail’ command is extremely efficient compared to reading in the entire file using the url() function or similar, as tail will not read in the entire file but only the number of lines at the end that we specify. This is more important when the log file is millions of lines long.
We then loop through every line in the chunk that we loaded in. If we have previously parsed and then acted on one of these lines, we skip it, as we do not want to repeatedly execute the same command over and over. If we detect that a player has logged in in one of the lines, we send them a greeting, and if a player has said a command (as indicated by !!) then we execute that command.
The invokeCmd handler is the handler which does the actual execution of a command:
## contains a huge switch with all the possible commands in it ## pCmd is the command to execute, pName is the name of a player, ## pAdditionalParams contains data specific to the command # you're executing on invokeCmd pCmd, pName, pAdditionalParams replace space with empty in pName switch pCmd case "saystring" put "say" && pAdditionalParams into tStuff break case "tellplayer" put "tell" && pName && pAdditionalParams into tStuff break ################################### MANY COMMANDS OMITTED #################################### default put \ "say NO SUCH COMMAND! ERROR! DOES NOT COMPUTE! WHY?" \ into tStuff break end switch put tStuff & return & return ## we do NOT want semicolons or newline characters in the # command string. kick the player instead. if ";" is in tLine or "\r" is in tLine then invokeCmd "kick", pName, \ "Don't try and get clever with the command string." else ## executes a shell command to write tStuff to the minecraft # server screen put shell("screen -S ftbserver -X stuff $'" & tStuff & "\r'" \ ) into url("file:davebotexecutionlog.txt") end if end invokeCmd
In this handler, we take the name of the command and optionally the name of the player and some additional parameters, and then a huge switch statement determines what the script does based on these parameters. Generally, it will build a string that we are going to write to the minecraft server screen. The line we’re most interested in here is the one that does just that:
put shell("screen -S ftbserver -X stuff $'" & tStuff & "\r'") \ into url("file:davebotexecutionlog.txt")
tStuff is the string that we are going to write to the minecraft server screen. Let’s break down that shell command a little bit. We do:
screen : the screen command
-S: specifies the name of the screen we’re writing data to - ftbserver is the name of the screen that we specified in the script I use to initialize the minecraft server.
-X stuff $ : specifies that we are going to just ‘stuff’ the following string into the screen
‘“ & tStuff & “‘ : livecode drops tStuff in here - note we have apostrophes on either side, these are not to be missed
\r : a newline character - as though we’d pressed enter right after the string we stuff in
into url(“file:davebotexecutionlog.txt”) : write whatever the result is to a text file (log results)
Above, I mentioned that ftbserver is the name of the screen. We name the screen in the shell command used to start up the minecraft server:
screen -AmdS ftbserver ./jre1.6.0_24/bin/java -Xms512M -Xmx3G \ -jar minecraft_server.jar
That covers the core mechanism of how this works. The whole script + supporting files are included - obviously, this bot could be vastly expanded to do a vast range of useful tasks, think IRC bot style. And, the basic mechanism could be adapted to a very wide variety of tasks, not just a minecraft server bot.
You can download a zip with all the relevant files in it here.