revUp - Updates and news for the LiveCode community
Issue 152 | July 12th 2013 Contact the Editor | How to Contribute

Server is a Big Deal
LiveCode Community Server is available. Get your hands dirty creating your own server bot.

by David Williams

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!

Programming Davebot
In this newsletter article, I’m going to be running through how to set up a server script that stays alive forever, reads from a log file, parses for input from that log file, and then writes data to a separate process or carries out other actions based on that input. Specifically, a Minecraft server bot. Please note, that even if you have no intention of ever delving into the world of Minecraft, the code and techniques presented here are applicable to a wide range of applications other than this.

Firstly, let’s take a look at what we’re planning to achieve:

Server Bot Image

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:

Process Structure

We have the 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 script constantly reads from the log file (at intervals of 5 seconds). The 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 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 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:

Unix Screen

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


The 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:


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 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
      put shell("tail -n" && sConfig["TAIL_SIZE"] && quote & \
         "ForgeModLoader-server-0.log" & quote) \
 into sProcessedCommands
        	## 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
        	case "tellplayer"
           	put "tell" && pName && pAdditionalParams into tStuff
           	put \
 into tStuff
      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."
        	## 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.

David Williams

About the Author

David Williams is a Software Developer and Server Admin for RunRev Ltd. In his spare time he likes to play guitar and run Minecraft.

Main Menu

What's New

Hatch a Genius with LiveCode