Trunk Scripting

From Tronwiki
Revision as of 10:03, 13 August 2010 by Voodoo (Talk | contribs)
Jump to: navigation, search





The armagetronad-ct branch comes from an attempt to build armagetronad trunk with scripting enabled. As I faced issues compiling or running it with ruby, and the underlying wrapper, swig, support most of the common scripting language, I started this branch to add an abstraction layer, and eventually, introduce other scripting language support.

At this time, we were switching our script from php to python, and I was unable to compile with ruby, so I'd give Python a try ...

Armagetronad-ct is nothing but an experimental branch, as were armahacktron and sty+ct, based on armagetron trunk, currently being 0.3.x serie. Therefore, there are two major differences with sty+ct:

  • sty is not (yet) fully ported to 0.3.x serie and has not been included to this branch. So no flags, no balls, no zombies, no shooting...
  • although you can compile (still true?) trunk with zones v1 or v2, this branch has scripting support for zones v2 only...

There's also a few extra features that comes from sty+ct or has been added to provide scripting support:

  • DELAY_COMMAND comes from sty+ct.
  • INGAME_PARSING, INGAME_PARSING_DTD_VALIDATION and INGAME_PARSING_DTD allow to parse additional data during a round. It's meant to be use for zones. Any other parsing might be confusing and might lead to undefine behavior.

