Chapter 1: CogScript

So... you want to write a cog script, eh? This article will take you from the bleeding obvious basics to some of the most advanced aspects of the cog language. First of all, let's talk about cog itself.

Things you will need to write cog well:

1.) CogWriter and it's syntax checker.
2.) The JKSpecs, available @ darkjed.com. The specs will hold the answer to half of your questions. Look there before posting a question on a forum.
3.) JED to test your cogs.
4.) The cog board @ darkjedi.com to answer any specific questions that you may have.
5.) A lot of patience and determination.

Basic Rules: Cog Language

Cog Script is a language that was designed by the LEC programming team to handle effects in JK. By effects, I mean anything in the level that happens once it loads. Moving objects, sounds, weapons firing, force powers, goals, actor's.

Everything that changes is controlled by Cog. Cog itself is a very basic C-based language that is compiled at runtime. What does this mean to you?

Runtime compiled (as opposed to Design Time Compiled) means that you can simply type something, add it to a level, load jk, and test it. This feature allows editors to easily create new things for JK.

Cog works by reacting to messages that the engine creates. Messages can be anything from entering a sector, to killing a thing. The JK Specs have a list of all messages used in JK. Learn these messages, and than continue reading.

Cog accepts many data types. Read the JK specs again to see the data types.

Cog is also very volatile. This means that if you have the smallest error in your script, the code will not compile at all and JK will ignore it.

Cog is not case sensitive . For example, SYMbOls is the same as symbols.

Cog allows Commenting, or lines that the engine does not read. In the symbols section, the # key at the beginning of the line tells the engine not to read it. In the code section, the // keys do the same.

The symbols section of cog is sensitive to spaces. But the code section is not. So the symbols code must have spaces in between references. More on this later.

Cog Uses many operators, here are some of them:
Set something Equal to : =
Check for Equality: ==
Check for In-Equality: !=
Greater Than: >
Less than: <
Greater than or equal to: >=
Less than or equal to: <=
And: &&
or: ||

So lets sum it up; Cog controls everything moving (dynamic) in a level, and needs to be perfect or else it will not work. Okay, now to the basics.


Before we begin...

...we must continue the tradition of programming books. Copy this code and save it as a cog file. Then add it to your level via jed and run the level.


symbols

message startup

end

code

startup:

  Sleep(1);
  Print("Hello World!");
  return;
end
Okay...now that that is over with...

File Format

Okay, so we know how cog works, and that we need to have everything correct or else it will not work. Now we must explain syntax. Each cog file has two sections, the symbols and code sections.

Symbols

In laymans term's, the symbols section tells the code section what to start with, and the code section takes over from there, performing all of the operations.


Basic Rules: Symbols Section

Custom messages (not defined by the engine) need not be defined in the symbols code

Un-assigned variables are given random values. Make sure that any unassigned variables are not used until they are given a value. For example:


symbols

message startup

int  myint

end

code

startup:

PrintInt(myint);
//the result would be random every time, becuase the int has not been assigned. 
//on the other hand...
symbols

message startup

int  myint
surface mysurf
end

code

startup:

myint = GetWallCel(mysurf); //returns the current Cel of a surface
PrintInt(myint);
//this example would print either a 0,1,2... depending on the value of the surface.
Unused values cause problems. Don't leave a message around if it isn't used. Don't leave an un-used variable around either. Sloppy programming causes problems.

The symbols section is where everything used in the script needs to be referenced. Messages and DataTypes must be referenced here. Here is an example of a symbols section:


symbols  

#note: the spaces between each word are vital in the symbols code
message entered

thing  kyle
thing  rbf

sound  victory=win.wav

end
#tell the engine that the symbols code it done.

Code

The code section is where the grunt work of the script is handled. This is where you actually write code per se. What is code? Code is anything below the code section of your script that does something or returns something ('return' means to retrieve a value from the engine.). Here is an example:


code

  kyle = GetLocalPlayerThing();
	//this is code. the variable kyle has now been assigned to the engine's definition of the local player. The local player is the character playing the game...

	//another example
  rbf  = jkGetWeirdestThing();
	//This line is fake, but take a guess at what the value of rbf now is? Right, it is the weirdest thing in jk.


