Page 1 of 1

Tutorial: How to do DAT menu.DAT

Posted: Wed Jun 08, 2011 9:13 am
by behind_you
[speech]Modding for quake is great because you can do just about anything you want to. However, it's extremely difficult since about 99% everything you can do with quake these days is unknown. There are hardly any tutorials that point people on the right path and newbies are left to fend for themselves. Being a noob myself, I know all about these stuggles. That's why I am going to try to educate everyone about what little I know, just to make things easier. Of course, my techniques are nowhere near perfect, but if they were, what would there be to learn?[/speech]

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
This is a simple float that determines which menu you want to see. Add this after:

Code: Select all

float NONE = -1;
float OFFLINEMENU = 0;
	float SELECTDIFFICULTY = 0.1;
	float LOADGAME = 0.2;
float INGAMEMENU = 1;
	float SAVEGAME = 1.1;
These constant floats represent your menus. Notice how I tabbed some of them out to look like submenus. This simply makes life easier for you when you go to code when each menu will appear and when you will inevitably build on this. You could use their number values instead and get a migraine if you want.

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');
};
Using the selecttext function, three selectable text items have been made in your start menu. To explain how it works:

'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 :D.

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

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');

};
As you can see, these options use a new function I made called exectext. After selecting difficulty level, this will start the game with whatever difficulty level you chose. I also included a return option that returns you to the previous menu.

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');
};
This uses another function called cmdtext. It's exactly the same as exectext except it doesn't mess with any cvars. This ingame menu uses very basic saving and loading techniques. You can expand it as much as you want.

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();
Now the use of the constant floats is truly appreciated! This checks what menumode it is and draws the appropriate menu.

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!

Posted: Wed Jun 08, 2011 3:16 pm
by Baker
This is nice, I've never seen any documentation for menu.dat creation before.

Posted: Wed Jun 08, 2011 8:13 pm
by Feared
Nice indeed.

I'm not a fan of these free file hosting sites that make you wait 30 seconds before downloading your file that might be 20kb. These sites also usually delete files after not being downloaded for 30 days or so.

Here's a mirror,
http://bfeared.com/library/quake/dump/makemenuqc.rar

Posted: Thu Jun 09, 2011 1:03 am
by qbism
Now we need a programming tutorial to put menu.dat support in our engines! :)
EDIT: can start by (re) reading Baker's CSQC: Global Implementation Project

Posted: Mon Jun 13, 2011 6:22 am
by behind_you
Thanks a lot! And thanks for re uploading it, feared.

People really need to share their ideas and post tutorials to make life easier for the rest of us!

EDIT: Using your link for the tutorial Feared. Thanks again!

Re: Tutorial: How to do DAT menu.DAT

Posted: Fri Dec 16, 2011 1:48 am
by Blackstar1000
can someone make a youtube video of this thing in action?

Re: Tutorial: How to do DAT menu.DAT

Posted: Fri Dec 16, 2011 10:08 pm
by toneddu2000
actually compiler gave me some error compiling it (some const names not consistent) and even after fixing errors menu doesn't work correctly(menu always shows on screen,even after pressed esc key). So, maybe, first to do a video, someone should correct the code!

Re: Tutorial: How to do DAT menu.DAT

Posted: Sun Feb 19, 2012 4:38 pm
by toneddu2000
Ok I fixed it. Next week I'll post it. Now I need just one help. When I select a sub menu, the menu items of the preceding menu are still visible.
I made an example. If I create a hierarchy like this:
MAIN MENU
---- NEW GAME
-------- EASY
-------- MEDIUM
-------- HARD
---- OPTIONS
If i click on MAIN MENU it shows me NEW GAME string but under it, it also shows the NEW GAME string! So it becomes a mess!
I tried to use the clearscene function from CSQC but it doesn't work with menuqc (the compiler didn't return any errors but it gave me quakec error at runtime)
This is the (ugky I know) code I used to detected the menu clicked (I used behind_you code with some makeups)

Code: Select all

