In Dark Forces, Kyle Katarn, a young mercenary successfully infiltrated the Empire. Jedi Knight continues the story of Katarn as he embarks on a quest into his past and learns the mysterious ways of the Jedi. With this knowledge, he must stop seven Dark Jedi from unlocking the powers of a hidden Jedi burial ground. This task forces Katarn to decide his destiny. If he chooses the Dark side, he will come into enormous power. If he chooses the Light side, he faces seemingly insurmountable evil. Whatever path Katarn chooses will change the face of the galaxy forever.

Post tutorial Report RSS DataMaster's Cog scripting tutorial: Chapter 2

This is Chapter 2 of a Cog scripting tutorial for Jedi Knight originally posted to Saber's Domain. It goes over the functions and syntax of the language

Posted by on - Advanced Client Side Coding

This is part 2 of DataMaster's domain's cog scripting tutorials. It has been mirrored here for archival purposes. You can find sample scripts, programs and further documentation here. You can also find parts one and three on moddb.

Chapter 2

Introduction


Now that you understand how Jedi Knight works, we'll move on to the Cog language. We'll start with basic syntax and look at complete cogs. Then in the next few sections, we'll go over each part of a cog file in detail. After that, we'll come up with some basic patch ideas and write cogs for them.

Cog Structure


Hello World

Now it's time to start looking at how cog files are put together. Almost every language has someway of addressing the human world, and Cog is no exception. This tutorial will proudly continue with the tradition of beginning a coding tutorial with the infamous Hello World.

# HelloWorld.cog
#
# Print "Hello World" on the screen when the level starts.
#
# [SM]
#==============================================#
symbols

message startup

end
#==============================================#
code
#-----------------------------------
startup:
   Sleep(1);
   Print("Hello World"); // Print the string

return;
#-----------------------------------
end

All this cog does is print "Hello World" to the screen when a level is loaded. The first command in the code section is

Sleep(1); This tells the cog to wait for 1 second before doing anything. The next command is Print("Hello World"); This prints the text to screen. The next command is return; This command tells JK to stop reading the cog.

Comments

When JK reads a cog, it will ignore any text to the right of a pound sign or two forward slashes. This gives you a way to add comments to your code. You can use the pound sign anywhere in both the symbols and code sections (explained below), but the forward slashes can only be used in the code section. Otherwise, there's no difference between them.

Comments are added to a cog to either improve its structure or to explain what a command is doing. Comments can visually improve a cog's structure so that it's easier to see where one part of a cog ends and another begins - this is done in the cog above with pound signs and dashes.

Other comments explain something that isn't obvious when read. Although the print command is very easy to understand, the comment beside it is only there as an example.

The Header

A header is a bunch of comments that come before the symbols section to give the reader some basic information about the cog - such as: file name, purpose, and author. It might also contain the cog's version, a history of its updates, and the date it was last updated. The header is optional, but every cog you write should have one.

Symbols Section

The symbols section begins with the keyword symbols; and ends with the keyword end; In the symbols section, every variable that the cog uses will be defined and optionally given an initial value. Don't worry about variables yet, we'll cover them in much more detail later on.

Code Section

The code section contains all of the commands that a cog can perform. When an event happens in a game, a message is sent out to associated cogs. If the cogs are listening for that message (if they have it declared in their symbols section), then JK will look for that message's name in the code section and execute any code that comes after it until it finds a return command.

You're given a lot of freedom when it comes to whitespace (spaces, tabs, and line breaks) in the code section. With the symbols section, each variable declaration must be on its own line. But JK allows you to write everything between code and end on one line if you want. But you should always try to make your code as readable as possible. This means lots of comments and proper indentation.

It's easy to think that you'll never need comments to understand what you've written. But that's far from the truth. It doesn't matter how good your memory is, once you have to come back to a cog you've written a few weeks before, you'll find that you have no memory of most of the cog. It's much better to leave comments for yourself, than to waste time later by trying to figure out what you've already done.

Variables


Variables (or symbols) are like storage places with a name. We store values in variables. These values can be numbers or text. All variables should be declared in the symbols section. A symbol declaration tells JK to reserve memory for the value that the variable will be given in the code section. With most variables, you can assign an initial value when you declare the variable.

On this page, I'm going to explain how to declare and use the different types of variables that JK uses. For more technical information on variable types, look in the Symbol Types section of this reference.

Integers

An integer is a whole number - a number without a fractional portion. You'll use integers more than anything else when writing cogs. A typical declaration looks like this:

int     yourInt=1

int is short for integer. With every variable that you define, the variable's type will come first. Next is the variable's name - in this case, yourInt. In this declaration, the integer is given an initial value of 1. You can also add arguments after the declaration which tell JK how to use the variable - we'll cover that later on this page.

Floating Point Numbers

Floating point numbers, or floats, are numbers with a fractional portion. They're called floats because the decimal point is said to "float" between digits (note that it's a mistake to say that it's a "decimal point" because there's nothing about a floating point that's restricted to base 10).

JK has two floating point types: float and flex. Apparently, both types are exactly the same. Float is the standard programming term, but flex is more commonly used in Cog. Their declarations look like this:

float     yourFloat=1.1
flex      yourFlex=1.2

Floats are most often used to store physics variables like speed, mass, and vector axes.

Templates

Templates are defined by giving the template's name as it's listed in its JKL file. Template variables are usually used to get a reference to a specific template since you won't normally refer to a template by name in the code section. A declaration looks like this:

template     yourTemp=+tempName

A template contains an integer value. This integer is the template's number in its JKL's listing. For the static.jkl, there is an offset number that's added to the list number.

Vectors

Vectors are special variables that store three floating point numbers. Vectors do not accept an initial value in the symbols section but you can pass a value to them from a JKL. Their declaration looks like this:

vector     yourVec

A vector's value can be expressed in several different ways, but when passing a value from a JKL, you'll use the form (x/y/z)

File Variables

File variables are used to get a reference to a resource file (to save space and time, all of the variables that just load resource files are grouped together here). They are declared like this:

ai        yourAI=example.ai
material  yourMat=example.mat
sound     yourSound=example.wav
keyframe  yourKey=example.key
model     yourModel=example.3do

