+ if (*cl_playermodel.string)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("playermodel %s", cl_playermodel.string));
+ }
+ if (*cl_playerskin.string)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("playerskin %s", cl_playerskin.string));
+ }
+
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("rate %i", cl_rate.integer));
+
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, "spawn");
+ }
+ break;
+
+ case 3:
+ if (cls.netcon)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, "begin");
+ }
+ break;
+
+ case 4:
+ Con_ClearNotify();
+ break;
+ }
+}
+
+/*
+==================
+CL_ParseServerInfo
+==================
+*/
+void CL_ParseServerInfo (void)
+{
+ char *str;
+ int i;
+ protocolversion_t protocol;
+ int nummodels, numsounds;
+
+ Con_DPrint("Serverinfo packet received.\n");
+
+ // if server is active, we already began a loading plaque
+ if (!sv.active)
+ SCR_BeginLoadingPlaque();
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+//
+// wipe the client_state_t struct
+//
+ CL_ClearState ();
+
+// parse protocol version number
+ i = MSG_ReadLong ();
+ protocol = Protocol_EnumForNumber(i);
+ if (protocol == PROTOCOL_UNKNOWN)
+ {
+ Host_Error("CL_ParseServerInfo: Server is unrecognized protocol number (%i)", i);
+ return;
+ }
+ // hack for unmarked Nehahra movie demos which had a custom protocol
+ if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && demo_nehahra.integer)
+ protocol = PROTOCOL_NEHAHRAMOVIE;
+ cls.protocol = protocol;
+ Con_DPrintf("Server protocol is %s\n", Protocol_NameForEnum(cls.protocol));
+
+ cl.num_entities = 1;
+
+ if (protocol == PROTOCOL_QUAKEWORLD)
+ {
+ cl.qw_servercount = MSG_ReadLong();
+
+ str = MSG_ReadString();
+ Con_Printf("server gamedir is %s\n", str);
+#if 0
+ // FIXME: change gamedir if needed!
+ if (strcasecmp(gamedirfile, str))
+ {
+ Host_SaveConfig_f();
+ cflag = 1;
+ }
+
+ Com_Gamedir(str); // change gamedir
+
+ if (cflag)
+ {
+ // exec the new config stuff
+ }
+#endif
+
+ cl.gametype = GAME_DEATHMATCH;
+ cl.maxclients = 32;
+
+ // parse player number
+ i = MSG_ReadByte();
+ cl.qw_spectator = (i & 128) != 0;
+ cl.playerentity = cl.viewentity = (i & 127) + 1;
+ cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores));
+
+ // get the full level name
+ str = MSG_ReadString ();
+ strlcpy (cl.levelname, str, sizeof(cl.levelname));
+
+ // get the movevars
+ cl.qw_movevars_gravity = MSG_ReadFloat();
+ cl.qw_movevars_stopspeed = MSG_ReadFloat();
+ cl.qw_movevars_maxspeed = MSG_ReadFloat();
+ cl.qw_movevars_spectatormaxspeed = MSG_ReadFloat();
+ cl.qw_movevars_accelerate = MSG_ReadFloat();
+ cl.qw_movevars_airaccelerate = MSG_ReadFloat();
+ cl.qw_movevars_wateraccelerate = MSG_ReadFloat();
+ cl.qw_movevars_friction = MSG_ReadFloat();
+ cl.qw_movevars_waterfriction = MSG_ReadFloat();
+ cl.qw_movevars_entgravity = MSG_ReadFloat();
+
+ // seperate the printfs so the server message can have a color
+ Con_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n\2%s\n", str);
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ if (cls.netcon)
+ {
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, 0));
+ }
+
+ cls.state = ca_connected;
+ cls.signon = 1;
+
+ // note: on QW protocol we can't set up the gameworld until after
+ // downloads finish...
+ // (we don't even know the name of the map yet)
+ }
+ else
+ {
+ // parse maxclients
+ cl.maxclients = MSG_ReadByte ();
+ if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD)
+ {
+ Host_Error("Bad maxclients (%u) from server", cl.maxclients);
+ return;
+ }
+ cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores));
+
+ // parse gametype
+ cl.gametype = MSG_ReadByte ();
+
+ // parse signon message
+ str = MSG_ReadString ();
+ strlcpy (cl.levelname, str, sizeof(cl.levelname));
+
+ // seperate the printfs so the server message can have a color
+ if (cls.protocol != PROTOCOL_NEHAHRAMOVIE) // no messages when playing the Nehahra movie
+ Con_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n\2%s\n", str);
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ // parse model precache list
+ for (nummodels=1 ; ; nummodels++)
+ {
+ str = MSG_ReadString();
+ if (!str[0])
+ break;
+ if (nummodels==MAX_MODELS)
+ Host_Error ("Server sent too many model precaches");
+ if (strlen(str) >= MAX_QPATH)
+ Host_Error ("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1);
+ strlcpy (cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels]));
+ }
+ // parse sound precache list
+ for (numsounds=1 ; ; numsounds++)
+ {
+ str = MSG_ReadString();
+ if (!str[0])
+ break;
+ if (numsounds==MAX_SOUNDS)
+ Host_Error("Server sent too many sound precaches");
+ if (strlen(str) >= MAX_QPATH)
+ Host_Error("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1);
+ strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds]));
+ }
+
+ // touch all of the precached models that are still loaded so we can free
+ // anything that isn't needed
+ Mod_ClearUsed();
+ for (i = 1;i < nummodels;i++)
+ Mod_FindName(cl.model_name[i]);
+ // precache any models used by the client (this also marks them used)
+ cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false);
+ cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false);
+ cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false);
+ cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false);
+ Mod_PurgeUnused();
+
+ // do the same for sounds
+ // FIXME: S_ServerSounds does not know about cl.sfx_ sounds
+ S_ServerSounds (cl.sound_name, numsounds);
+
+ // precache any sounds used by the client
+ cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true);
+ cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true);
+ cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true);
+ cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true);
+ cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true);
+ cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true);
+ cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true);
+
+ // now we try to load everything that is new
+
+ // world model
+ CL_KeepaliveMessage ();
+ cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, true);
+ if (cl.model_precache[1]->Draw == NULL)
+ Con_Printf("Map %s not found\n", cl.model_name[1]);
+
+ // normal models
+ for (i=2 ; i<nummodels ; i++)
+ {
+ CL_KeepaliveMessage();
+ if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, false))->Draw == NULL)
+ Con_Printf("Model %s not found\n", cl.model_name[i]);
+ }
+
+ // sounds
+ for (i=1 ; i<numsounds ; i++)
+ {
+ CL_KeepaliveMessage();
+ // Don't lock the sfx here, S_ServerSounds already did that
+ cl.sound_precache[i] = S_PrecacheSound (cl.sound_name[i], true, false);
+ }
+
+ // we now have the worldmodel so we can set up the game world
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_BoundingBoxForEntity(&cl.entities[0].render);
+ R_Modules_NewMap();
+ }
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+}
+
+void CL_ValidateState(entity_state_t *s)
+{
+ model_t *model;
+
+ if (!s->active)
+ return;
+
+ if (s->modelindex >= MAX_MODELS && (65536-s->modelindex) >= MAX_MODELS)
+ Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS);
+
+ // colormap is client index + 1
+ if ((!s->flags & RENDER_COLORMAPPED) && s->colormap > cl.maxclients)
+ {
+ Con_DPrintf("CL_ValidateState: colormap (%i) > cl.maxclients (%i)\n", s->colormap, cl.maxclients);
+ s->colormap = 0;
+ }
+
+ model = cl.model_precache[s->modelindex];
+ if (model && model->type && s->frame >= model->numframes)
+ {
+ Con_DPrintf("CL_ValidateState: no such frame %i in \"%s\" (which has %i frames)\n", s->frame, model->name, model->numframes);
+ s->frame = 0;
+ }
+ if (model && model->type && s->skin > 0 && s->skin >= model->numskins && !(s->lightpflags & PFLAGS_FULLDYNAMIC))
+ {
+ Con_DPrintf("CL_ValidateState: no such skin %i in \"%s\" (which has %i skins)\n", s->skin, model->name, model->numskins);
+ s->skin = 0;
+ }
+}
+
+void CL_MoveLerpEntityStates(entity_t *ent)
+{
+ float odelta[3], adelta[3];
+ CL_ValidateState(&ent->state_current);
+ VectorSubtract(ent->state_current.origin, ent->persistent.neworigin, odelta);
+ VectorSubtract(ent->state_current.angles, ent->persistent.newangles, adelta);
+ if (!ent->state_previous.active || ent->state_previous.modelindex != ent->state_current.modelindex)
+ {
+ // reset all persistent stuff if this is a new entity
+ ent->persistent.lerpdeltatime = 0;
+ ent->persistent.lerpstarttime = cl.mtime[1];
+ VectorCopy(ent->state_current.origin, ent->persistent.oldorigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.oldangles);
+ VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.newangles);
+ // reset animation interpolation as well
+ ent->render.frame = ent->render.frame1 = ent->render.frame2 = ent->state_current.frame;
+ ent->render.frame1time = ent->render.frame2time = cl.time;
+ ent->render.framelerp = 1;
+ // reset various persistent stuff
+ ent->persistent.muzzleflash = 0;
+ VectorCopy(ent->state_current.origin, ent->persistent.trail_origin);
+ }
+ else if (cls.timedemo || cl_nolerp.integer || DotProduct(odelta, odelta) > 1000*1000 || (cl.fixangle[0] && !cl.fixangle[1]))
+ {
+ // don't interpolate the move
+ // (the fixangle[] check detects teleports, but not constant fixangles
+ // such as when spectating)
+ ent->persistent.lerpdeltatime = 0;
+ ent->persistent.lerpstarttime = cl.mtime[1];
+ VectorCopy(ent->state_current.origin, ent->persistent.oldorigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.oldangles);
+ VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.newangles);
+ }
+ else if (ent->state_current.flags & RENDER_STEP)
+ {
+ // monster interpolation
+ if (DotProduct(odelta, odelta) + DotProduct(adelta, adelta) > 0.01)
+ {
+ ent->persistent.lerpdeltatime = bound(0, cl.mtime[1] - ent->persistent.lerpstarttime, 0.1);
+ ent->persistent.lerpstarttime = cl.mtime[1];
+ VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin);
+ VectorCopy(ent->persistent.newangles, ent->persistent.oldangles);
+ VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.newangles);