Cubescript

From Platinum Arts Sandbox Free 3D Game Maker
Revision as of 08:24, 4 August 2010 by Hirato (Talk | contribs) (Writing in Cubescript)

Jump to: navigation, search

Cubescript is the scripting language utilised within the sauerbraten/cube2 engine. it is used to set aliases, variables, create binds and generate the menu.

Writing in Cubescript

Aliases

The brunt of cubescript is concerned with Aliases. Aliases have 3 basic uses.

  • store lists and values for extended periods of time
  • store temporary lists and values
  • grouping a set of commands which are useful when combined

There are two ways to declare an alias. You can do it via infix notation, or by using the alias command. It should be noted that aliases are the exception to the rule, infix notation is only available for creating aliases.

A VERY important note, alias specific operations are available to aliases only. this means builtin variables can't be set or overridden through alias, =, pop and push (among others).

 //command
 alias myalias [1]
 
 //infix variant
 myalias = [1]

executing aliases

To execute an alias, you execute it like any other command. if I had an alias named myalias, I would simply invoke '/myalias to execute it.

These aliases also have access to a class of special aliases. Those are the temporaries in which arguments are stored. There's also a useful variable named 'numargs' which tell you how many arguments were provided. The aliases containing the arguments are named arg. they are numbered and enumerated, starting from 1, so you'd use $arg1 to access the first argument. This does not start at 0, since arg0 is the command's name (arg0 is also not defined.. ever, nor included in numargs).

 //prints all arguments to screen, one by one
 //see the other comments for an explanation
 print = [                                    //declares an alias named print
   loop i $numargs [                          //loops $numargs times, setting i to the current iteration
     echo (getalias (concatword arg (+ $i 1)) //gets the value of the arguments, by accessing $arg1 $arg2 $arg3....
   ]                                          //close loop
 ]                                            //close print alias
 print I like unicorns                        //should print I like unicorns on 3 separate lines

push and pop

push and pop are used when you want to set something to a new value without destroying the previous one. The valid commands are as follows and only work with aliases. Note that the previous value is inaccessible until you pop the stack.

Also in lieu to the above. When executing an alias, the values of its arguments are pushed on top of the aliases and popped after execution.

  • push A N
    • pushes N onto A's stack
  • pop A
    • pops A's stack, restoring the previous value.

 //an example when you store 10 numbers on top of each other and then print them
 loop i 10 [
   push myalias $i
 ]
 while [! (strcmp $myalias "")] [
   echo $myalias
   pop myalias
 ]

Syntax

Cubescript is not your typical scripting language. Cubescript is not the most flexible, or the best language, but it more than adequate for our needs and purposes. Cubescript draws its main inspiration from quakescript and from Lisp. It is therefor said that Cubescript contains many "Lispisms". Below is a summary of notable features/quirks.

  • comments are denoted by //
  • both newlines and ; denote the end of a function.
  • newlines will automatically close of any strings you may have forgotten to.
  • substitutions are done through the use of $ and @ tokens, (See below)
  • everything is a function which may take arguments. This means Polish notation is used for mathematical/comparative expressions
  • there are no arrays or vectors.
  • most implementations have a 25 word limit. This means commands can be invoked with up to 24 arguments (sandbox does not contain this limit as of 2.6)

one liner example

 alias hi [say "hi"; sleep 5000 [say "Hi again"]]

multi liner example

 alias hi [
   say "hi"
   sleep 5000 [
     say "Hi again"
   ]
]

it's obvious what the above code does, it'll make your character say hi, and wait 5 seconds before saying hi again.

indenting is done in levels, the Tabulator (TAB) key is usually used to indent the text. while not necessary, it tends to make it bigger, and more legible as seen in the above example (when you get to big stuff, you'll thank yourself for indenting it.

As a rule of thumb, pretty much only [ increases the indentation level. Also note, ", ] and ) can close each other, so make sure you close them off properly

containers

Containers in cubescript are of 3 specific types, "", () and [] - all of these have significant differences in operation and use

"" container

"" containers are used to store things EXACTLY as they are written, with a few small exceptions - in-text commands which are denoted by a preceding ^. They are as follows

  • ^f#
    • ^f# is used to colour text. # must be replaced by either a number or a capital letter (A-Z). r and s are special control characters which will restore and save the text's current colour respectively.
  • ^n
    • insert a '\n' character (newline/linefeed)
  • ^t
    • inserts a '\t' character (tabulator)
  • ^"
    • inserts a quote (")

() container

() containers are used mainly for logic/arithmetic operations. They are usually nested inside [] blocks, and unless they are nested inside []'s they're permanently substituted with their results.

For example - the following will turn into an infinite loop. The condition is initially evaluated and substituted with one and since 1 can never = 0, it's true forever more

 i = 0
 while (= $i 0) [i = (+ $i 1)]

the following code on the otherhand is devoid of such issues. Being nested inside the [] means it's not substituted and evaluated each cycle

 i = 0
 while [(= $i 0)] [i = (+ $i 1)]

[] container

This container is used for nesting, deferring execution of it's contents, deferring calculations and substitutions of () containers, and substitutions of aliases/variables written with a @ prefix (note several levels are needed to achieve this). [] containers are also the ONLY container capable of spanning multiple lines.

This is the most used container and due to it's properties, is ideal for creating aliases.

generally statements in cubescript can't span multiple lines. If you should use this container and occupy several, the execution of the prior command continues exactly where this container finished off. This can be demonstrated with an if statement.

 if 1 [
   do
   stuff
 ] [
   do
   other
   stuff
 ]

substitution

there are two principle ways of substituting values in cubescript, the first is through the use of $ tokens, the second is through the use of @ tokens.

a very simple example:

 myalias = 5
 echo $myalias

myalias is substituted into the echo statement, so you should see 5 printed to the top-left of the screen

using the @ token would have also resulted in the same behaviour

 myalias = 5
 echo @myalias

There's a big difference between the two, the @ token can be used to concatenate stuff together, $ can't. @ is also substituted immediately up to the first level of nesting. (this can be incremented by using additional @ tokens, ie @@ for 2 levels of nesting). Do take note that too few @ tokens may cause a substitution to happen too late, and too many may likewise cause it to happen too early.

An example of use in a level 1 nest

 myalias = "I am awesome"
 mymenu = [guitext @myalias]

if you were to type /echo $mymenu, it should read: guitext "I am awesome"; using the $ token on the other hand

 myalias = "I am awesome"
 mymenu = [guitext $myalias]

typing /echo $mymenu should produce: guitext $myalias


An example of concatenation

as i mentioned, @ also allows you to concatenate stuff together, which could result in shorter and simpler scripts. It should be noted that this can also result in extreme obfuscation

 myalias0 = 5
 myalias1 = 3.1415
 myalias2 = 2.481
 
 loop i 3 [
   echo $myalias@i
 ]

this should print 5, 3.1415 and 2.481 respectively

the purely $ token equivalent

 myalias0 = 5
 myalias1 = 3.1415
 myalias2 = 2.481
 
 loop i 3 [
   echo (getalias (concatword myalias $i))
 ]

Branching/Decisions

Decisions are based on 6 key commands, if, ?, cond, case (integer), casef (float) and cases (string). There are also other ways to do this. For example you might be able to easily query something about an object, where it's obviously unique. You would then execute an alias whose name has this unique bit attached onto the end. This is an advanced method and is generally not recommended.

if and ?

Arguments: Cond True False

Unlike most other languages, cubescript's if contains an implicit else, in short we do not have an else statement. The provided condition is true if it ends up as a series of alpha numerics, or in the case of a number, as a value other than 0.

 myalias1 = 5
 myalias2 = "woot"
 
 //if else-if else
 if $myalias1 [
   echo "myalias1 is true"
 ] [
   //this shows the alpha numerics case
   if $myalias2 [
     echo "myalias1 is false and myalias2 is true"
   ] [
     echo "myalias1 is false and myalias2 is false"
   ]
 ]

? acts exactly the same way, but there's one HUGE difference. ? does not execute the chosen result.

 //an example of where ? can be used where if would fail
 items = 1
 echo (format "You are carrying %1 item%2." $items (? (= $items 1) "" "s"))

cond

basically you provided a several pairs of arguments, the first half of the pair is the condition, and the second is the result. To put it in a programming perspective, this is like creating a long line of if else's together. Execution stops when the first condition is matched. Should you place several conditions where more than one may be true at any one time, only the first will be executed.

The following example assumes you have a light entity selected

 lightstrength = [
   cond [(< (ea 0) 0)] [
     echo "It's like a black hole!"
   ] [(= (ea 0) 0)] [
     echo "Blindlier than science!"
   ] [(< (ea 0) 8)] [
     echo "It's weaker than Billy's night light!"
   ] [(< (ea 0) 32)] [
     echo "Nice night light you got there"
   ] [(< (ea 0) 96)] [
     echo "It's like a lamp, but it's not a lamp"
   ] [(< (ea 0) 512)] [
     echo "By Jove! I can actually see the ground!"
   ] [(>= (ea 0) 512)] [
     echo "Dark places are icky, don't you agree?"
   ]
 ]

case

This works similar to switches in most programming languages. The biggest difference is there is no break; so there are no fallthroughs. (for example in C/C++, case 5: case 6: [...]; break; could cause both cases to execute the same code).

The first argument tells it which variable to use. The following arguments are given in pairs, the first half is the state, the second is the result.

The following example requires a particles entity to be selected, it will print out what type it is

 parttype = [
   pname = "Invalid Particle"
 
   case (ea 0) 0 [
     pname = "Fire and Smoke"
   ] 1 [
     pname = "Fire"
   ] 2 [
     pname = "Smoke Plume"
   ] 3 [
     pname = "Smoke"
   ] 4 [
     pname = "Fountain"
   ] 5 [
     pname = "Explosion"
   ] 6 [
     pname = "Meter"
   ] 7 [
     pname = "Vs Meter"
   ] 8 [
     pname = "Text"
   ] 9 [
     pname = "Flare"
   ] 10 [
     pname = "Lightning"
   ] 11 [
     pname = "Fire"
   ] 12 [
     pname = "Smoke"
   ] 13 [
     pname = "Water"
   ] 14 [
     pname = "Snow"
   ] 15 [
     pname = "Leaves"
   ] 32 [
     pname = "Lens Flare"
   ] 33 [
     pname = "Fixed Size Lens Flare"
   ] 34 [
     pname = "Sparkly Lens Flare"
   ] 35 [
     pname = "Sparkly Fixed Size Lens Flare"
   ]
 
   echo The particle you have selected is of type: $pname
 ]

Naming

another way of doing decisions/branches is to use paths with different names. This has limited usage though. in short it requires you to query some information to concatenate onto another word, which you would then execute. There are exceptions. Other than the number of results you can query, there is no limit here. A handy trick is to have several results execute the same alias, though this requires that they differ only slightly at most in operation.

 //This example demonstrates the exception
 //it requires you to have an entity selected
 //This will conflict with showquickgui
 newgui "particles" [guitext "It's like a random series of nothing"]
 newgui "light" [guitext "SHINY!!!!"]
 newgui "jumppad" [guitext "boing!"]
 
 identify = [showgui (et)]

 //shows a more standard example, at present this is exclusively used by edithud (see stdedit.cfg)
 //this also requires you to have an entity selected.
 identlight = [echo "it's so bright..."]
 identdynlight = [identlight] // to demonstrate how different results can execute the same script
 identjumppad = [echo "boing? boing!"]
 identparticles = [echo "Please don't change it to a lens flare!"]
 
 identify = [ if (enthavesel) [ident@(et)] [echo Select an entity first!]]

Lists

Ordered lists are the closest cubescript has to the likes of arrays. They are declared the same as any other alias. Lists can use the "" and the [] container, the latter is recommended. Remember that it breaks at the next whitespace, this can be avoided by surrounding the element in quotes. To retrieve a specific item, at list index is used.

 //declares 2 equivalent lists of 3 elements
 mylist1 = [one two "three four"]
 mylist2 = "one two ^"three four^""
 
 //prints the 3rd element
 echo (at $mylist 2)

loops

There are two ways to loop through lists, depending on circumstances you may, or may not want to know the index. If you do not need to know the index, you would use looplist var list body. Otherwise you'd generally invoke loop var num with a supporting listlen list invocation.

 mylist = [
   0
   1
   3.14159265359
   2.71828182845
   sqrt(-1)
 ]
 
 //demonstrates looplist
 looplist var $mylist [
   echo $var
 ]
 
 //demonstrates loop + listlen combo
 loop i (listlen $mylist) [
   echo (at $mylist $i)
 ]

concatenation

This is unfortunately not simple. To concatenate an item onto the end of a list you are required to make the list equal itself plus the item. This is best achieved through the use of concatword and format.

 mylist = [1 2 3 4 5 6 7 8 9 10]
 
 //using concatword
 mylist = (concatword $mylist " " 11)
 
 //using format
 mylist = (format "%1 %2" $mylist 12)
 
 //since these lack spaces, concat and prettylist can be used as well...
 mylist = (concat $mylist 13)
 mylist = (prettylist $mylist 14)
 
 //should print 1 2 3 4 5 6 7 8 9 10 11 12 13 14
 echo mylist is $mylist

concat returns whatever is given to it, spaces included. the only difference with concatword is that concatword ignores spaces. concat and prettylist are ideal for merging lists into one...

 mylist1 = [1 2 3 4 5]
 mylist2 = [6 7 8 9 10]
 mylist3 = [11 12 13 14 15]
 mylist4 = [16 17 18 19 20]
 mylist5 = [21 22 23 24 25]
 mylist6 = [26 27 28 29 30]
 finallist = ""
 
 //the use of prettylist
 finallist = (prettylist $mylist1 $mylist2)
 
 //concat can combine them all, and we defined extra lists to show that...
 finallist = (concat $finallist $mylist3 $mylist4 $mylist5 $mylist6)

finding

there are two ways to find things, the first is to use listfind, and the second is to loop through the list. Both are demonstrated in the example below

 mylist = [
   "Jack"
   "Jane"
   "Joseph"
 ]
 
 //This is an example of looking for a single element, note that this variant of...
 //strcmp unlike its C counterpart returns 1 when strings are equal
 //in this example, a 0 should be printed at the top of your screen - listfind returns the index
 echo (listfind f $mylist [strcmp $f "Jack"])
 
 //In this example, -1, as Jill is not in the list
 echo (listfind f $mylist [strcmp $f "Jill"])
 
 //in this example, we want to find anything which contains an e and add it into a list
 results = ""
 looplist var $mylist [
   if (>= (strstr $var "e") 0) [
     results = (concatword $results "^"" $var " ^"")
   ]
 ]
 
 //this should print: Jane Joseph
 echo $results

List of commands

General

The following commands are quite often used in a lot of scripts.

  • alias N B
    • creates an alias named N with body B, the intext version is denoted as N = B (equivalent to alias N B)
  • at S I
    • returns the Ith element in string S, note that counting starts from 0. If it's out of range, "" is returned
  • getalias A
    • returns the value of A. this also does not produce a warning should an alias be undeclared, nor complains when fetching built in variables.
  • if C T F (F is optional)
    • if condition C is true, execute T, otherwise F is executed
  • listlen S
    • returns the amount of elements in list S
  • loop V N B
    • loops N times, and aliases the current iteration to V; B is executed every iteration.
  • result S
    • returns the string, useful when aliases need to be executed
  • rnd N L
    • chooses a random number between 0 and N-1 (inclusive). L sets the lower limit. eg rnd 12 7 will return either 7 8 9 10 or 11
  • sleep N B
    • executes B after a delay of N milliseconds. NOTE: even if N is <= 0, it'll still wait till the next frame before executing it
  • while C B
    • executed B while C is true. NOTE: surround C in a [] container, or you risk an infinite loop due to the condition not being reevaluated.

Map Configuration

ONLY APPLIES TO FPSGAME

The following commands are useful in level only aliases

  • triggerstate
  • level_trigger
    • level trigger is invoked as level_trigger_1 = [] as an example, the end number is the 4th number in the mapmodel's strings
  • level_base
    • Used to give bases names in capture mode. This is unfortunately unused in PAS

Binds

The following commands are useful in binds.

  • onrelease

Gui Creation

These commands are used to create various menus.

  • cleargui
  • guibar
  • guibutton
  • guicheckbox
  • guifield
  • guikeyfield
  • guiimage
  • guilist
  • guirolloveraction
  • guirollovername
  • guislider
  • guilistslider
  • guinameslider
  • guistayopen
  • guitab
  • guitext
  • guititle
  • guistrut
  • newgui

Comparisons

The functions here can change the values, or return a logical comparison for all intents and purposes, the C version of booleans apply here; values not equal to 0 are true - and functions that return true return 1

Integer Arithmetic

  • + A B
    • returns the value of the numbers added together
  • * A B
    • returns the result of multiplying A and B
  • - A B
    • returns the value of A - B
  • = A B
    • returns true of the two are equal
  •  != A B
    • returns true if the two aren't equal
  • < A B
    • returns true if A is less-than B
  • > A B
    • returns true if A is greater-than B
  • <= A B
    • returns true if A is less-than-or-equal-to B
  • >= A B
    • returns true if A is greater-than-or-equal-to B
  •  ! A
    • if the argument is false, it returns true
  • &&
    • returns true if all arguments are true
  • ||
    • returns true it at least one argument is true
  • div A B
    • returns the value of A divided by B
  • mod A B
    • returns the modulus of the two arguments
  • min
    • returns the lowest valued argument of all provided arguments
  • max
    • returns the highest valued argument of all provided arguments

Bitwise Integer Operations

  • ^ A B
    • returns the value of A xor B ie (^ 8 4) is 12 - (^ 12 4) is 8
  • & A B
    • returns the value of the bits both A and B have
  • | A B
    • returns the value of the bits either A or B have
  • ~ A
    • returns the inverted bitwise version of A ie (& (~ 128) 255) is 127
  • ^~ A B
    • xor;s the arguments after applying an inversion on B
  • &~ A B
    • and's the arguments after applying an inversion on B
  • |~ A B
    • or's the arguments after applying an inversion on B
  • << A N
    • shifts A N bits
  • >> A N
    • shifts A -N bits

Floating Point Arithmetic

Generally these just have an -f suffix

  • +f A B
    • returns the value of the numbers added together
  • *f A B
    • returns the result of multiplying A and B
  • -f A B
    • returns the value of A - B
  • =f A B
    • returns true of the two are equal
  •  !=f A B
    • returns true if the two aren't equal
  • <f A B
    • returns true if A is less-than B
  • >f A B
    • returns true if A is greater-than B
  • <=f A B
    • returns true if A is less-than-or-equal-to B
  • >=f A B
    • returns true if A is greater-than-or-equal-to B
  • divf A B
    • returns the value of A divided by B
  • modf A B
    • returns the modulus of the two arguments
  • minf
    • returns the lowest valued argument of all provided arguments
  • maxf
    • returns the highest valued argument of all provided arguments

Strings

  • =s A B
    • returns 1 if the string matches, 0 otherwise
  •  !=s A B
    • returns 0 if the string matches, 1 otherwise
  • <s A B
    • returns true if the C strcmp returns less-than 0
  • >s A B
    • returns true if the C strcmp returns greater-than 0
  • <=s A B
    • returns true if the C strcmp returns less-than-or-equal-to 0
  • >=s A B
    • returns true if the C strcmp returns greater-than-or-equal-to 0
  • strcmp A B
    • returns 1 if the string matches, 0 otherwise (unlike the C version with returns the disparity)
  • strstr H N
    • returns the position of N in H - otherwise -1 is returned
  • strlen S
    • returns the length of the string
  • strreplace S O N
    • replaces all instances of O inside string S with N

String/text formatting

These commands are used to format aliases which are normally huge bits of text

  • concat C
    • everything you type after will be returned; with expressions and substitutions performed
  • concatword C
    • the next 25 arguments will be executed and subsequently concatenated without any spaces between them
  • format F S1 S2 S3 S4 S5 S6 S7 S8 S9
    • F is a string containing %# tokens, format substitutes the arguments with their respective %# tokens (ie: S5 will replace %5)

See Also