To put the nail in the coffin of this section; Cog is made up of two parts, each just as integral as the other. Don't assign un-used messages/variables, and never overbake brownies.



Functions

No, this isn't a recurring nightmare of Algebra class from high school, this type of function is easy to learn. Functions run cog. Nothing works without functions. Most JK functions are what I call utility functions , they simply do what you tell them to do. For example, MoveToFrame(thing, frame, speed) . This function does exactly what you tell it to. The other two types of functions are Getters/Setters. These types of functions simply retrieve info from the engine (getters), or set the info for the engine to use in the future (setters). Some examples of getters:


thing = GetLocalPlayerThing();

int = GetSourceRef();

int = GetNumFrames(thing);


...and the setters...

SetParam(int);

SetSectorThrust(sector, vector, speed);

SetActorExtraSpeed(thing, flex);
These functions should give you a feel for how the Getters/Setters work.


Function Parameters

Parawhat? Parameters are simply values passed with the function. Without parameters, the engine has no clue what is going on. For example:


SetSectorThrust(sector, vector, speed);
sector is parameter 0
vector is param(eter) 1
speed is param 2
Easy, right? I will go more in detail on this topic later on.


Function Syntax


Basic Rules: Function Syntax

Every function must end with a semicolon;

Every parameter must be seperated with a comma. (Note: I use a comma plus a space: SetSectorThrust(sector, vector, speed); It is easier to read)

Getter functions must have a result or else their purpose is extraneous. For example:


GetLocalPlayerThing();
//this does nothing at all.

kyle = GetLocalPlayerThing();

//this sets kyle equal to the local player.


Chapter 2: How cog works

ID's, Sources, Senders, Types , and Parents

Scenario: A newbie scripter wants to make something happen with a cog. They post a question on a forum, and somebody explains to them on how he/she must 'get the sender id', 'find the source' , or 'get the type' . But, the he/she doesn't know what these things are. Almost every new cog scripter runs into this dillema.
Well you need not look any farther than this section. Let's start with Senders:


Senders

By now I hope you understand when messages are sent from the engine. In this scenario, let's imagine a switch. Actually, lets make it two switches.
So we have two switches in the cog so far. Write the code for two switches:

symbols

surface  switch0

surface  switch1
easy, right? Good. Now, we want these switches to do two different things, but be in the same cog. This seems like a problem to some people, but it is rather easy to solve it.
First of all, lets add the message to tell when a switch is activated

symbols

message  activated

surface  switch0

surface  switch1

end

code

activated:
What next, you ask? Well let's do a brief re-call of the situation. Activated is called when something in the symbols code is pressed (via spacebar). So how can we tell which switch was pressed? In other words, who sent the message?

symbols

message  activated

surface  switch0

surface  switch1

sound	wav0

end

code

activated:

//here we are going to do a check

if(GetSenderRef() == switch0) {

//if the sender was switch0 , AKA if someone pressed switch0
	Print("switch0 was pressed");
	//print to the console
	SetWallCel(switch0, 1);
	//set the switch to it's activated position
	PlaySoundPos(wav0, SurfaceCenter(GetSenderRef()), 1, -1, -1, 0);
	//play wav0 from a postion (the center of the sender, or the center of switch0)
	//now, we close the brackets
	}

if(GetSenderRef() == switch1) {

	//if the sender was switch1 , AKA if someone pressed switch1
	Print("switch1 was pressed");
	//print to the console
	SetWallCel(switch1, 1);
	//set the switch to it's activated position
	PlaySoundPos(wav0, SurfaceCenter(GetSenderRef()), 1, -1, -1, 0);
	//play wav0 from a postion (the center of the sender, or the center of switch1)
	//now, we close the brackets
	}

Study this code until you understand what's going on with the GetSenderRef() check. It is pretty simple if you think along the lines of
"If ( if ) the sender (GetSenderRef() ) is equal to ( == ) the switch ( switch0) , continue ( { ). Other wise, skip the code inside the brackets ( } )


Basic Rules: Senders

A sender is whatever triggered the message. It can be a thing, surface, player, sector, etc.

Use GetSenderRef() to find the sender of a message. When using the function outside of an if{} statement, it is best to set it equal to an int ( int = GetSenderRef(); )


