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.
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.
...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; endOkay...now that that is over with...
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.
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 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.
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.
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
GetLocalPlayerThing(); //this does nothing at all. kyle = GetLocalPlayerThing(); //this sets kyle equal to the local player.
symbols surface switch0 surface switch1easy, 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.
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
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(); )
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; endCool, huh? Imagine the possibilities now that you can figure out who did things.
A sender is who/what caused the message to be sent. Who pushed the switch, who entered the sector, who killed yun, etc
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
The parent of a thing is whoever fired the shot.
The parent can be used for a few messages, most notably damaged and killed .
//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.
The type is only used as a check. You still need to use GetSender or GetSource later on to reference your sender or source.
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; endIsn't that easy? Linkid's can be effectively used to group like references and to save you time.
Do not overuse IDs. Use them only if a GetSenderRef() check would be less efficient.
IDs are local to each cog
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; endSimple 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.
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();
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).
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 }
You can stop an extended timer by using KillTimerEx(id);
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; endAs 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.
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.
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.
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:
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).
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.
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.
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.
and a pretty swell HTMLer too.. Not to mention modest. |