Although this branch main target is dedicated server, it might be extended to some client side features. For example, you can define new chat command from scripting on server side. This might be extended to client side soon as it allows to build dynamic instant messages (ex: /hi [<playername>] to salute last entered or specified player(s).

Eventually, if some servers start using it and this branch becomes somehow useful, we might extend it to other languages as ruby, lua or php, or polish it enough to deserve to be merged in its right place: armagetron mainline.


We are working on linux only and there's no support for any other plateform. So here is a raw install procedure for debian/ubuntu users :

First, get armagetron dependencies: Check the following wiki pages, section "Building from sources".

Add extra dependencies for scripting. For python:

 sudo apt-get install python-dev swig

Get the branch:

 bzr co  lp:~armagetronad-ct/armagetronad/armagetronad-ct

Build with scripting enabled:

 cd armagetronad-ct
 ./configure --disable-etc --enable-python --disable-glout --prefix=/your/install/dir/here --enable-armathentication
     --enable-automakedefaults --disable-useradd --disable-initscripts
 make install

Running a server

The server expects to find scripting related files in a specific place: a subdirectory of armagetron data directory called "scripts". More precisely, it expects to find a file called File extension vary depending on scripting language. So far, as only python support exists, we'll assume we are working with python as during install part.

The first step will be to set up out server specific directory tree. Personaly, I like to keep all config, var, and scripts directory in one place for a specific server. So let's start by setting up our directory tree:


I'll assume you have include into the config subdir your config files.

So far, you'll have to copy the file generated by swig during compilation into the scripts directory. This file is located into the src/swig/ext subdir in you source file directory (the armagetronad-ct directory from install step). Eventually, this file must be copied with other required files into the installation data scripts subdirectory. In this case, this directory should be append in below. Warning, don't forget to update this file each time you reinstall/update your armagetronad-dedicated binary.

You'll have to create the scripts/ file and include a few lines in it:

 print("Start scripting initialization.")
 import sys
 import armagetronad
 # your code goes here
 print("End scripting initialization.")


Let's run it, taking care of setting the userdata directory

 /path/to/your/binary/armagetron-dedicated --userdatadir="/home/username/tron/servers/hello_scripting/"

Constants, Types and Classes


AccessLevel - enumeration used to check authentication level

   tAccessLevel_Owner = 0,
   tAccessLevel_Admin = 1,
   tAccessLevel_Moderator = 2,
   tAccessLevel_Armatrator = 5,
   tAccessLevel_Referee = 6,
   tAccessLevel_TeamLeader = 7,
   tAccessLevel_TeamMember = 8,
   tAccessLevel_Local      = 12,
   tAccessLevel_Remote = 15,
   tAccessLevel_DefaultAuthenticated = 15,
   tAccessLevel_FallenFromGrace = 16,
   tAccessLevel_Shunned = 17,
   tAccessLevel_Authenticated = 19,
   tAccessLevel_Program = 20,
   tAccessLevel_Invalid = 255


Color -


Coord -



armagetronad.LadderLogWriter - Ladderlog stuff

  • get_writer(string llog) - Read ladderlog
    • llog - The ladderlog message you wish to recieve
  • is_enabled() - is_enabled()
  • set_all(bool on) - All ladderlog messages on or off
    • on - All ladderlog messages On or off
  • set_callback(function func) - Callback function
    • func - the function you declare

Output -

  Output(string identifier)
  Output set_template_parameter_string(int num, string parameter)
  Output set_template_parameter_float(int num, float parameter)
  append(Output o)
  bool is_empty()

ConfItem -

  AccessLevel required_level()
  load_all(string s)
  static ConfItem find(string name)
  set(string s)
  get(string s)

AccessLevelSetter -

  tAccessLevelSetter(ConfItem item, AccessLevel level)

ConfItemScript -

  ConfItemScript(string title, function proc);

LadderLogWriter -

  eLadderLogWriter(string ID, bool enabledByDefault)
  static LadderLogWriter find(string ID)
  bool is_enabled()         //!< check this if you're going to make expensive calculations for ladderlog output
  enabled(bool b)            //!< set or unset enabled flag
  static set_all(bool enabled)      //!< enable or disable all writers
  set_callback(proc)         //!< bind a procedure from scripting language to this ladder log writer.

PlayerConf -

  string name()
  string team_name()
  int id()
  PlayerConf player_config(int p)
  bool is_in_game(int p)

AccessLevelHolder -

  AccessLevel get_access_level()
  set_access_level(AccessLevel level)

Player -

  • is_chatting() - bool - tells if the player is chatting
  • is_spectating() - bool - tells if the player is on the grid or not
  • stealth_mode() - bool - ???
  • bool - is this player allowed to change teams? - {{{2}}}
    • informPlayer - ???
  • set_team_change_allowed(bool allowed) - set if this player should always be allowed to change teams
    • allowed -
  Team next_team()               //!< return the team I will be next round
  Team current_team()               //!< return the team I am in
  int team_position()               //!< return my position in the team
  Team find_default_team()            //!< find a good default team for us
  join_default_team()               //!< register me in a good default team
  join_team(Team team)               //!< register me in the given team (callable on the server)
  join_team_wish(Team team)            //!< express the wish to be part of the given team (always callable)
  team_name(string)               //!< set teamname to be used for my own team
  update_team()                  //!< update team membership
  create_new_team()                   //!< create a new team and join it (on the server)
  create_new_team_wish()                //!< express the wish to create a new team and join it
  static bool is_enemy(Player a, Player b)      //!< determines whether two players are opponents and can score points against each other
  remove_from_game()               //!< suspend the player from playing, forcing him to spectate
  suspend(int rounds = 5)
  authenticate(string authName, AccessLevel accessLevel = tAccessLevel_Authenticated,
                    Player admin = 0, bool messages = true)   //!< make the authentification valid
  deauthenticate(Player admin = 0, bool messages = true)   //!< make the authentification invalid
  bool is_authenticated()               //!< is the authentification valid?
  bool is_active()
  bool is_silenced()
  set_silenced(bool silenced)
  bool is_suspended()
  bool is_human()
  add_score_output(int points, Output reasonwin, Output reasonlose)
  int score()
  int total_score()
  static string ranking(int MAX=12, bool cut = true)   //!< returns a ranking list
  static real ranking_graph(real y, int MAX)      //!< prints a ranking list
  static reset_score()               //!< resets the ranking list
  static remove_chatbots()            //!< removes chatbots and idling players from the game
  static complete_rebuild()            //!< same as above, but rebuilds every ePlayerNetID.
  static clear_all()               //!< deletes all ePlayerNetIDs.
  static spectate_all(bool spectate=true)         //!< puts all players into spectator mode.
  chat(string s)
  set_color(real r, real g, real b)
  set_trail_color(real r, real g, real b)
  bool is_logged_in()
  AccessLevel get_last_access_level()
  Cycle cycle()
  static Player find_player(string name)         //!< finds a player by name using lax name matching.
  static int players_count()
  static Player players_get(int i)
  static string filter_name(string in)         //!< filters a name (removes unprintables, color codes and spaces)
  bool is_allowed_to_rename()            //!< tells if the user can rename or not, takes care about everything
  allow_rename(bool allow)            //!< Allows a player to rename (or not)
  string name()                  //!< Gets this player's name without colors.
  string user_name()               //!< Gets this player's full name. Use for writing to files or comparing with admin input.
  string log_name()               //!< Gets this player's name, cleared for system logs (with escaped special characters). Use for writing to files.
  string filtered_authenticated_name()         //!< Gets the filtered, ecaped authentication name
  Player set_name(string name)            //!< Sets this player's name. Sets processed names (colored, username, nameFromCLient) as well.
  Player set_user_name(string userName)         //!< Sets this player's name, cleared for system logs. Use for writing to files or comparing with admin input. The other names stay unaffected.

ChatCommand -

 ChatCommand(string ID, proc, AccessLevel level)
 set_access_level (AccessLevel level)
 static ChatCommand find(string ID)

Team -

  static int num_teams()
  static Team team(int i)
  declare_winner(string message)
  int rounds_played()      //!< number of rounds played (updated right after spawning, so it includes the current round)
  set_locked(bool locked)      //!< sets the lock status (whether invitations are required)
  bool is_locked()      //!< returns the lock status
  bool is_locked_for(Player p)   //!< returns if a team is locked to join by a certain player (due to ACCESS_LEVEL_PLAY)
  invite(Player player)      //!< invite the player to join
  uninvite(Player player)      //!< revoke an invitation
  bool is_invited(Player player)   //!< check if a player is invited
  static bool is_enemy_player(Team team, Player player)   //!< determines whether the player is an enemy of the team
  static bool is_enemy_team(Team team1, Team team2)   //!< determines whether two teams are enemies
  static string ranking(int MAX = 6, bool cut = true)   //!< return ranking information
  static real ranking_graph(real y, int MAX = 6)      //!< print ranking information
  add_player(Player player)            //!< register a player
  remove_player(Player player)            //!< deregister a player
  bool player_may_join(Player player)         //!< see if the given player may join this team
  bool is_new_team_allowed()            //!< is it allowed to create a new team?
  static swap_players(Player player1, Player player2)   //!< swaps the team positions of the two players (same team or not)
  shuffle(int startID, int stopID)         //!< shuffles the player at team postion startID to stopID
  bool balance_this_team()
  bool is_human()
  int team_id()
  int score()
  add_score(int s)
  set_score(int s)
  add_score_output(int points, Output reasonwin, Output reasonlose)
  int num_players()
  Player player(int i)
  vector_Player players()
  int num_human_players()         //!< number of human players
  int num_ai_players()          //!< number of AI players
  int alive_players()         //!< how many of the current players are currently alive?
  Player oldest_player()         // the oldest player
  Player oldest_human_player()      // the oldest human player
  Player oldest_ai_player         // the oldest AI player
  Player youngest_player()      // the youngest player
  Player youngest_human_player()      // the youngest human player
  Player youngest_ai_player()      // the youngest AI player
  bool is_alive()            // is any of the players currently alive?
  unsigned short red()
  unsigned short green()
  unsigned short blue()
  string name()

SpawnPoint -

  SpawnPoint(Coord loc, Coord dir)
  Coord position()
  Coord direction()
  spawn(Coord loc, Coord dir)
  real danger()

Arena -

  static Arena get_arena()
  int winding_number()
  int direction_winding(const eCoord& dir)
  Coord get_direction(int winding)
  new_spawn_point(Coord loc, Coord dir)
  Coord get_random_pos( REAL factor )
  SpawnPoint closest_spawn_point(Coord loc)
  SpawnPoint least_dangerous_spawn_point()
  static real size_multiplier()

Cycle -

  int winding_number()
  bool vulnerable()
  static bool respawn_cycle(Coord pos, Coord dir, Player p, bool warn=true)
  real max_walls_length()
  real this_walls_length()
  bool do_turn(int dir)
  drop_wall( bool buildNew=true )
  inherited from gCycleMovement:
  Coord spawn_direction()
  move_safely(Coord dest, real startTime, real endTime)
  real get_distance()
  real get_rubber()
  unsigned short get_turns()
  unsigned short get_braking()
  real get_braking_reservoir()
  Coord get_last_turn_pos()
  real get_last_turn_time()
  real get_acceleration()
  set_rubber(real rubber)
  set_braking_reservoir(real brakingReservoir)
  inherited from eGameObject:
  real last_time()
  Coord position()
  Coord direction()
  Coord last_direction
  real death_time()
  real speed()
  bool alive()         //! tells whether the object is alive
  real lag()         //!< expected average network latency
  real lag_threshold()      //!< tolerated network latency variation

Sample Scripts


You'll first have to load armagetronad module which also required to set path accordingly.

print("Start scripting initialization.")

import sys
import armagetronad

Change/get game settings

You might want to change some settings from scripting. There's 2 ways to achieve that. You can send a string as you can do from console:

armagetronad.ConfItem.load_all("CONSOLE_MESSAGE test line\n")
armagetronad.ConfItem.load_line("CONSOLE_MESSAGE test line 4\nCONSOLE_MESSAGE test line 5\nCONSOLE_MESSAGE test line 6")

You can also look for the corresponding setting item so you can use it as much as you want:

console_message_conf.set("Welcome from scripting ...\n")

You might want to get or test any setting value (well, except those bind to a procedure like SPAWN_ZONE:


Game events

You might want to react on game event. You can do it binding a procedure to a ladder log message.

def round_winner(args):
    print args
    print("PLAYER_MESSAGE "+p+' "Congratulation from script '+p+' !"')
    armagetronad.ConfItem.load_line("PLAYER_MESSAGE "+p+' "Congratulation from script '+p+' !"')
    ci = armagetronad.ConfItem.find("CYCLE_SPEED")
    print "cycle_speed " + ci.get()

def new_round(args):
    ci = armagetronad.ConfItem.find("CONSOLE_MESSAGE")
    for i in range(armagetronad.Team.num_teams()):
        ci.set("Team name: "
        for i in range(t.num_players()):
            ci.set("Player name: "


Any script procedure binded to a ladder log message must take a single argument (here named args). This argument is a list of 0 to n value of any type, depending on the value returned by corresponding ladder log message.

Create new Commands\Settings

You might be able to define new command from scripting. The new command will be available from console within the game, from other console commands like DELAY_COMMAND. Let's build a RESPAWN_PLAYER command:

def respawn_player(args):
    # first parse parameters: <player name> <message flag> <x> <y> <dirx> <diry>
    lst = args.split()
    player = armagetronad.Player.find_player(lst[0])
    flag   = int(lst[1])
    pos    = armagetronad.Coord(float(lst[2]), float(lst[3]))
    dirt   = armagetronad.Coord(float(lst[4]), float(lst[5]))
    armagetronad.Cycle.respawn_cycle(pos, dirt, player, flag)
respawn_player_conf = armagetronad.ConfItemScript("RESPAWN_PLAYER", respawn_player)
respawn_player_access_level = armagetronad.AccessLevelSetter(respawn_player_conf, armagetronad.tAccessLevel_Moderator)

Every command defined in scripting must take a single argument (here named args). Don't forget to set the appropriate access level! Default one is Owner.

Chat commands

You're now able to define chat commands. Here's an example :

def substitute(args): 
    lst = args.split() 
    print lst 
    p_out = armagetronad.Player.find_player(lst[1]) 
    p_in = armagetronad.Player.find_player(lst[2]) 
    if (not p_out.is_spectating()) and p_in.is_spectating(): 
        t = p_out.current_team() 
        print "can't substitute !" 

cc = armagetronad.ChatCommand("/substitute",substitute,armagetronad.tAccessLevel_Moderator)

Once again, command chat must take exactly one argument which will be a list of exactly 1 string. Pretty pointless list but it's to handle all bindings the very same way.

Personal tools