CTP2 Bureau : SLIC Scripting Language
CTP2 Bureau
The Modding
Apolyton CTP2 forums
Apolyton CTP2 archives
Source Code Project SVN

SLIC Scripting Language

by Joe Rumsey

Date: December 13, 2000


SLIC Language Specification

SLIC uses a C-like syntax for most things. There a few large differences, and many features of C don't exist in SLIC, but if you can read C, you can understand most everything in SLIC.
The basic components of the SLIC language are:

  • Variables - SLIC supports integer variables, as well as special variables to contain units, cities, armies, and locations, and a large set of special built in variables (described in Built-in variables) All variables need to be declared prior to use, and are always initialized to 0 (or an invalid object for types other than integers) Both local and global variables are supported. A variable declared inside a segment is local to that segment. Variables declared outside any segment are global.
  • Statements - function calls and expressions (including assignments). Examples:
        Message(g.player, 'AMessage');
    a = b;
    a = b * c + (d / e);
    a = b + UnitsInCell(city.location);
  • If/ElseIf/Else clauses - much like C, except that ElseIf is used instead of "else if". Note that SLIC is case insensitive, thus If, if, elseif, ElseIf, ELSEIF, else, eLsE, and so on, are all valid. Example:
        if(a) {
    } elseif (b) {
    } else {
  • While Loops - again, these work like C. This loop sends one message for every city the given player owns:
      aCity = 0;
    while(aCity < player.cities) {
    Message(player, 'AMessageAboutACity');
    aCity = aCity + 1;
  • For loops - In addition to while loops, which were present in SLIC 1, SLIC 2 has for loops. They look like C for loops:
      for(x = 0; x < 10; x = x + 1) {
    // code
    Note that unlike C, the comma operator is not supported, meaning this does not work:
      for(x = 0, y = 0; x < 10; x = x + 1) {
    // code
  • Arrays - User defined arrays are now implemented. The syntax for using an array is much like C. User arrays in SLIC grow dynamically - any insert is legal, the array will be grown to fit the index being inserted. An array is declared like this:
    int_t myIntArray[];
    unit_t myUnitArray[];

Examples of array usage:

  myIntArray[2] = myIntVar + 3;
myUnitArray[myIntVar] = myUnitVar;
myIntArray[x] = myIntArray[x - 1];
  • Messages - Described below
  • Event handlers - Described below
  • Buttons - One of SLIC's primary functions is handling the in game messages. So a method of specifying the buttons to place on messages is provided. See the section below on buttons

Message Boxes

A MessageBox is a section of SLIC code that defines a dialog box to be displayed to the user. The general format is:
MessageBox 'name' {

The name can be any string, and may contain spaces, but may not continue past the end of a line.

A typical command might be:


Which would cause the dialog box to display the text pointed at by the string ID "THIS_IS_A_MESSAGE_BOX". Note that string IDs in SLIC are always preceded by ID_. There are other possible commands for message boxes:


  • EyePoint(location) - Any variable that contains a location (unit, city, unit.1.location, etc.) can be used with this function to provide an "EyePoint" button that centers the map on that location.
  • EyeDropdown(startIndex, list variable) - Any builtin variable that can be used as a list (currently unit, city, and discovery) can be used with this function to provide a dropdown list of eyepoint locations to zoom to, or in discovery's case of things to research. Discovery is really only useful for the choose a discovery dialog. The startIndex parameter is useful to make the dropdown list start at an index other than 1. For example, when the discovery messagebox is invoked, discovery.1 is the discovery that was just discovered, and discoveries that can now be researched start at discovery.2.
  • Alert Boxes

    An AlertBox is exactly the same as a MessageBox, the differences being that a MessageBox starts as an icon, while an AlertBox opens immediately, has a smaller window, and must contain at least one button. AlertBoxes are modal, meaning the user has to respond to them before the game can proceed.


    Messages and alerts can have buttons attached. A button can have any text on it, and can execute any valid slic code when clicked. Inside the code for a messagebox or alertbox, a button is specified like this:
    Button (stringid) {

    code is the code to be run when the button is clicked. As an example, here is a message box with an "OK" button and a "Tell me more" button:

    messageBox 'MSampleBox' {
    Button(ID_OK) {
    Kill(); // Close this message
    Button(ID_TELL_ME_MORE) {
    // Provide another messagebox with more info.
    // Does not close this box, since there is no Kill() in this button.
    Message(g.player, 'MSampleTellMeMore');
    There is also a special button-like statement called OnClose which doesn't take a name, and runs when the message is closed. Here is an example of how to send another message when one message is closed (no matter how the first message was closed)
    messageBox 'MFirstMessage' {
    OnClose {
    Message(g.player, 'MSecondMessage');

    messageBox 'MSecondMessage' {

    Built-in Variables

    The SLIC engine contains many predefined variables with many different meanings. See Built-in variables for a discussion of variable concepts and a full list of builtin variables.

    Event handlers and generating events

    Event handlers let you insert scipted code to be executed when many different events happen in the game. A complete list of events that can be handled can be found in the Event List. To write an event handler, you declare a code block like this: HandleEvent(BeginTurn) 'MyBeginTurnHandler' post


    SLIC provides a number of functions for performing operations or checking values that would otherwise be difficult or impossible. Here is a list. VOID functions do not return values and cannot be used in expressions. INT functions do and can.

    >> Function reference

    In addition, users may implement their own functions. A declaration looks like:

    int_f AddTwo(int_t num)
    return num + 2;

    Note that the type of the function is int_f not int_t. The latter will result in a syntax error. Valid function types are int_f and void_f. Any variable type can be used as an argument.

    Important note! As this documenation is being prepared, the first patch for CTP2 is about to be released. Unfortunately a bug with functions was discovered too late to fix. Specifically, in some cases, using members of unit, army, city, and location variables that are function parameters may not always work as expected. There is, however, a workaround. Copy the function parameter to a local variable and use that variable instead. Example:

    // This version may fail sometimes!
    int_f DoesAHumanOwnThisUnit(unit_t theUnit)
    if(IsHumanPlayer(theUnit.owner)) {
    return 1;
    return 0;

    // This version should always work
    int_f DoesAHumanOwnThisUnit(unit_t theUnit)
    unit_t copiedUnit;
    copiedUnit = theUnit;
    if(IsHumanPlayer(copiedUnit.owner)) {
    return 1;
    return 0;

    The author apologizes for this and promises that if there is another patch it will be fixed. But the above workaround should always work.

    User variables

    In addition to the built-in variables, any number of user variables may be used. All variables need to be declared prior to use and are always initialized to 0 or an invalid object. You may assign the result of any expression to a user variable:
    my_var = 1 + 1;
    have_flanker = IsFlankingUnit(unit);
    counter = counter + 1;
    turns_to_go = turns_to_go - 1;
    last_checked_year = g.year;
    And so on. Their values are global and persistent - they will not change between various slic objects or when execution stops. In addition to integer variables, there are Unit, City, Army, and Location variables. The complete list of types is:
    army_t armyVar;
    city_t cityVar;
    int_t intVar;
    location_t locationVar;
    unit_t unitVar;

    Only integer variables can be used in mathematical expressions, but == (equality), != (inequality), and =(assignment) work for all types.

    Here is an example that creates a unit in a city at the beginning of that city's turn, then as soon as that unit moves, kills it.
    unit_t myExampleUnit;
    int_t saveUnit;

    // A handler that runs at the beginning of the turn for every city
    HandleEvent(CityBeginTurn) 'MyCityBeginTurnHandler' post
    saveUnit = 1;
    Event:CreateUnit(city[0].owner, city[0].location, city[0],UnitDB(UNIT_WARRIOR), 0);

    // Disable this handler as soon as it's fired once

    // A handler that runs whenever a unit is created, use it to store our
    // created unit
    HandleEvent(CreateUnit) 'MyCreateUnitHandler' post
    if(saveUnit) {
    // The city begin turn handler sets a flag saying the next
    // unit created is ours
    myExampleUnit = unit[0];
    saveUnit = 0;

    // Only need to run this once

    // A handler that fires whenever an army moves.
    HandleEvent(MoveUnits) 'MyMoveUnitsHandler' post {
    int_t i;
    unit_t checkUnit;
    for(i = 0; i < army[0].size; i = i + 1) {
    GetUnitFromArmy(army[0], i, checkUnit);
    if(checkUnit == myExampleUnit) {
    Event:KillUnit(checkUnit, 0, -1);

    Database Access

    SLIC provides direct read-only access to much of the data contained in the game databases. Here is the list of databases than can be used:
  • UnitDB
  • AdvanceDB
  • TerrainDB
  • BuildingDB
  • WonderDB
  • FeatDB
  • ResourceDB
  • OrderDB
  • TerrainImprovememain_td_lightB
  • Governmemain_td_lightB
  • StrategyDB
  • DiplomacyDB
  • PersonalityDB
  • A record in any of these databases can be accessed by name or by index. For example, to create a tank, you can use UnitDB(UNIT_TANK) in a call to the CreateUnit event. If you have the integer index of the tank stored in a variable, you can also use UnitDB(theTankIndex) (although this is a little silly since it amounts to the same thing as writing just "theTankIndex").

    But it doesn't stop there. Many fields from each database are also accessible. Any bitfield, integer, or floating point value that is in the main body of a record (NOT values in sub-structures!) can be used. Floating point values are multiplied by 100 and converted to ints, since SLIC does not otherwise support floating point variables.


      tankAttack = UnitDB(UNIT_TANK).Attack;
    costOfAdvInfantryTactics = AdvanceDB(ADVANCE_ADV_INFANTRY_TACTICS).Cost;
    minimumHappinessForDefaultStrategy = StrategyDB(STRATEGY_DEFAULT).MinimumHappiness;

    Please explore the database text files to see what data you might be interested in. There are far too many fields available to enumerate here.

    Loading your script

    You can cause your SLIC file to be loaded by using a

    #include "yourfile.slc"
    in script.slc. Your file should be placed in default/gamedata, the same place as script.slc.
    This site is currently maintained by: BureauBert (Webmaster)  Maquiladora (Manager)  Legal Notice: Legal Notice Statistics: 88124 unique users since 02.05.2004 (» Site Statistics) Hosted by: WebhostOne
    Share this page:  VK Facebook Twitter LiveJournal OK MR Google+ LinkedIn tumblr Pinterest Blogger Digg Evernote