Notice that no path is given. JK will look for these files in their respective resource subfolders. All of these variable types contain integer values. This value will be the number of the resource in its JKL's list for that file type. In some cases, JK will add a resource that's not already in the JKL's list.

Cog

Cog variables do not refer to an actual .cog file. Remember from before that cogs are useless to us as a piece of text. Cog variables hold an integer - this integer is the cog's index number in the jkl's list. This is why cogs aren't given an initial value, their value needs to be passed from the level - because the level knows exactly what a cog's number will be. The declaration looks like:

cog     yourCog

Messages

Messages are declared in the symbols section to tell JK what events the cog is listening for. Messages aren't "used" in the code section like other variables. Their declaration looks like this:

message     startup

Message variables must be given message names (refer the the Messages Section for a listing). Like most variables, messages hold integer values, but these values are set within JK's executable. Messages are only declared so that JK will see the declarations, you can tell by printing the value of an undeclared message that the cog already knows what a message's value is.

Thing

A thing declaration looks like this:

thing      yourThing

Things have integer values - the number of the thing in the thing list. You can give them initial values just like integers, but it's not that helpful from the cog itself. A thing that's already placed in the level should be left open for the level's JKL to define.

Surface

Surface variables contain the integer number of surfaces in the JKL's list. Their declaration looks like this:

surface    yourSurf

Surfaces should be left open for the level to define.

Sector

A sector is a group of surfaces that form a room. Level geometry is created by adjoining (combining) sectors. Like surfaces, sectors are integers containing the value of a sector in the JKL's list. Their declarations look like:

sector     yourSec

Sectors should be left open for the level to define.

String

Cog doesn't really have a string symbol type, but in the code section you can assign strings to string values, and they will keep the value. We know that there is no string symbol type that you can use in the symbols section because it would be written in the exe with the other types.


More About Variables


Passing from a JKL

Level cogs receive their events from things that the level defines for them. Let's say our cog's symbols looks like this:

symbols

thing     myThing

message  created

end

And the cog's listing in the level JKL looks like this:

Section: cogs
World cogs 10
#Num Script          Symbol values
0: yourCog.cog 18
end

The value of 18 is being passed to the variable myThing. So when the level finishes loading, myThing will have a value of 18. This is how the level defines variables.

When the level passes a thing, sector, or surface value to one of its cogs, it associates the cog with that object. In our example above, yourCog will receive the created message from myThing.

If you don't want to define a variable in the JKL, or you do but don't want to receive its events, you'll need to add symbol extensions after your variable.

Symbol Extensions

After a variable's declaration, you can add arguments that tell JK how to use the variable. These arguments will be refered to as symbol extensions in this reference. And they are: desc, local, mask, linkid, and nolink.

Desc serves only to add commenting to a variable. A typical use would look like:

thing     myThing     desc=this_is_my_thing

JK won't complain either way, but it's considered bad syntax to have spaces or special characters in the description.

Local is the most common extension used in cog. This extension tells JK that the variable will not be given a value by the JKL. For example, let's say your symbols looks like this:

symbols

thing     myThing
int       myVar     local
thing     myOtherThing

message  created

end

And your JKL looks like this:

Section: cogs
World cogs 10
#Num Script          Symbol values
0: yourCog.cog 18 20
end

myThing will be given a value of 18, and myOtherThing will be given the value of 20. Because myVar had the local extension, JK skipped over it when it assigned the JKL's values.

Mask is used to limit the events that the cog receives from a thing. All events have source and a sender. The source is the thing that caused the event. And the sender is the thing that the event happened to. When the event happens to the sender, it will send a message out to its cogs telling them what's happened. The message includes the thing that caused the event - the source.

Mask Flags are used to block messages that have irrelevant sources. Let's say you want your cog to listen for the touched event of a thing so that your cog can do something when the thing is shot. If the thing is touched by the player, you don't want him to do anything. Instead of adding extra code to your cog to make sure the source of the touched event is a laser, you can use mask flags to block out events caused by players.

For this example, the declaration would look like:

thing     myThing     mask=0x8

Flags have not been covered yet, so it's ok if 0x8 makes no sense. The 0x8 flag tells JK that only the cog will only receive messages of events that were caused by a weapon. If we wanted the reverse - if we wanted only events caused by the player and nothing else, the flag would be 0x400. If we wanted both players and weapons, the flag would be 0x408.

Remember that there are default masks for things, sectors, and surfaces. These are explained in the symbols documentation. By using the mask extension, you are overriding this default. To receive all the messages that a thing sends, use a flag of 0xfff.

Linkid is used to make a group out of a bunch of symbols so that it's easier to refer to them in the code section. A declaration looks like:

thing     myThing     linkid=1
thing     myOtherThing     linkid=1

Then in the code section, you'd be able to refer to all the objects that have a linkid of 1 instead of having to write the variable name of every one of the objects in your group.

Nolink is used when you want to define a thing in the JKL, but you don't want to listen to any of its events. Its declaration looks like:

thing     myThing     nolink

Remember that all of these symbol extensions are only useful in level cogs. To class and inventory cogs, these are useless. However, it's considered proper syntax to add local to all of a class or inventory cog's variables. This is a way of telling the reader that all of the variables are being defined within the cog.

Naming Conventions

In all of the examples with variables, names like oneWordTwoWord and redFishBlueFish are used. The first character is lowercase to show that it's a variable - and the beginning of every other word in the name is capitalized to make it easier to read. This method of writing variable names is often called camel-hump notation.

Another common method is to use underscores: one_word_two_word and red_fish_blue_fish. In the author's opinion, this is worse because underscores make you type out more characters, and you get more typos trying to shift-hit the underscore key.

A constant is a variable that's given a value in the symbols section and never changed in the code section. To make it clear that a constant shouldn't have its value changed, many programmers use all capital letters: ONEWORDTWOWORD and REDFISHBLUEFISH. This is a good rule to follow, but it can be an eyesore.

Keywords, symbol types, and symbol extensions are most often written with lowercase letters. Some programmers prefer to capitalize the first letter, but it's just not worth your time for keywords that you'll use in most of your cogs.

Undeclared Variables

