CounterStrike clone
Created By: Koolio
eMail: koolio@mdqnet.net
Difficulty Scale: Hard


Alrighty then, this is the first tut in my series of 'How to make a mod' tuts. The goal of these tuts is to teach you a little something about QuakeC and show you how a mod is being made.
We'll be creating a CounterStrike clone. You'll learn how to make:
- A menu to select teams
- Team specific spawns
- An observer mode
- A new func_ brush for the bombzone
And more. But these four are the first four tuts in decending order.
I'll write these tuts as if you're a beginner at QC so you might find some things that make you think "DUH! That's easy", but not every newbee thinks it's easy.

Let's start by making our mod dir. Make a new directory in your Quake dir and call it CS-bomb. You can call it whatever you want this is what I called it. Next create a sub-dir in the newly created dir called src. Download the QuakeC source and a good compiler like FrikQCC (the best I ever used). Place the compiler and the source in the source dir and run FrikQCC. If all goes well you should end up with a progs.dat in the CS-bomb dir. Congratulations! You made your first "mod" (not that it changes anything but still...)

Now to move on to the interessting stuff. Download this zip and unzip it to your cs-bomb dir. These are the terrorist and counter terrorist models (NOTE: They only work in GL or WIN Quake), and a map. Open the file called defs.qc in your source dir. Scroll all the way to the end and add this:

.float menu;
float   MENU_OFF                = 0;
float   MENU_SELECT_TEAM       = 8; // Select your team
float   MENU_SELECT_EXIT        = 9; // Exits the menu
float		NO_TEAM			= 0;
float		TERROR_TEAM			= 1;
float		COUNTER_TEAM		= 2;
Okay, now to explain this: the float means that same value is global: the same for everybody in the game. When a float has a dot (.) in front of it, it means every player has his/her own value for it. Like .float health stores each players health, but float deathmatch stores the current deathmatch mode which of course should be the same for all players. The .float menu stores if you have your menu switched on or not. The other floats are used as substitutes for values. If you type the word TERROR_TEAM the compiler reads it as 1, this means you could also just write 1 but this makes it easier to read.
Another thing you might or might not recognize is the //. Two /'s next to each other is a comment, the compiler ignores everything on that line after the // so you can write some notes there like I did above. There's another method of commenting which works like so:
/*
This is a comment
*/
As you can see everything between the /* and the */ gets ignored no matter how many lines they make up. This is usefull if you need to comment out a lot of lines of code or have a big comment to go with your code. Now to make use of these. Make a new qc file. Just rename a .txt file to a .qc file. Call the file menu.qc. Now open it up and place the following in it:

float	modelindex_player, modelindex_eyes; //Stores your player model
// Got to float the above early

// MENU.QC
// Here are all the functions and subroutines for the menu system
// Done by Michl
// Modified for CS-Bomb tuts by Koolio, Koolio@mdqnet.net
// Started October 24th, 1999
// Bomb mission started August 17, 2000
void() MainMenu;
void() MenuSelect;
//You have to define these before they are being used. Because MenuToggle calls MainMenu() before it's being written

void() MenuToggle = //This turns the menu on and off
{
        if (self.menu == MENU_OFF) //If it's off
        {
                self.menu = MENU_SELECT_TEAM; //Turn it on and go to the proper menu page
                MainMenu ();
        }
else
        MenuSelect (); //Otherwise it's already on so use it to select stuff from the menu
};

//MainMenu() shows different pages depending on what you selected.
//You should be able to guess what each section does. That's why the MENU_'s etc come in handy.
//If I used a 1,2,3 system instead you'd have a pretty hard time to guess what this means

void() MainMenu =	//Koolio,The menu selection has an exit that is quite useless
			//Later on we'll change that to a random team select
{
if (self.menu == MENU_SELECT_TEAM)
        {
        self.menu = MENU_SELECT_TEAM;
        centerprint (self, ">Team Selection\n  Exit           ");
        return;
        }
if (self.menu == MENU_SELECT_EXIT)
        {
        self.menu = MENU_SELECT_EXIT;
        centerprint (self, " Team Selection\n >Exit           ");
        return;
        }
if (self.menu == TERROR_TEAM)
        {
centerprint (self, "         Counter-Terrorist\n>Terrorist");
        }
else if (self.menu == COUNTER_TEAM)
        {
centerprint (self, "        >Counter-Terrorist\n Terrorist");
        }
};

void() MenuSelect = //Selects the current menu item
{
        if (self.menu == MENU_SELECT_TEAM)
                {
                self.menu = COUNTER_TEAM;
                return;
                }
        if (self.menu == MENU_SELECT_EXIT)
                {
                self.menu = MENU_OFF;
                centerprint (self, "");
                return;
                }

        if (self.menu == TERROR_TEAM) //Makes you into a terrorist
                {
                self.team = self.menu; //Sets your team to the number of the menu, in this case it's 1
                bprint (self.netname); //Shows your name to everybody
                bprint (" is joining the Terrorist team\n"); //And a nice little text to show what team you're joining
                setmodel (self, "progs/terror.mdl"); //Turns you into a terrorist
                self.skin = 0; 			     //Default skin
                modelindex_player = self.modelindex; //This is a special trick I'll explain below
                setsize (self, '-16 -16 -24', '16 16 32'); //Sets size to standard player size, putclientinserver does this too but it doesn't hurt
                self.weaponmodel = "progs/v_shot.mdl"; //Sets your default weapon model (shotgun)
                self.menu = MENU_OFF;  			//turn the menu off
                centerprint (self, "");               //Remove the menu
                MainMenu ();					
                }
        else if (self.menu == COUNTER_TEAM)	 //Basically the same except it sets your team different and another model
                {
                self.team = self.menu;
                bprint (self.netname);
                bprint (" is joining the Counter-Terrorist team\n");
                setmodel (self, "progs/counter.mdl");
                self.skin = 0;
                modelindex_eyes = self.modelindex;
                setsize (self, '-16 -16 -24', '16 16 32');
                self.weaponmodel = "progs/v_shot.mdl";
                self.menu = MENU_OFF;
                centerprint (self, "");
                MainMenu ();
                }
        };

