So, I managed to get my hands on a menu.dat source (thank you giffe and others) and started tinkering around with it. Creating and manipulating your own menus can be hard unless you know what you are doing. That's why I'm dedicating this tutorial to uncovering the secrets of menu.dat and how to make your own menu through nothing but QuakeC.
The Pros:
- Easily design your own menus
- Only needs some qc knowledge
The Cons:
- Menu is slow during gameplay
- Menu sometimes crashes when entering game, must be engine bug
What you need:
Download this file. It's the menuqc scratch I got and modified together along with the fteqcc compiler you will need.
OK let's get started. Extract the zip file to a separate folder. You will find the compiler 'fteqccgui.exe' along the the .qc and .qh files and the progs.dat source.
Open menu.qc first. The first thing you will see is the m_init(); function. This function is called when the menu is loaded (when you start the game). It contains stuff like returning the screensize for use and other things of the sort. I will explain the other codes found here as I go along.
Right below you will find the m_keyup(); and m_keydown(); functions. These functions detect mouse and keyboard input. So if you want to recieve what the user is typing, this is where to do it. Notice how I already put some code that will detect when the mouse button is clicked or released.
After this, you will find a float function (I think that's what it's called) called check_mouse. This is an extremely important function. It will return TRUE or FALSE depending on if your mouse is on a selectable menu item.
Head down to m_draw();. This is the code that draws what you will see. AKA the equivilant of csqc's CSQC_UpdateView(); Here we will do things like draw the mouse cursor and draw the menus.
The bottom has other menu functions. You do not need to worry about these things. m_shutdown(); is executed when you exit, so that may be of use to anyone(ie like writing cvars with FRIK_FILE).
OK. The files msys.qc and mbuiltin.qh contains a bunch of definitions for use. I've added my own at the bottom of these files.
Before beginning, I would like to mention that there are a lot of ways to do this. Some are better than my way. Some are worse. I'll illustrate the technique I use to get my menus to show up.
Now let's begin. First off, we must define our menus for use. So open msys.qh and scroll down to the bottom. Find the following to the bottom of msys.qh:
Code: Select all
float menumode; //which menu to show. menu options below
Code: Select all
float NONE = -1;
float OFFLINEMENU = 0;
float SELECTDIFFICULTY = 0.1;
float LOADGAME = 0.2;
float INGAMEMENU = 1;
float SAVEGAME = 1.1;
Now open mbuiltin.qh and near the bottom you will find a function I wrote for you called selecttext. You will be using this function to draw selectable text that will lead to another menu.
Alright now that you have some sort of picture on how your menu will look like. Create a new file and name it "mdraw.qc" (without quotes). Add this function:
Code: Select all
void() offlinemenu =
{
drawstring(centerscreen - '0 30 0', "Welcome to your new menu!", '20 20 0', '0 0 1', 1, 0);
selecttext (centerscreen, "New Game", SELECTDIFFICULTY, '8 8 0');
selecttext (centerscreen, "Load Game", LOADGAME, '8 8 0');
selecttext (centerscreen + '0 15 0', "Options", OPTIONS, '8 8 0');
cmdtext (centerscreen + '0 30 0', "Quit", "quit", '8 8 0');
};
'centerscreen' is the first parm of selecttext. It's the position the text will be written. notice how the other options add 15 y units to it, just to prevent overlapping.
The second parm is a string that represents how the text will look like.
The third parm is what menu you will be taken to when you click on the text. Notice how I used the constant floats I defined in the beginning. Told you it makes life easier .
Forth parm is the text size. Feel free to change BUT NEVER ASSIGN ANY Z VARIABLE! It's pointless. Text is 2D. Z variables don't affect your text and you will simply be spammed with warnings.
For an explanation of the selecttext function itself:
void(vector pos, string txt, float gomenu, vector textsize) selecttext =
{
local float charnum, talpha;
local vector txtsize;
charnum = strlen(txt); //gets how many characters (including spaces) are in the text
txtsize_x = textsize_x * charnum; //multiplies the size of each character by how many characters, effectively getting how big the whole string is
txtsize_y = textsize_y; //y stays the same
if (check_mouse(pos, txtsize)) //if your mouse is positioned on the text
{
talpha = 1; //brighten the text
if (soundonce != txt) //play the sound of your choice once
{
localsound("sound/menu/menu3.wav");
soundonce = txt;
}
if (mhit == 0) //if you click on the text while positioned on the text
{
menumode = gomenu; //head over to whatever menu specified
}
mhit = 1;
}
else
{
talpha = 0.75; //otherwise, make text less bright and do nothing
}
drawstring(pos, txt, textsize, '1 1 1', talpha, 0); //draw the text
};
Now our first menu is complete! Let's make another! This time we will make a menu that allows you to select the difficulty level you want to play. This beats the tacky Quake technique of choosing which teleport to go through to choose difficulty .
Add this to the end of mdraw.qc:
Code: Select all
void() selectdifficulty =
{
drawstring(centerscreen - '0 30 0', "Select Difficulty!", '20 20 0', '1 0 0', 1, 0);//Title
exectext (centerscreen, "Easy", "skill", "0", "map e1m1\n", '8 8 0');
exectext (centerscreen + '0 15 0', "Normal", "skill", "1", "map e1m1\n", '8 8 0');
exectext (centerscreen + '0 30 0', "Hard", "skill", "2", "map e1m1\n", '8 8 0');
exectext (centerscreen + '0 45 0', "Nightmare", "skill", "3", "map e1m1\n", '8 8 0');
selecttext (centerscreen + '0 60 0', "Return", OFFLINE, '8 8 0');
};
Now an explanation:
The first parm of exectext is the position. I change the y variable of some options just to keep from overlapping
Second parm is the text you will see.
Third parm is what cvar to change, in this case the "skill" cvar.
Forth parm is what to change the cvar's value to.
Fifth parm is what to type in the console after. In this case it's "map e1m1" which starts the map you want.
Last parm is text size. NO Z VALUE!!!!
The exectext function is pretty much the same as selecttext besides setting cvar and starting the map.
HAHA! We got the jist of it! Now we made a menu and made it select difficulty level and start the game! Nice!
One thing that pissed me off the most about Quake is how the menu was always the same no matter if you were starting the game, in the middle of the game, or even in a multiplayer game! Let's fix that!
Add this ingamemenu and savegame function at the bottom of mdraw.qc:
Code: Select all
void() ingamemenu =
{
drawstring(centerscreen - '0 30 0', "In the Game!", '20 20 0', '1 0 0', 1, 0);//Title
selecttext (centerscreen, "Save Game", SAVEGAME, '8 8 0');
selecttext (centerscreen + '0 15 0', "Load Game", LOADGAME, '8 8 0');
cmdtext (centerscreen + '0 30 0', "Quit", "quit", '8 8 0');
};
void() savemenu =
{
drawstring(centerscreen - '0 30 0', "Save it!", '20 20 0', '1 0 0', 1, 0);//Title
cmdtext (centerscreen, "Save Slot 1", "save s01", '8 8 0');
cmdtext (centerscreen + '0 15 0', "Save Slot 2", "save s02", '8 8 0');
cmdtext (centerscreen + '0 30 0', "Save Slot 3", "save s03", '8 8 0');
selecttext (centerscreen + '0 45 0', "Return", NONE, '8 8 0');
};
void() loadmenu =
{
drawstring(centerscreen - '0 30 0', "Load it!", '20 20 0', '1 0 0', 1, 0);//Title
cmdtext (centerscreen, "Load Slot 1", "load s01", '8 8 0');
cmdtext (centerscreen + '0 15 0', "Load Slot 2", "load s02", '8 8 0');
cmdtext (centerscreen + '0 30 0', "Load Slot 3", "load s03", '8 8 0');
selecttext (centerscreen + '0 45 0', "Return", NONE, '8 8 0');
};
You should get the jist of things now. Basic menus complete! Now to actually implement them!
Open menu.qc. Find the m_draw(); function. Add this before time = gettime():
Code: Select all
if (menumode == NONE)
{
if (clientstate() == CS_CONNECTED)
{
menumode = INGAME;
}
else
{
menumode = OFFLINE;
}
}
else if (menumode == INGAMEMENU)
ingamemenu();
else if (menumode == SELECTDIFFICULTY)
skillselect();
else if (menumode == SAVEGAME)
savemenu();
else if (menumode == LOADGAME)
loadmenu();
else
offlinemenu();
There you have it! Your own basic menu! Compile and place the menu.dat file inside your game directory! I plan on adding to this tutorial so look out!