/* =========================================================================== Copyright (C) 1997-2001 Id Software, Inc. This file is part of Quake 2 source code. Quake 2 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 2 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 2 source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // ui_utils.c -- misc support functions for the menus #include #ifdef _WIN32 #include #endif #include "../client/client.h" #include "ui_local.h" #include #include /* ======================================================================= MISC UTILITY FUNCTIONS ======================================================================= */ #if 0 /* ================= FreeFileList ================= */ void FreeFileList (char **list, int n) { int i; for (i = 0; i < n; i++) { if (list && list[i]) { free(list[i]); list[i] = 0; } } free(list); } /* ================= ItemInList ================= */ qboolean ItemInList (char *check, int num, char **list) { int i; for (i=0;i= len) return; if (start > len) return; // i=start so default stays first! for (i=start; ii; j--) list[j] = list[j-1]; list[i] = strdup(insert); return; } } list[len] = strdup(insert); } /* ========================== UI_LoadAssetList Generic file list loader Used for fonts, huds, and crosshairs ========================== */ char **UI_LoadAssetList (char *dir, char *nameMask, char *firstItem, int *returnCount, int maxItems, qboolean stripExtension, qboolean frontInsert, qboolean (*checkName)(char *p)) { char **list = 0, **itemFiles; char *path = NULL, *curItem, *p, *ext; // char findName[1024]; int nItems = 0, nItemNames, i, baseLen, extLen; // check pointers if (!dir || !nameMask || !firstItem || !returnCount || !checkName) return NULL; if (maxItems < 2) // must allow at least 2 items return NULL; list = malloc(sizeof(char *) * (maxItems+1)); memset(list, 0, sizeof(char *) * (maxItems+1)); // set first item name list[0] = strdup(firstItem); nItemNames = 1; #if 1 itemFiles = FS_GetFileList(va("%s/%s", dir, nameMask), NULL, &nItems); for (i=0; ii ;j--) list[j] = list[j-1]; list[i] = strdup(insert); return; } } list[len] = strdup(insert); } char **UI_SetFontNames (void) { char *curFont; char **list = 0, *p;//, *s; int nfonts = 0, nfontnames; char **fontfiles; char *path = NULL; int i;//, j; list = malloc( sizeof( char * ) * UI_MAX_FONTS ); memset( list, 0, sizeof( char * ) * UI_MAX_FONTS ); list[0] = strdup("default"); nfontnames = 1; fontfiles = FS_GetFileList("fonts/*.*", NULL, &nfonts); for (i=0; i 0) { FS_FreeFileList (ui_font_names, ui_numfonts); } ui_font_names = NULL; ui_numfonts = 0; } /* ======================================================================= HUD NAME LOADING ======================================================================= */ // TODO: Enable this when HUD loading is working #if 0 #define UI_MAX_HUDS 128 char **ui_hud_names = NULL; int ui_numhuds = 0; /* ========================== UI_IsValidHudName ========================== */ qboolean UI_IsValidHudName (char *name) { int len = (int)strlen(name); if ( !strcmp(name+max(len-4,0), ".hud") ) return true; return false; } /* ========================== UI_LoadHudNames ========================== */ void UI_LoadHudNames (void) { ui_hud_names = UI_LoadAssetList ("scripts/huds", "*.hud", "default", &ui_numhuds, UI_MAX_HUDS, true, true, UI_IsValidHudName); } /* ========================== UI_FreeHudNames ========================== */ void UI_FreeHudNames (void) { if (ui_numhuds > 0){ FS_FreeFileList (ui_hud_names, ui_numhuds); } ui_hud_names = NULL; ui_numhuds = 0; } #endif /* ======================================================================= CROSSHAIR LOADING ======================================================================= */ #define UI_MAX_CROSSHAIRS 101 // none + ch1-ch100 char **ui_crosshair_names = NULL; //char **ui_crosshair_display_names = NULL; //char **ui_crosshair_values = NULL; int ui_numcrosshairs = 0; /* ========================== UI_SortCrosshairs ========================== */ void UI_SortCrosshairs (char **list, int len) { int i, j; char *temp; qboolean moved; if (!list || len < 2) return; for (i=(len-1); i>0; i--) { moved = false; for (j=0; j atoi(strdup(list[j+1]+2)) ) { temp = list[j]; list[j] = list[j+1]; list[j+1] = temp; moved = true; } } if (!moved) break; // done sorting } } #if 0 char **UI_SetCrosshairNames (void) { char *curCrosshair; char **list = 0, *p; int ncrosshairs = 0, ncrosshairnames; char **crosshairfiles; char *path = NULL; int i; list = malloc( sizeof( char * ) * UI_MAX_CROSSHAIRS+1 ); memset( list, 0, sizeof( char * ) * UI_MAX_CROSSHAIRS+1 ); list[0] = strdup("none"); // was default ncrosshairnames = 1; crosshairfiles = FS_GetFileList("pics/ch*.*", NULL, &ncrosshairs); for (i=0; i 9) continue; if (!isNumeric(p[2])) continue; if (namelen >= 8 && !isNumeric(p[3])) continue; // ch100 is only valid 5-char name if (namelen == 9 && (p[2] != '1' || p[3] != '0' || p[4] != '0')) continue; num = (int)strlen(p)-4; p[num] = 0; //NULL; curCrosshair = p; if (!FS_ItemInList(curCrosshair, ncrosshairnames, list)) { FS_InsertInList(list, strdup(curCrosshair), ncrosshairnames, 1); // i=1 so none stays first! ncrosshairnames++; } //set back so whole string get deleted. p[num] = '.'; } // sort the list UI_SortCrosshairs (list, ncrosshairnames); if (ncrosshairs) FS_FreeFileList( crosshairfiles, ncrosshairs ); ui_numcrosshairs = ncrosshairnames; return list; } #endif /* ========================== UI_IsValidCrosshairName ========================== */ qboolean UI_IsValidCrosshairName (char *name) { int namelen; if ( !UI_IsValidImageFilename(name) ) return false; // filename must be chxxx if ( strncmp(name, "ch", 2) ) return false; namelen = (int)strlen(strdup(name)); if (namelen < 7 || namelen > 9) return false; if ( !isNumeric(name[2]) ) return false; if ( namelen >= 8 && !isNumeric(name[3]) ) return false; // ch0 is invalid if ( namelen == 7 && name[2] == '0' ) return false; // ch100 is only valid 5-char name if ( namelen == 9 && (name[2] != '1' || name[3] != '0' || name[4] != '0') ) return false; // ch100-ch128 are only valid 5-char names /* if ( namelen == 9 && ( !isNumeric(name[4]) || name[2] != '1' || (name[2] == '1' && name[3] > '2') || (name[2] == '1' && name[3] == '2' && name[4] > '8') ) ) return false;*/ return true; } /* ========================== UI_LoadCrosshairs ========================== */ void UI_LoadCrosshairs (void) { // int i; ui_crosshair_names = UI_LoadAssetList ("pics", "ch*.*", "none", &ui_numcrosshairs, UI_MAX_CROSSHAIRS, true, false, UI_IsValidCrosshairName); UI_SortCrosshairs (ui_crosshair_names, ui_numcrosshairs); /* ui_crosshair_display_names = malloc( sizeof(char *) * (UI_MAX_CROSSHAIRS+1) ); memcpy(ui_crosshair_display_names, ui_crosshair_names, sizeof(char *) * (UI_MAX_CROSSHAIRS+1)); ui_crosshair_display_names[0] = strdup("chnone"); ui_crosshair_values = malloc( sizeof(char *) * (UI_MAX_CROSSHAIRS+1) ); memset(ui_crosshair_values, 0, sizeof(char *) * (UI_MAX_CROSSHAIRS+1) ); for (i=0; i 0) { FS_FreeFileList (ui_crosshair_names, ui_numcrosshairs); /* if (ui_crosshair_display_names) { if (ui_crosshair_display_names[0]) { free (ui_crosshair_display_names[0]); } free (ui_crosshair_display_names); } FS_FreeFileList (ui_crosshair_values, ui_numcrosshairs); */ } ui_crosshair_names = NULL; // ui_crosshair_display_names = NULL; // ui_crosshair_values = NULL; ui_numcrosshairs = 0; } /* ============================================================================= SAVEGAME / SAVESHOT HANDLING ============================================================================= */ char ui_savestrings[UI_MAX_SAVEGAMES][32]; qboolean ui_savevalid[UI_MAX_SAVEGAMES+1]; time_t ui_savetimestamps[UI_MAX_SAVEGAMES]; qboolean ui_savechanged[UI_MAX_SAVEGAMES]; qboolean ui_saveshotvalid[UI_MAX_SAVEGAMES+1]; char ui_mapname[MAX_QPATH]; //qboolean ui_mapshotvalid; char ui_saveload_shotname[MAX_QPATH]; /* ========================== UI_Load_Savestrings ========================== */ void UI_Load_Savestrings (qboolean update) { int i; FILE *fp; fileHandle_t f; char name[MAX_OSPATH]; char mapname[MAX_TOKEN_CHARS]; char *ch; time_t old_timestamp; struct stat st; for (i=0; i= UI_MAX_SAVEGAMES) return NULL; if ( ui_savevalid[index] && ui_saveshotvalid[index] ) { if ( index == 0 ) Com_sprintf(ui_saveload_shotname, sizeof(ui_saveload_shotname), "/levelshots/%s.pcx", ui_mapname); else // Com_sprintf(ui_saveload_shotname, sizeof(ui_saveload_shotname), "/save/kmq2save%03i/shot.jpg", index); Com_sprintf(ui_saveload_shotname, sizeof(ui_saveload_shotname), "/"SAVEDIRNAME"/kmq2save%03i/shot.jpg", index); } else if ( ui_saveshotvalid[UI_MAX_SAVEGAMES] ) Com_sprintf(ui_saveload_shotname, sizeof(ui_saveload_shotname), UI_NOSCREEN_NAME); else // no saveshot or nullshot return NULL; return ui_saveload_shotname; } /* ========================== UI_UpdateSavegameData ========================== */ void UI_UpdateSavegameData (void) { UI_Load_Savestrings (true); UI_ValidateSaveshots (); // register saveshots } /* ========================== UI_InitSavegameData ========================== */ void UI_InitSavegameData (void) { int i; for (i=0; i= UI_MAX_SAVEGAMES) return false; return ui_savevalid[index]; } /* ========================== UI_SaveshotIsValid ========================== */ qboolean UI_SaveshotIsValid (int index) { // check index if (index < 0 || index >= UI_MAX_SAVEGAMES) return false; return ui_saveshotvalid[index]; } /* =============== UI_CanOpenSaveMenu =============== */ qboolean UI_CanOpenSaveMenu (void *unused) { return (Com_ServerState() > 0); } /* ======================================================================= SERVER LISTING Copyright (C) 2001-2003 pat@aftermoon.net for modif flanked by ======================================================================= */ int ui_num_servers; // user readable information char ui_local_server_names[UI_MAX_LOCAL_SERVERS][UI_LOCAL_SERVER_NAMELEN]; //char *ui_serverlist_names[UI_MAX_LOCAL_SERVERS+1]; // network address netadr_t ui_local_server_netadr[UI_MAX_LOCAL_SERVERS]; #if 1 // Added code for compute ping time of server broadcasted // The server is displayed like : // "protocol ping hostname mapname nb players/max players" // "udp 100ms Pat q2dm1 2/8" int global_udp_server_time; int global_ipx_server_time; int global_adr_server_time[16]; netadr_t global_adr_server_netadr[16]; /* ========================== UI_AddToServerList ========================== */ void UI_AddToServerList (netadr_t adr, char *info) { int i; int iPing; char *pszProtocol; if (ui_num_servers == UI_MAX_LOCAL_SERVERS) return; while ( *info == ' ' ) info++; // Ignore if duplicated for (i=0; i 0 ) // udp server { iPing = Sys_Milliseconds() - global_udp_server_time ; pszProtocol = "UDP" ; } else // ipx server { iPing = Sys_Milliseconds() - global_ipx_server_time ; pszProtocol = "IPX" ; } } Com_sprintf( ui_local_server_names[ui_num_servers], sizeof(ui_local_server_names[0]), "%s %4dms %s", pszProtocol, iPing, info ) ; ui_local_server_netadr[ui_num_servers] = adr; ui_num_servers++; } // #else void UI_AddToServerList (netadr_t adr, char *info) { int i; if (ui_num_servers == UI_MAX_LOCAL_SERVERS) return; while ( *info == ' ' ) info++; // ignore if duplicated for (i=0 ; i= UI_MAX_LOCAL_SERVERS || index >= ui_num_servers) return; if ( Q_stricmp( ui_local_server_names[index], NO_SERVER_STRING ) == 0 ) return; Com_sprintf (buffer, sizeof(buffer), "connect %s\n", NET_AdrToString (ui_local_server_netadr[index])); Cbuf_AddText (buffer); UI_ForceMenuOff (); cls.disable_screen = 1; // Knightmare- show loading screen } /* ========================== UI_InitServerList ========================== */ void UI_InitServerList (void) { int i; // ui_serverlist_names[UI_MAX_LOCAL_SERVERS] = NULL; for ( i = 0; i < UI_MAX_LOCAL_SERVERS; i++ ) { Com_sprintf (ui_local_server_names[i], sizeof(ui_local_server_names[i]), NO_SERVER_STRING); // ui_serverlist_names[i] = ui_local_server_names[i]; } } /* ======================================================================= START SERVER MAP LIST ======================================================================= */ gametype_names_t gametype_names[] = { {MAP_DM, "dm ffa team teamdm"}, {MAP_COOP, "coop"}, {MAP_CTF, "ctf"}, {MAP_3TCTF, "3tctf"}, }; maptype_t ui_svr_maptype; static int ui_svr_nummaps; char **ui_svr_mapnames; static int ui_svr_maplist_sizes[NUM_MAPTYPES] = {0, 0, 0, 0}; static char **ui_svr_maplists[NUM_MAPTYPES] = {NULL, NULL, NULL, NULL}; int ui_svr_listfile_nummaps; static char **ui_svr_listfile_mapnames; static int ui_svr_arena_nummaps[NUM_MAPTYPES]; static char **ui_svr_arena_mapnames[NUM_MAPTYPES]; //static byte *ui_svr_mapshotvalid; // levelshot truth table static byte *ui_svr_mapshotvalid[NUM_MAPTYPES] = {NULL, NULL, NULL, NULL}; // levelshot truth tables char ui_startserver_shotname [MAX_QPATH]; qboolean ui_svr_coop = false; qboolean ui_svr_ctf = false; void UI_BuildStartSeverLevelshotTables (void); void UI_FreeStartSeverLevelshotTables (void); /* =============== UI_ParseArenaFromFile Partially from Q3 source =============== */ qboolean UI_ParseArenaFromFile (char *filename, char *shortname, char *longname, char *gametypes, size_t bufSize) { int len; fileHandle_t f; char buf[MAX_ARENAS_TEXT]; char *s, *token, *dest; len = FS_FOpenFile (filename, &f, FS_READ); if (!f) { Com_Printf (S_COLOR_RED "UI_ParseArenaFromFile: file not found: %s\n", filename); return false; } if (len >= MAX_ARENAS_TEXT) { Com_Printf (S_COLOR_RED "UI_ParseArenaFromFile: file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT); FS_FCloseFile (f); return false; } FS_Read (buf, len, f); buf[len] = 0; FS_FCloseFile (f); s = buf; // get the opening curly brace token = COM_Parse (&s); if (!token) { Com_Printf ("UI_ParseArenaFromFile: unexpected EOF\n"); return false; } if (token[0] != '{') { Com_Printf ("UI_ParseArenaFromFile: found %s when expecting {\n", token); return false; } // go through all the parms while (s < (buf + len)) { dest = NULL; token = COM_Parse (&s); if (token && (token[0] == '}')) break; if (!token || !s) { Com_Printf ("UI_ParseArenaFromFile: EOF without closing brace\n"); break; } if (!Q_strcasecmp(token, "map")) dest = shortname; else if (!Q_strcasecmp(token, "longname")) dest = longname; else if (!Q_strcasecmp(token, "type")) dest = gametypes; if (dest) { token = COM_Parse (&s); if (!token) { Com_Printf ("UI_ParseArenaFromFile: unexpected EOF\n"); return false; } if (token[0] == '}') { Com_Printf ("UI_ParseArenaFromFile: closing brace without data\n"); break; } if (!s) { Com_Printf ("UI_ParseArenaFromFile: EOF without closing brace\n"); break; } // strncpy(dest, token); Q_strncpyz (dest, bufSize, token); } } if (!shortname || !strlen(shortname)) { Com_Printf (S_COLOR_RED "UI_ParseArenaFromFile: %s: map field not found\n", filename); return false; } if (!strlen(longname)) longname = shortname; return true; } /* =============== UI_SortArenas =============== */ void UI_SortArenas (char **list, int len) { int i, j; char *temp, *s1, *s2; qboolean moved; if (!list || len < 2) return; for (i=(len-1); i>0; i--) { moved = false; for (j=0; j 0) { temp = list[j]; list[j] = list[j+1]; list[j+1] = temp; moved = true; } } if (!moved) break; // done sorting } } /* =============== UI_LoadArenas =============== */ void UI_LoadArenas (void) { char *p, *s, *s2, *tok, *tok2; char **arenafiles = NULL; char **tmplist = NULL; char *path = NULL; // char findName[1024]; char shortname[MAX_TOKEN_CHARS]; char longname[MAX_TOKEN_CHARS]; char gametypes[MAX_TOKEN_CHARS]; char scratch[200]; int i, j, len, narenas = 0, narenanames = 0; size_t nameSize; qboolean type_supported[NUM_MAPTYPES]; // // free existing lists and malloc new ones // for (i=0; i= ui_svr_maplist_sizes[ui_svr_maptype]) return NULL; Q_strncpyz (startmap, sizeof(startmap), strchr( ui_svr_maplists[ui_svr_maptype][index], '\n' ) + 1); if (ui_svr_mapshotvalid[ui_svr_maptype][index] == M_UNSET) { // init levelshot Com_sprintf(mapshotname, sizeof(mapshotname), "/levelshots/%s.pcx", startmap); if (R_DrawFindPic(mapshotname)) ui_svr_mapshotvalid[ui_svr_maptype][index] = M_FOUND; else ui_svr_mapshotvalid[ui_svr_maptype][index] = M_MISSING; } if (ui_svr_mapshotvalid[ui_svr_maptype][index] == M_FOUND) Com_sprintf(ui_startserver_shotname, sizeof(ui_startserver_shotname), "/levelshots/%s.pcx", startmap); else if (ui_svr_mapshotvalid[ui_svr_maptype][ui_svr_maplist_sizes[ui_svr_maptype]] == M_FOUND) Com_sprintf(ui_startserver_shotname, sizeof(ui_startserver_shotname), UI_NOSCREEN_NAME); else return NULL; return ui_startserver_shotname; } /* =============== UI_SetCoopMenuMode =============== */ void UI_SetCoopMenuMode (qboolean value) { ui_svr_coop = value; } /* =============== UI_Coop_MenuMode =============== */ qboolean UI_Coop_MenuMode (void) { return ui_svr_coop; } /* =============== UI_CanOpenDMFlagsMenu =============== */ qboolean UI_CanOpenDMFlagsMenu (void *unused) { return !ui_svr_coop; } /* =============== UI_SetCTFMenuMode =============== */ void UI_SetCTFMenuMode (qboolean value) { ui_svr_ctf = value; } /* =============== UI_CTF_MenuMode =============== */ qboolean UI_CTF_MenuMode (void) { return ui_svr_ctf; } /* ======================================================================= PLAYER MODEL LOADING ======================================================================= */ playermodelinfo_s ui_pmi[MAX_PLAYERMODELS]; char *ui_pmnames[MAX_PLAYERMODELS]; int ui_numplayermodels; // save skins and models here so as to not have to re-register every frame struct model_s *ui_playermodel; struct model_s *ui_weaponmodel; struct image_s *ui_playerskin; char *ui_currentweaponmodel; //char ui_playerconfig_playermodelname[MAX_QPATH]; //char ui_playerconfig_playerskinname[MAX_QPATH]; //char ui_playerconfig_weaponmodelname[MAX_QPATH]; /* ========================== UI_IsSkinIcon ========================== */ static qboolean UI_IsSkinIcon (char *name) { int len; char *s, scratch[1024]; // strncpy(scratch, name); Q_strncpyz(scratch, sizeof(scratch), name); *strrchr(scratch, '.') = 0; s = scratch; len = (int)strlen(s); return (!strcmp(s+max(len-2,0), "_i")); } /* ========================== UI_IconOfSkinExists ========================== */ static qboolean UI_IconOfSkinExists (char *skin, char **files, int nfiles, char *suffix) { int i; char scratch[1024]; // strncpy(scratch, skin); Q_strncpyz (scratch, sizeof(scratch), skin); *strrchr(scratch, '.') = 0; // strncat(scratch, suffix); Q_strncatz (scratch, sizeof(scratch), suffix); // strncat(scratch, "_i.pcx"); for (i = 0; i < nfiles; i++) { if ( strcmp(files[i], scratch) == 0 ) return true; } return false; } /* ========================== UI_IsValidSkin Adds menu support for TGA and JPG skins ========================== */ static qboolean UI_IsValidSkin (char **filelist, int numFiles, int index) { if ( UI_IsValidImageFilename(filelist[index]) && !UI_IsSkinIcon(filelist[index]) ) { if ( UI_IconOfSkinExists (filelist[index], filelist, numFiles-1 , "_i.pcx") || UI_IconOfSkinExists (filelist[index], filelist, numFiles-1 , "_i.tga") #ifdef PNG_SUPPORT || UI_IconOfSkinExists (filelist[index], filelist, numFiles-1 , "_i.png") #endif // PNG_SUPPORT || UI_IconOfSkinExists (filelist[index], filelist, numFiles-1 , "_i.jpg")) return true; } return false; } /* ========================== UI_PlayerConfig_ScanDirectories ========================== */ static qboolean UI_PlayerConfig_ScanDirectories (void) { char findname[1024]; char scratch[1024]; int ndirs = 0, npms = 0; char **dirnames; char *path = NULL; int i; ui_numplayermodels = 0; // loop back to here if there were no valid player models found in the selected path do { // // get a list of directories // do { path = FS_NextPath(path); Com_sprintf( findname, sizeof(findname), "%s/players/*.*", path ); if ( (dirnames = FS_ListFiles(findname, &ndirs, SFF_SUBDIR, 0)) != 0 ) break; } while (path); if (!dirnames) return false; // // go through the subdirectories // npms = ndirs; if (npms > MAX_PLAYERMODELS) npms = MAX_PLAYERMODELS; if ( (ui_numplayermodels + npms) > MAX_PLAYERMODELS ) npms = MAX_PLAYERMODELS - ui_numplayermodels; for (i = 0; i < npms; i++) { int k, s; char *a, *b, *c; char **skinnames; char **skiniconnames; char **imagenames; int nimagefiles; int nskins = 0; qboolean already_added = false; if (dirnames[i] == 0) continue; // check if dirnames[i] is already added to the ui_pmi[i].directory list a = strrchr(dirnames[i], '/'); b = strrchr(dirnames[i], '\\'); c = (a > b) ? a : b; for (k=0; k < ui_numplayermodels; k++) if (!strcmp(ui_pmi[k].directory, c+1)) { already_added = true; break; } if (already_added) { // todo: add any skins for this model not already listed to skinDisplayNames continue; } // verify the existence of tris.md2 // strncpy(scratch, dirnames[i]); // strncat(scratch, "/tris.md2"); Q_strncpyz(scratch, sizeof(scratch), dirnames[i]); Q_strncatz(scratch, sizeof(scratch), "/tris.md2"); if ( !Sys_FindFirst(scratch, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM) ) { free(dirnames[i]); dirnames[i] = 0; Sys_FindClose(); continue; } Sys_FindClose(); // verify the existence of at least one skin // strncpy(scratch, va("%s%s", dirnames[i], "/*.*")); // was "/*.pcx" Q_strncpyz(scratch, sizeof(scratch), va("%s%s", dirnames[i], "/*.*")); // was "/*.pcx" imagenames = FS_ListFiles (scratch, &nimagefiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM); if (!imagenames) { free(dirnames[i]); dirnames[i] = 0; continue; } // count valid skins, which consist of a skin with a matching "_i" icon for (k = 0; k < nimagefiles-1; k++) if ( UI_IsValidSkin(imagenames, nimagefiles, k) ) nskins++; if (!nskins) continue; // make short name for the model a = strrchr(dirnames[i], '/'); b = strrchr(dirnames[i], '\\'); c = (a > b) ? a : b; // strncpy(ui_pmi[ui_numplayermodels].displayname, c+1, MAX_DISPLAYNAME-1); // strncpy(ui_pmi[ui_numplayermodels].directory, c+1); Q_strncpyz(ui_pmi[ui_numplayermodels].displayname, sizeof(ui_pmi[ui_numplayermodels].displayname), c+1); Q_strncpyz(ui_pmi[ui_numplayermodels].directory, sizeof(ui_pmi[ui_numplayermodels].directory), c+1); skinnames = malloc(sizeof(char *) * (nskins+1)); memset(skinnames, 0, sizeof(char *) * (nskins+1)); skiniconnames = malloc(sizeof(char *) * (nskins+1)); memset(skiniconnames, 0, sizeof(char *) * (nskins+1)); // copy the valid skins if (nimagefiles) for (s = 0, k = 0; k < nimagefiles-1; k++) { char *a, *b, *c; if ( UI_IsValidSkin(imagenames, nimagefiles, k) ) { a = strrchr(imagenames[k], '/'); b = strrchr(imagenames[k], '\\'); c = (a > b) ? a : b; // strncpy(scratch, c+1); Q_strncpyz(scratch, sizeof(scratch), c+1); if ( strrchr(scratch, '.') ) *strrchr(scratch, '.') = 0; skinnames[s] = strdup(scratch); skiniconnames[s] = strdup(va("/players/%s/%s_i.pcx", ui_pmi[ui_numplayermodels].directory, scratch)); s++; } } // at this point we have a valid player model ui_pmi[ui_numplayermodels].nskins = nskins; ui_pmi[ui_numplayermodels].skinDisplayNames = skinnames; ui_pmi[ui_numplayermodels].skinIconNames = skiniconnames; // make short name for the model // a = strrchr(dirnames[i], '/'); // b = strrchr(dirnames[i], '\\'); // c = (a > b) ? a : b; // Q_strncpyz(ui_pmi[ui_numplayermodels].displayname, sizeof(ui_pmi[ui_numplayermodels].displayname), c+1); // Q_strncpyz(ui_pmi[ui_numplayermodels].directory, sizeof(ui_pmi[ui_numplayermodels].directory), c+1); FS_FreeFileList (imagenames, nimagefiles); ui_numplayermodels++; } if (dirnames) FS_FreeFileList (dirnames, ndirs); // if no valid player models found in path, // try next path, if there is one } while (path); // (s_numplayermodels == 0 && path); return true; //** DMP warning fix } /* ========================== UI_LoadPlayerModels ========================== */ void UI_LoadPlayerModels (void) { UI_PlayerConfig_ScanDirectories (); } /* ========================== UI_FreePlayerModels ========================== */ void UI_FreePlayerModels (void) { int i; for (i = 0; i < ui_numplayermodels; i++) { int j; for (j = 0; j < ui_pmi[i].nskins; j++) { if (ui_pmi[i].skinDisplayNames[j]) free(ui_pmi[i].skinDisplayNames[j]); ui_pmi[i].skinDisplayNames[j] = NULL; } free(ui_pmi[i].skinDisplayNames); ui_pmi[i].skinDisplayNames = NULL; ui_pmi[i].nskins = 0; } } /* ========================== UI_PlayerModelCmpFunc ========================== */ int UI_PlayerModelCmpFunc (const void *_a, const void *_b) { const playermodelinfo_s *a = (const playermodelinfo_s *) _a; const playermodelinfo_s *b = (const playermodelinfo_s *) _b; // // sort by male, female, then alphabetical // if ( strcmp(a->directory, "male") == 0 ) return -1; else if (strcmp( b->directory, "male") == 0 ) return 1; if ( strcmp(a->directory, "female") == 0 ) return -1; else if (strcmp( b->directory, "female") == 0 ) return 1; return strcmp(a->directory, b->directory); } /* ========================== UI_InitPlayerModelInfo ========================== */ void UI_InitPlayerModelInfo (int *modelNum, int *skinNum) { int i; int currentdirectoryindex = 0; int currentskinindex = 0; char currentdirectory[1024]; char currentskin[1024]; if (ui_numplayermodels <= 0) { modelNum = skinNum = 0; return; } // strncpy( currentdirectory, Cvar_VariableString ("skin") ); Q_strncpyz(currentdirectory, sizeof(currentdirectory), Cvar_VariableString ("skin")); if ( strchr( currentdirectory, '/' ) ) { // strncpy( currentskin, strchr( currentdirectory, '/' ) + 1 ); Q_strncpyz(currentskin, sizeof(currentskin), strchr( currentdirectory, '/' ) + 1); *strchr( currentdirectory, '/' ) = 0; } else if ( strchr( currentdirectory, '\\' ) ) { // strncpy( currentskin, strchr( currentdirectory, '\\' ) + 1 ); Q_strncpyz(currentskin, sizeof(currentskin), strchr( currentdirectory, '\\' ) + 1); *strchr( currentdirectory, '\\' ) = 0; } else { // strncpy( currentdirectory, "male" ); // strncpy( currentskin, "grunt" ); Q_strncpyz(currentdirectory, sizeof(currentdirectory), "male"); Q_strncpyz(currentskin, sizeof(currentskin), "grunt"); } qsort( ui_pmi, ui_numplayermodels, sizeof( ui_pmi[0] ), UI_PlayerModelCmpFunc ); memset( ui_pmnames, 0, sizeof( ui_pmnames ) ); for (i = 0; i < ui_numplayermodels; i++) { ui_pmnames[i] = ui_pmi[i].displayname; if (Q_stricmp( ui_pmi[i].directory, currentdirectory ) == 0) { int j; currentdirectoryindex = i; for (j = 0; j < ui_pmi[i].nskins; j++) { if (Q_stricmp( ui_pmi[i].skinDisplayNames[j], currentskin ) == 0) { currentskinindex = j; break; } } } } // precache this player model and skin UI_UpdatePlayerModelInfo (currentdirectoryindex, currentskinindex); if (modelNum) *modelNum = currentdirectoryindex; if (skinNum) *skinNum = currentskinindex; } /* ========================== UI_UpdatePlayerModelInfo ========================== */ void UI_UpdatePlayerModelInfo (int mNum, int sNum) { char scratch[MAX_QPATH]; Com_sprintf( scratch, sizeof(scratch), "players/%s/tris.md2", ui_pmi[mNum].directory ); ui_playermodel = R_RegisterModel (scratch); // Q_strncpyz (ui_playerconfig_playermodelname, sizeof(ui_playerconfig_playermodelname), scratch); Com_sprintf( scratch, sizeof(scratch), "players/%s/%s.pcx", ui_pmi[mNum].directory, ui_pmi[mNum].skinDisplayNames[sNum] ); ui_playerskin = R_RegisterSkin (scratch); // Q_strncpyz (ui_playerconfig_playerskinname, sizeof(ui_playerconfig_playerskinname), scratch); // show current weapon model (if any) if (ui_currentweaponmodel && strlen(ui_currentweaponmodel)) { Com_sprintf (scratch, sizeof(scratch), "players/%s/%s", ui_pmi[mNum].directory, ui_currentweaponmodel); ui_weaponmodel = R_RegisterModel(scratch); if (!ui_weaponmodel) { Com_sprintf (scratch, sizeof(scratch), "players/%s/weapon.md2", ui_pmi[mNum].directory); ui_weaponmodel = R_RegisterModel (scratch); } } else { Com_sprintf (scratch, sizeof(scratch), "players/%s/weapon.md2", ui_pmi[mNum].directory); ui_weaponmodel = R_RegisterModel (scratch); } // Q_strncpyz (ui_playerconfig_weaponmodelname, sizeof(ui_playerconfig_weaponmodelname), scratch); } /* ========================== UI_UpdatePlayerSkinInfo ========================== */ void UI_UpdatePlayerSkinInfo (int mNum, int sNum) { char scratch[MAX_QPATH]; Com_sprintf(scratch, sizeof(scratch), "players/%s/%s.pcx", ui_pmi[mNum].directory, ui_pmi[mNum].skinDisplayNames[sNum]); ui_playerskin = R_RegisterSkin(scratch); // Q_strncpyz (ui_playerconfig_playerskinname, sizeof(ui_playerconfig_playerskinname), scratch); } /* ========================== UI_HaveValidPlayerModels ========================== */ qboolean UI_HaveValidPlayerModels (void *unused) { return (ui_numplayermodels > 0); }