void() menuchoice
{

if (menumode == RETURNTOMAIN)
      {
	  drawstring(centerscreen - '0 30 0', "", '20 20 0', '0 0 1', 1, 0);
   
      drawstring(centerscreen - '0 50 0', "", '20 20 0', '0 0 1', 1, 0);

      drawstring(centerscreen - '0 70 0', "", '20 20 0', '0 0 1', 1, 0);

      drawstring(centerscreen - '0 90 0', "", '20 20 0', '0 0 1', 1, 0);
	  
	  drawstring(centerscreen - '0 110 0', "", '20 20 0', '0 0 1', 1, 0);
	  
	  drawstring(centerscreen - '0 130 0', "", '20 20 0', '0 0 1', 1, 0);
	 
	  mainmenu();
	  }
	  
if (menumode == SELECTDIFFICULTY)
      {
	  drawstring(centerscreen - '0 30 0', "", '20 20 0', '0 0 1', 1, 0);
   
      drawstring(centerscreen - '0 50 0', "", '20 20 0', '0 0 1', 1, 0);

      drawstring(centerscreen - '0 70 0', "", '20 20 0', '0 0 1', 1, 0);

      drawstring(centerscreen - '0 90 0', "", '20 20 0', '0 0 1', 1, 0);
	  skillselect();
	  }
	  
if (menumode == SAVEGAME)
      {
	  drawstring(centerscreen - '0 30 0', "", '20 20 0', '0 0 1', 1, 0);
   
      drawstring(centerscreen - '0 50 0', "", '20 20 0', '0 0 1', 1, 0);

      drawstring(centerscreen - '0 70 0', "", '20 20 0', '0 0 1', 1, 0);

      drawstring(centerscreen - '0 90 0', "", '20 20 0', '0 0 1', 1, 0);
	  skillselect();
	  }
	  
}
As you can see I even tried to fill blank strings in the same position of every item BEFORE the new menu it shows but they don't replace the old ones.
I just need a "sweeping function" to clear the screen
Thanks in advance

PS: just a side note: do you know how to use fonts (ttf files)
Putting these lines in default.cfg

Code: Select all

loadfont console gfx/fonts/arial.ttf
loadfont menu gfx/fonts/arial.ttf
con_textsize 10
it uses the Arial font (don't worry I know it's copyrighted - it was just a test! :) ) for the console but NOT for the menu

Re: Tutorial: How to do DAT menu.DAT

Posted: Wed Feb 29, 2012 2:00 pm
by toneddu2000
Any possible hint guys? I still can't display new items because old items are still present in the menu tree

Re: Tutorial: How to do DAT menu.DAT

Posted: Wed Mar 07, 2012 9:12 pm
by Ghost_Fang
Use "else if" when calling your menu types in m_draw,

Code: Select all

if (menumode == INGAMEMENU)
      ingamemenu();
  else if (menumode == SINGLEPLAYER)
      singleplayer();      
  else if (menumode == SELECTDIFFICULTY)
      selectdifficulty();
  else
	  mainmenu();
That fixed it for me

Re: Tutorial: How to do DAT menu.DAT

Posted: Thu Mar 08, 2012 8:31 am
by toneddu2000
Thanks a lot Ghost_Fang! I'll try as soon as I can!

Re: Tutorial: How to do DAT menu.DAT

Posted: Sun Mar 25, 2012 2:21 am
by JasonX
Any updates on the TTF font thing?

Re: Tutorial: How to do DAT menu.DAT

Posted: Sun Mar 25, 2012 4:56 pm
by toneddu2000
JasonX wrote:Any updates on the TTF font thing?
Unfortunately still not. Fonts are displayed only on the console menu but not in the game menu.
behind_new never replied so,without his permission, I can't upload the menu source code :(
my intent was to release it under MIT license, so everyone could use it even without releasing code changes

Re: Tutorial: How to do DAT menu.DAT

Posted: Tue Sep 11, 2012 12:16 am
by Mexicouger
How can you call the menu via an impulse in-game?