How To Cog - Chapter 2.8 - Writing Cogs

This section will try to explain a good method for scripting the effects that you want to put in a game. You'll read about three example scenarios, and see how to write the cogs they require.

Your Programs

Choose a text-editing tool. There are several programs designed specifically for writing cogs which can be on the web.

Whatever you use, you will want to look for features like syntax highlighting and syntax checking. Tools 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. (Parsec is a decent syntax checker for Jedi Knight cogs.)

Another thing you'll want is an advanced find & replace tool, especially one that allows regular expressions (which allows search & replace with tabs, line breaks, wildcards, etc). MDIs (multiple document interfaces) are very useful for working on more than one file at a time.

Your References

The only full-scale references [as of 14-Apr-2006] for JK are the JKSpecs and the DataMaster. The JKSpecs is an earlier reference that was created to cover all aspects of JK editing. Although a useful reference, it is a bit out of date and lacks content. The DataMaster is the Cog reference. It contains just about everything you want or need to know for cogging.

Those references are not being maintained [as of 14-Apr-2006]. The info they contain are being placed on the JK Editing Hub website. [As of 14-Apr-2006, most of the information in the DataMaster has been placed on the website.]

First Example Cog

For this example, you need to have a mod folder inside your JK folder 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, you're going to let the player walk on any surface. This was already done in 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 this takes only a few lines of code. Don't be one of those people.

Outline

Now that you know what you want, you need to figure out how you'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 you need a simple hotkey cog that you'll create from scratch. With any new cog, it's 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 add a cog file to the Windows new file list so you can use Explorer's File->New menu.

Writing

For this example, the cog will be written first and later JK will be made to use it. Your template looks like this:

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



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



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

The first thing you're going to fill out is the header. The cog will be saved as hotkey_walk.cog, so this is what you'll put as the cog's name on the first line. Then on the third line, you'll write out the cog's purpose - 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 you'll write down the variables you're going to use. Most of the time, you won't know what variables you need until you get to the code section, but you can write the ones you know you need before going to the code. Because this is a hotkey cog that will use the activated and deactivated messages, you'll add those. You'll also add a variable named player because you know from your intent you're going to be working with the player.

For now, that's it for the symbols. You'll go on to the code section and add the message handlers. You won't write anything in them yet, because you should have all the basic things written out first. The 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 you have a way to start and stop the effect with a script, you need to find out how you want to create the effect. 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 on your own, you will need to look through your references to find a solution. There's also nothing wrong with looking at someone else's code to figure out how what you need can be done.

The Physics Flags that you'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. You'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 you're going to add 0x10 and 0x80 (0x90) to make the player attach to wall surfaces and align himself to them (flags are used to contain many boolean values and covered in Chapter 3).

The first line will go in the activated handler. Here you'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 you'll add the 0x90 Physics Flags and remove (clear) the 0x800 flag. Since you haven't run the cog yet, you'll put a Print() command in the message so that if you don't see anything happen but you see the printed text, you'll know the cog did run but your code was wrong.

The deactivated handler will do the opposite of activated. You'll set the flags you cleared, and clear the flags you set. Player does not have to be redefined because the cog will store the value of a variable for other message handlers. You'll also add a print to the deactivated handler so that you'll know when the effect has ended. Your 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, you'll save your cog as hotkey_walk.cog in the cog folder. Now you're going modify some of JK's resource files to make JK treat this cog as a hotkey cog. You'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

Your bin is named f_speed because in order to make use of the deactivated message in the cog, you must make your bin an inventory item's bin. You can do this by adding the 0x002 flag to the bin, but you also need an icon to go along with the bin. Instead of going through the trouble of creating a new icon file, you can use the name of a bin that you know has an icon - such as f_speed. 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 Cog

This example will deal with making a new level cog. Say that you want a goal to be completed when a boss is killed. You'll need your 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 you'll need to listen for the startup and killed messages of the boss. Because goals are part of the player's inventory system, you'll need a player variable. You'll need to have the boss variable left open for the level to define because the boss is "placed" in the level. For a goal completetion sound, you'll add a variable named doneSnd. Your 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, you're going to initialize your goal. First, you'll define the player, then tell 99 her offset number is 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, you'll set the goal's flag to 0x2 to make it completed, and you'll play a sound so that you'll be able to tell you completed the objective without looking it up. Your 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 your cog. Now you'll work on setting up the level to use it. You should already 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. The extra line at the end of the file is important - don't leave it out. The jkl's name must be theboss.jkl. If you change the jkl name, change "THEBOSS" to whatever the new name is. Now that you have the cog and the goal text set up, you 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, you're going to pass some arguments to the cog you 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, you're giving a value of 1 (if the boss is not thing 1, replace the 1 with his thing number) 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 you'll get the boss' killed message. 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.

To get you familiar with the real workings of JKLs, 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.

Third Example Cog

In this example, you'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 add the cog and jkl resource subfolders.

Since you're going to be modifying the bryar pistol, you'll take its cog (weap_bryar.cog) out of the res2.gob and put it in your patch's cog folder. And since you'll need to make a template for the grappling hook, you'll also need to extract the static.jkl and put that in your JKL folder. You'll have to decide how you'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. You 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 you decide.

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

  template     grappleTpl=+grapple     local	

Now skip down to the fire message. Right under the player assignment, 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 you'll add your 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 you don't need to use. The POV verbs will create an animation for the internal camera. After that, you'll need to return because you don't want to run the bryar's original firing code.

Now if you still want to use the bryar's grappling hook when you have no energy ammo, you'll need to fix the autoselect message so that you can select a gun that has no ammo. So right under the player assignment in autoselect, 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. You don't really care if the player has either, so you're going to let the player select it regardless. The ReturnEx() verb is used to return a value to whatever sent the message.

So you're going to return a value of 1 meaning the weapon is selectable. After this you'll return so the original code won't run. And that's all you need to change in the bryar cog. Now it's time to create your 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 you'll make the first template a base for your +grapple template. When JK loads these templates, weap_bryar will be able to create grappling hooks in the game from them. The most important part of this template is its class cog (class_grapple.cog) - which you'll write shortly. Go up to the cogscripts section and add an entry for class_grapple.cog.

Section: cogscripts
World scripts 51
0: class_grapple.cog
end

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

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

symbols

message	created
message	pulse
message	removed

thing		grapple		local
thing		player		local

end

Your created handler will have to define your 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;

Your 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 you do this right away, the player will start moving towards the hook before it's landed in a wall somewhere. So you'll need to add a condition to check for the hook's speed. If its speed is low enough, then you 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 you'll have to add a condition for that too. Your 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. 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, your player keeps attaching himself to the ground instead of flying up to where the hook landed. So you'll need to detach him from the ground if he's attached. You don't want to continually detach him, so you'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, you need to stop your 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. Ypu also need to make sure that the hook that's being removed is the same one you're using. Ypu don't care if an old hook is being removed - only the current one. Your 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 mod. Use your shortcut to test it out in a game.


Previous: Arrays Up: How To Cog Next: Review
  • Create:
This page was last modified 16:11, 14 April 2006.   This page has been accessed 2,409 times.