X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=host_cmd.c;h=a62b08324be7aa6bad9b80428204e4579b7853b1;hb=f7750d34bc095248efb2aec0cefb2e3db367ab3b;hp=4d2b93b6eb76617231bf405b14151e017e68a1fa;hpb=add1a1b0abc10b8e720d74aeac52ad8276ee9fe0;p=xonotic%2Fdarkplaces.git diff --git a/host_cmd.c b/host_cmd.c index 4d2b93b6..a62b0832 100644 --- a/host_cmd.c +++ b/host_cmd.c @@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "sv_demo.h" #include "image.h" +#include "prvm_cmds.h" #include "utf8lib.h" // for secure rcon authentication @@ -107,7 +108,7 @@ static void Host_Status_f (void) if (svs.clients[i].active) players++; print ("host: %s\n", Cvar_VariableString ("hostname")); - print ("version: %s build %s\n", gamename, buildstring); + print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername); print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol)); print ("map: %s\n", sv.name); print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); @@ -379,9 +380,11 @@ static void Host_Map_f (void) svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients); } +#ifdef CONFIG_MENU // remove menu if (key_dest == key_menu || key_dest == key_menu_grabbed) MR_ToggleMenu(0); +#endif key_dest = key_game; svs.serverflags = 0; // haven't completed an episode yet @@ -414,9 +417,11 @@ static void Host_Changelevel_f (void) return; } +#ifdef CONFIG_MENU // remove menu if (key_dest == key_menu || key_dest == key_menu_grabbed) MR_ToggleMenu(0); +#endif key_dest = key_game; SV_SaveSpawnparms (); @@ -449,9 +454,11 @@ static void Host_Restart_f (void) return; } +#ifdef CONFIG_MENU // remove menu if (key_dest == key_menu || key_dest == key_menu_grabbed) MR_ToggleMenu(0); +#endif key_dest = key_game; allowcheats = sv_cheats.integer != 0; @@ -551,7 +558,7 @@ LOAD / SAVE GAME void Host_Savegame_to(prvm_prog_t *prog, const char *name) { qfile_t *f; - int i, k, l, lightstyles = 64; + int i, k, l, numbuffers, lightstyles = 64; char comment[SAVEGAME_COMMENT_LENGTH+1]; char line[MAX_INPUTLINE]; qboolean isserver; @@ -641,11 +648,13 @@ void Host_Savegame_to(prvm_prog_t *prog, const char *name) FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]); // darkplaces extension - save buffers - for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); i++) + numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); + for (i = 0; i < numbuffers; i++) { prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED)) { + FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS); for(k = 0; k < stringbuffer->num_strings; k++) { if (!stringbuffer->strings[k]) @@ -764,12 +773,11 @@ static void Host_Loadgame_f (void) const char *t; char *text; prvm_edict_t *ent; - int i, k; + int i, k, numbuffers; int entnum; int version; float spawn_parms[NUM_SPAWN_PARMS]; prvm_stringbuffer_t *stringbuffer; - size_t alloclen; if (Cmd_Argc() != 2) { @@ -786,9 +794,11 @@ static void Host_Loadgame_f (void) if (cls.demoplayback) CL_Disconnect (); +#ifdef CONFIG_MENU // remove menu if (key_dest == key_menu || key_dest == key_menu_grabbed) MR_ToggleMenu(0); +#endif key_dest = key_game; cls.demonum = -1; // stop demo loop in case this fails @@ -988,6 +998,8 @@ static void Host_Loadgame_f (void) memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles)); memset(sv.model_precache[0], 0, sizeof(sv.model_precache)); memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache)); + BufStr_Flush(prog); + while (COM_ParseToken_Simple(&t, false, false, true)) { if (!strcmp(com_token, "sv.lightstyles")) @@ -1023,44 +1035,48 @@ static void Host_Loadgame_f (void) else Con_Printf("unsupported sound %i \"%s\"\n", i, com_token); } + else if (!strcmp(com_token, "sv.buffer")) + { + if (COM_ParseToken_Simple(&t, false, false, true)) + { + i = atoi(com_token); + if (i >= 0) + { + k = STRINGBUFFER_SAVED; + if (COM_ParseToken_Simple(&t, false, false, true)) + k |= atoi(com_token); + if (!BufStr_FindCreateReplace(prog, i, k, "string")) + Con_Printf("failed to create stringbuffer %i\n", i); + } + else + Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token); + } + else + Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n"); + } else if (!strcmp(com_token, "sv.bufstr")) { - COM_ParseToken_Simple(&t, false, false, true); - i = atoi(com_token); - COM_ParseToken_Simple(&t, false, false, true); - k = atoi(com_token); - COM_ParseToken_Simple(&t, false, false, true); - stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); - // VorteX: nasty code, cleanup required - // create buffer at this index - if(!stringbuffer) - stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecordAtIndex(&prog->stringbuffersarray, i); - if (!stringbuffer) - Con_Printf("cant write string %i into buffer %i\n", k, i); + if (!COM_ParseToken_Simple(&t, false, false, true)) + Con_Printf("unexpected end of line when parsing sv.bufstr\n"); else { - // code copied from VM_bufstr_set - // expand buffer - if (stringbuffer->max_strings <= i) + i = atoi(com_token); + stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string"); + if (stringbuffer) { - char **oldstrings = stringbuffer->strings; - stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128); - while (stringbuffer->max_strings <= i) - stringbuffer->max_strings *= 2; - stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0])); - if (stringbuffer->num_strings > 0) - memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0])); - if (oldstrings) - Mem_Free(oldstrings); + if (COM_ParseToken_Simple(&t, false, false, true)) + { + k = atoi(com_token); + if (COM_ParseToken_Simple(&t, false, false, true)) + BufStr_Set(prog, stringbuffer, k, com_token); + else + Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n"); + } + else + Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n"); } - // allocate string - stringbuffer->num_strings = max(stringbuffer->num_strings, k + 1); - if(stringbuffer->strings[k]) - Mem_Free(stringbuffer->strings[k]); - stringbuffer->strings[k] = NULL; - alloclen = strlen(com_token) + 1; - stringbuffer->strings[k] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); - memcpy(stringbuffer->strings[k], com_token, alloclen); + else + Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token); } } // skip any trailing text or unrecognized commands @@ -1071,6 +1087,15 @@ static void Host_Loadgame_f (void) } Mem_Free(text); + // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace) + numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); + for (i = 0; i < numbuffers; i++) + { + if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) ) + if (stringbuffer->flags & STRINGBUFFER_TEMP) + BufStr_Del(prog, stringbuffer); + } + if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: finished\n"); @@ -1097,7 +1122,10 @@ static void Host_Name_f (void) if (Cmd_Argc () == 1) { - Con_Printf("name: %s\n", cl_name.string); + if (cmd_source == src_command) + { + Con_Printf("name: %s\n", cl_name.string); + } return; } @@ -1198,7 +1226,7 @@ static void Host_Name_f (void) PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name); if (strcmp(host_client->old_name, host_client->name)) { - if (host_client->spawned) + if (host_client->begun) SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name); strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name)); // send notification to all clients @@ -1224,7 +1252,10 @@ static void Host_Playermodel_f (void) if (Cmd_Argc () == 1) { - Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string); + if (cmd_source == src_command) + { + Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string); + } return; } @@ -1281,7 +1312,10 @@ static void Host_Playerskin_f (void) if (Cmd_Argc () == 1) { - Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string); + if (cmd_source == src_command) + { + Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string); + } return; } @@ -1316,7 +1350,7 @@ static void Host_Playerskin_f (void) PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin); if (strcmp(host_client->old_skin, host_client->playerskin)) { - //if (host_client->spawned) + //if (host_client->begun) // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin); strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin)); /*// send notification to all clients @@ -1609,8 +1643,11 @@ static void Host_Color_f(void) if (Cmd_Argc() == 1) { - Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15); - Con_Print("color <0-15> [0-15]\n"); + if (cmd_source == src_command) + { + Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15); + Con_Print("color <0-15> [0-15]\n"); + } return; } @@ -1628,8 +1665,11 @@ static void Host_TopColor_f(void) { if (Cmd_Argc() == 1) { - Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15); - Con_Print("topcolor <0-15>\n"); + if (cmd_source == src_command) + { + Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15); + Con_Print("topcolor <0-15>\n"); + } return; } @@ -1640,8 +1680,11 @@ static void Host_BottomColor_f(void) { if (Cmd_Argc() == 1) { - Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15); - Con_Print("bottomcolor <0-15>\n"); + if (cmd_source == src_command) + { + Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15); + Con_Print("bottomcolor <0-15>\n"); + } return; } @@ -1649,14 +1692,18 @@ static void Host_BottomColor_f(void) } cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"}; +cvar_t cl_rate_burstsize = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"}; static void Host_Rate_f(void) { int rate; if (Cmd_Argc() != 2) { - Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer); - Con_Print("rate \n"); + if (cmd_source == src_command) + { + Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer); + Con_Print("rate \n"); + } return; } @@ -1670,6 +1717,27 @@ static void Host_Rate_f(void) host_client->rate = rate; } +static void Host_Rate_BurstSize_f(void) +{ + int rate_burstsize; + + if (Cmd_Argc() != 2) + { + Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer); + Con_Print("rate_burstsize \n"); + return; + } + + rate_burstsize = atoi(Cmd_Argv(1)); + + if (cmd_source == src_command) + { + Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize); + return; + } + + host_client->rate_burstsize = rate_burstsize; +} /* ================== @@ -1725,7 +1793,12 @@ static void Host_Pause_f (void) } sv.paused ^= 1; - SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un"); + if (cmd_source != src_command) + SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un"); + else if(*(sv_adminnick.string)) + SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un"); + else + SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un"); // send notification to all clients MSG_WriteByte(&sv.reliable_datagram, svc_setpause); MSG_WriteByte(&sv.reliable_datagram, sv.paused); @@ -1746,7 +1819,10 @@ static void Host_PModel_f (void) if (Cmd_Argc () == 1) { - Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string); + if (cmd_source == src_command) + { + Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string); + } return; } i = atoi(Cmd_Argv(1)); @@ -1774,11 +1850,12 @@ Host_PreSpawn_f */ static void Host_PreSpawn_f (void) { - if (host_client->spawned) + if (host_client->prespawned) { - Con_Print("prespawn not valid -- already spawned\n"); + Con_Print("prespawn not valid -- already prespawned\n"); return; } + host_client->prespawned = true; if (host_client->netconnection) { @@ -1804,11 +1881,17 @@ static void Host_Spawn_f (void) client_t *client; int stats[MAX_CL_STATS]; + if (!host_client->prespawned) + { + Con_Print("Spawn not valid -- not yet prespawned\n"); + return; + } if (host_client->spawned) { Con_Print("Spawn not valid -- already spawned\n"); return; } + host_client->spawned = true; // reset name change timer again because they might want to change name // again in the first 5 seconds after connecting @@ -1936,7 +2019,17 @@ Host_Begin_f */ static void Host_Begin_f (void) { - host_client->spawned = true; + if (!host_client->spawned) + { + Con_Print("Begin not valid -- not yet spawned\n"); + return; + } + if (host_client->begun) + { + Con_Print("Begin not valid -- already begun\n"); + return; + } + host_client->begun = true; // LordHavoc: note: this code also exists in SV_DropClient if (sv.loadgame) @@ -2075,7 +2168,7 @@ static void Host_Give_f (void) case '8': case '9': // MED 01/04/97 added hipnotic give stuff - if (gamemode == GAME_HIPNOTIC) + if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) { if (t[0] == '6') { @@ -2459,9 +2552,13 @@ static void Host_PQRcon_f (void) { int n; const char *e; - lhnetaddress_t to; lhnetsocket_t *mysocket; - char peer_address[64]; + + if (Cmd_Argc() == 1) + { + Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0)); + return; + } if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0) { @@ -2473,9 +2570,7 @@ static void Host_PQRcon_f (void) n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); if (cls.netcon) - { - InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address)); - } + cls.rcon_address = cls.netcon->peeraddress; else { if (!rcon_address.string[0]) @@ -2483,10 +2578,9 @@ static void Host_PQRcon_f (void) Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); return; } - strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1); + LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer); } - LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer); - mysocket = NetConn_ChooseClientSocketForAddress(&to); + mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address); if (mysocket) { sizebuf_t buf; @@ -2499,7 +2593,7 @@ static void Host_PQRcon_f (void) MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string MSG_WriteString(&buf, Cmd_Args()); StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK)); - NetConn_Write(mysocket, buf.data, buf.cursize, &to); + NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address); SZ_Clear(&buf); } } @@ -2520,9 +2614,13 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld { int i, n; const char *e; - lhnetaddress_t to; lhnetsocket_t *mysocket; - char vabuf[1024]; + + if (Cmd_Argc() == 1) + { + Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0)); + return; + } if (!rcon_password.string || !rcon_password.string[0]) { @@ -2534,7 +2632,7 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); if (cls.netcon) - to = cls.netcon->peeraddress; + cls.rcon_address = cls.netcon->peeraddress; else { if (!rcon_address.string[0]) @@ -2542,9 +2640,9 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); return; } - LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer); + LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer); } - mysocket = NetConn_ChooseClientSocketForAddress(&to); + mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address); if (mysocket && Cmd_Args()[0]) { // simply put together the rcon packet and send it @@ -2560,13 +2658,13 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld } for (i = 0;i < MAX_RCONS;i++) if(cls.rcon_commands[i][0]) - if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i])) + if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i])) break; ++cls.rcon_trying; if(i >= MAX_RCONS) - NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later + NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos])); - cls.rcon_addresses[cls.rcon_ringpos] = to; + cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address; cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value; cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS; } @@ -2576,16 +2674,19 @@ static void Host_Rcon_f (void) // credit: taken from QuakeWorld char argbuf[1500]; dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args()); memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24); - if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n)) + if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n)) { buf[40] = ' '; strlcpy(buf + 41, argbuf, sizeof(buf) - 41); - NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to); + NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address); } } else { - NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to); + char buf[1500]; + memcpy(buf, "\377\377\377\377", 4); + dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s", n, rcon_password.string, Cmd_Args()); + NetConn_WriteString(mysocket, buf, &cls.rcon_address); } } } @@ -2686,7 +2787,6 @@ static void Host_FullInfo_f (void) // credit: taken from QuakeWorld { char key[512]; char value[512]; - char *o; const char *s; if (Cmd_Argc() != 2) @@ -2700,27 +2800,33 @@ static void Host_FullInfo_f (void) // credit: taken from QuakeWorld s++; while (*s) { - o = key; - while (*s && *s != '\\') - *o++ = *s++; - *o = 0; - + size_t len = strcspn(s, "\\"); + if (len >= sizeof(key)) { + len = sizeof(key) - 1; + } + strlcpy(key, s, len + 1); + s += len; if (!*s) { Con_Printf ("MISSING VALUE\n"); return; } + ++s; // Skip over backslash. - o = value; - s++; - while (*s && *s != '\\') - *o++ = *s++; - *o = 0; - - if (*s) - s++; + len = strcspn(s, "\\"); + if (len >= sizeof(value)) { + len = sizeof(value) - 1; + } + strlcpy(value, s, len + 1); CL_SetInfo(key, value, false, false, false, false); + + s += len; + if (!*s) + { + break; + } + ++s; // Skip over backslash. } } @@ -2947,6 +3053,8 @@ void Host_InitCommands (void) Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors"); Cvar_RegisterVariable (&cl_rate); Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed"); + Cvar_RegisterVariable (&cl_rate_burstsize); + Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed"); Cvar_RegisterVariable (&cl_pmodel); Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");