You should always have your variables declared in the symbols section, not doing this is considered to be sloppy coding. But this doesn't mean undeclared variables won't work. JK is nice enough to recognize that a variable hasn't been declared, so it will declare the variable when it finds an assignment for it in the code section. Also, JK will convert a variable's type to the type of the value it's assigned to - this is something you should never do, but be aware that JK does this for you. If you try to retrieve the value of an undeclared variable, JK will return -1 as the value.

There are memory limits to how many variables you can have in the symbols section. It's not known how these limits work, but declaring your variables can solve some memory problems.

Cog Code


With the Variables tutorial, you learned most of what you need to know about the symbols section. Coding, however, is a lot more diverse. So in this tutorial, I'm going to give a general explanation of everything the code section does. Then in the more advanced tutorials, we'll cover some of the same topics in a lot more detail.

Message Handing

When we refer to messages in the symbols section, they're just variables that need to be declared. Messages in the code section are much different. When something specific happens in the game - when an event occurs - a message is sent by JK to any cogs that are listening for it. JK looks in the event object's associated cogs to see if they're listening for messages about that kind of event. If a cog is listening, then JK will look in that cog's code section for a label to start code execution. A typical message handler in the code section looks like this:

startup:
 // do something

return;

Although some commonly refer to a block of code beginning with a message name and ending with a return as a "message," this is a misuse of the term. It should be called a message handler. The message itself is what JK sends to the cog. The cog's code will "handle" that message. A message name ended with a colon in the code section is called a label. Labels are like starting points in the code section. JK will start executing code right after a label and stop executing when the return command is given or when JK reaches the end of the cog.

The engine is not the only thing that can send messages. There are commands that we can use to send messages to cogs the same way the engine does.

Statements

A statement can be loosely defined as the syntax needed to form a complete command. In the next few sections, you'll learn about assignments, loops, and conditions. These are all statements usually ending with a semicolon. Up until now, we've been refering to lines of code as commands, but once you're familiar with what real code looks like, we'll use more proper terminology.

Operations

An operation is one of the most basic things that Cog does. Operations involve operators and operands (pretty basic, huh). An operator is symbol representing the type of operation to be performed - e.g., addition or multiplication. An operand is the value (or variable) on which the operation will be performed - e.g., 1 or var1. An assignment is an operation which looks like this:

var2 = var1;

The '=' is the operator and var1 and var2 are the operands. The semicolon at the end marks the end of the statement. Here's a more complicated example:

var2 = var1 * var3;

Here there are two operations: the assignment of var2 and the multiplication of var1 and var3. The multiplication has a higher precedence (refer to the Precedence Chart) and will be performed first. Then JK will assign var2 to the resultant value. You should already be familiar with mathmatical operations involving multiple operators and parentheses.

Assignments are easy to use as examples because they form a statement and nothing else has to be explained for you to understand them. Some languages treat assignments as operations that return a value - so you could write y = x = 1, and y would get the value of 1. Cog does not allow this, though.

You'll see operations used in many places throughout cog. In any place in the code section that you can input a value, you can give an equation, a variable, or a verb that returns a value (a function - which we'll cover later).

Keywords

Keywords are special, reserved words that JK will look for in your code. You've already seen how return works to end code execution, and how end ends both the code and symbols sections. Other common keywords include if, for, while, do, else, and call. These other keywords will be covered later on.

Verbs

Cog's verbs are the commands that do the work in JK. Verb is another programming term that's been borrowed from language terminology. In programming, a verb is kind of like another name for the object methods (subroutines belonging to a complex variable). But with cog, all of the commands you'll use can be referred to as verbs.

But you will also hear these commands refered to as procedures or functions. A procedure is a command that simply performs an action, and a fuction is a command that performs an action and then returns a value to whatever is assigned to it.

A typical procedure will look like this:

DoSomething(); 

The syntax here is simple. The procedure has a name, followed by a set of parentheses to enclose its parameters, and then a semicolon to end the command. The standard naming convention for verbs is camel-hump notation with the first letter capitalized - at least, that's what this reference will use.

All verbs have a set of parentheses to enclose their parameters. In programming, a parameter is an allotted space for an argument (variable or value) to be passed to the verb. When an argument is passed to a verb, the verb will use that value to perform whatever command it is meant to do. It is important to remember that a parameter is only a space for a type of variable; parameters do not have a value - arguments are the values. The example procedure above has no parameters, but it still must have the parentheses. This procedure has three parameters:

DoSomethingMore(1, 2, var3); 

The first two arguments are given directly as numbers, but the third argument is a variable. The value of this variable will be used by the procedure - the variable will not be changed (this is known as passing by value). When used by themselves, verbs form a complete statement and end with a semicolon.

A function works much the same as a procedure, but the function will return a value to any variable assigned to it. A typical function will look like:

myVar = ReturnSomething(); 

myVar is a variable that you have declared in your symbols section. The equals sign is cog's assignment operator - the value taken from the right will be given to the variable on the left (right-to-left Associativity). The function does not need to have a variable assigned to it. Although it's considered bad syntax, you can leave out the variable assignment and the function will work the same as a procedure.

When cog reads a verb's parameters, it will start from the right and go to the left. Cog will ignore any extra parameters on the left, and any ones not entered will have a default value of -1. It's not bad syntax to leave out arguments if they should be given the default value.

Code Blocks

In some cases you'll need to group a bunch of statements together so you can treat all of the statements as a group - called a code block. You can do this by writing an opening curly bracket just before the first statement and a closing curly bracket after the last statement's semicolon. Here's an example:

startup:
 statement1;
 {
  statement2;
  statement3;
 }
 statement4;

Return;

Statements 1 and 4 are outside of the block, and statements 2 and 3 are inside. Although we haven't gone over any reasons to use code blocks, you will need to understand them before going on to the conditions tutorial. Just remember that code blocks are used to group many statements so they can be treated as a whole.

The example above is properly indented, but this is not a syntax requirement. This last example will look the same as the first to JK:

startup:
 statement1;
 {statement2;statement3;}
 statement4;
Return;

Conditional Statements


The if statement allows you to check something in your cog or in the game and execute code based on the result. Suppose that you want to make sure your player is alive before letting him fire his weapon. In this example, the if statement is used to do that:

fire:
 if(GetThingHealth(player) < 1) // end firing and return.

 // continue with firing.

Return;