void() MenuUp = //Scrolls through the menu
{
        if (self.menu == MENU_OFF) //If the menu is off
                return;            //Leave the function
        else if (self.menu == MENU_SELECT_TEAM)
                self.menu = MENU_SELECT_EXIT;
        else if (self.menu == MENU_SELECT_EXIT)
                self.menu = MENU_SELECT_TEAM;
        else if (self.menu == COUNTER_TEAM)
                self.menu = TERROR_TEAM;
        else if (self.menu == TERROR_TEAM)
                self.menu = COUNTER_TEAM;
        MainMenu ();
};

void() MenuDown = //Same as menu up
{
        if (self.menu == MENU_OFF)
                return;
        else if (self.menu == MENU_SELECT_TEAM)
                self.menu = MENU_SELECT_EXIT;
        else if (self.menu == MENU_SELECT_EXIT)
                self.menu = MENU_SELECT_TEAM;
        else if (self.menu == COUNTER_TEAM)
                self.menu = TERROR_TEAM;
        else if (self.menu == TERROR_TEAM)
                self.menu = COUNTER_TEAM;
                MainMenu ();
};
The comments should pretty much explain this. One thing I'll have to explain is the modelindex_ trick I used. Normally modelindex_player and modelindex_eyes are used for switching between eyes (when you have the invicibility powerup) and the normal player. I used these for switching between player models instead. modelindex_eyes is for counters and modelindex_player is for terrors. However we need to disable something in client.qc to make this work. So open up client.qc and find this comment:
// oh, this is a hack!

Normally this sets your model to the eyes and sets modelindex_eyes to contain the path to the eyes model then it quickly sets your model to the player.mdl and does the same with modelindex_player. Comment out the following lines:

	setmodel (self, "progs/eyes.mdl");
	modelindex_eyes = self.modelindex;

	setmodel (self, "progs/player.mdl");
	modelindex_player = self.modelindex;
So they look like this:

	//setmodel (self, "progs/eyes.mdl");
	//modelindex_eyes = self.modelindex;

	//setmodel (self, "progs/player.mdl");
	//modelindex_player = self.modelindex;
The next thing we need to do is also in client.qc: Find this piece of code:

	if (self.view_ofs == '0 0 0')
		return;		// intermission or finale
After that add this:

	//Koolio, CT-Bomb tut, DotF menu code
	// Added by Michl (October 24, 1999)
        if (self.menu != 0)   // Quite simply, displays the menu (over and over)
                MainMenu ();  // if it is turned on.
Save and close client.qc and open up weapons.qc. Find ImpulseCommands() and after

	if (self.impulse == 12)
		CycleWeaponReverseCommand ();
Add this:

if (self.impulse == 20) // Begin Michl's menu code (October 24th, 1999)
                MenuToggle ();
        if (self.impulse == 21)
                MenuUp ();
        if (self.impulse == 22)
                MenuDown (); // End Michl's menu code (October 24th, 1999)
Last change, scroll all the way to the top to W_Precache and at the end add this:

	//Koolio, playermodel precaches
	precache_model ("progs/terror.mdl");	//The terrorist player
	precache_model ("progs/counter.mdl");	//The counter-terrorist player
This precaches the model so it can be used. Save and close.
A good idea would be to add aliases to your config:

alias menu "impulse 20"
alias menuup "impulse 21"
alias menudown "impulse 22"
Semi-final step: In client.qc find this set of code in CheckPowerups:

	// use the eyes
		self.frame = 0;
		self.modelindex = modelindex_eyes;
	}
	else
		self.modelindex = modelindex_player;	// don't use eyes
Replace with:

	//Koolio, disable this	
	// use the eyes
	//	self.frame = 0;
	//	self.modelindex = modelindex_eyes;
	} // <-- You still need the closing bracket
	//else
	//	self.modelindex = modelindex_player;	// don't use eyes
Last step: open up progs.src, which holds what .qc files to compile. Before subs.qc add:

cs-bomb/menu.qc
Compile the code and run it. Enjoy your new menu!

Most code by Michl taken from Duel of the Fates. Credit goes out to him I merely modified it to suit my needs,commented it and used my modelindex_ trick on it. The models are converted Q2 PPM's. I got them from Brambo to test my player.qc in Natas so I don't know the authors. Tutorial written by Koolio, koolio@mdqnet.net, and HTML-ized by Kryten, kryten@inside3d.com You can use this tut in your mod provided me and (in this case) Michl get credit.