July 18, 2017

Anatomy of a Game Mod

A game mod implements a card game.

The anatomy of a game mod is a folder as follows:

lua/
|_game_provider.lua     -- provides descriptor object for the game for use in lobby
|_game.lua              -- implementation of the game itself
|_<any other .lua files>
|_<.lp LuaPages used in the game for dialogs etc.>
|_resources.properties  -- Java .properties file used to supply resource strings
|_manifest.properties -- Manifest for the mod (needed to recognise as a mod) 
lp/
|_description.lp -- description of the game used in lobby screen and for in-game help
static/
|_<any other resources such as images referenced by the description>

The folder name may only contain alphanumeric characters, minus sign -, underscore_, period.. Spaces are not allowed.

manifest.properties

This file is a standard Java .properties file which defines information about the mod. Currently this just has a single property title which contains a longer description of the mod shown in the Mods screen. In future it will be used to keep track of things such as versioning and version compatibility.

game_provider.lua

This file defines how the game appears in the lobby and other basic information such as the type of card deck in use and supported features. The sample mod is commented to show what can be configured. This file is loaded when the list of games is requested in the game lobby. When it is loaded, the game is yet to start and it returns a simple Lua table that initialises the module.

game.lua

This file implements the card game (such as Whist, Pontoon etc.) that has been described in the game_provider file. When the file is executed, it should return a Lua metatable for the game, which subclasses the game prototype.

When the game is started from the lobby, an instance will be created; it will inherit the methods defined here, plus any in the parent game class. A minimal implementation needs to provide implementations of new, can_start, start.

Initially, the game constructs an instance with new when a new game is selected in the lobby. At this point players are still joining the game, and if necessary you should implement get_partners to return which partnerships they will be joining. If there are settings that can be modified, you should return them in get_setting_infos.

Implement can_start to make any checks that should be made before the game starts.

If can_start returns true then the game can be started, once started the start method is called. In the start method you should create the table layout and set the initial state by calling set_state.

From this point onwards, the game is entirely state-driven and control transfers to the current state.

GameState

Game states are implemented by instances of the GameState prototype.

The sample mod contains several examples of GameState subclasses. The GameState class has a number of methods:

  • enter – this is always called on first entering the state and is the first method to be called. It will also be called if the game is loaded from a saved game (for example, if the app is killed after being backgrounded and then restarted), so it is important that you ensure that it can be called more than once without any bad side effects.
  • exit – this is always called just after leaving the state, if there is any tidying up to do, this is the place to do it.
  • do_ai_actions – this is called only once so is a good point to do any operations that need to be made on entering the state. It is called after a slight delay, in order to give users a chance to follow what is happening on screen.
  • get_actions, create_action – these are used to add context-specific actions to the toolbar at the top of the screen.
  • subscribe_event – call this to subscribe to events in order to respond to actions such as a user playing a card. (There is no unsubscribe method – they are automatically unsubscribed when the state exits)
  • get_what_next – this implements context-sensitive help to tell any players what to do next.

The state table in the GameState object should be used to store any additional state information that needs to be persisted – it can be used to store simple data types. However most of the information should be tracked either in the player’s hands, the card table or the Stat objects, so there shouldn’t be much need to track information.

The message_provider field should be used in order to display any messages to players (such as scores at the end of a hand). Each player can be shown the same or different messages.

Once you have determined that it’s time to move to the next state (typically by processing an event), you should call set_state or async_set_state to move to the next state.

Ending the game

Once you have determined the game is over, end the game by setting all the player states to one of WON, LOST, DRAWN depending on what happened. You should also show a message of ID VICTORY_MESSAGE_ID which will be incorporated into the end-game screen.