GetThingHealth() is a verb that returns the value of a thing's health. If this value is less than 1, then we can assume that the player has no health and is dead. JK will evaluate this less-than operation and return true if the player's health is less than 1 or false if it's not. When the if statement has a true value, it runs the following line or block of code. Here we have a comment to explain what code would be there.
Now in some cases you'll need to run code only if the condition was false or you may need to check a few more conditions. To do this, you chain if statements together to look like this:

startup:
 if(var1 == var2) // do this
 else if(var1 == var3) // do this again
 else if(var1 == var4) // do this again
 else if(var1 == var5) // do this again
 else if(var1 == var6) // do this again
 else // do that.
Return;

Or you can use one if statement and the else keyword.

startup:
 if(var1 == var2) // do this
 else // do that.
Return;

The code after an else statement will only run if the last if statement had a false value. As you see in the first example, you can chain many if statements with the else keyword. In this last example, if statements are used with code blocks:

if(var1 == 1)
{
 // code line 1
 // code line 2
}
else if(var1 == 2)
{
 // code line 1
 // code line 2
}
else
{
 // code line 1
 // code line 2
}

It makes no difference whether you have a single statement or a code block after a conditional statement.

Conditions

Condition is a word used to refer to the expression used by conditional statements. For the if statement, the condition is enclosed by a set of parentheses. This expression can be a lot more complicated than in the examples you've seen.

First, a value of 1 is a true value, and a value of 0 is a false value. There is a variable type specifically for this kind of value called boolean. Cog does not use this type, but because it is a programming standard, we commonly refer to a true or false value as being boolean. Remember that all conditions must be evaluated to a boolean value.

Most conditions are simple relational tests - one value or variable is compared to another. For example:

if(var1 == 1) // go!
else if(var1 <= 1) // go!
else if(var1 >= 1) // go!
else if(var1 < 1) // go!
else if(var1 > 1) // go!

These are all relational tests which you should know from your mathmatics classes. But unlike math, we will evaluate a relational test and find a boolean value. For this next example, var1 has a value of 1:

if(var1 == 1) // go!
if(1 == 1) // go!
if(1) // go!

These three lines of code are intended to show you how JK evaluates a relational test. And since 1 is a true value, we will go! Now let's assume that var1 is 5:

if(var1 == 1) // go!
if(5 == 1) // go!
if(0) // go!

Because 5 is not equal to 1, the result was 0 - and because 0 is false value, the following code wasn't executed. In this case, had there been an else statement, the else statement's code would have been executed.

Logical Operators

Now we're going to look at using logical operators in our conditions. A relational test is just a simple comparision, but one condition combine many tests and reverse their boolean value.

The "not" operator: This operator is symbolized by an exclamation mark. It's purpose is to reverse the value of a condition. An example:

var=5;
if(!var < 1) // Do something.

Five is not less than one, so the relational test is false. But, the not operator reverses the value to true. Look at this example:

var=0;
if(!var) // Run code

Var has a value of zero. And since zero is false, the condition would normally be false and would not run the code. But the not operator changes the value to 1, and the code is run. Note that the not operator must be placed at the beginning of the relational test - either inside or outside a pair of parentheses. Here are a few more examples that are all true:

if(!(0)) // Do stuff
if(!0) // Do stuff
if(!(5 < 5)) // Run code

if(!7 == 6)
{
   // Run code
}

The "and" operator: This operator, symbolized by two ampersands ( '&&' ), is used to combine two relational tests into one condition. For example:

if(var1 < 5 && var2 < 10) // Do something

The logical "and" operator requires that both relational tests be true for the entire condition to be true. Notice that the relational tests are not enclosed by parentheses. LEC and many cog programmers choose to do this, but it is not necessary. Because Logical Operators have a lower precedence than Relational Operators, the relational tests are performed first, and then the "and" operators are processed. It is because of this that you don't have to enclose relational tests in parentheses to force them to be processed first. If you did enclose the seperate conditions, they would look like this:

if((var1 < 5) && (var2 < 10)) // Run code

When the relational tests in a condition are evaulated, the value of the test is used in place of the relational test. It would look something like this:

if(1 && 0 && 1 && 1 && 1) // Run code

In that example, 1 out of five relational tests was false. Because all tests had to be true for the entire condition to be true, the condition was false and the code was not run. Here are two more examples:

if(var1 == 5 && var2 == 3 && var3 == 0) // Run code
if(!var1 && var2 && var3 == 10) // Run code

The "or" operator: This operator is denoted with two braces like this, ' || '. This operator will make a condition true if either of two relational tests is true. For example:

if(var1 < 3 || var2 == 10) // Run code

If either relational test was true, then the condition would be true and the code would be run. As with the "and" operator, the relational tests are usually enclosed by parentheses, but don't have to be.

Loops


Although there are variations, a loop is basically a block of code that is run continuously while a condition is true. In this text, I'll explain how to use loops in Cog. Using loops can be dangerous without safeguards. If the loop's condition is always true and nothing happens to make it false, the loop will continue indefinitely. So it's a good practice to include safeguards in your loops.

The For Loop

The for loop looks like this:

for(variable assignment; condition; variable incrementation)
{
    // run code
}

The for loop consists of three statements which are seperated by two semicolons. The first part is where the counting variable is assigned a value. In the second part, the true/false condition is defined. In the third statement, another variable assignment is done. This assignment usually increments the counting variable by one.

Remember the order in which the loop's statements are processed. First the variable assignment is performed. This is only done once. Next, the condition is tested. If the condition is true, then the code block (or a single statement) is run. After the code block is run, the third statement of the loop is executed. Remember that the third statement runs after the condition is tested.

In this first example, we have a normal loop which sets an array (covered in the next section) of five variables to -1.

for(i=0; i < 5; i=i+1)
{
      var[i]=-1;
}

Or:

for(i=0; i < 5; i=i+1) var[i]=-1;

First, i is set to 0. Next the condition is tested. If i is less than 5, the code will run and var[i]will be set to -1. After var[i]has been assigned, i is incremented by one. Then it "loops" and tests the condition again. The loop continues until i is equal to five. Then the loop terminates. Our example loop ran five times. If we had used i <= 5, then the loop would have run one more time.

For loops can also run internally as in the following example:

