/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #include "ui_local.h" #define MODEL_BACK0 "menu/art/back_0" #define MODEL_BACK1 "menu/art/back_1" #define MODEL_SELECT "menu/art/opponents_select" #define MODEL_SELECTED "menu/art/opponents_selected" #define MODEL_FRAMEL "menu/art/frame1_l" #define MODEL_FRAMER "menu/art/frame1_r" #define MODEL_PORTS "menu/art/player_models_ports" #define MODEL_ARROWS "menu/art/gs_arrows_0" #define MODEL_ARROWSL "menu/art/gs_arrows_l" #define MODEL_ARROWSR "menu/art/gs_arrows_r" #define LOW_MEMORY (5 * 1024 * 1024) static char* playermodel_artlist[] = { MODEL_BACK0, MODEL_BACK1, MODEL_SELECT, MODEL_SELECTED, MODEL_FRAMEL, MODEL_FRAMER, MODEL_PORTS, MODEL_ARROWS, MODEL_ARROWSL, MODEL_ARROWSR, NULL }; #define PLAYERGRID_COLS 4 #define PLAYERGRID_ROWS 4 #define MAX_MODELSPERPAGE (PLAYERGRID_ROWS*PLAYERGRID_COLS) #define MAX_PLAYERMODELS 256 #define ID_PLAYERPIC0 0 #define ID_PLAYERPIC1 1 #define ID_PLAYERPIC2 2 #define ID_PLAYERPIC3 3 #define ID_PLAYERPIC4 4 #define ID_PLAYERPIC5 5 #define ID_PLAYERPIC6 6 #define ID_PLAYERPIC7 7 #define ID_PLAYERPIC8 8 #define ID_PLAYERPIC9 9 #define ID_PLAYERPIC10 10 #define ID_PLAYERPIC11 11 #define ID_PLAYERPIC12 12 #define ID_PLAYERPIC13 13 #define ID_PLAYERPIC14 14 #define ID_PLAYERPIC15 15 #define ID_PREVPAGE 100 #define ID_NEXTPAGE 101 #define ID_BACK 102 typedef struct { menuframework_s menu; menubitmap_s pics[MAX_MODELSPERPAGE]; menubitmap_s picbuttons[MAX_MODELSPERPAGE]; menubitmap_s framel; menubitmap_s framer; menubitmap_s ports; menutext_s banner; menubitmap_s back; menubitmap_s player; menubitmap_s arrows; menubitmap_s left; menubitmap_s right; menutext_s modelname; menutext_s skinname; menutext_s playername; playerInfo_t playerinfo; int nummodels; char modelnames[MAX_PLAYERMODELS][128]; int modelpage; int numpages; char modelskin[64]; int selectedmodel; } playermodel_t; static playermodel_t s_playermodel; /* ================= PlayerModel_UpdateGrid ================= */ static void PlayerModel_UpdateGrid( void ) { int i; int j; j = s_playermodel.modelpage * MAX_MODELSPERPAGE; for (i=0; i<PLAYERGRID_ROWS*PLAYERGRID_COLS; i++,j++) { if (j < s_playermodel.nummodels) { // model/skin portrait s_playermodel.pics[i].generic.name = s_playermodel.modelnames[j]; s_playermodel.picbuttons[i].generic.flags &= ~QMF_INACTIVE; } else { // dead slot s_playermodel.pics[i].generic.name = NULL; s_playermodel.picbuttons[i].generic.flags |= QMF_INACTIVE; } s_playermodel.pics[i].generic.flags &= ~QMF_HIGHLIGHT; s_playermodel.pics[i].shader = 0; s_playermodel.picbuttons[i].generic.flags |= QMF_PULSEIFFOCUS; } if (s_playermodel.selectedmodel/MAX_MODELSPERPAGE == s_playermodel.modelpage) { // set selected model i = s_playermodel.selectedmodel % MAX_MODELSPERPAGE; s_playermodel.pics[i].generic.flags |= QMF_HIGHLIGHT; s_playermodel.picbuttons[i].generic.flags &= ~QMF_PULSEIFFOCUS; } if (s_playermodel.numpages > 1) { if (s_playermodel.modelpage > 0) s_playermodel.left.generic.flags &= ~QMF_INACTIVE; else s_playermodel.left.generic.flags |= QMF_INACTIVE; if (s_playermodel.modelpage < s_playermodel.numpages-1) s_playermodel.right.generic.flags &= ~QMF_INACTIVE; else s_playermodel.right.generic.flags |= QMF_INACTIVE; } else { // hide left/right markers s_playermodel.left.generic.flags |= QMF_INACTIVE; s_playermodel.right.generic.flags |= QMF_INACTIVE; } } /* ================= PlayerModel_UpdateModel ================= */ static void PlayerModel_UpdateModel( void ) { vec3_t viewangles; vec3_t moveangles; memset( &s_playermodel.playerinfo, 0, sizeof(playerInfo_t) ); viewangles[YAW] = 180 - 30; viewangles[PITCH] = 0; viewangles[ROLL] = 0; VectorClear( moveangles ); UI_PlayerInfo_SetModel( &s_playermodel.playerinfo, s_playermodel.modelskin ); UI_PlayerInfo_SetInfo( &s_playermodel.playerinfo, LEGS_IDLE, TORSO_STAND, viewangles, moveangles, WP_MACHINEGUN, qfalse ); } /* ================= PlayerModel_SaveChanges ================= */ static void PlayerModel_SaveChanges( void ) { trap_Cvar_Set( "model", s_playermodel.modelskin ); trap_Cvar_Set( "headmodel", s_playermodel.modelskin ); trap_Cvar_Set( "team_model", s_playermodel.modelskin ); trap_Cvar_Set( "team_headmodel", s_playermodel.modelskin ); } /* ================= PlayerModel_MenuEvent ================= */ static void PlayerModel_MenuEvent( void* ptr, int event ) { if (event != QM_ACTIVATED) return; switch (((menucommon_s*)ptr)->id) { case ID_PREVPAGE: if (s_playermodel.modelpage > 0) { s_playermodel.modelpage--; PlayerModel_UpdateGrid(); } break; case ID_NEXTPAGE: if (s_playermodel.modelpage < s_playermodel.numpages-1) { s_playermodel.modelpage++; PlayerModel_UpdateGrid(); } break; case ID_BACK: PlayerModel_SaveChanges(); UI_PopMenu(); break; } } /* ================= PlayerModel_MenuKey ================= */ static sfxHandle_t PlayerModel_MenuKey( int key ) { menucommon_s* m; int picnum; switch (key) { case K_KP_LEFTARROW: case K_LEFTARROW: m = Menu_ItemAtCursor(&s_playermodel.menu); picnum = m->id - ID_PLAYERPIC0; if (picnum >= 0 && picnum <= 15) { if (picnum > 0) { Menu_SetCursor(&s_playermodel.menu,s_playermodel.menu.cursor-1); return (menu_move_sound); } else if (s_playermodel.modelpage > 0) { s_playermodel.modelpage--; Menu_SetCursor(&s_playermodel.menu,s_playermodel.menu.cursor+15); PlayerModel_UpdateGrid(); return (menu_move_sound); } else return (menu_buzz_sound); } break; case K_KP_RIGHTARROW: case K_RIGHTARROW: m = Menu_ItemAtCursor(&s_playermodel.menu); picnum = m->id - ID_PLAYERPIC0; if (picnum >= 0 && picnum <= 15) { if ((picnum < 15) && (s_playermodel.modelpage*MAX_MODELSPERPAGE + picnum+1 < s_playermodel.nummodels)) { Menu_SetCursor(&s_playermodel.menu,s_playermodel.menu.cursor+1); return (menu_move_sound); } else if ((picnum == 15) && (s_playermodel.modelpage < s_playermodel.numpages-1)) { s_playermodel.modelpage++; Menu_SetCursor(&s_playermodel.menu,s_playermodel.menu.cursor-15); PlayerModel_UpdateGrid(); return (menu_move_sound); } else return (menu_buzz_sound); } break; case K_MOUSE2: case K_ESCAPE: PlayerModel_SaveChanges(); break; } return ( Menu_DefaultKey( &s_playermodel.menu, key ) ); } /* ================= PlayerModel_PicEvent ================= */ static void PlayerModel_PicEvent( void* ptr, int event ) { int modelnum; int maxlen; char* buffptr; char* pdest; int i; if (event != QM_ACTIVATED) return; for (i=0; i<PLAYERGRID_ROWS*PLAYERGRID_COLS; i++) { // reset s_playermodel.pics[i].generic.flags &= ~QMF_HIGHLIGHT; s_playermodel.picbuttons[i].generic.flags |= QMF_PULSEIFFOCUS; } // set selected i = ((menucommon_s*)ptr)->id - ID_PLAYERPIC0; s_playermodel.pics[i].generic.flags |= QMF_HIGHLIGHT; s_playermodel.picbuttons[i].generic.flags &= ~QMF_PULSEIFFOCUS; // get model and strip icon_ modelnum = s_playermodel.modelpage*MAX_MODELSPERPAGE + i; buffptr = s_playermodel.modelnames[modelnum] + strlen("models/players/"); pdest = strstr(buffptr,"icon_"); if (pdest) { // track the whole model/skin name Q_strncpyz(s_playermodel.modelskin,buffptr,pdest-buffptr+1); strcat(s_playermodel.modelskin,pdest + 5); // seperate the model name maxlen = pdest-buffptr; if (maxlen > 16) maxlen = 16; Q_strncpyz( s_playermodel.modelname.string, buffptr, maxlen ); Q_strupr( s_playermodel.modelname.string ); // seperate the skin name maxlen = strlen(pdest+5)+1; if (maxlen > 16) maxlen = 16; Q_strncpyz( s_playermodel.skinname.string, pdest+5, maxlen ); Q_strupr( s_playermodel.skinname.string ); s_playermodel.selectedmodel = modelnum; if( trap_MemoryRemaining() > LOW_MEMORY ) { PlayerModel_UpdateModel(); } } } /* ================= PlayerModel_DrawPlayer ================= */ static void PlayerModel_DrawPlayer( void *self ) { menubitmap_s* b; b = (menubitmap_s*) self; if( trap_MemoryRemaining() <= LOW_MEMORY ) { UI_DrawProportionalString( b->generic.x, b->generic.y + b->height / 2, "LOW MEMORY", UI_LEFT, color_red ); return; } UI_DrawPlayer( b->generic.x, b->generic.y, b->width, b->height, &s_playermodel.playerinfo, uis.realtime/2 ); } /* ================= PlayerModel_BuildList ================= */ static void PlayerModel_BuildList( void ) { int numdirs; int numfiles; char dirlist[2048]; char filelist[2048]; char skinname[64]; char* dirptr; char* fileptr; int i; int j; int dirlen; int filelen; qboolean precache; precache = trap_Cvar_VariableValue("com_buildscript"); s_playermodel.modelpage = 0; s_playermodel.nummodels = 0; // iterate directory of all player models numdirs = trap_FS_GetFileList("models/players", "/", dirlist, 2048 ); dirptr = dirlist; for (i=0; i<numdirs && s_playermodel.nummodels < MAX_PLAYERMODELS; i++,dirptr+=dirlen+1) { dirlen = strlen(dirptr); if (dirlen && dirptr[dirlen-1]=='/') dirptr[dirlen-1]='\0'; if (!strcmp(dirptr,".") || !strcmp(dirptr,"..")) continue; // iterate all skin files in directory numfiles = trap_FS_GetFileList( va("models/players/%s",dirptr), "tga", filelist, 2048 ); fileptr = filelist; for (j=0; j<numfiles && s_playermodel.nummodels < MAX_PLAYERMODELS;j++,fileptr+=filelen+1) { filelen = strlen(fileptr); COM_StripExtension(fileptr,skinname); // look for icon_???? if (!Q_stricmpn(skinname,"icon_",5)) { Com_sprintf( s_playermodel.modelnames[s_playermodel.nummodels++], sizeof( s_playermodel.modelnames[s_playermodel.nummodels] ), "models/players/%s/%s", dirptr, skinname ); //if (s_playermodel.nummodels >= MAX_PLAYERMODELS) // return; } if( precache ) { trap_S_RegisterSound( va( "sound/player/announce/%s_wins.wav", skinname), qfalse ); } } } //APSFIXME - Degenerate no models case s_playermodel.numpages = s_playermodel.nummodels/MAX_MODELSPERPAGE; if (s_playermodel.nummodels % MAX_MODELSPERPAGE) s_playermodel.numpages++; } /* ================= PlayerModel_SetMenuItems ================= */ static void PlayerModel_SetMenuItems( void ) { int i; int maxlen; char modelskin[64]; char* buffptr; char* pdest; // name trap_Cvar_VariableStringBuffer( "name", s_playermodel.playername.string, 16 ); Q_CleanStr( s_playermodel.playername.string ); // model trap_Cvar_VariableStringBuffer( "model", s_playermodel.modelskin, 64 ); // find model in our list for (i=0; i<s_playermodel.nummodels; i++) { // strip icon_ buffptr = s_playermodel.modelnames[i] + strlen("models/players/"); pdest = strstr(buffptr,"icon_"); if (pdest) { Q_strncpyz(modelskin,buffptr,pdest-buffptr+1); strcat(modelskin,pdest + 5); } else continue; if (!Q_stricmp( s_playermodel.modelskin, modelskin )) { // found pic, set selection here s_playermodel.selectedmodel = i; s_playermodel.modelpage = i/MAX_MODELSPERPAGE; // seperate the model name maxlen = pdest-buffptr; if (maxlen > 16) maxlen = 16; Q_strncpyz( s_playermodel.modelname.string, buffptr, maxlen ); Q_strupr( s_playermodel.modelname.string ); // seperate the skin name maxlen = strlen(pdest+5)+1; if (maxlen > 16) maxlen = 16; Q_strncpyz( s_playermodel.skinname.string, pdest+5, maxlen ); Q_strupr( s_playermodel.skinname.string ); break; } } } /* ================= PlayerModel_MenuInit ================= */ static void PlayerModel_MenuInit( void ) { int i; int j; int k; int x; int y; static char playername[32]; static char modelname[32]; static char skinname[32]; // zero set all our globals memset( &s_playermodel, 0 ,sizeof(playermodel_t) ); PlayerModel_Cache(); s_playermodel.menu.key = PlayerModel_MenuKey; s_playermodel.menu.wrapAround = qtrue; s_playermodel.menu.fullscreen = qtrue; s_playermodel.banner.generic.type = MTYPE_BTEXT; s_playermodel.banner.generic.x = 320; s_playermodel.banner.generic.y = 16; s_playermodel.banner.string = "PLAYER MODEL"; s_playermodel.banner.color = color_white; s_playermodel.banner.style = UI_CENTER; s_playermodel.framel.generic.type = MTYPE_BITMAP; s_playermodel.framel.generic.name = MODEL_FRAMEL; s_playermodel.framel.generic.flags = QMF_LEFT_JUSTIFY|QMF_INACTIVE; s_playermodel.framel.generic.x = 0; s_playermodel.framel.generic.y = 78; s_playermodel.framel.width = 256; s_playermodel.framel.height = 329; s_playermodel.framer.generic.type = MTYPE_BITMAP; s_playermodel.framer.generic.name = MODEL_FRAMER; s_playermodel.framer.generic.flags = QMF_LEFT_JUSTIFY|QMF_INACTIVE; s_playermodel.framer.generic.x = 376; s_playermodel.framer.generic.y = 76; s_playermodel.framer.width = 256; s_playermodel.framer.height = 334; s_playermodel.ports.generic.type = MTYPE_BITMAP; s_playermodel.ports.generic.name = MODEL_PORTS; s_playermodel.ports.generic.flags = QMF_LEFT_JUSTIFY|QMF_INACTIVE; s_playermodel.ports.generic.x = 50; s_playermodel.ports.generic.y = 59; s_playermodel.ports.width = 274; s_playermodel.ports.height = 274; y = 59; for (i=0,k=0; i<PLAYERGRID_ROWS; i++) { x = 50; for (j=0; j<PLAYERGRID_COLS; j++,k++) { s_playermodel.pics[k].generic.type = MTYPE_BITMAP; s_playermodel.pics[k].generic.flags = QMF_LEFT_JUSTIFY|QMF_INACTIVE; s_playermodel.pics[k].generic.x = x; s_playermodel.pics[k].generic.y = y; s_playermodel.pics[k].width = 64; s_playermodel.pics[k].height = 64; s_playermodel.pics[k].focuspic = MODEL_SELECTED; s_playermodel.pics[k].focuscolor = colorRed; s_playermodel.picbuttons[k].generic.type = MTYPE_BITMAP; s_playermodel.picbuttons[k].generic.flags = QMF_LEFT_JUSTIFY|QMF_NODEFAULTINIT|QMF_PULSEIFFOCUS; s_playermodel.picbuttons[k].generic.id = ID_PLAYERPIC0+k; s_playermodel.picbuttons[k].generic.callback = PlayerModel_PicEvent; s_playermodel.picbuttons[k].generic.x = x - 16; s_playermodel.picbuttons[k].generic.y = y - 16; s_playermodel.picbuttons[k].generic.left = x; s_playermodel.picbuttons[k].generic.top = y; s_playermodel.picbuttons[k].generic.right = x + 64; s_playermodel.picbuttons[k].generic.bottom = y + 64; s_playermodel.picbuttons[k].width = 128; s_playermodel.picbuttons[k].height = 128; s_playermodel.picbuttons[k].focuspic = MODEL_SELECT; s_playermodel.picbuttons[k].focuscolor = colorRed; x += 64+6; } y += 64+6; } s_playermodel.playername.generic.type = MTYPE_PTEXT; s_playermodel.playername.generic.flags = QMF_CENTER_JUSTIFY|QMF_INACTIVE; s_playermodel.playername.generic.x = 320; s_playermodel.playername.generic.y = 440; s_playermodel.playername.string = playername; s_playermodel.playername.style = UI_CENTER; s_playermodel.playername.color = text_color_normal; s_playermodel.modelname.generic.type = MTYPE_PTEXT; s_playermodel.modelname.generic.flags = QMF_CENTER_JUSTIFY|QMF_INACTIVE; s_playermodel.modelname.generic.x = 497; s_playermodel.modelname.generic.y = 54; s_playermodel.modelname.string = modelname; s_playermodel.modelname.style = UI_CENTER; s_playermodel.modelname.color = text_color_normal; s_playermodel.skinname.generic.type = MTYPE_PTEXT; s_playermodel.skinname.generic.flags = QMF_CENTER_JUSTIFY|QMF_INACTIVE; s_playermodel.skinname.generic.x = 497; s_playermodel.skinname.generic.y = 394; s_playermodel.skinname.string = skinname; s_playermodel.skinname.style = UI_CENTER; s_playermodel.skinname.color = text_color_normal; s_playermodel.player.generic.type = MTYPE_BITMAP; s_playermodel.player.generic.flags = QMF_INACTIVE; s_playermodel.player.generic.ownerdraw = PlayerModel_DrawPlayer; s_playermodel.player.generic.x = 400; s_playermodel.player.generic.y = -40; s_playermodel.player.width = 32*10; s_playermodel.player.height = 56*10; s_playermodel.arrows.generic.type = MTYPE_BITMAP; s_playermodel.arrows.generic.name = MODEL_ARROWS; s_playermodel.arrows.generic.flags = QMF_INACTIVE; s_playermodel.arrows.generic.x = 125; s_playermodel.arrows.generic.y = 340; s_playermodel.arrows.width = 128; s_playermodel.arrows.height = 32; s_playermodel.left.generic.type = MTYPE_BITMAP; s_playermodel.left.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS; s_playermodel.left.generic.callback = PlayerModel_MenuEvent; s_playermodel.left.generic.id = ID_PREVPAGE; s_playermodel.left.generic.x = 125; s_playermodel.left.generic.y = 340; s_playermodel.left.width = 64; s_playermodel.left.height = 32; s_playermodel.left.focuspic = MODEL_ARROWSL; s_playermodel.right.generic.type = MTYPE_BITMAP; s_playermodel.right.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS; s_playermodel.right.generic.callback = PlayerModel_MenuEvent; s_playermodel.right.generic.id = ID_NEXTPAGE; s_playermodel.right.generic.x = 125+61; s_playermodel.right.generic.y = 340; s_playermodel.right.width = 64; s_playermodel.right.height = 32; s_playermodel.right.focuspic = MODEL_ARROWSR; s_playermodel.back.generic.type = MTYPE_BITMAP; s_playermodel.back.generic.name = MODEL_BACK0; s_playermodel.back.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS; s_playermodel.back.generic.callback = PlayerModel_MenuEvent; s_playermodel.back.generic.id = ID_BACK; s_playermodel.back.generic.x = 0; s_playermodel.back.generic.y = 480-64; s_playermodel.back.width = 128; s_playermodel.back.height = 64; s_playermodel.back.focuspic = MODEL_BACK1; Menu_AddItem( &s_playermodel.menu, &s_playermodel.banner ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.framel ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.framer ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.ports ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.playername ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.modelname ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.skinname ); for (i=0; i<MAX_MODELSPERPAGE; i++) { Menu_AddItem( &s_playermodel.menu, &s_playermodel.pics[i] ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.picbuttons[i] ); } Menu_AddItem( &s_playermodel.menu, &s_playermodel.player ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.arrows ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.left ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.right ); Menu_AddItem( &s_playermodel.menu, &s_playermodel.back ); // find all available models // PlayerModel_BuildList(); // set initial states PlayerModel_SetMenuItems(); // update user interface PlayerModel_UpdateGrid(); PlayerModel_UpdateModel(); } /* ================= PlayerModel_Cache ================= */ void PlayerModel_Cache( void ) { int i; for( i = 0; playermodel_artlist[i]; i++ ) { trap_R_RegisterShaderNoMip( playermodel_artlist[i] ); } PlayerModel_BuildList(); for( i = 0; i < s_playermodel.nummodels; i++ ) { trap_R_RegisterShaderNoMip( s_playermodel.modelnames[i] ); } } void UI_PlayerModelMenu(void) { PlayerModel_MenuInit(); UI_PushMenu( &s_playermodel.menu ); Menu_SetCursorToItem( &s_playermodel.menu, &s_playermodel.pics[s_playermodel.selectedmodel % MAX_MODELSPERPAGE] ); }