Date: June 18, 1998
Last Updated: May 20,
1999
Contents
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:
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' {
commands
}
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:
Text(ID_THIS_IS_A_MESSAGE_BOX);
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:
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: ...
Button (stringid) {
code
}
...
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) {
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' {
Text(ID_CLOSING_SENDS_ANOTHER_MESSAGE);
OnClose {
Message(g.player, 'MSecondMessage');
}
}
messageBox 'MSecondMessage' {
Text(ID_SENT_WHEN_FIRST_MESSAGE_CLOSED);
}
Triggers
A trigger is a section of SLIC code that is run when specific
game events occur. A trigger has this format: Trigger 'name' when (expression) {
commands
}
There is also a special format for triggering by UI components: Trigger 'name' on "UI Component Name" when (expression) {
commans
}
It behaves in the same way as the normal format, except that it is only
evaluated when the UI component is used. The name follows the same rules
as for message boxes.
The body of the trigger is executed only when the expression is true
(not equal to 0).
A SLIC expression is a C-style mathematical expression. These are all
valid expressions:
1 + 1
a + b
a * (b + c)
(a * b) + c
a && b
a || b
a < b
a > 2
a <= b
a >= b
a != b
!a
(a && !b) || c
The variables used in the expression for a trigger determine when the
trigger might be run. For example, if the built-in variable unit.built is
used, the trigger condition will be checked whenever a unit is built. However,
any given trigger is never placed in more than one list of triggers to be
checked. So while a trigger whose only built-in variable in the conditional is
g.player will be checked whenever a player's turn begins, one that uses
both g.player and unit.built will only be checked when a unit is
built. The author has tried to make the order of which variables takes
precedence as logical as possible. Some variables make no sense when used in the
same expression (E.G. city.built and unit.sighted can't be fired
at the same time). In other cases, the list the trigger is added to should be
the one that will fire more often.
Built-in Variables
The SLIC engine contains many predefined variables
with many different meanings. See Built-in
variables and trigger events for a discussion of variable concepts and a
full list of builtin variables.
Trigger Priorities
As mentioned in Built-in
variables and trigger events,, the built in variables used in a trigger's
conditional determine when that trigger is evaluated. A trigger is never added
to more than one event list, however. There is a preset prioritization of
trigger lists, defined below. The list the trigger is added to is the
lowest list here. For example, if a trigger has both g.year and
unit.built, it is added to UNIT_BUILT. The name of the lists below are the event
type the list is associated with. All the triggers in a list will be checked
when that event occurs.
Functions
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.
Regions
There are a number of functions above which act on regions. A
region is a parallelogram or union of parallelograms on the map. They can be
defined in two ways.
A simple region:
Region name [ x1,y2, x2, y2];
Or a complex region built from a union of any number of simple regions: Region name = name1 + name2 [+ ... + namen]
WARNING: Regions have not been thoroughly tested, they may not work
as advertised!
User variables
In addition to the built-in variables, any number of user
variables may be used. Integer variables do not need to be declared prior to
use, and are always initialized to 0. 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 user
variables, there are Unit, City, and Location variables. These must be declared
outside of any object prior to use. They cannot be used in expressions EXCEPT
with the == operator. That is, you can check to see if a unit is the same as
another unit: if(myUnitVariable == unit.1) {
// they are the same unit
}
The same works for cities and locations.
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. TypeUnit myExampleUnit;
trigger 'CreateAUnit' when(city.beginturn && g.player == 1) {
CreateUnit(g.player, UnitType("UNIT_WARRIOR"), city.location, 0, myExampleUnit);
DisableTrigger('CreateAUnit');
}
trigger 'KillAUnit' when(unit.moved && unit == myExampleUnit) {
KillUnit(myExampleUnit);
DisableTrigger('KillAUnit');
}
Miscellany
What? No Functions?
While SLIC does not directly support user defined
functions, there is a workaround. Using the Abort() function, you can create a
MessageBox object that never displays a message. So call Abort() as the first
line of a MessageBox object, then write any code you want inside the object, and
call that MessageBox from anywhere you'd like with Message(0, 'NameOfYourMessageBox');
The first argument to message is a
player index, but since this message isn't going to display anything, it doesn't
matter what index you use.
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
Back to Civilization: Call
to Power