for(i=0; player[i]!= GetSenderRef() && i < 10; i=i+1);

That loop was not used to run a statement or block of code. We needed the loop to return the number of the player in our array. When the loop finished, i should have been a number like 5. That would mean that player[5] in our array of players was the senderref.

If i reached 10 and the the loop had not yet found a match for the senderref, the loop would terminate because i has to be less than 10 for the condition to be true. So if the loop finished leaving i at 10, that would mean the senderref was not in our array. This is an advanced technique, and it doesn't matter if it doesn't make that much sense now.

The above description barely covers the for loop which is by far the most versatile. For more information, look up the for keyword in the Keywords Section.

The While Loop

The while loop runs a statement or block of code as long as a condition is true. For example:

i=0;
while(i != 21)
{
     var[i]=-1;
     i=i+1;
}

As you can see, the while loop can be made to do the same tasks as the for loop, but the while loop does not have the built-in counting variable. The while loop is best used as in the following example:

findThing=FirstThingInView(player, 180, 5, 0xfff);
while(findThing != -1)
{
     SetThingUserData(findThing, 1);
     findThing=NextThingInView();
}

First, findThing is set to a value other than negative one. This value should be a thing somewhere near the player. Because findThing is not equal to -1, the loop begins. The loop sets the userdata of findThing to one. Then findThing is assigned to the next thing in view of the player. If there was another thing in view, findThing will not be equal to -1 and the while loop will continue. This while loop will set the userdata of all things in view of the player to 1.

When there are no more things in view, NextThingInView() will return -1. findThing will then be equal to -1 and the condition that the while loop tests will be false. When the condition is false, the loop ends.

The do..while Loop

The do..while loop is designed to run a block of code at least once regardless of the loop's condition.

do
{
    // run code
}
while(condition);

The block of code is run before JK checks the condition. If the condition is false, the loop ends. But if the condition is true, the loop will continue until the condition is false.

Like other conditional statements, do..while can run a single statement or a code block. For example:

do var1=var1+1;
while(var1 < var2);

Notice that because the code to run is given before the condition, the while() statement ends with a semicolon.

Arrays


In your code, you'll often need to access a variable without knowing its name. Arrays allow you store values in an array of variables. Then you can use a variable's index number instead of its name to retrieve the value of the variable.

Cog does not have a true array system like most programming languages. Instead, all the variables in the array must be listed consecutively in the symbols section (array variables can be declared in the code section, but that's not recommended). The array index number is used to find the variable so many variables down from the top of the list.

Note that if you try to access an array variable that does not exist, JK will crash. Arrays aren't unstable, you just have to be careful when using them.

Here's a typical list of variables used in an array:

int     array0     local
int     array1     local
int     array2     local
int     array3     local
int     array4     local
int     array5     local

By convention, all variables in the array have the same name and end with the index number that is used to find them.

To retrieve the value of one of the variables in the array, first list the top variable in the array. In this case it's array0. After that variable, put the array index number enclosed in brackets. So you would use array0[5] to access the array5 variable.

Now lets assign a value to each of the array variables:

int     array0=0     local
int     array1=1     local
int     array2=2     local
int     array3=3     local
int     array4=4     local
int     array5=5     local

And in the code section, the value of the array4 will be printed:

#-----------------------------
startup:
   Sleep(1);
   PrintInt(array0[4]);

Return;
#-----------------------------

That code will print 4 to the screen on startup.

It's convenient to have the variable's name match up with the index number that is used to call it. But variables don't have to be named that way. For example:

int     var        local
int     red        local
int     green     local
int     blue       local
int     yellow    local
int     orange   local

In this case, var[4] can be used to access the variable, yellow.

Arrays are most useful when used in combination with loops. Lets say you have ten surfaces (0 - 9) defined in the symbols section and you need to change their material. Look at this example:

#-----------------------------
activated:
   for(i=0; i < 10; i=i+1) SetSurfaceMat(surf0[i], newmat);

Return;
#-----------------------------

Here, one line of code did the work that would have taken ten lines if arrays and loops had not been used.

Writing Cogs


You should now be familiar with the basics of cog syntax, but so far, nothing has been said about actually writing a useful cog. So with this tutorial, we're going to write a few cogs.

Your Programs

The program you use to write cogs will be your text editor of choice. Sure, there are a few stubborn editors out there who'll extoll notepad to its death, but you should be using an editor that's made for what you're doing. There are several available programs designed specifically for writing cogs, but they don't really provide good enough features to make their use worthwhile.

In the author's opinion, Editplus is the best text editor for cogs (cog syntax files for Editplus can be downloaded from Saber's Domain). The choice is up to you, but you will want to look for features like syntax highlighting and syntax checking. Editors that support syntax highlighting will let you assign colors to verbs, keywords, comments, punctuation, etc. Syntax checking requires a program to read through your code and check for errors. The best cog syntax checker for JK is Parsec which is available from Saber's Domain.

Another thing you'll want is an advanced find & replace tool. Editplus supports regular expressions which allow you to tell the program to search & replace with tabs, line breaks, wildcards, etc. This is not a commonplace feature in text editors, but the more powerful ones usually have something like this. MDIs (multiple document interfaces) are very useful for working on more than one file at a time.

Your References

