|
|
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.
|