Date: December 13, 2000
This is the SLIC2 scripting language reference.
Contents
- Language
- Message Boxes
- Alert Boxes
- Buttons
- Built-in variables
- Events
- Functions
- User variables
- Database access
- Miscellany
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:
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:
{
elseif (b)
{
else
{
- While Loops - again, these work like C. This loop sends one message for every city the given player owns:
while(aCity < player.cities)
{
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:
{
{
- 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:
unit_t myUnitArray[];
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..
Buttons
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:.
...
{
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'
{
Text(ID_THIS_IS_A_SAMPLE);
Button(ID_OK)
{
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'
{
Text(ID_CLOSING_SENDS_ANOTHER_MESSAGE);
OnClose
{
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.
Functions
SLIC provides a number of functions for performing operations or checking values that would otherwise be difficult or impossible. Here is a list of functions. VOID functions do not return values and cannot be used in expressions. INT functions do and can. In addition to the builtin functions, users may implement their own functions. A declaration looks like:
int_f AddTwo(int_t num)
{
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 0;
// This version should always work
int_f DoesAHumanOwnThisUnit(unit_t theUnit)
{
unit_t copiedUnit;
copiedUnit = theUnit;
if(IsHumanPlayer(copiedUnit.owner))
{
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
{
Event:CreateUnit(city[0].owner, city[0].location, city[0],UnitDB(UNIT_WARRIOR), 0);
// Disable this handler as soon as it's fired once
DisableTrigger('MyCityBeginTurnHandler');
// A handler that runs whenever a unit is created, use it to store our
// created unit
HandleEvent(CreateUnit) 'MyCreateUnitHandler' post
{
{
// unit created is ours
myExampleUnit = unit[0];
saveUnit = 0;
// Only need to run this once
DisableTrigger('MyCreateUnitHandler');
// A handler that fires whenever an army moves.
HandleEvent(MoveUnits) 'MyMoveUnitsHandler' post
{
unit_t checkUnit;
for(i = 0; i < army[0].size; i = i + 1)
{
if(checkUnit == myExampleUnit)
{
DisableTrigger('MyMoveUnitsHandler');
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
- TerrainImprovementDB
- GovernmentDB
- 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 does not 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.
Examples:
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.
Joe Rumsey