Sources

Alright. So you know how to find out what sent the message, but now you want to know who was the source of the message is.
The Source is what caused the message to be sent in the first place.
To continue the example with the switches, the source would be whoever pressed the switch. In singleplayer it is usually just kyle, but it can get hairy in multiplayer scripts.
Example time again. This time, let's check to see who entered a sector, and than let's do something to them.


symbols

message entered
#if someone enters a sector...

thing  player   local
#this is a local reference AKA you do not see this in your editor's window

sector	sec0
#the sector to be entered

int	status=0	local
#more on this later
end

code

entered:

	//okay so the message was called, and the sender has to be sec0 since nothing else has been initialized in the cog yet
	
	if(status != 0) return;
	//here we check to see if the sector has been entered yet.
	player = GetSourceRef();
	//easy, right? The player is the source of the message.
	status=1;
	//so nothing happens the sector is entered a second time.

	//Lets give the player some health
	HealThing(player, 40.0);
	return;
end
Cool, huh? Imagine the possibilities now that you can figure out who did things.


Basic Rules: Sources

A sender is who/what caused the message to be sent. Who pushed the switch, who entered the sector, who killed yun, etc

Parents

Scenario: kyle dies. You want to figure out who killed him. Well the logical answer is the guy holding the saber over his perished body. In a way, that is correct. But the engine does not think like you or I do. The engine is a bad soul. The engine thinks the murderer is the saber that the enemy is holding. In other words, a projectile. Who owns the projectile? The true murderer.
The Scenario via cog:

symbols

message killed
#when someone dies
message startup
#when the level loads

thing  kyle local
#the player

thing oj	local
#the murderer


int    bloodyglove	local
#what the murderer used, i know oj didn't use a glove, but it sounds cooler :)

end

code

startup:
//some quick handy work
kyle = GetLocalPlayerThing();
//this is an SP cog. MP guide may come at a later date
CaptureThing(kyle);
//this lets the engine send out killed messages
return;

killed:

	If(GetSenderRef()!=kyle) Return;
	//if kyle wasnt killed, return.
	
	bloodyglove = GetSourceRef();
	//the projectile, or saber.

	//so the source is ours, but not the actual killer...

	oj = GetThingParent(bloodyglove);

	//bingo, the glove fits, we have a killer!

	Print("oj killed kyle!");
	return;
end


Basic Rules: Parents

The parent of a thing is whoever fired the shot.

The parent can be used for a few messages, most notably damaged and killed .


Types

Scenario: Something is damaged, and you need to find out what type of thing it is. (by thing, i mean surface or thing). This is where we use GetSenderType() and GetSourceType()
These two functions are very effective.

//here is a snippet from infiltrate's main code:

