SWC uses an in-house language for scripting in game entities (currently only
NPCs, items, droids, and quests, but eventually much more) based on a combination of
concepts taken from the programming languages LISP and Scheme. LISP was originally
described by John McCarthy in 1960. A PDF version
of its technical and formal description is available online, but you don't need to
(and probably don't want to) read it in order to start using the language.

Most scripts for in game entities are written by the NPC team, but Custom
NPCs can have their scripts edited by players.

You can access the NPC script editor through the NPC Inventory,
any custom NPCs that you have permission to edit the scripts for will have an
Edit Script link available.

This language is designed for syntactical simplicity and clarity. It is composed entirely
of S-Expressions, which are defined by either a single "atom" or a list of multiple atoms.
An atom is a single value, like a number, string, or variable name (usually called a symbol).
Symbols can contain most characters except for spaces, semicolons, single or double quotes,
parentheses, and brackets (others may be added to this exception list if they are used for
future language extensions). S-Expressions group atoms within parentheses to form lists;
therefore all of these are valid S-Expressions:

Code

10
(1 10)
x
(x 5 "this string is one atom")

Each S-Expression is evaluated in exactly the same way--there is only one syntactical form
for instructions in SWC Lisp. If the S-Expression is a single atom, it evaluates to itself
or its defined value if it is a symbol. Thus 42 evaluates to 42, while x evaluates to the
previously assigned value for x. If it is a list, then the first element of the list
determines which operation is to be performed on the rest of the list. Effectively, the first
element selects a function to be evaluated with the remaining elements as arguments. For
example, to add together some numbers:

Code

(+ 1 2)

This evaluates to 3. S-Expressions can be nested and they will be resolved from the inside out.
There is no operator precedence to remember in SWC Lisp, because the grouping of S-Expressions
makes the exact sequence of evaluation explicit. Each S-Expression can be evaluated and
substituted into an outer S-Expression until a single value is left:

Code

(+ 1 (* (- 2 1) (+ 1 2)))
(+ 1 (* 1 3))
(+ 1 3)
4

Although the syntax is completely regular, there are a few special statements that do not
correspond to "function" calls: instead they define the necessary language constructs required
to make a programming language. We will encounter two of these used to create variables and functions.

SWC Lisp also supports comments, which begin with a ; and continue to the end of the line.

Like other languages, SWC Lisp allows you to define variables and functions. A variable is created
by calling defvar with a symbol and a value:

Code

(defvar x 1)

This defines a variable named x with the value 1. It can be used in subsequent statements as a normal
number would be:

Code

(+ x 1)
2

Functions are defined using the defun form and two arguments: the function declaration, consisting
of a name and possibly argument names, and a function body. For example, a function that adds one to its
argument:

Code

(defun (incr x)
(+ x 1))

Whenever a linebreak occurs inside an S-Expression, it is customary to indent the following lines by
two additional spaces, in order to improve clarity. Trailing parenthesis are included on the final line.
Extra whitespace (spaces, tabs, newlines, etc.) are all ignored by the interpreter. This function can
now be called with any argument desired, including other lists as before:

Code

(incr 1)
2
(incr x)
2
(incr (* 2 3))
7

If a function does not take any arguments, it should be defined with an atom for the name. It may
seem pointless to define a function without arguments, but when we create functions that do things
(such as displaying messages to the player), rather than simply calculate things, it is often useful
to group a series of operations into a single function for convenience if they need to be repeated.
Additionally, multiple statements can be included in a function body. All of them will be executed,
but the value of the final statement will used for the function's value. Examples of both of these
situations will be shown below.

Additionally, a function may declare optional arguments along with a default expression by
putting a pair of the form (name default) in place of the argument name. This default eenvironment
xpression is evaluated at call time if the default value is needed. Default values cannot reference
earlier arguments; they are evaluated in the scope in which the function was defined. This is
illustrated in the example below.

Variables can also be "quoted" by placing a single quote before the symbol. This indicates to
the interpreter that this specific mention of this symbol should not be evaluated and instead used
as a literal string, but if it is evaluated a second time, it will be used to look up a variable name.
This has a number of important uses, most of which are beyond the scope of this introduction, but it
is necessary in order to access the persistent variables. Expressions can be quoted as well, in
which case a literal list containing exactly the values specified is produced.

Code

; Both of these produce the same result, a list containing the literal symbols a, b, and c
(list 'a 'b 'c)
'(a b c)

Or, variables can be "quasiquoted" by placing a backtick (`) in front. This applies to
expressions as well. The difference is that elements inside an expression that has been quasiquoted
can be unquoted by prepending a comma (,) before the element to be unquoted, causing them to be
evaluated, even though the rest of the expression is not. This is best illustrated with an
example,

Code

`(a b ,(+ 1 2))
; The result is (a b 3), because the first two symbols are not evaluated but the last expression is

Quasiquoting is primarily a shorthand used to simplify complex nested lists that may require a
mixture of evaluated and unevaluated information to be combined.

In addition to globally visible variables created with `defvar`, it is possible to create local
variables. In this case, the `let` form is used to define a new variable in a local scope. These
variables take precedence over any other variables that might share the same name, but they do not
refer to the same underlying value.

Note

Temporary variables only exist within the environment in which they are defined.

Code

(let ((nexta (+ a 1))
(doublea (* a 2)))
; Here we can evaluate nexta and doublea, for instance using dbg-pp to see their values
(dbg-pp nexta doublea))
; Here nexta and doublea are not defined!

When referencing a variable inside a local scope, the name resolution happens "inside out." The
innermost scope is checked first, then the next one outside of it, and so on. This means it is
possible to shadow function arguments inside a `let` block but preserve their value outside of
it. You may nest as many local scopes as you like but it is easier to understand if all local
variables are defined at once where possible.

Code

(defun (test x)
(dbg-pp x) ; Will print the argument
(let ((x (+ x 1)))
(dbg-pp x))) ; Will print the argument + 1 instead even though they use the same name!

From a more technical perspective, let is functionally equivalent to let* as defined in scheme
and racket. A variable may be used immediately after it has been bound within the same declaration
block, rather than requiring nesting. let does not support letrec-type recursive binding or mutual
binding.

SWC Lisp supports the manipulation of functions as a data type. That means that after a function
is created with defun, it can be manipulated as a normal variable, including being passed to
another function as an argument (this behavior may be familiar to you from a language such as
Javascript, but it has enjoyed increased adoption in many major languages). However, you may also
wish to create functions locally or create functions with some saved internal values.A

A function can be declared with the lambda expression form, which is very similar to the defun
syntax described above except that the function does not have a name. A (trivial) function that adds
together its two arguments can be defined like this

Code

(lambda (x y)
(+ x y))

Alone the lambda syntax is not very interesting. However, a lambda expression can bind local
variables, creating a closure, which can then be used in another context. For example, with defun we
can create a "factory" function which produces other functions that have templated behavior.

In this example the function `incr-factory` evaluates to another function, rather than a number
or other simple value, allowing us to create multiple functions that can be evaluated again to
produce an offset. The use of lambda functions and closures is a more advanced feature but can be
used to create very sophisticated npc script systems.

Important

Functions created with lambda cannot be passed to as arguments to add-response or add-action

We will be working to improve the argument passing capabilities of add-response and add-action in
order to create opportunity for more dynamic, parameterizable user interactions, but for now it is
not possible to pass closures to these functions, nor is it possible to pass additional
arguments.

SWC Lisp also supports a single type of conditional statement, "cond," which evaluates a series of
tests and then executes the body statements for the first test that produces a true value. With some
hypothetical helper functions we can produce the next largest even number for any input:

Code

(defun (next-even x)
(cond
[(even? x) x]
[#t (+ x 1)]))

In this case, square brackets are used, rather than parentheses, to denote each of the possible
conditions. This is purely cosmetic and done to increase the readability of the language. Each
condition list consists of a test S-Expression as the first element. If it produces a true value,
all of the remaining S-Expressions in the body of that condition are evaluated. This also introduces
the constant "#t" which is always true. A cond statement must always have a condition that always
evaluates to true or an error is produced. This statement can be empty if need be.

A set of Boolean functions is also available which allows the user to combine logical expressions
into compound conditions: `and`, `not`, and `or`. Comparison operators are also available for equality
testing between two atoms (`eq?`) or numerical comparison (`ge?` `le?` `lt?` `gt?` for the
traditional math operator versions `>=`, `<=`, `<`, and `>`).

Note

Predicates are usually named with trailing ? to indicate to the programmer that they ask yes/no
questions and produce true or false values. This is a convention, not a language requirement.

Now that the language is defined, we can use it to build NPC scripts. At their core, NPC scripts
consist of a set of function definitions that print messages to the user and offer opportunities
for further interaction, which is done using two built-in functions: `say` and `add-response`. All
scripts start with a call to a function named `start` when the "interact" button is pressed on the
in-game UI. Let's make a simple NPC script that displays a message and offers the user a chance to
respond, and then he introduces himself.

The first function, `start`, has two function calls in its body. `say` is used to show the given
string to the player. "add-response" creates an option for the player to respond with the string
given as its first argument ("Hello, who are you?"), and when the player selects this option, the
function given as the second argument is called (the function corresponding to the symbol my-name).

The second function, `my-name`, is called after the player has chosen her response. It
concatenates a fixed string with the NPC's name to create a message to be displayed to the user.
Because this function does not add any responses, the player only sees the option to terminate the
conversation.

Passive conversational NPCs can be built using only the `say` and `add-response` functions. The
functions `describe` and `ooc` are also available to display a message as description or as
OOC-styled text indicating extra instructions that may be relevant to the player. A non-verbal
action to be taken by the player may be created with `add-action`.

Most of the interesting NPC interactions, relating to quests, asset transfer, etc. are not yet
available to custom NPCs. A suitable permission system will be created to prevent abuse before
custom NPCs will be able to modify quest status. The description given here is for reference,
planning, and for NPC team members who may be working on quest scripts that have the appropriate
access.

There are other primitive operations available to NPCs, depending on script context. Quest-type
NPCs have the ability to modify the character's quest status, including distributing rewards.
Quests are defined as a series of objectives, where some objectives may be sub-objectives to be
completed as part of another quest. Quest progression can be nonlinear and there are no requirements
on the order of completion of sub-objectives in order to complete a parent quest. To manipulate the
quest data corresponding to the currently interacting character, the procedure is simple. First,
obtain references to the quest objectives (by ID) that a particular script will be modifying:

Of course, this NPC would ask the player if he wanted to begin hunting every time that
the two interacted, which is usually undesirable. Instead, we could use conditions to ask about
the player's progress instead if he has already begun the hunting quest, by using a set of
predicates that test quest state (`quest-active?`, `quest-failed?` and `quest-finished?`):

None of these quest interactions so far have dealt with rewards for quest completion--those
must be handled explicitly as well. Examples seem tedious, but functions like `add-credits`,
`remove-credits`, and `add-xp` are also defined to provide the ability to give players rewards.
There are also related predicates, such as `has-credits?`.

SWC Lisp offers the ability to store data (or state information) between interactions and
therefore provide memory of interactions between characters and other entities. This state falls
into a number of categories, each of which are handled analogously: global variables, entity
variables (two types, e and o), context variables (two types, y and x), and session variables. More
may be added if the need arises. Global variables are shared by all scripts, entities, and
characters. They require the most care to avoid naming conflicts and are only available to
admin-curated scripts. Entity variables are specific to the entity being interacted with (e type)
or specific to both the entity and the character (o type), context variables specify one (y type) or
two (x type) context entities to be used to do an arbitrary variable lookup, and session variables
are specific to the exact conversation happening right now: they reset between conversations. Not
all variables are available to all script writers--some may be restricted based on the type of
entity and/or script being written.

We begin by creating a reference to a variable of the desired type. For G-, E-, S-, and O-
variables, the context is implied (current entity/character/session), but for X- and Y- variables,
context must be supplied in the form of an entity reference Additionally, a default value is
supplied if the variable has not been used or created before:

The variable name provided to `yvar`, `evar`, etc. is quoted. This is the first
significant use of quoting in SWC Lisp, and while it is a minor point it is very important
(and unfortunately may lead to errors as players leave out the quote).

Later, we can retrieve how many confirmed rancor kills a player has (based on how many
trophies they have turned in) in order to update quest status or inform them of progress:

And, of course, we need a way to manipulate variable values, which can be done using `set-var!`.
Functions which change a variable's value, rather than merely declaring or using it, (called
mutators) are frequently indicated by an exclamation point at the end of the function name (read
out loud as bang). Several mutators exist for manipulating the state of SWC game objects beyond
taking an "action" like saying something or awarding credits. We can assign a new value to a G-,
E-, C-, Q-, or S- variable like so:

Be aware that regular variables (created with `defvar` alone) cannot be modified with
`set-var!`. Only the results of a `gvar`, `evar`, `ovar`, `svar`, `xvar`, or `yvar` expression can
be modified with `set-var!`. This may seem like a significant limitation, but once you are
comfortable with the variable system, you will not even realize that assignment does not exist in
general.

SWC Lisp also allows users to create reusable code modules that can be shared with others. There
are multiple core SWC-curated libraries with functionality not included in the base, for example
`swclib` which defines a large number of additional functions for convenience when interacting with
the core library features. A module can be created by any player, edited collaboratively, and
restricted by password when desired.

Modules follow the same syntax for declaration as normal NPC scripts, except they do not require
the `start` symbol to be defined (and defining it has no effect). Instead, they use another
primitive, `module-export` to define functions and variables to be available in the calling
script. `module-export` accepts a variable number of arguments which can be symbol names or pairs
which allow you to rename the symbol

In this case, after the script module is loaded, two functions, `is-emperor?` and `is-dlots?`
will be available, but the internal variables and function names will not be visible to the place
where the module is used.

Modules are loaded using the `load` primitive, which accepts one or two arguments. For modules
without an access key, only the name is required. Otherwise, both a name and an access key are
required.

Code

(load "swclib")
(load "private-lib" "p4ssw0rd")

Modules may be used by other modules modules. Only one "instance" of a module will be created, so
if it maintains internal state (svars, etc.) be aware that they will be shared by all such
instances. This allows for superior communication, but it can introduce complications if you are not
careful with variable usage.

The quest starts when the player talks to the first NPC, James Walker. He uses conditions
to prevent the quest from being assigned multiple times and to change his responses based on
the current quest status:

Code

(defvar target (get-npc 10072))
(defvar walking-tutorial (get-quest 2))
(defun start
(cond
[(quest-finished? walking-tutorial) (say "Sorry, I don't have any more work for you")]
[(quest-started? walking-tutorial) (say (concat "Go talk to my brother " (get-name target) " and let him know his weapon shipment came in, please."))]
[#t
(say (concat "Hello there! You look like you could use some exercise. Why don't you walk over to my brother " (get-name target) " at and let him know his weapon shipment came in? You can walk by selecting the Travel button at the top of your Ground Travel page, which is linked on the right menu. Then enter your coordinates and hit Go!"))
(add-response "Hey, who are you calling out of shape? Forget it! You can tell him yourself." decline)
(add-response "I guess I can do that. I hope there's something in it for me." accept)]))
(defun decline
(say "Fine. If you change your mind before someone else comes along, let me know."))
(defun accept
(say "Great! I'm sure he'll have a tip for you.")
(quest-start walking-tutorial))

Two variables are declared at the top to identify objects he interacts with: a quest and a
"target" NPC, both of which are specified directly by ID. In this case, the target is Ryan Walker,
his brother, whom we must talk to finish the quest and receive our reward. The quests are created
separately (currently managed by admin tools), and the appropriate ID number should be noted to
add it to the script.

Ryan also checks for quest status and tailors his response appropriately. When the player
elects to turn the quest in, he marks the objective as done and gives the player a reward.

Code

(defvar walking-tutorial (get-quest 2))
(defun start
(say "Yeah? What do you want?")
(cond
[(quest-started? walking-tutorial) (add-response "Um.. There's a weapon shipment for you. I don't know where it is. Or what it is. Maybe I should have gotten more information about this. I sure hope you're not up to something shady." done)]
[#t (add-response "N..Nothing. Sorry to bother you." no-response)]))
(defun done
(say "Really? Well it's about time! Here's something for your trouble. Listen, why don't you use this money to buy some equipment? I hear there's a guy in the Shop (at coords?) who can help you. Walk over to the Shop in this city and use the Board button on your Travel page to enter.")
(quest-finish walking-tutorial)
(add-xp 25 (concat "Finished quest: " (get-name walking-tutorial)))
(add-credits 100000))
;; This shows no response from the NPC and gives the character no opportunity to react further
(defun no-response #t)

Ryan also has an empty response function, `no-response`, which is a technique that can be used
when the player should get the last line in a conversation.

self : A reference to the entity that the script is running on (current NPC being talked to, current item being talked to, etc.)
character : A reference to the character currently interacting with this script
empty : A list terminator required for advanced scripts

This list includes only currently implemented and available SWC-related functions.
Language-builtin functions are listed separately.

Note

This list is incomplete. All of the previously available wildcards, for instance, have been
implemented, although some have slightly different names or usages, but they have not all
been added here yet. Feel free to ask in #swc-dev if you are looking for a function not
included here.

Functions available as part of the language that do useful programming-related things. These
might be more useful in more sophisticated scripts. Currently these are of limited use as our
library functions don't create data that needs to be manipulated in this way.

This language and its integration with SWC are both a work in progress. New features will be
added over time and hopefully appropriate documentation will be added here as well. SWC LISP has
essentially reached v1.0 as all of the "essential" features have been completed, but additional
suggestions are always welcome.