ATTENTION: WORK IN PROGRESS
Acknowledgements
This tutorial is a contribution by members of the CW3 beta team. Their contributions are gratefully acknowledged.
Special thanks to:
The most important thing that you will need to know is the basics of CW3 gameplay and CW3 editor. It is a good thing if you have programmed before but if that wasn't a stack language it sometimes is a disadvantage. You don't have to have any programming skills to start with CRPL.
In order to proceed, you will need to know how to get a script to run, including attaching it to a CRPL Core. The collapsed section below contains a button-by-button guide.
First of all, if you have programmed before, try to forget that while reading. CRPL is (unlike many others) a stack based language, that means you put (push) all numbers on a big stack of numbers and functions (like SetCreeper or QueueMove) get (pop) the last number(s) from the stack and do something with it (create a spore, emit creeper). Think of a stack of plates in the cafeteria, you can only take the top plate. Then someone brings more plates, those go on top and people start taking from the top again. The whole script will be executed once per frame. In scripts, you can use '#' to skip the rest of the line. Think of it as a comment in your script.
#this is a comment and will not be executed
Time to create your first CRPL script! Try to use the functions CurrentCoords and SetCreeper. CurrentCoords puts two numbers on the stack (the X and Y coordinates of the CRPL tower) and SetCreeper pops 3 numbers from the stack (X and Y coords the creeper will be set on and the amount of creeper). The following code sets the creeper height to 5 every frame (there are 30 frames in an in-game second):
#Set the creeper to height 5 on the current position CurrentCoords 5 SetCreeper
Instead of '5' you could pick another number or a variable (explained later) and instead of CurrentCoords you could pick two number to indicate the cell where the creeper has to be set.
Here are 2 more examples of what you could write:
#Set the creeper to 1 on a random position RandCoords 1 SetCreeper
One more useful functions before I move on to the next section, you can use TIME Delay to stop the execution of the script for the set amount of time. So if you want to add 20 creeper every 3 seconds, you could write:
#first we add the creeper CurrentCoords 20 AddCreeper #then wait 3 seconds 90 Delay
This of course gives many more possibilities, check out this one:
CurrentCoords -10 AddCreeper 30 Delay RandCoords 5 AddCreeper 30 Delay CurrentCoords 15 AddCreeper 60 Delay
There's a built-in function to show numbers that are currently on the stack. To enable the trace log, you must call the function ShowTraceLog. After that you can use the function Trace to pop an item from the stack and show it on the trace log (removes the item from the stack!). Use Trace2, Trace3, Trace4, Trace5 and TraceStack to pop and show 2, 3, 4, 5 or the whole stack on the trace log (TraceStack doesn't pop anything from the list). In the examples below we use the following notation to show how an operation affects the stack: OPERATION (BEFORE – AFTER).
You can use multiple operators, but remember that they only pop the last added items from the stack. So where we normally would write (1+2)*(3+4), you now have to write
1 2 add 3 4 add mul
The most important thing here is that you keep in mind that what you've put on the stack last, will be the first you take off (Last In First Out). So:
8 5 4 add # will result in 8 9 # because 5+4=9.
If you add another 'add' the sum of 9 and 8 will be calculated since that are the last two items on the stack.
If you want a challenge, read the following piece of code:
once ShowTraceLog 8 9 5 sub 3 8 mul add 5 7 8 add add mod div Trace endonce
Try to guess what's in the trace log.
Now you should know how the stack and trace and stack system works. You should probably never use the trace functions in custom maps, but use it as debug tool.
3+9= becomes: 3 9 add 4*5= becomes: 4 5 mul 4+2*4= becomes: 4 2 4 mul add (4+2)*4= becomes: 4 2 add 4 mul (1+2)*(3+4)= becomes: 1 2 add 3 4 add mul (7-4)*(2+3+4)= becomes: 7 4 sub 2 3 add 4 add mul (16/4)/2= becomes: 16 4 div 2 div 16/(4/2)= becomes: 16 4 2 div div
If you have problems to visualise it, put the same code in a comment and put brackets around it:
# Send a spore to the top part of the map # CurrentCoords (RandCoords 2 div) (5 4 sub) (2 3 mul) CreateSpore CurrentCoords RandCoords 2 div 5 4 sub 2 3 mul CreateSpore
# Has a lot of conditions before deciding if the code following must be executed or not # ((((0 32 80 GetUnitCountInRange) 1 gte) (<-Eaten 20 gt) and) (GetRunnerCount 20 lt) and) if 0 32 80 GetUnitCountInRange 1 gte <-Eaten 20 gt and GetRunnerCount 20 lt and if
Sometimes you want a condition to check if code should be executed or not. Like in many other languages, this is possible with the if function. Since we can't use brackets to show what piece of code must be skipped if the condition is not true, we must use endif. The if function pops one number from the stack and if it is not equal to 0, the code will be executed. If that number is 0, the program skips the code until the next endif.
if may also be used in combination with else. This function always has to be in between the if and endif functions (if … else … endif
), and works as you'd expect from the name. When an if function evaluates to true the code in between else and endif will be skipped and the code will continue from endif. When an if function evaluates to false the code in between if and else will be skipped and the code will continue from else.
Typing the words true and false simply push a 1 and 0 on the stack respectively. There are a lot of functions to compare numbers, most of these functions pop two items from the stack and push 0 (false) or 1 (true) back on the stack. You can find the full list in other wiki pages. I'll highlight some:
Function | Description |
---|---|
and | true if last 2 items are true |
or | true if at least one of the last 2 items are true |
xor | true if exactly one of the last 2 items are true |
not | true if the last item is false |
gt | 'greater than', pops 2 items from the stack, if the first is greater than the second, it results in true |
gte | 'greater than or equal' |
lt | 'lower than' |
lte | 'lower than or equal' |
eq | 'equal' true if the last 2 item have the same value |
neq | 'not equal', true if the last 2 items are not the same |
eq0 | true if the last item on the stack is equal to 0 |
neq0 | true if the last item is not equal to 0 |
Here are some examples:
Arg1 | Arg2 | Operation | Result |
---|---|---|---|
false | false | or | False |
true | false | or | True |
true | true | or | True |
true | true | and | True |
false | true | and | False |
false | false | and | False |
false | false | xor | false |
false | true | xor | true |
true | true | xor | false |
false | not | True | |
true | not | False | |
2 | eq0 | False | |
9 | neq0 | True | |
5 | 5 | eq | True |
4 | 5 | eq | False |
4 | 7 | neq | True |
9 | 2 | gt | True |
9 | 9 | gt | False |
6 | 7 | lt | True |
5 | 5 | lt | False |
2 | 2 | gte | True |
6 | 4 | lte | False |
1 | 1 | sub neq0 | False |
Written as:
Arg1 Arg2 Operation
Time to learn some more useful commands, I'll take the pre-made towers as example. A function is shown by the function name followed by brackets with the arguments in the order in which they must be put on the stack, seperated by a ','. Empty brackets mean that you don't need to give any arguments.
A variable: a way to store numbers without using the stack once stored. You can use ->VARNAME to pop the last item from the stack and store it as variable and you can use <-VARNAME to push the value of the variable on the stack (doesn't remove the variable). Please note that instead of VARNAME you can use any word. Examples:
16 ->mynumb 2 ->n2 4 ->endnumb while <-mynumb <-endnumb neq repeat <-mynumb <-n2 div endwhile
As you may have noticed, I used while, repeat and endwhile. These functions form a loop. There are different loops you can create in CRPL.
Let's start with a while loop, like in the example. A while loop has 3 functions and has the following form:
while repeat (condition) … endwhile
When while is read, the code between while and repeat is executed (and should push true or false on the stack). If true is read, the code will be executed until endwhile and execution return to while. If false is read, the code between repeat and endwhile is skipped and the execution continues at endwhile.
# Add 5 creeper to 5 random locations every 5 seconds 5 ->times 5 ->creeper 150 ->wait 0 ->numb while <-numb 4 lte repeat RandCoords <-creeper AddCreeper <-numb 1 add ->numb endwhile <-wait Delay # lte means lower than or equal
There's also a 'do' loop. A do loop has 2 functions and has the following form:
do (limit, index) … loop
do pops 2 items from the stack, if the index is bigger or equal to the limit, the execution skips to loop, else the loop will run. When loop is read, the execution returns to do, the index is raised with 1 and everything starts again. Simply put: the code between do and loop will repeat as many times as (limit-index
), it is skipped if that value is 0 or lower.
Within a do loop the value of index can be accessed by typing I. This will push the value of index onto the stack.
# Show the trace log, clear it, then show the numbers 0 through 4 in the log ShowTraceLog ClearTraceLog 5 0 do I Trace loop
In a do or while loop, you can use break to stop the loop immediatly and continue at loop or endwhile
# deposit creeper in random spots in a range of 20 round the Core # 'while true' = repeat endlessly while true repeat CurrentCoords 20 RandCoordsInRange 60 AddCreeper 30 Delay CurrentCoords GetCreeper 1 gt if break endif # the only way to break out of the while-loop # is if the core has creeper under it that is 1 high or higher (1 gt = greater than 1) endwhile
If you want to use the same piece of code multiple times or want a better overview, you can use functions. In the main code, use @FUNCTION to call the function. At the end of the code, use :FUNCTION to define the function. The function is the piece of code between :FUNCTION and the end of the script or another function. If you want to give arguments or return a value, use the stack. An example to help you:
@getnumb # pass the execution to :getnumb @emit # pass the execution to :emit # -------------- end of main body (using a line helps to visualize it) :emit # once @emit is read, execution continues here ->numb # store a variable and do something with it CurrentCoords <-numb AddCreeper :getnumb # define a new function, the :emit function stops here and execution returns to the main body 5 # push 5 on the stack # end of the code, the :getnumb function stops here and execution returns to the main body
And how it looks without comments:
@getnumb @emit #--------------- :emit ->numb CurrentCoords <-numb AddCreeper :getnumb 5
CRPL allows you to modify the attributes of units, which is arguably the most powerful feature of CRPL. Unit attributes may look very complicated but they're actually pretty simple: You give the unit UID (unique identifier), the attribute you want to get/set and then call the GetUnitAttribute or SetUnitAttribute function.
Function | Description |
---|---|
Self () | Pushes the UID of the current unit on the stack |
GetUnitAttribute (unit UID, attribute) | Finds the unit with the given UID, gets the given attribute and pushes it onto the stack |
SetUnitAttribute (unit UID, attribute, value) | Finds the unit with the given UID and sets the given attribute to the given value |
Each attribute has a number. The 'attribute' part you pass to the above functions has to contain the number of the desired attribute. Though you could type 17 in order to select a units Ammo-attribute, all attribute values are stored as constant variables that you can access them at any point in your code. This means you don't have to learn which attribute has has which value, you can simply type CONST_AMMO and that will add 17 (the attribute number of Ammo) to the stack.
Here's a short list with some of the attributes you can use (The full list can be found on the GetUnitAttribute page):
Attribute | Description | Value |
---|---|---|
CONST_COORDX | The x coordinate of the unit. | 0 |
CONST_COORDY | The y coordinate of the unit. | 1 |
CONST_AMMO | The unit's ammo. Floating point value. | 17 |
CONST_COUNTSFORVICTORY | Whether the CrplTower must be destroyed before map victory on annihilation game modes. Only works for CRPLTowers. | 27 |
CONST_CREATEPZ | Whether the CrplTower creates a power zone when destroyed. Only works for CRPLTowers. | 21 |
CONST_HEALTH | The unit's health. Floating point value. | 15 |
CONST_MAXAMMO | The unit's max ammo. Floating point value. | 18 |
CONST_MAXAMMOAC | The unit's max AntiCreeper ammo. Floating point value. | 10 |
CONST_MAXHEALTH | The unit's max health. Floating point value. | 16 |
CONST_NULLIFIERDAMAGES | Whether the CrplTower can be targeted and damaged by Nullifiers. Only works for CRPLTowers. | 18 |
Remember that simply typing an attributes name doesn't return a unit's value of that attribute. In order to get or set the the attributes of a unit you have to use a their UID and the GetUnitAttribute and SetUnitAttribute functions.
If this is still a bit hard to follow, here's an example:
:randUnitBuildMode # This function puts a random unit back into build mode # GetUnitsInRange first pushes the UIDs of all units in range, then the amount of units # If there are no units in range it pushes 0 on the stack CurrentCoords 10000 GetUnitsInRange ->unitCount 0 <-unitCount RandInt ->unitNr # create a nr where 0 <= unitNr < unitCount <-unitCount 0 do # for each unit I <-unitNr eq if # if unit is the chosen one CONST_ISBUILDING true SetUnitAttribute else pop endif loop
If you use $VARNAME:DEFAULT at the start of your code you can define the variable when adding scripts to units in-game. In other words, you can set these variables in-game so they are different for other cores running the same script.
$amtToEmit:10 $interval:15 CurrentCoords <-amtToEmit SetCreeper <-interval Delay
Take a look at $amtToEmit:10
. When you attach the script to a core in-game you can choose a value for the variable amtToEmit. If you don't input a number in-game, 10 is used. This is a very powerful mechanism to use the same script over different cores or if you want to give the script to other map makers.
You should use it instead of
10 ->amtToEmit
where possible. Also try to use this instead of fixed values (10 AddCreeper
), which should help you alot if you want to make small balance changes later. Ofcourse a good coder can change values directly from within the script, but that'll get harder as your scripts grow larger.
CRPL reference with all available functions: CRPL Reference
Guide to examine map resources to extract scripts and custom images from another map.
If you need more practice or help to a common problem, you might be able to find it at the CRPL Interactive Tutorials
This wiki page is largely based off of a guide on the KC forums, some changes have been made however. The original guide on the KC forums: http://knucklecracker.com/forums/index.php?topic=12253.0