damaged:
        type = GetParam(1);
	//what type of damage is it?

	killer = GetThingParent(GetSourceRef());
	//look at that, another practical use of parents! :)

        if ((GetSenderID() == 1) && 
            (BitTest(16,type)))
	//a test to make sure the saber did the damage
   {

		If(GetSenderType()==6)
       {
			
		//type of 6 is a surface. See JKSpecs
			surface = GetSenderRef();
			//surface code
			Return;
        }
  }
	If(type==16)
	//another test to make sure the saber did the damage
  {

	If(GetSenderType()==3)
	//if the sender's TYPE is 3 (a thing)
  {
	thing = GetSenderRef();
	//sender type of 3 is a thing.

	if(GetSenderRef()==PanelThing)
	{
		//code for the thing
	}

So, as you see from that example, the type of the sender (or source, for that matter) can be used to easily do different things to each situation. This may be a bit over your head right now, but it will make sense later on.


Basic Rules: Types

The type is only used as a check. You still need to use GetSender or GetSource later on to reference your sender or source.


IDs

Scenario, you have two switches and a door. Each switch opens a door, but when the door is pushed (activated: message is sent) you dont want the door to open. This could be a job for GetSenderRef() like we discussed above, but we can also use IDs.
IDs are simple declarations that tell the engine that a group of references are alike.
The Scenario in Cog:


symbols

message activated
#when the switches (or door) are pushed

surface  switch0  linkid=1
surface  switch1  linkid=1
#your switches are now grouped thanks to linkid

thing    door
#the door is not associated with the switches

end

code

activated:

//here we check for the ID

if(GetSenderID() == 1) {

	//if the ID of the sender is 1, in other terms, if one of the switches was pressed

	//open door code here

	}

	return;
end

Isn't that easy? Linkid's can be effectively used to group like references and to save you time.


Basic Rules: IDs

Do not overuse IDs. Use them only if a GetSenderRef() check would be less efficient.

IDs are local to each cog


Timers

Scenario: You have a cogscript that does something to the player, the level, or something else. After a predetermined amount of time, you want these changes to change back to the way they used to be.
Scenario via cog: What we need to do here is set up a timer message. Timer's will wait an amount of time, than activate the message.

symbols

message activated
message timer

thing player local

thing console

flex waittime=4.0

end

code

activated:

	if(GetSenderRef() != console) return;
	player = GetSourceRef();
	//you should know what this does. if you do not, go back and re-read the sender/source sections
	
	SetThingFlags(player, 4);
	//magseals the player. Energy bounces off him.
	
	SetTimer(waittime);
	//in 4 seconds, the timer: message will be called
	return;

timer:

	//4 seconds have passed

	ClearThingFlags(player, 4);
	//unmagseal the player.
	return;

end 
Simple stuff. Timer's can get complex, and you eventually will find need to use multiple timers in one cog. When you need to use more than one timer, you need to set up the cog for Extended Timers.


Basic Rules: Timers

The timer parameter is in seconds, not MS.

The timer will only be called once.

To stop a timer during it's countdown, use KillTimer();


Extended Timers

So SetTimer(); is easy, but so is the situation. Let's be practical. If you are attempting to write a script that involves many moving parts (parts that also move back to their original positions), you are going to need one timer for each part. This is where SetTimerEx() comes into play.
SetTimerEx(); Will set an extended timer message. Go read the specs of it @ the JKSpecs, and come back to this article.

Done? Good.
Scenario: Two Doors in one cog. Each door goes up a frame, and than goes down after a certain amount of time.
Scenario via cog:



symbols

message activated
message timer

thing  door0

thing  door1

flex   sleeptime=3.0

end

code

activated:

	If(GetSenderRef() == door0) {

	SetTimerEx(sleeptime, 0707, 0, 0);
	MoveToFrame(door0, 1, 1);

	// Set The Timer for sleeptime, ID 0707, with two null parameters. 
	// dont worry about the paramters yet.
	}

	else {

	SetTimerEx(sleeptime, 0708, 0, 0);
	MoveToFrame(door1, 1, 1);
	//different id, becuase it is a different door
	}
	return;

timer:

	if(GetSenderID() == 0707) {
	
	//move the door back down

	MoveToFrame(door0, 0, 1);
	}


	if(GetSenderID() == 0708) {
	
	//move the door back down

	MoveToFrame(door1, 0, 1);
	}


Okay, a lot of things are going on here. When we set the extended timer, we need to include 4 parameters (0, 1, 2, 3).
Param 0 is the sleeptime. Just like in good ole' SetTimer();
Param 1 is the ID. The id is checked in the timer: code with GetSenderID().
Param 2 is a spot for you to pass your own values with. For example, you can pass the player who opened the door if you please. If you do not want to pass a value, you must put a value of 0 there.
Param 3 is just like param2.

This cog also uses the If Else combo in the activated: code. Let me Explain.

Since we have two things in the cog which need to be activated, we can save some time by using If Else. The If statement checks it for door0. if it was not door0, that it must be door1. That is where the Else comes in.



if(test on two values) {

 // execute code if it passes the test

 }

else (if it didnt pass the if test, execute this code) {

 //code

 }

Basic Rules: Extended Timers

You can stop an extended timer by using KillTimerEx(id);


The Pulse

Pulses are often confused with Timers, but they are very different and are used for different purposes.
Timers do not repeat the call to the message, however, pulses do.

Scenario: A pulsing sector light.
Scenario via cog:


symbols

message startup

sector sec0

int status=-1	local

flex pulserate=0.1

int minlight=0
int maxlight=1
int pulseval

end

code

startup:

	status=1;
	//tell the engine that we are at level 1.

	SetPulse(pulserate);
	//set the pulse message to be called. it will be repeatedly called

	return;

pulse:

	if(status == 1) {

	SetSectorLight(sec0, minlight, pulserate);
	//set sec0 to minlight, at the fade rate of pulsrate
	status=0;
	//change the value. this way the next time pulse is called we set adifferent light value.
	}

	else if (status == 0) {

	SetSectorLight(sec0, maxlight, pulserate);

	status=1;
	//next pulse we want to call the other light value
	}

	return;
end
As you see here, the pulse can be used to acheive the looping effect. This example is simple (used by LEC), but it describes the principle very well.

Basic Rules: The Pulse

You can stop a pulse by using: SetPulse(0);

Pulses have been known to eat of system resources (especially during MP games). Don't say i didn't warn you.


Chapter 3: Ideas that you need to know


Outputting Text and Debugging

Outputting text via cog is a simple and easy to use editing tool. It creates the little yellow lines you see on the top of your screen. There are two types of text to output. Debug text and level text. Debug text is often mistakenly used by authors (myself included) who are too lazy to set up the level text.
Open up the cog output specs please.
Debug Text includes functions such as Print("text"):, PrintInt(int);, etc

Level text is everything else.
Why is debug text bad to use in levels? Becuase when system resources get low, they are one of the first things that go. For example, your message may not print at all on some low end machines if you use debug text.


Debug Text

Insert a print("text"); command in your cog file to see if a part of the code is being executed. for example, if you think your cog is not working fully becuase of a faulty if statement, you would put the print code inside of the brackets.

example:




if(sometest) {

 //questionable if statement.

  print("if i can see this, than the code is being executed");
  //code

}

If you do not see the text outputted, than you know one of two things:
a.) There is a syntax error and your cog isn't starting up. In that case, insert a print in the startup message
b.) The if statement is not true for the selected test.