The only full-scale references for JK are the JKSpecs from the Code Alliance and the DataMaster (which is what you're probably reading this tutorial in). The JKSpecs is an earlier reference that was created to cover all aspects of JK editing. The DataMaster was created later on when more was known about JK. Just about everything in the DM was tested prior to its inclusion, so the information is a lot more accurate. But the DM only covers cog and flag information - there's nothing about templates or anythinng not related to cog.

So you'll need to use both references together to get the information you'll need for editing. Before going to the web for help with a problem, you should always consult the appropriate reference.

First Example Cog

We're assuming here that you have a patch folder inside your JK directory that has these folders already created: cog, ui, and misc.

Intent: First you'll need to decide what you want to do. As you gain experience, you'll learn more about what you can and can't do, but you should always pick something that seems simple to start with. For this first example, we're going to let the player walk on any surface. This was done is a patch known as Walk on Walls. The creators of this patch butchered a few of JK's cogs and then claimed they'd done something so complicated that they were "pushing JK's limits." But in truth, it takes only a few lines of code. Don't be one of those people.

Outline: Now that we know what we want to do, we need to figure out how we'll do it. One of the easiest ways to change anything in JK is to make a new hotkey to create your effect - and upon the release of the hotkey, the effect will end. So we know we'll need a simple hotkey cog that we can create from scratch. With any new cog, it's just a waste of time to type in the header and the section keywords. If you're using Editplus, you can use its file template feature to create a new cog file with the basic stuff already typed in. Or you can use a program like TweakUI to add a file to the windows new file list so you can create a new file using the windows right-click menu.

Writing: For the purposes of instruction, we're going to write the cog first and then get JK to use it. So right now we have an outline that looks like this:

# .cog
#
#
#
# []
#==============================================================#
symbols



end
#==============================================================#
code
#------------------------------------------------------



#------------------------------------------------------
end

The first thing we're going to fill out is the header. We're going to save this cog as hotkey_walk.cog, so this is what we'll put as the cog's name on the first line. Then on the third line, we'll write out the cog's intent - to enable the player to walk on walls. And on the fifth line, you'll write the screen name that other editors will know you by. You can add whatever information you want people to know - such as your email address, the release date, copyright info, or what have you.
Next we'll write down the variables we're going to use. Most of the time, you won't know what variables you need until you get to the code section, but we'll write the ones we know we need before going to the code. Because this is a hotkey cog that will use the activated and deactivated messages, we need to add them. We'll also add a variable named player because we know from our intent we're going to need it.

For now, that's it for the symbols. We'll go on to the code section and add the message handlers. We won't write anything in them yet, because we want to have all the basic things written out first. Our cog should now look like this:

# hotkey_walk.cog
#
# This cog will let the player walk on any surface he touches.
#
# [SreenName]
#==============================================================#
symbols

message  activated
message  deactivated

thing     player

end
#==============================================================#
code
#------------------------------------------------------
activated:


return;
#------------------------------------------------------
deactivated:


return;
#------------------------------------------------------
end

Now that we have a way to start and stop the effect with a script, we need to find out how we want to create the effect. Pretty much the easiest way to do this is to change a few Physics Flags that tell JK how to make the player attach to surfaces. When you reach this decision process on your own, you will need to look through your references to find a solution. Remember that there's nothing wrong with looking at someone else's code to figure out how it can be done.

The Physics Flags that we're going to use are 0x1 for gravity, 0x10 for thing alignment, and 0x80 for making things attach to all surfaces. Physics Flags are assigned to things that use them. We're going to remove the 0x800 flag from the player so that he'll be able to attach to wall surfaces without sliding down, and we're going to add 0x10 and 0x80 (0x90) to make the player attach to wall surfaces and align himself to them. It's alright if you don't understand flags at this point - they'll be covered in the next chapter. For now, just understand that they're like settings for objects.

The first line will go in the activated handler. Here we'll define the player variable as the local player. GetLocalPlayerThing() will return the thing number of the player and the assignment operator will assign that value to the player variable. Next we'll add the 0x90 Physics Flags and remove (clear) the 0x800 flag. Since we haven't run the cog yet, we'll put a Print() command in the message so that if we don't see anything happen but we see the printed text, we'll know the cog did run but our code was wrong.

The deactivated handler will do the opposite of activated. We'll set the flags we cleared, and clear the flags we set. Player does not have to be redefined because the cog will store the value of a variable for other message handlers. We'll also add a print to the deactivated handler so that we'll know when the effect has ended. Our cog should now look like this:

# hotkey_walk.cog
#
# This cog will let the player walk on any surface he touches.
#
# [SreenName]
#==============================================================#
symbols

message  activated
message  deactivated

thing     player

end
#==============================================================#
code
#------------------------------------------------------
activated:
 player=GetLocalPlayerThing();
 SetPhysicsFlags(player, 0x90);
 ClearPhysicsFlags(player, 0x800);
 Print("beginning");

return;
#------------------------------------------------------
deactivated:
 ClearPhysicsFlags(player, 0x90);
 SetPhysicsFlags(player, 0x800);
 Print("ending");

return;
#------------------------------------------------------
end

At this point, we'll save our cog as hotkey_walk.cog in the cog folder. Now we're going modify some of JK's resource files to make JK treat this cog as a hotkey cog. We'll need to edit the items.dat from res2.gob and jkstrings.uni from res1hi.gob. Extract both of these files using ConMan or some other gob extraction program.

Put the items.dat in the misc folder of your patch dir and open it up. Near the end of the file, add this line just before hotkeyOffset:

f_speed 116  1 1 0x122 cog=hotkey_walk.cog

You should recognize this from the inventory tutorial of the first chapter. Our bin is named f_speed because in order to make use of the deactivated message in the cog, we must make our bin an inventory item's bin. We can do this by adding the 0x2 flag to the bin, but we'll also need an icon to go along with the bin. Instead of going through the trouble of creating a new icon file, we can just use the name of a bin that we know has an icon. Now save the items.dat and you're done with it.

Now take the jkstrings.uni and put it in the ui folder. Open it and search for "Activate16". Right under that line, add this:

   "ACTIVATE17" 0 "Surface Walking"

This part of the jkstrings is where JK gets the text to put in the set hotkeys box. Adding another activate in this list will make JK look for another bin in the items.dat that has the 0x100 flag. The 0 is just there as a divider, and the text in quotation will show up in the box as the hotkey's name. After pasting this line in the file, save and close it.

That's all you need to use this cog. Using the path command with a shortcut, run your patch and see how it works.

Second Example CogNow that you know to write a new hotkey cog, let's look at making a new level cog. Let's say that you want a goal to be completed when a boss is killed. So we'll need our cog to set up the goal, and then complete it when the actor dies.

The goal will be set up on startup, and it will be completed when the boss is killed, so we'll need to listen for the startup message and the killed message of the boss. Because goals are part of the player's inventory system, we'll need a player variable. We'll need to have the boss variable left open for the level to define because we need to listen for the boss' killed message. For a goal completetion sound, we'll add a variable named doneSnd. Our outline should look like this:

# level_boss.cog
#
# Set up a goal and complete it when a boss is killed.
#
# [ScreenName]
#==============================================================#
symbols

message  startup
message  killed

thing      player      local
thing      boss

sound    doneSnd

end
#==============================================================#
code
#------------------------------------------------------
startup:


return;
#------------------------------------------------------
killed:


return;
#------------------------------------------------------
end

For the startup message, we're going to initialize our goal. First, we'll define the player, then tell 99 her offset number 1000 (this will correspond to the goal number in cogstrings.uni), and then set the first goal's flag to 0x1 so that it will be visible in the game's objectives window.
For the killed message, we'll set the goal's flag to 0x2 to make it completed, and we'll play a sound so that you'll be able to tell you completed the objective without looking at them. Our cog will now look like:

# level_boss.cog
#
# Set up a goal and complete it when a boss is killed.
#
# [ScreenName]
#==============================================================#
symbols

message  startup
message  killed

thing      player      local
thing      boss

sound    doneSnd

end
#==============================================================#
code
#------------------------------------------------------
startup:
 // define the player.
 player = GetLocalPlayerThing();
 // set the level's string offset.
 SetInv(player, 99, 1000);
 // make the first goal visible.
 SetGoalFlags(player, 0, 0x1);

return;
#------------------------------------------------------
killed:
 // complete the first goal.
 SetGoalFlags(player, 0, 0x2);
 // play our goal-completion sound.
 PlaySoundLocal(doneSnd, 1, 0, 0);

return;
#------------------------------------------------------
end

And that's it for our cog. Now we'll work on setting up the level to use it. For instructional purposes, we'll assume that you have a patch folder with the jkl, cog, and misc folders already created. The jkl folder should contain a simple jedi knight level (theboss.jkl) with thing 0 being the player and thing 1 being the boss.

In the misc folder, you'll create the cogstrings.uni and paste this text into it:

MSGS 4

"GOAL_01000" 0 "Kill teh boss!"
"THEBOSS" 0 "Teh 1337 Boss Killing L3v3L"
"THEBOSS_TEXT_00" 0 "Kill teh uber boss...."
"THEBOSS_TEXT_01" 0 "and then stand there and look stupid."

END

This file will give JK some text to display while the level loads and text for the first goal. Notice the goal offset number is 1000. The extra line at the end of the file is important - don't leave it out. The jkl's name must be named theboss.jkl. If you change the jkl name, change "THEBOSS" to whatever the new name is. Now that we have the cog and the goal text set up, we need to get the level to load the cog. So open up the jkl and go to the cogsscripts section. Add this line at the end of the cogscripts section:

16: level_boss.cog

This is assuming that there were already fifteen cogs in that section - just add your cog to the end of the list. After that, remember to up the cogscripts count. Now go to the cogs section right below it. Here, we're going to pass some arguments to the cog we just added. Add this line:

0: level_boss.cog 1 accomplish1.wav

If there are already cogs in this section, just add yours to the end of the list. With this line, we're giving a value of 1 to the cog's first variable and giving the number of a sound to the cog's second variable. The sound name is not passed as text, instead JK will get the sound's number from the sound listing and pass that. When a thing variable is given a value from the level, JK will associate that thing with the cog - this is how we'll get the boss' killed message. Remember to update the cogs count after adding this line - it should be whatever the cogscript count is plus one.

The last file you need to create is the episode.jk - this is the file which tells JK what levels can be loaded from this episode. Create this file in the root of your path directory and then paste this text into it:

"Teh Boss Killing Episode"

TYPE  1

SEQ   1

10:      1     1        LEVEL    theboss.jkl   0           0           -1    -1

end

After you save and close this file, everything should now be set up for you to test the cog. Create a shortcut and run the patch using the path command. Just so you know what you're really doing, this section explains how to set up the cog without using a level editing program like JED or JKEdit. In reality, you'll hardly ever have to manually edit a .jkl file. But since you need to know about these sections and how they work - and because this tutorial shouldn't have to explain how to use a level editor - you've been shown the manual way to add a level cog.

Third Example Cog

For your next trick, we're going to make the secondary fire of the bryar pistol into a grappling hook. There isn't going to be that much finesse - the bryar is going to shoot some sort of projectile, and then the player is going to be pulled towards it. So you'll start by creating your patch folder in the JK directory and the cog and jkl resource subfolders.

Since we're going to be modifying the bryar pistol, we'll take its cog (weap_bryar.cog) out of the res2.gob and put it in our patch's cog folder. And since we'll need to make a template for the grappling hook, we'll also need to extract the static.jkl and put that in our JKL folder. Now we'll want to decide how we're going to do this. The simple way to do it is to have a pulse somewhere that makes the player move towards the grappling hook. We could have this code inside of weap_bryar, but that's already a weapon cog, and adding extra code to it would be messy. A cleaner approach would be to give the grappling hook a class cog. So that's what we'll do.

All weap_bryar needs to do is fire a new secondary projectile, so we'll need to add a new template variable for the projectile:

template     grappleTpl=+grapple     local 

Now we can skip down to the fire message. Right under the player assignment, we'll add:

 mode = GetSenderRef();

The sender of the fire message is the mode of fire - 0 for primary and 1 for secondary. Next there's some code that checks the player's health, and right under that is where we'll add our firing code:

 if(mode == 1)
 {
  FireProjectile(player, grappleTpl, fireSound, 8, '0 0 0', '0 0 0', 0, 0x0, 0, 0);
  SetPOVShake('0.0 -.003 0.0', '1.0 0.0 0.0', .05, 80.0);
  jkPlayPOVKey(player, povfireAnim, 1, 0x38);
  return;
 }

This code checks to make sure it's the secondary mode of fire and then fires the grappling hook. FireProjectile() is a do-it-all verb that creates the hook in the game, plays a sound, does the firing animation, and bunch of other things we don't need to use. The POV verbs will create an animation for the internal camera. After that, we'll need to return because we don't want to run the bryar's original firing code.

Now if we don't have any energy ammo, and we still want to use the bryar's grappling hook, we'll need to fix the autoselect message so that we can select a gun that has no ammo. So right under the player assignment in autoselect, we'll add this code:

 if(GetSenderRef() == -1)
 {
  ReturnEx(1);
  return;
 }

The autoselect message's sender tells you what type of query the autoselect handler is supposed to answer. A sender of -1 means the player is trying to mount the weapon - so the autoselect message is being asked if the player has the gun and ammo to use it. We don't really care if the player has either, so we're going to let the player select it regardless. The ReturnEx() verb is used to return a value to whatever sent the message.

So we're going to return a value of 1 meaning the weapon is selectable. After this we'll return so the original code won't run. And that's all you need to change in the bryar cog. Let's go ahead and create our weapon template. Open up the static.jkl and paste this text at the end:

Section: templates
World templates 2

_base          none         orient=(0/0/0) type=weapon collide=1 move=physics thingflags=0x400 timer=10 mass=5 
+grapple      _base       model3d=mana1.3do vel=(0/8/0) cog=class_grapple.cog typeflags=0x180

end
The first template in a jkl must be a base template because JK doesn't allow cogs to use the first template
 - the reason is unclear. So we'll make the first template a base for our +grapple template. When JK loads
 these templates, weap_bryar will be able to create grappling hooks in the game from them. For us, the
 most important part of this template is its class cog (class_grapple.cog) - which we'll write shortly. Now
 go up to the cogscripts section and add an entry for class_grapple.cog.

Section: cogscripts
World scripts 51
0: class_grapple.cog
end
Remember to up both the cogscripts and cogs counts. Now we're ready to start writing the cog that will do 
all of the work. First we'll think of the events that we'll need to act on. When the hook is created, we'll 
need to start our effect - so that's a message we'll add. A pulse is the easiest way to create a continuous 
effect, so we'll add that too for lack of a better idea. And to stop the effect, we'll add a removed message 
so that when the hook is taken out of the game, the effect will end.

Now that we have our events, we need to find out what objects we'll be working with. Since we're only moving the player to the hook, we have two things we know we'll need before starting with the code. Our symbols section should look like this:

symbols

message created
message pulse
message removed

thing  grapple  local
thing  player  local

end
Our created handler will have to define our two things and then start the pulse to make the player move. 
It should look something like this:

created:
 grapple=GetSenderRef();
 player=GetLocalPlayerThing();
 SetPulse(0.1);

return;
Our pulse handler will be the most complicated part of this patch. It's not something you can think of in its 
entirety before starting to write it. Basically, it needs to make the player move towards the hook. 
SetThingVel() can do this easily. But if we do this right away, the player will start moving towards the hook 
before it's landed in a wall somewhere. So we'll need to add a condition to check for the hook's speed. If its 
speed is low enough, then we can start moving the player. One thing that you should always remember 
when working with the player is that he can die at any time. If he dies when he's using his grappling hook, 
then he should let go and fall to the ground, so we'll have to add a condition for that too. Our pulse 
message should look something like this:

One thing that you should always remember when working with the player is that he can die at any time. If he dies when he's using his grappling hook, then he should let go and fall to the ground, so we'll have to add a condition for that too. Our pulse message should look something like this:

pulse:
	// if the player has died, stop the effect.
	if(GetThingHealth(player) < 1) { SetPulse(0); StopThing(player); return; }
	// if the grapple is hardly moving (speed is less than 0.1), then pull the player.
	if(VectorLen(GetThingVel(grapple)) < 0.1) SetThingVel(player, VectorSub(GetThingPos(grapple), GetThingPos(player)));

return;

If you're new to cog and looking through these coding examples, you're probably wondering how you're supposed to find the verbs and flags that make this work. There's really not one good way - you need to look at other people's examples, read references, and ask questions if you have to. Most of the time, you'll write out some code, and when you get to test it, you'll find something unexpected comes up that prevents your effect from working.

In this case, our player keeps attaching himself to the ground instead of flying up to where the hook landed. So we'll need to detach him from the ground if he's attached. We don't want to continually detach him, so we'll need to add a condition to make sure he has the 0x1 attachment flag which means he's attached to a surface (this correction is made in the completed cog).

And in the removed handler, we need to stop our effect. The hook is given a timer in its template - at the end of that timer, the hook will be taken out of the game and the removed message will be sent to its cogs. We also need to make sure that the hook that's being removed is the same one we're using. We don't care if an old hook is being removed - only the current one. Our removed handler might look like this:

removed:
 // make sure this is the current grapple.
 if(GetSenderRef() != grapple) return;

 // when the grapple is removed from the game,
 // stop the pulse and stop the player's movement.
 SetPulse(0);
 StopThing(player);

return;

And the whole cog should look like:

# class_grapple.cog
#
# Class cog for the grappling projectile.
#
# [ScreenName]
#==============================================================#
symbols

message	created
message	pulse
message	removed

thing		grapple		local
thing		player		local

end
#==============================================================#
code
#------------------------------------------------------
created:
	grapple=GetSenderRef();
	player=GetLocalPlayerThing();
	SetPulse(0.1);

return;
#------------------------------------------------------
pulse:
	// if the player has died, stop the effect.
	if(GetThingHealth(player) < 1) { SetPulse(0); StopThing(player); return; }
	// if the grapple is hardly moving (speed is less than 0.1), then pull the player.
	if(VectorLen(GetThingVel(grapple)) < 0.1)
	{
		if(GetThingAttachFlags(player) & 0x1) DetachThing(player);
		SetThingVel(player, VectorSub(GetThingPos(grapple), GetThingPos(player)));
	}

return;
#------------------------------------------------------
removed:
	// make sure this is the current grapple.
	if(GetSenderRef() != grapple) return;

	// when the grapple is removed from the game,
	// stop the pulse and stop the player's movement.
	SetPulse(0);
	StopThing(player);

return;
#------------------------------------------------------
end

And that's it for this patch. Use your shortcut to test it out in a game.

Review


In the chapter, you've learned the syntax of a cog file, and a little about how to use it. The symbols section is easy to understand, but the code section is so complicated that only a small part of it can be covered in a tutorial like this. Everything you need to know is in this reference, and once you're done with this tutorial, you should read through the reference to gain a broader understanding of the commands available to you.

The "Writing Cogs" tutorial is intended to teach you how to write a cog. But not by showing you what to write, it's supposed to teach you how to come up with a good design and implement it.

Chapter 2 is the end of the beginner's section of this tutorial. You can either go on to chapter 3 now, or you can try editing for a while first. Chapter 3 contains information you need to know, but until you've gained some experience, it might not make much sense.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: