TextInput Tutorial

In some mission types you may want to allow the player to type commands in via the chat menu; perhaps for an adventure game style mission or maybe even to help when developing the mission (to trigger events you want to test).

To hook chat, you can use the TextInput library. This tutorial will demonstrate creating simple chat hooks.

The Plan

For the tutorial we will hook up some simple commands to move a ship around. The commands we will set up are as follows:

  • Stop
  • North
  • East
  • South
  • West

Some Setup

The map provided already has a ship on it - the Galaxy Class, USS Test. In the setup function of the mission we need to add the following code to get access to the ship.

local finder = EntityFinder( function (e) return e:isType(Entity.Type.Craft) and e.name == "USS Test" )
local ship = finder:findOne()

So, when the mission setup function is called by MMM, we will look through the entities on the map and find the one that is called "USS Test". Since we know there is only one of these, we know it is the ship we are looking for. We then store it in the local variable ship, which we will use later.

Creating the Hook Function

Now we need to create the hook function. Add this function to the code file, outside of the definitons of other functions.

function TextInputTutorial:onTextEntered( text, hook )
    --We first check the object is still valid; if it is no longer
    --valid then we abort
    local ship = hook.argument

    if not ship.valid then return end

    --We lowercase the text as we don't care about the case
    text = string.lower( text )

    --Our five commands are stop, north, south, east and west
    if text == "stop" then
        ship:giveOrder( GameObject.Order.Stop )
    elseif text == "north" then
        ship:giveOrder( GameObject.Order.Go, ship.position + Vector(0,0,250) )
    elseif text == "south" then
        ship:giveOrder( GameObject.Order.Go, ship.position + Vector(0,0,-250) )
    elseif text == "east" then
        ship:giveOrder( GameObject.Order.Go, ship.position + Vector(250,0,0) )
    elseif text == "west" then
        ship:giveOrder( GameObject.Order.Go, ship.position + Vector(-250,0,0) )
    end
end

So, in the code above we first check to make sure that the ship entity is still valid. If it isn't, we return from the function immediately since accessing the ship would cause errors.

We then transform the text so that it is all lowercase so that our comparisons don't fail if someone has capitalised the first letter, for example. Then we compare the command entered to each of our options.

If we get the stop command, we issue the stop order to the ship. If we get one of the directional commands, we give the ship a move order (the x axis is west to east, and the z axis is south to north).

Hooking Chat

Now that the hook function is defined, we can proceed to set it up so that it is called when the player enters some text. Add the following code to the end of the setup function, below the code that gets ship.

TextInput:hook( "onTextEntered", self, TextInputTutorial.onTextEntered, ship )

Now, whenever the user enters some text in the chat box our function will be called, with the entered text passed as the first argument and the ship as the second.

Script So Far

Here's how the entire script for the mission looks.

class 'TextInputTutorial'

function TextInputTutorial:__init()
    
end

function TextInputTutorial:setup( )
    local finder = EntityFinder( function (e) return e:isType(Entity.Type.Craft) and 
                                                     e.name == "USS Test" end )
    local ship = finder:findOne()
    
    TextInput:hook( self, TextInputTutorial.onTextEntered, ship )
end

function TextInputTutorial:onTextEntered( text, hook )
    --We first check the object is still valid; if it is no longer
    --valid then we abort
    local ship = hook.argument

    if not ship.valid then return end

    --We lowercase the text as we don't care about the case
    text = string.lower( text )

    --Our five commands are stop, north, south, east and west
    if text == "stop" then
        ship:giveOrder( GameObject.Order.Stop )
    elseif text == "north" then
        ship:giveOrder( GameObject.Order.Go, ship.position + Vector(0,0,250) )
    elseif text == "south" then
        ship:giveOrder( GameObject.Order.Go, ship.position + Vector(0,0,-250) )
    elseif text == "east" then
        ship:giveOrder( GameObject.Order.Go, ship.position + Vector(250,0,0) )
    elseif text == "west" then
        ship:giveOrder( GameObject.Order.Go, ship.position + Vector(-250,0,0) )
    end
end

MMM.register( TextInputTutorial() )

Using Patterns

Instead of listening to all chat input we can choose to listen to specific patterns using Lua patterns. To do this we use TextInput:hook, passing an additional pattern parameter.

By using a pattern parameter we offset all of the decision code into MMM, so our code can be cleaner - it will also scale better than a giant if/elseif statement. Here's the script rewritten to use patterns.

class 'TextInputTutorial'

function TextInputTutorial:__init()
    
end

function TextInputTutorial:setup( )
    --Find the ship (the USS Test)
    local finder = EntityFinder( function (e) return e:isType(Entity.Type.Craft) and e.name == "USS Test" end )
    local ship = finder:findOne()

    --Hooking our commands. We use patterns for this. Since N, E, S, and W are all similar we map them
    --to one function and pass a direction argument in too.
    TextInput:hook( "stop", self, TextInputTutorial.onStop, ship, "^stop$" )
    TextInput:hook( "north", self, TextInputTutorial.onMove, { moveShip = ship, move = Vector(0,0,250) }, "^north$" )
    TextInput:hook( "east", self, TextInputTutorial.onMove, { moveShip = ship, move = Vector(250,0,0) }, "^east$" )    
    TextInput:hook( "south", self, TextInputTutorial.onMove, { moveShip = ship, move = Vector(0,0,-250) }, "^south$" )
    TextInput:hook( "west", self, TextInputTutorial.onMove, { moveShip = ship, move = Vector(-250,0,0) }, "^west$" )
  
    Objectives:load( "TextInputTutorialObjectives.txt" )
    Objectives.visible = true

end

function TextInputTutorial:onStop( text, hook )
    local ship = hook.argument
    if not ship.valid then return end
    ship:giveOrder( GameObject.Order.Stop )
end

function TextInputTutorial:onMove( text, hook )
    local ship = hook.argument.moveShip
    if not ship.valid then return end
    ship:giveOrder( GameObject.Order.Go, ship.position + hook.argument.move )
end

MMM.register( TextInputTutorial() )

The patterns above do the same as before - they look for text at the start of the string (^) that then have the keyword and are followed by the end of the string ($). Using this method we are able to use one function for movement and just pass an extra argument in.

Download

Download for map and scripts.