Setting up startup debug's
If your cog is suspect of being faulty, first you need to set up a simple check to see if it is starting up.



message startup

[...]

startup:

   sleep(1);
	//sleep the cog for a bit, so the level can load

   print("Hello.");
This is the easiest way to see if your cog is starting up. If you do not get any text, make sure the syntax is correct (via cogwriter).
If the syntax is correct, you have to go up one phase of debugging.

Basic Rules: Debugging/Debug Text

No variables can have the name of a reference type (ie you cannot call a sector 'sector').

You cannot name variables system names: goto, call, etc.

If your cog prints MessageEx errors, you need to make sure that the flags=someflag line is typed correctly.

If your level freezes when you try a new cog, take note of what was going on when the system froze. If it happened when someone died, and the cog has a killed: message, you have your smoking gun.

Level Text

Level text is used to display text in a level. This text can be made before startup (via cogstrings.uni) or during the level's progression (a la CTF scoring). Let's tackle the first topic first.

Unicode is a simple way for you to set up level messages. Jed has an episode editor which makes it much easier to set up. Learn how to use it.
jkPrintUNIString(destination, Flexmsg);
For SP or Local Scripts, I usually put a reference to the localplayer for destination. The flex is the number of the unistring.

Dynamically setting up strings is not difficult at all. Let's take a peak at CTF's method:


	jkStringClear();
	//clear the current string, so we can make a new one.

         jkStringConcatUNIString(msgbase + 5);
	//add a unistring to the string
	//msgbase is a way to easily change int values. For isntance
	//if msg base is 1000, the above string is 1005. The base for
	//the next level may be 2000, allowing the abiblity to have a different string
	//without modifying the code.

         jkStringConcatSpace();
	//concanate (add) a space to the string	

         jkStringConcatInt(score_limit);
	//add an integer to the string.

     

         jkStringOutput(-3, -1);
	//display the string to everybody in the game.
This is a very simple method of printing out events that can be changed at any time. Study this method if you want to dynamiclly set up a string, it works wonders.

Basic Rules: Level Text

If the text doesn't print, first check to make sure the correct unicode number is referenced.

That's it for now. I now suggest that you delve deep into the Cog Specs and learn as many functions as you can, best of luck and feel free to drop me an email.

Pele thinks that Entropy is one hell of a guy
and a pretty swell HTMLer too..
Not to mention modest.

Back to D A R K N E S S   F A L L S