/* cl_main.c - client main loop Copyright (C) 1996-1997 Id Software, Inc. Portions Copyright (C) 1999,2000 Nelson Rush. Copyright (C) 1999,2000 contributors of the QuakeForge project Please see the file "AUTHORS" for a list of contributors This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Will have problems with isspace() when comiling w/ gcc on SGI without this */ #ifdef sgi #define _LINT #define UNDEFINE_LINT #endif #include #ifdef UNDEFINE_LINT #undef _LINT #endif #include #include #ifdef QUAKEWORLD #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef UQUAKE #include #endif #include #include #include #include #ifdef __sun /* Sun's model_t in sys/model.h conflicts w/ Quake's model_t */ #define model_t sunmodel_t #endif #ifdef _WIN32 #include #else #include #include #endif #ifdef __sun #undef model_t #endif #ifndef INADDR_LOOPBACK # define INADDR_LOOPBACK (u_int32_t)0x7f000001 #endif // we need to declare some mouse variables here, because the menu system // references them even when on a unix system. qboolean noclip_anglehack; // remnant from old quake //cvar_t rcon_password = {"rcon_password", "", CVAR_NONE}; cvar_t *rcon_password; //cvar_t rcon_address = {"rcon_address", ""}; cvar_t *rcon_address; //cvar_t cl_timeout = {"cl_timeout", "60"}; cvar_t *cl_timeout; // these two are not intended to be set directly //cvar_t cl_name = {"_cl_name", "player", CVAR_ARCHIVE}; //cvar_t cl_color = {"_cl_color", "0", CVAR_ARCHIVE}; cvar_t *cl_name; cvar_t *cl_color; //cvar_t cl_shownet = {"cl_shownet","0"}; // can be 0, 1, or 2 //cvar_t cl_nolerp = {"cl_nolerp","0"}; cvar_t *cl_shownet; cvar_t *cl_nolerp; //cvar_t cl_sbar = {"cl_sbar", "1", CVAR_ARCHIVE}; //cvar_t cl_hudswap = {"cl_hudswap", "1", CVAR_ARCHIVE}; //cvar_t cl_maxfps = {"cl_maxfps", "0", CVAR_ARCHIVE}; cvar_t *cl_sbar; cvar_t *cl_hudswap; cvar_t *cl_maxfps; //cvar_t lookspring = {"lookspring","0", CVAR_ARCHIVE}; //cvar_t lookstrafe = {"lookstrafe","0", CVAR_ARCHIVE}; //cvar_t sensitivity = {"sensitivity","3", CVAR_ARCHIVE}; cvar_t *lookspring; cvar_t *lookstrafe; cvar_t *sensitivity; //cvar_t m_pitch = {"m_pitch","0.022", CVAR_ARCHIVE}; //cvar_t m_yaw = {"m_yaw","0.022", CVAR_ARCHIVE}; //cvar_t m_forward = {"m_forward","1", CVAR_ARCHIVE}; //cvar_t m_side = {"m_side","0.8", CVAR_ARCHIVE}; cvar_t *m_pitch; cvar_t *m_yaw; cvar_t *m_forward; cvar_t *m_side; //cvar_t entlatency = {"entlatency", "20"}; //cvar_t cl_predict_players = {"cl_predict_players", "1"}; //cvar_t cl_predict_players2 = {"cl_predict_players2", "1"}; //cvar_t cl_solid_players = {"cl_solid_players", "1"}; cvar_t *entlatency; cvar_t *cl_predict_players; cvar_t *cl_predict_players2; cvar_t *cl_solid_players; //cvar_t localid = {"localid", ""}; cvar_t *localid; #ifdef QUAKEWORLD static qboolean allowremotecmd = true; #endif // // info mirrors // //cvar_t password = {"password", "", CVAR_USERINFO|CVAR_SERVERINFO }; cvar_t *password; //cvar_t spectator = {"spectator", "",CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *spectator; //cvar_t name = {"name","unnamed", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *name; //cvar_t team = {"team","", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *team; //cvar_t skin = {"skin","", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *skin; //cvar_t topcolor = {"topcolor","0", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *topcolor; //cvar_t bottomcolor = {"bottomcolor","0", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *bottomcolor; //cvar_t rate = {"rate","2500", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *rate; //cvar_t noaim = {"noaim","0", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *noaim; //cvar_t msg = {"msg","1", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO}; cvar_t *msg; //extern cvar_t cl_hightrack; extern cvar_t *cl_hightrack; client_static_t cls; client_state_t cl; entity_state_t cl_baselines[MAX_EDICTS]; // FIXME: put these on hunk? efrag_t cl_efrags[MAX_EFRAGS]; entity_t cl_static_entities[MAX_STATIC_ENTITIES]; lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES]; dlight_t cl_dlights[MAX_DLIGHTS]; // refresh list // this is double buffered so the last frame // can be scanned for oldorigins of trailing objects int cl_numvisedicts, cl_oldnumvisedicts; entity_t *cl_visedicts, *cl_oldvisedicts; entity_t cl_visedicts_list[2][MAX_VISEDICTS]; double connect_time = -1; // for connection retransmits quakeparms_t host_parms; qboolean host_initialized; // true if into command execution qboolean nomaster; double host_frametime; int host_framecount; int host_hunklevel; byte *host_basepal; byte *host_colormap; netadr_t master_adr; // address of the master server //cvar_t host_speeds = {"host_speeds","0"}; // set for running times cvar_t *host_speeds; //cvar_t show_fps = {"show_fps","0"}; // set for running times cvar_t *show_fps; //cvar_t developer = {"developer","0"}; cvar_t *developer; void Master_Connect_f (void); char *server_version = NULL; // version of server we connected to char emodel_name[] = "emodel"; char pmodel_name[] = "pmodel"; char prespawn_name[] = "prespawn %i 0 %i"; char modellist_name[] = "modellist %i %i"; char soundlist_name[] = "soundlist %i %i"; #ifdef QUAKEWORLD void CL_BeginServerConnect(void) { connect_time = 0; CL_CheckForResend(); } #endif /* ================= CL_Changing_f Just sent as a hint to the client that they should drop to full console ================= */ void CL_Changing_f (void) { #ifdef QUAKEWORLD if (cls.download) // don't change when downloading return; #endif S_StopAllSounds (true); cl.intermission = 0; cls.state = ca_connected; // not active anymore, but not disconnected Con_Printf ("\nChanging map...\n"); } #ifdef QUAKEWORLD /* ================= CL_CheckForResend Resend a connect message if the last one has timed out ================= */ void CL_CheckForResend (void) { netadr_t adr; char data[2048]; double t1, t2; if (connect_time == -1) return; if (cls.state != ca_disconnected) return; if (connect_time && realtime - connect_time < 5.0) return; t1 = Sys_DoubleTime (); if (!NET_StringToAdr (cls.servername, &adr)) { Con_Printf ("Bad server address\n"); connect_time = -1; return; } if (!NET_IsClientLegal(&adr)) { Con_Printf ("Illegal server address\n"); connect_time = -1; return; } if (adr.port == 0) adr.port = BigShort (27500); t2 = Sys_DoubleTime (); connect_time = realtime+t2-t1; // for retransmit requests Con_Printf ("Connecting to %s...\n", cls.servername); snprintf(data, sizeof(data), "%c%c%c%cgetchallenge\n", 255, 255, 255, 255); NET_SendPacket (strlen(data), data, adr); } #endif /* ===================== CL_ClearState ===================== */ void CL_ClearState (void) { int i; S_StopAllSounds (true); #ifdef QUAKEWORLD Host_ClearMemory (); #elif UQUAKE if (!sv.active) Host_ClearMemory (); #endif CL_ClearTEnts (); // wipe the entire cl structure memset (&cl, 0, sizeof(cl)); SZ_Clear (&cls.netchan.message); // clear other arrays memset (cl_efrags, 0, sizeof(cl_efrags)); #ifdef UQUAKE memset (cl_entities, 0, sizeof(cl_entities)); memset (cl_temp_entities, 0, sizeof(cl_temp_entities)); #endif memset (cl_dlights, 0, sizeof(cl_dlights)); memset (cl_lightstyle, 0, sizeof(cl_lightstyle)); CL_ClearTEnts (); // // allocate the efrags and chain together into a free list // cl.free_efrags = cl_efrags; for (i=0 ; i [0-13]\n"); return; } if (Cmd_Argc() == 2) top = bottom = atoi(Cmd_Argv(1)); else { top = atoi(Cmd_Argv(1)); bottom = atoi(Cmd_Argv(2)); } top &= 15; if (top > 13) top = 13; bottom &= 15; if (bottom > 13) bottom = 13; snprintf(num, sizeof(num), "%i", top); Cvar_Set ("topcolor", num); snprintf(num, sizeof(num), "%i", bottom); Cvar_Set ("bottomcolor", num); } /* ================ CL_Connect_f ================ */ void CL_Connect_f (void) { char *server; Con_Printf("Cmd_Argc(): %d\n", Cmd_Argc()); Con_Printf("Args: "); Cmd_Echo_f(); if (Cmd_Argc() != 2) { Con_Printf ("usage: connect \n"); return; } server = Cmd_Argv (1); CL_Disconnect (); strncpy (cls.servername, server, sizeof(cls.servername)-1); CL_BeginServerConnect(); } /* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ void CL_ConnectionlessPacket (void) { char *s; int c; MSG_BeginReading (); MSG_ReadLong (); // skip the -1 c = MSG_ReadByte (); if (!cls.demoplayback) Con_Printf ("%s: ", NET_AdrToString (net_from)); // Con_DPrintf ("%s", net_message.data + 5); if (c == S2C_CONNECTION) { Con_Printf ("connection\n"); if (cls.state >= ca_connected) { if (!cls.demoplayback) Con_Printf ("Dup connect received. Ignored.\n"); return; } Netchan_Setup (&cls.netchan, net_from, cls.qport); MSG_WriteChar (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, "new"); cls.state = ca_connected; Con_Printf ("Connected.\n"); allowremotecmd = false; // localid required now for remote cmds return; } // remote command from gui front end if (c == A2C_CLIENT_COMMAND) { char cmdtext[2048]; Con_Printf ("client command\n"); if ((*(unsigned *)net_from.ip != *(unsigned *)net_local_adr.ip && *(unsigned *)net_from.ip != htonl(INADDR_LOOPBACK)) ) { Con_Printf ("Command packet from remote host. Ignored.\n"); return; } #ifdef _WIN32 ShowWindow (mainwindow, SW_RESTORE); SetForegroundWindow (mainwindow); #endif s = MSG_ReadString (); strncpy(cmdtext, s, sizeof(cmdtext) - 1); cmdtext[sizeof(cmdtext) - 1] = 0; s = MSG_ReadString (); while (*s && isspace((int)*s)) s++; while (*s && isspace((int)s[strlen(s) - 1])) s[strlen(s) - 1] = 0; if (!allowremotecmd && (!*localid->string || strcmp(localid->string, s))) { if (!*localid->string) { Con_Printf("===========================\n"); Con_Printf("Command packet received from local host, but no " "localid has been set. You may need to upgrade your server " "browser.\n"); Con_Printf("===========================\n"); return; } Con_Printf("===========================\n"); Con_Printf("Invalid localid on command packet received from local host. " "\n|%s| != |%s|\n" "You may need to reload your server browser and QuakeWorld.\n", s, localid->string); Con_Printf("===========================\n"); Cvar_Set("localid", ""); return; } Cbuf_AddText (cmdtext); allowremotecmd = false; return; } // print command from somewhere if (c == A2C_PRINT) { Con_Printf ("print\n"); s = MSG_ReadString (); Con_Print (s); return; } // ping from somewhere if (c == A2A_PING) { char data[6]; Con_Printf ("ping\n"); data[0] = 0xff; data[1] = 0xff; data[2] = 0xff; data[3] = 0xff; data[4] = A2A_ACK; data[5] = 0; NET_SendPacket (6, data, net_from); return; } if (c == S2C_CHALLENGE) { Con_Printf ("challenge\n"); s = MSG_ReadString (); cls.challenge = atoi(s); CL_SendConnectPacket (); return; } #if 0 if (c == svc_disconnect) { Con_Printf ("disconnect\n"); Host_EndGame ("Server disconnected"); return; } #endif Con_Printf ("unknown: %c\n", c); } #endif /* ===================== CL_Disconnect Sends a disconnect message to the server This is also called on Host_Error, so it shouldn't cause any errors ===================== */ void CL_Disconnect (void) { #ifdef QUAKEWORLD byte final[10]; #endif connect_time = -1; #ifdef _WIN32 SetWindowText (mainwindow, "QuakeWorld: disconnected"); #endif // stop sounds (especially looping!) S_StopAllSounds (true); // bring the console down and fade the colors back to normal // SCR_BringDownConsole (); // if running a local server, shut it down if (cls.demoplayback) CL_StopPlayback (); else if (cls.state != ca_disconnected) // else if (cls.state >= ca_connected) { if (cls.demorecording) CL_Stop_f (); #ifdef QUAKEWORLD final[0] = clc_stringcmd; strcpy (final+1, "drop"); Netchan_Transmit (&cls.netchan, 6, final); Netchan_Transmit (&cls.netchan, 6, final); Netchan_Transmit (&cls.netchan, 6, final); #else Con_DPrintf ("Sending clc_disconnect\n"); SZ_Clear (&cls.netchan.message); MSG_WriteByte (&cls.netchan.message, clc_disconnect); NET_SendUnreliableMessage (cls.netcon, &cls.netchan.message); SZ_Clear (&cls.netchan.message); NET_Close (cls.netcon); #endif cls.state = ca_disconnected; cls.demoplayback = cls.demorecording = cls.timedemo = false; #ifdef UQUAKE if (sv.active) SV_Shutdown(false); #endif } #ifdef QUAKEWORLD Cam_Reset(); if (cls.download) { Qclose(cls.download); cls.download = NULL; } CL_StopUpload(); #endif #ifdef UQUAKE cls.demoplayback = cls.timedemo = false; cls.signon = 0; #endif } void CL_Disconnect_f (void) { CL_Disconnect (); #ifdef UQUAKE if (sv.active) SV_Shutdown (false); #endif } #ifdef QUAKEWORLD /* ===================== CL_Download_f ===================== */ void CL_Download_f (void) { char *p, *q; if (cls.state == ca_disconnected) { Con_Printf ("Must be connected.\n"); return; } if (Cmd_Argc() != 2) { Con_Printf ("Usage: download \n"); return; } snprintf(cls.downloadname, sizeof(cls.downloadname), "%s/%s", com_gamedir, Cmd_Argv(1)); p = cls.downloadname; for (;;) { if ((q = strchr(p, '/')) != NULL) { *q = 0; Sys_mkdir(cls.downloadname); *q = '/'; p = q + 1; } else break; } strcpy(cls.downloadtempname, cls.downloadname); cls.download = Qopen (cls.downloadname, "wb"); cls.downloadtype = dl_single; MSG_WriteByte (&cls.netchan.message, clc_stringcmd); SZ_Print (&cls.netchan.message, va("download %s\n",Cmd_Argv(1))); } #endif /* ===================== CL_EstablishConnection Host should be either "local" or a net address to be passed on ===================== */ void CL_EstablishConnection (char *host) { if (cls.state == ca_dedicated) return; if (cls.demoplayback) return; CL_Disconnect (); #ifdef UQUAKE cls.netcon = NET_Connect (host); if (!cls.netcon) Host_Error ("CL_Connect: connect failed\n"); #endif Con_DPrintf ("CL_EstablishConnection: connected to %s\n", host); cls.demonum = -1; // not in the demo loop now cls.state = ca_connected; #ifdef UQUAKE cls.signon = 0; // need all the signon messages before playing #endif } /* ================== CL_FullInfo_f Allow clients to change userinfo ================== Casey was here :) */ void CL_FullInfo_f (void) { char key[512]; char value[512]; char *o; char *s; if (Cmd_Argc() != 2) { Con_Printf ("fullinfo \n"); return; } s = Cmd_Argv(1); if (*s == '\\') s++; while (*s) { o = key; while (*s && *s != '\\') *o++ = *s++; *o = 0; if (!*s) { Con_Printf ("MISSING VALUE\n"); return; } o = value; s++; while (*s && *s != '\\') *o++ = *s++; *o = 0; if (*s) s++; if (!stricmp(key, pmodel_name) || !stricmp(key, emodel_name)) continue; #ifdef QUAKEWORLD Info_SetValueForKey (cls.userinfo, key, value, MAX_INFO_STRING); #endif } } #ifdef QUAKEWORLD /* ================== CL_FullServerinfo_f Sent by server when serverinfo changes ================== */ void CL_FullServerinfo_f (void) { char *p; if (Cmd_Argc() != 2) { Con_Printf ("usage: fullserverinfo \n"); return; } strcpy (cl.serverinfo, Cmd_Argv(1)); if ((p = Info_ValueForKey(cl.serverinfo, "*qf_version")) && *p) { if (!server_version) Con_Printf("QuakeForge Version %s Server\n", p); server_version = strdup(p); } else if ((p = Info_ValueForKey(cl.serverinfo, "*version")) && *p) { if (!server_version) Con_Printf("Version %s Server\n", p); server_version = strdup(p); } } #endif #ifdef UQUAKE /* =============== CL_LerpPoint Determines the fraction between the last two messages that the objects should be put at. =============== */ float CL_LerpPoint (void) { float f, frac; f = cl.mtime[0] - cl.mtime[1]; if (!f || cl_nolerp->value || cls.timedemo || sv.active) { cl.time = cl.mtime[0]; return 1; } if (f > 0.1) { // dropped packet, or start of demo cl.mtime[1] = cl.mtime[0] - 0.1; f = 0.1; } frac = (cl.time - cl.mtime[1]) / f; //Con_Printf ("frac: %f\n",frac); if (frac < 0) { if (frac < -0.01) { SetPal(1); cl.time = cl.mtime[1]; // Con_Printf ("low frac\n"); } frac = 0; } else if (frac > 1) { if (frac > 1.01) { SetPal(2); cl.time = cl.mtime[0]; // Con_Printf ("high frac\n"); } frac = 1; } else SetPal(0); return frac; } #endif /* ===================== CL_NextDemo Called to play the next demo in the demo loop ===================== */ void CL_NextDemo (void) { char str[1024]; if (cls.demonum == -1) return; // don't play demos #ifdef UQUAKE SCR_BeginLoadingPlaque (); #endif if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS) { cls.demonum = 0; if (!cls.demos[cls.demonum][0]) { Con_Printf ("No demos listed with startdemos\n"); cls.demonum = -1; return; } } snprintf(str, sizeof(str),"playdemo %s\n", cls.demos[cls.demonum]); Cbuf_InsertText (str); cls.demonum++; } #ifdef QUAKEWORLD /* ==================== CL_Packet_f packet Contents allows \n escape character ==================== */ void CL_Packet_f (void) { char send[2048]; int i, l; char *in, *out; netadr_t adr; if (Cmd_Argc() != 3) { Con_Printf ("packet \n"); return; } if (!NET_StringToAdr (Cmd_Argv(1), &adr)) { Con_Printf ("Bad address\n"); return; } in = Cmd_Argv(2); out = send+4; send[0] = send[1] = send[2] = send[3] = 0xff; l = strlen (in); for (i=0 ; imodel) { Con_Printf ("EMPTY\n"); continue; } Con_Printf ("%s:%2i (%5.1f,%5.1f,%5.1f) [%5.1f %5.1f %5.1f]\n" ,ent->model->name,ent->frame, ent->origin[0], ent->origin[1], ent->origin[2], ent->angles[0], ent->angles[1], ent->angles[2]); } } #endif /* ================== CL_Quit_f ================== */ void CL_Quit_f (void) { #ifdef QUAKEWORLD if (1 /* key_dest != key_console */ /* && cls.state != ca_dedicated */) { M_Menu_Quit_f (); return; } #endif CL_Disconnect (); Sys_Quit (); } #ifdef QUAKEWORLD /* ===================== CL_Rcon_f Send the rest of the command line over as an unconnected command. ===================== */ void CL_Rcon_f (void) { char message[1024]; int i; netadr_t to; if (!rcon_password->string) { Con_Printf ("You must set 'rcon_password' before\n" "issuing an rcon command.\n"); return; } message[0] = 255; message[1] = 255; message[2] = 255; message[3] = 255; message[4] = 0; strcat (message, "rcon "); strcat (message, rcon_password->string); strcat (message, " "); for (i=1 ; i= ca_connected) to = cls.netchan.remote_address; else { if (!strlen(rcon_address->string)) { Con_Printf ("You must either be connected,\n" "or set the 'rcon_address' cvar\n" "to issue rcon commands\n"); return; } NET_StringToAdr (rcon_address->string, &to); } NET_SendPacket (strlen(message)+1, message , to); } #endif #ifdef UQUAKE /* =============== CL_ReadFromServer Read all incoming data from the server =============== */ int CL_ReadFromServer (void) { int ret; cl.oldtime = cl.time; cl.time += host_frametime; do { ret = CL_GetMessage (); if (ret == -1) Host_Error ("CL_ReadFromServer: lost server connection"); if (!ret) break; cl.last_received_message = realtime; CL_ParseServerMessage (); } while (ret && cls.state >= ca_connected); if (cl_shownet->value) Con_Printf ("\n"); CL_RelinkEntities (); CL_UpdateTEnts (); // // bring the links up to date // return 0; } #endif #ifdef QUAKEWORLD /* ================= CL_ReadPackets ================= */ void CL_ReadPackets (void) { // while (NET_GetPacket ()) while (CL_GetMessage()) { // // remote command packet // if (*(int *)net_message.data == -1) { CL_ConnectionlessPacket (); continue; } if (net_message.cursize < 8) { Con_Printf ("%s: Runt packet\n",NET_AdrToString(net_from)); continue; } // // packet from server // if (!cls.demoplayback && !NET_CompareAdr (net_from, cls.netchan.remote_address)) { Con_DPrintf ("%s:sequenced packet without connection\n" ,NET_AdrToString(net_from)); continue; } if (!Netchan_Process(&cls.netchan)) continue; // wasn't accepted for some reason CL_ParseServerMessage (); // if (cls.demoplayback && cls.state >= ca_active && !CL_DemoBehind()) // return; } // // check timeout // if (cls.state >= ca_connected && realtime - cls.netchan.last_received > cl_timeout->value) { Con_Printf ("\nServer connection timed out.\n"); CL_Disconnect (); return; } } #endif /* ================= CL_Reconnect_f The server is changing levels ================= */ void CL_Reconnect_f (void) { #ifdef QUAKEWORLD if (cls.download) // don't change when downloading return; #endif S_StopAllSounds (true); if (cls.state == ca_connected) { Con_Printf ("reconnecting...\n"); MSG_WriteChar (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, "new"); return; } #ifdef QUAKEWORLD if (!*cls.servername) { Con_Printf("No server to reconnect to...\n"); return; } #endif CL_Disconnect(); #ifdef QUAKEWORLD CL_BeginServerConnect(); #endif } #ifdef UQUAKE /* =============== CL_RelinkEntities =============== */ void CL_RelinkEntities (void) { entity_t *ent; int i, j; float frac, f, d; vec3_t delta; float bobjrotate; vec3_t oldorg; dlight_t *dl; // determine partial update time frac = CL_LerpPoint (); cl_numvisedicts = 0; // // interpolate player info // for (i=0 ; i<3 ; i++) cl.velocity[i] = cl.mvelocity[1][i] + frac * (cl.mvelocity[0][i] - cl.mvelocity[1][i]); if (cls.demoplayback) { // interpolate the angles for (j=0 ; j<3 ; j++) { d = cl.mviewangles[0][j] - cl.mviewangles[1][j]; if (d > 180) d -= 360; else if (d < -180) d += 360; cl.viewangles[j] = cl.mviewangles[1][j] + frac*d; } } bobjrotate = anglemod(100*cl.time); // start on the entity after the world for (i=1,ent=cl_entities+1 ; imodel) { // empty slot if (ent->forcelink) R_RemoveEfrags (ent); // just became empty continue; } // if the object wasn't included in the last packet, remove it if (ent->msgtime != cl.mtime[0]) { ent->model = NULL; continue; } VectorCopy (ent->origin, oldorg); if (ent->forcelink) { // the entity was not updated in the last message // so move to the final spot VectorCopy (ent->msg_origins[0], ent->origin); VectorCopy (ent->msg_angles[0], ent->angles); } else { // if the delta is large, assume a teleport and don't lerp f = frac; for (j=0 ; j<3 ; j++) { delta[j] = ent->msg_origins[0][j] - ent->msg_origins[1][j]; if (delta[j] > 100 || delta[j] < -100) f = 1; // assume a teleportation, not a motion } // interpolate the origin and angles for (j=0 ; j<3 ; j++) { ent->origin[j] = ent->msg_origins[1][j] + f*delta[j]; d = ent->msg_angles[0][j] - ent->msg_angles[1][j]; if (d > 180) d -= 360; else if (d < -180) d += 360; ent->angles[j] = ent->msg_angles[1][j] + f*d; } } // rotate binary objects locally if (ent->model->flags & EF_ROTATE) ent->angles[1] = bobjrotate; if (ent->effects & EF_BRIGHTFIELD) R_EntityParticles (ent); #ifdef QUAKE2 if (ent->effects & EF_DARKFIELD) R_DarkFieldParticles (ent); #endif if (ent->effects & EF_MUZZLEFLASH) { vec3_t fv, rv, uv; dl = CL_AllocDlight (i); VectorCopy (ent->origin, dl->origin); dl->origin[2] += 16; AngleVectors (ent->angles, fv, rv, uv); VectorMA (dl->origin, 18, fv, dl->origin); dl->radius = 200 + (rand()&31); dl->minlight = 32; dl->die = cl.time + 0.1; } if (ent->effects & EF_BRIGHTLIGHT) { dl = CL_AllocDlight (i); VectorCopy (ent->origin, dl->origin); dl->origin[2] += 16; dl->radius = 400 + (rand()&31); dl->die = cl.time + 0.001; } if (ent->effects & EF_DIMLIGHT) { dl = CL_AllocDlight (i); VectorCopy (ent->origin, dl->origin); dl->radius = 200 + (rand()&31); dl->die = cl.time + 0.001; } #ifdef QUAKE2 if (ent->effects & EF_DARKLIGHT) { dl = CL_AllocDlight (i); VectorCopy (ent->origin, dl->origin); dl->radius = 200.0 + (rand()&31); dl->die = cl.time + 0.001; dl->dark = true; } if (ent->effects & EF_LIGHT) { dl = CL_AllocDlight (i); VectorCopy (ent->origin, dl->origin); dl->radius = 200; dl->die = cl.time + 0.001; } #endif if (ent->model->flags & EF_GIB) R_RocketTrail (oldorg, ent->origin, 2); else if (ent->model->flags & EF_ZOMGIB) R_RocketTrail (oldorg, ent->origin, 4); else if (ent->model->flags & EF_TRACER) R_RocketTrail (oldorg, ent->origin, 3); else if (ent->model->flags & EF_TRACER2) R_RocketTrail (oldorg, ent->origin, 5); else if (ent->model->flags & EF_ROCKET) { R_RocketTrail (oldorg, ent->origin, 0); dl = CL_AllocDlight (i); VectorCopy (ent->origin, dl->origin); dl->radius = 200; dl->die = cl.time + 0.01; } else if (ent->model->flags & EF_GRENADE) R_RocketTrail (oldorg, ent->origin, 1); else if (ent->model->flags & EF_TRACER3) R_RocketTrail (oldorg, ent->origin, 6); ent->forcelink = false; if (i == cl.playernum + 1 && !cl_chasecam->value) continue; #ifdef QUAKE2 if ( ent->effects & EF_NODRAW ) continue; #endif if (cl_numvisedicts < MAX_VISEDICTS) { cl_visedicts[cl_numvisedicts] = *ent; cl_numvisedicts++; } } } #endif #ifdef QUAKEWORLD /* ======================= CL_SendConnectPacket called by CL_Connect_f and CL_CheckResend ====================== */ void CL_SendConnectPacket (void) { netadr_t adr; char data[2048]; double t1, t2; // JACK: Fixed bug where DNS lookups would cause two connects real fast // Now, adds lookup time to the connect time. // Should I add it to realtime instead?!?! if (cls.state != ca_disconnected) return; t1 = Sys_DoubleTime (); if (!NET_StringToAdr (cls.servername, &adr)) { Con_Printf ("Bad server address\n"); connect_time = -1; return; } if (!NET_IsClientLegal(&adr)) { Con_Printf ("Illegal server address\n"); connect_time = -1; return; } if (adr.port == 0) adr.port = BigShort (27500); t2 = Sys_DoubleTime (); connect_time = realtime+t2-t1; // for retransmit requests cls.qport = Cvar_VariableValue("qport"); // Arrgh, this was not in the old binary only release, and eats up // far too much of the 196 chars in the userinfo space, leaving nothing // for player use, thus, its commented out for the moment.. // //Info_SetValueForStarKey (cls.userinfo, "*ip", NET_AdrToString(adr), MAX_INFO_STRING); // Con_Printf ("Connecting to %s...\n", cls.servername); snprintf(data, sizeof(data), "%c%c%c%cconnect %i %i %i \"%s\"\n", 255, 255, 255, 255, PROTOCOL_VERSION, cls.qport, cls.challenge, cls.userinfo); NET_SendPacket (strlen(data), data, adr); } #endif #ifdef UQUAKE /* ================= CL_SendCmd ================= */ void CL_SendCmd (void) { usercmd_t cmd; if (cls.state < ca_connected) return; if (cls.signon == SIGNONS) { // get basic movement from keyboard CL_BaseMove (&cmd); // allow mice or other external controllers to add to the move //(*IN_Move) (&cmd); IN_Move(&cmd); // send the unreliable message CL_SendMove (&cmd); } if (cls.demoplayback) { SZ_Clear (&cls.netchan.message); return; } // send the reliable message if (!cls.netchan.message.cursize) return; // no message at all if (!NET_CanSendMessage (cls.netcon)) { Con_DPrintf ("CL_WriteToServer: can't send\n"); return; } if (NET_SendMessage (cls.netcon, &cls.netchan.message) == -1) Host_Error ("CL_WriteToServer: lost server connection"); SZ_Clear (&cls.netchan.message); } #endif #ifdef QUAKEWORLD /* ================== CL_SetInfo_f Allow clients to change userinfo ================== */ void CL_SetInfo_f (void) { if (Cmd_Argc() == 1) { Info_Print (cls.userinfo); return; } if (Cmd_Argc() != 3) { Con_Printf ("usage: setinfo [ ]\n"); return; } if (!stricmp(Cmd_Argv(1), pmodel_name) || !strcmp(Cmd_Argv(1), emodel_name)) return; Info_SetValueForKey (cls.userinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_INFO_STRING); if (cls.state >= ca_connected) Cmd_ForwardToServer (); } #endif #ifdef UQUAKE /* ===================== CL_SignonReply An svc_signonnum has been received, perform a client side setup ===================== */ void CL_SignonReply (void) { char str[8192]; Con_DPrintf ("CL_SignonReply: %i\n", cls.signon); switch (cls.signon) { case 1: MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, "prespawn"); cls.state = ca_onserver; break; case 2: MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, va("name \"%s\"\n", cl_name->string)); MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, va("color %i %i\n", ((int)cl_color->value)>>4, ((int)cl_color->value)&15)); MSG_WriteByte (&cls.netchan.message, clc_stringcmd); snprintf(str, sizeof(str), "spawn %s", cls.spawnparms); MSG_WriteString (&cls.netchan.message, str); break; case 3: MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, "begin"); Cache_Report (); // print remaining memory break; case 4: SCR_EndLoadingPlaque (); // allow normal screen updates cls.state = ca_active; break; } } #endif #ifdef QUAKEWORLD /* ==================== CL_User_f user Dump userdata / masterdata for a user ==================== */ void CL_User_f (void) { int uid; int i; if (Cmd_Argc() != 2) { Con_Printf ("Usage: user \n"); return; } uid = atoi(Cmd_Argv(1)); for (i=0 ; i /* ================= CL_Minimize_f ================= */ void CL_Windows_f (void) { // if (modestate == MS_WINDOWED) // ShowWindow(mainwindow, SW_MINIMIZE); // else SendMessage(mainwindow, WM_SYSKEYUP, VK_TAB, 1 | (0x0F << 16) | (1<<29)); } #endif /* =============== SetPal Debugging tool, just flashes the screen =============== */ void SetPal (int i) { #if 0 static int old; byte pal[768]; int c; if (i == old) return; old = i; if (i==0) VID_SetPalette (host_basepal); else if (i==1) { for (c=0 ; c<768 ; c+=3) { pal[c] = 0; pal[c+1] = 255; pal[c+2] = 0; } VID_SetPalette (pal); } else { for (c=0 ; c<768 ; c+=3) { pal[c] = 0; pal[c+1] = 0; pal[c+2] = 255; } VID_SetPalette (pal); } #endif } /* ================= CL_Init ================= */ void CL_Init (void) { #ifdef UQUAKE SZ_Alloc (&cls.netchan.message, 1024); #endif cls.state = ca_disconnected; #ifdef QUAKEWORLD Info_SetValueForKey (cls.userinfo, "name", "unnamed", MAX_INFO_STRING); Info_SetValueForKey (cls.userinfo, "topcolor", "0", MAX_INFO_STRING); Info_SetValueForKey (cls.userinfo, "bottomcolor", "0", MAX_INFO_STRING); Info_SetValueForKey (cls.userinfo, "rate", "2500", MAX_INFO_STRING); Info_SetValueForKey (cls.userinfo, "msg", "1", MAX_INFO_STRING); Info_SetValueForStarKey(cls.userinfo, "*qf_ver", QF_VERSION, MAX_INFO_STRING); Info_SetValueForStarKey(cls.userinfo, "*ver", VERSION, MAX_INFO_STRING); #endif CL_InitInput (); CL_InitTEnts (); #ifdef QUAKEWORLD CL_InitPrediction (); CL_InitCam (); Pmove_Init (); #endif // // register our commands // Cmd_AddCommand ("changing", CL_Changing_f); #ifdef UQUAKE Cmd_AddCommand ("entities", CL_PrintEntities_f); #endif Cmd_AddCommand ("disconnect", CL_Disconnect_f); Cmd_AddCommand ("record", CL_Record_f); #ifdef QUAKEWORLD Cmd_AddCommand ("version", CL_Version_f); Cmd_AddCommand ("rerecord", CL_ReRecord_f); #endif Cmd_AddCommand ("stop", CL_Stop_f); Cmd_AddCommand ("playdemo", CL_PlayDemo_f); Cmd_AddCommand ("timedemo", CL_TimeDemo_f); #ifdef QUAKEWORLD Cmd_AddCommand ("skins", Skin_Skins_f); Cmd_AddCommand ("allskins", Skin_AllSkins_f); Cmd_AddCommand ("quit", CL_Quit_f); Cmd_AddCommand ("connect", CL_Connect_f); Cmd_AddCommand ("reconnect", CL_Reconnect_f); Cmd_AddCommand ("rcon", CL_Rcon_f); Cmd_AddCommand ("packet", CL_Packet_f); Cmd_AddCommand ("user", CL_User_f); Cmd_AddCommand ("users", CL_Users_f); Cmd_AddCommand ("setinfo", CL_SetInfo_f); Cmd_AddCommand ("fullinfo", CL_FullInfo_f); Cmd_AddCommand ("fullserverinfo", CL_FullServerinfo_f); Cmd_AddCommand ("color", CL_Color_f); Cmd_AddCommand ("download", CL_Download_f); Cmd_AddCommand ("nextul", CL_NextUpload); Cmd_AddCommand ("stopul", CL_StopUpload); Cmd_AddCommand ("kill", NULL); Cmd_AddCommand ("pause", NULL); Cmd_AddCommand ("say", NULL); Cmd_AddCommand ("say_team", NULL); #endif // // forward to server commands // Cmd_AddCommand ("serverinfo", NULL); // // Windows commands // #ifdef _WIN32 Cmd_AddCommand ("windows", CL_Windows_f); #endif } void CL_InitCvars() { #ifdef QUAKEWORLD extern cvar_t *baseskin; extern cvar_t *noskins; #endif // // register our cvars // show_fps = Cvar_Get ("show_fps","0",0,"None"); host_speeds = Cvar_Get ("host_speeds","0",0,"None"); developer = Cvar_Get ("developer","0",0,"None"); cl_warncmd = Cvar_Get ("cl_warncmd","0",0,"None"); cl_name = Cvar_Get ("_cl_name","player",CVAR_ARCHIVE,"None"); cl_color = Cvar_Get ("_cl_color","0",CVAR_ARCHIVE,"None"); cl_upspeed = Cvar_Get ("cl_upspeed","200",0,"None"); cl_forwardspeed = Cvar_Get ("cl_forwardspeed","200",CVAR_ARCHIVE, "None"); cl_backspeed = Cvar_Get ("cl_backspeed","200",CVAR_ARCHIVE,"None"); cl_sidespeed = Cvar_Get ("cl_sidespeed","350",0,"None"); cl_movespeedkey = Cvar_Get ("cl_movespeedkey","2.0",0,"None"); cl_yawspeed = Cvar_Get ("cl_yawspeed","140",0,"None"); cl_pitchspeed = Cvar_Get ("cl_pitchspeed","150",0,"None"); cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey","1.5",0,"None"); cl_shownet = Cvar_Get ("cl_shownet","0",0,"None"); cl_nolerp = Cvar_Get ("cl_nolerp","0",0,"None"); cl_sbar = Cvar_Get ("cl_sbar","1",CVAR_ARCHIVE,"None"); cl_hudswap = Cvar_Get ("cl_hudswap","1",CVAR_ARCHIVE,"None"); cl_maxfps = Cvar_Get ("cl_maxfps","0",CVAR_ARCHIVE,"None"); cl_timeout = Cvar_Get ("cl_timeout","60",0,"None"); lookspring = Cvar_Get ("lookspring","0",CVAR_ARCHIVE,"None"); lookstrafe = Cvar_Get ("lookstrafe","0",CVAR_ARCHIVE,"None"); sensitivity = Cvar_Get ("sensitivity","3",CVAR_ARCHIVE,"None"); m_pitch = Cvar_Get ("m_pitch","0.022",CVAR_ARCHIVE,"None"); m_yaw = Cvar_Get ("m_yaw","0.022",CVAR_ARCHIVE,"None"); m_forward = Cvar_Get ("m_forward","1",CVAR_ARCHIVE,"None"); m_side = Cvar_Get ("m_side","0.8",CVAR_ARCHIVE,"None"); rcon_password = Cvar_Get ("rcon_password","",0,"None"); rcon_address = Cvar_Get ("rcon_address","",0,"None"); entlatency = Cvar_Get ("entlatency","20",0,"None"); cl_predict_players2 = Cvar_Get ("cl_predict_players2","1",0,"None"); cl_predict_players = Cvar_Get ("cl_predict_players","1",0,"None"); cl_solid_players = Cvar_Get ("cl_solid_players","1",0,"None"); localid = Cvar_Get ("localid","",0,"None"); #ifdef QUAKEWORLD baseskin = Cvar_Get ("baseskin","base",0,"None"); noskins = Cvar_Get ("noskins","0",0,"None"); // // info mirrors // name = Cvar_Get ("name","unnamed", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO,"None"); #endif password = Cvar_Get ("password","",CVAR_USERINFO|CVAR_SERVERINFO, "None"); spectator = Cvar_Get ("spectator","",CVAR_USERINFO|CVAR_SERVERINFO, "None"); skin = Cvar_Get ("skin","",CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO, "None"); team = Cvar_Get ("team","",CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO, "None"); topcolor = Cvar_Get ("topcolor","0", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO, "None"); bottomcolor = Cvar_Get ("bottomcolor","0", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO, "None"); rate = Cvar_Get ("rate","2500", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO, "None"); msg = Cvar_Get ("msg","1",CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO, "None"); noaim = Cvar_Get ("noaim","0", CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO, "None"); }