/cl_parse.c
C | 4332 lines | 3417 code | 468 blank | 447 comment | 928 complexity | f088ff2932dabf23136a6a26e62e3c94 MD5 | raw file
Possible License(s): GPL-2.0
Large files files are truncated, but you can click here to view the full file
- /*
- Copyright (C) 1996-1997 Id Software, Inc.
- 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.
- */
- // cl_parse.c -- parse a message received from the server
- #include "quakedef.h"
- #ifdef CONFIG_CD
- #include "cdaudio.h"
- #endif
- #include "cl_collision.h"
- #include "csprogs.h"
- #include "libcurl.h"
- #include "utf8lib.h"
- #ifdef CONFIG_MENU
- #include "menu.h"
- #endif
- #include "cl_video.h"
- const char *svc_strings[128] =
- {
- "svc_bad",
- "svc_nop",
- "svc_disconnect",
- "svc_updatestat",
- "svc_version", // [int] server version
- "svc_setview", // [short] entity number
- "svc_sound", // <see code>
- "svc_time", // [float] server time
- "svc_print", // [string] null terminated string
- "svc_stufftext", // [string] stuffed into client's console buffer
- // the string should be \n terminated
- "svc_setangle", // [vec3] set the view angle to this absolute value
- "svc_serverinfo", // [int] version
- // [string] signon string
- // [string]..[0]model cache [string]...[0]sounds cache
- // [string]..[0]item cache
- "svc_lightstyle", // [byte] [string]
- "svc_updatename", // [byte] [string]
- "svc_updatefrags", // [byte] [short]
- "svc_clientdata", // <shortbits + data>
- "svc_stopsound", // <see code>
- "svc_updatecolors", // [byte] [byte]
- "svc_particle", // [vec3] <variable>
- "svc_damage", // [byte] impact [byte] blood [vec3] from
- "svc_spawnstatic",
- "OBSOLETE svc_spawnbinary",
- "svc_spawnbaseline",
- "svc_temp_entity", // <variable>
- "svc_setpause",
- "svc_signonnum",
- "svc_centerprint",
- "svc_killedmonster",
- "svc_foundsecret",
- "svc_spawnstaticsound",
- "svc_intermission",
- "svc_finale", // [string] music [string] text
- "svc_cdtrack", // [byte] track [byte] looptrack
- "svc_sellscreen",
- "svc_cutscene",
- "svc_showlmp", // [string] iconlabel [string] lmpfile [short] x [short] y
- "svc_hidelmp", // [string] iconlabel
- "svc_skybox", // [string] skyname
- "", // 38
- "", // 39
- "", // 40
- "", // 41
- "", // 42
- "", // 43
- "", // 44
- "", // 45
- "", // 46
- "", // 47
- "", // 48
- "", // 49
- "svc_downloaddata", // 50 // [int] start [short] size [variable length] data
- "svc_updatestatubyte", // 51 // [byte] stat [byte] value
- "svc_effect", // 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate
- "svc_effect2", // 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate
- "svc_sound2", // 54 // short soundindex instead of byte
- "svc_spawnbaseline2", // 55 // short modelindex instead of byte
- "svc_spawnstatic2", // 56 // short modelindex instead of byte
- "svc_entities", // 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata
- "svc_csqcentities", // 58 // [short] entnum [variable length] entitydata ... [short] 0x0000
- "svc_spawnstaticsound2", // 59 // [coord3] [short] samp [byte] vol [byte] aten
- "svc_trailparticles", // 60 // [short] entnum [short] effectnum [vector] start [vector] end
- "svc_pointparticles", // 61 // [short] effectnum [vector] start [vector] velocity [short] count
- "svc_pointparticles1", // 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1
- };
- const char *qw_svc_strings[128] =
- {
- "qw_svc_bad", // 0
- "qw_svc_nop", // 1
- "qw_svc_disconnect", // 2
- "qw_svc_updatestat", // 3 // [byte] [byte]
- "", // 4
- "qw_svc_setview", // 5 // [short] entity number
- "qw_svc_sound", // 6 // <see code>
- "", // 7
- "qw_svc_print", // 8 // [byte] id [string] null terminated string
- "qw_svc_stufftext", // 9 // [string] stuffed into client's console buffer
- "qw_svc_setangle", // 10 // [angle3] set the view angle to this absolute value
- "qw_svc_serverdata", // 11 // [long] protocol ...
- "qw_svc_lightstyle", // 12 // [byte] [string]
- "", // 13
- "qw_svc_updatefrags", // 14 // [byte] [short]
- "", // 15
- "qw_svc_stopsound", // 16 // <see code>
- "", // 17
- "", // 18
- "qw_svc_damage", // 19
- "qw_svc_spawnstatic", // 20
- "", // 21
- "qw_svc_spawnbaseline", // 22
- "qw_svc_temp_entity", // 23 // variable
- "qw_svc_setpause", // 24 // [byte] on / off
- "", // 25
- "qw_svc_centerprint", // 26 // [string] to put in center of the screen
- "qw_svc_killedmonster", // 27
- "qw_svc_foundsecret", // 28
- "qw_svc_spawnstaticsound", // 29 // [coord3] [byte] samp [byte] vol [byte] aten
- "qw_svc_intermission", // 30 // [vec3_t] origin [vec3_t] angle
- "qw_svc_finale", // 31 // [string] text
- "qw_svc_cdtrack", // 32 // [byte] track
- "qw_svc_sellscreen", // 33
- "qw_svc_smallkick", // 34 // set client punchangle to 2
- "qw_svc_bigkick", // 35 // set client punchangle to 4
- "qw_svc_updateping", // 36 // [byte] [short]
- "qw_svc_updateentertime", // 37 // [byte] [float]
- "qw_svc_updatestatlong", // 38 // [byte] [long]
- "qw_svc_muzzleflash", // 39 // [short] entity
- "qw_svc_updateuserinfo", // 40 // [byte] slot [long] uid
- "qw_svc_download", // 41 // [short] size [size bytes]
- "qw_svc_playerinfo", // 42 // variable
- "qw_svc_nails", // 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8
- "qw_svc_chokecount", // 44 // [byte] packets choked
- "qw_svc_modellist", // 45 // [strings]
- "qw_svc_soundlist", // 46 // [strings]
- "qw_svc_packetentities", // 47 // [...]
- "qw_svc_deltapacketentities", // 48 // [...]
- "qw_svc_maxspeed", // 49 // maxspeed change, for prediction
- "qw_svc_entgravity", // 50 // gravity change, for prediction
- "qw_svc_setinfo", // 51 // setinfo on a client
- "qw_svc_serverinfo", // 52 // serverinfo
- "qw_svc_updatepl", // 53 // [byte] [byte]
- };
- //=============================================================================
- cvar_t cl_worldmessage = {CVAR_READONLY, "cl_worldmessage", "", "title of current level"};
- cvar_t cl_worldname = {CVAR_READONLY, "cl_worldname", "", "name of current worldmodel"};
- cvar_t cl_worldnamenoextension = {CVAR_READONLY, "cl_worldnamenoextension", "", "name of current worldmodel without extension"};
- cvar_t cl_worldbasename = {CVAR_READONLY, "cl_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"};
- cvar_t developer_networkentities = {0, "developer_networkentities", "0", "prints received entities, value is 0-10 (higher for more info, 10 being the most verbose)"};
- cvar_t cl_gameplayfix_soundsmovewithentities = {0, "cl_gameplayfix_soundsmovewithentities", "1", "causes sounds made by lifts, players, projectiles, and any other entities, to move with the entity, so for example a rocket noise follows the rocket rather than staying at the starting position"};
- cvar_t cl_sound_wizardhit = {0, "cl_sound_wizardhit", "wizard/hit.wav", "sound to play during TE_WIZSPIKE (empty cvar disables sound)"};
- cvar_t cl_sound_hknighthit = {0, "cl_sound_hknighthit", "hknight/hit.wav", "sound to play during TE_KNIGHTSPIKE (empty cvar disables sound)"};
- cvar_t cl_sound_tink1 = {0, "cl_sound_tink1", "weapons/tink1.wav", "sound to play with 80% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"};
- cvar_t cl_sound_ric1 = {0, "cl_sound_ric1", "weapons/ric1.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"};
- cvar_t cl_sound_ric2 = {0, "cl_sound_ric2", "weapons/ric2.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"};
- cvar_t cl_sound_ric3 = {0, "cl_sound_ric3", "weapons/ric3.wav", "sound to play with 10% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"};
- cvar_t cl_readpicture_force = {0, "cl_readpicture_force", "0", "when enabled, the low quality pictures read by ReadPicture() are preferred over the high quality pictures on the file system"};
- #define RIC_GUNSHOT 1
- #define RIC_GUNSHOTQUAD 2
- cvar_t cl_sound_ric_gunshot = {0, "cl_sound_ric_gunshot", "0", "specifies if and when the related cl_sound_ric and cl_sound_tink sounds apply to TE_GUNSHOT/TE_GUNSHOTQUAD, 0 = no sound, 1 = TE_GUNSHOT, 2 = TE_GUNSHOTQUAD, 3 = TE_GUNSHOT and TE_GUNSHOTQUAD"};
- cvar_t cl_sound_r_exp3 = {0, "cl_sound_r_exp3", "weapons/r_exp3.wav", "sound to play during TE_EXPLOSION and related effects (empty cvar disables sound)"};
- cvar_t cl_serverextension_download = {0, "cl_serverextension_download", "0", "indicates whether the server supports the download command"};
- cvar_t cl_joinbeforedownloadsfinish = {CVAR_SAVE, "cl_joinbeforedownloadsfinish", "1", "if non-zero the game will begin after the map is loaded before other downloads finish"};
- cvar_t cl_nettimesyncfactor = {CVAR_SAVE, "cl_nettimesyncfactor", "0", "rate at which client time adapts to match server time, 1 = instantly, 0.125 = slowly, 0 = not at all (bounding still applies)"};
- cvar_t cl_nettimesyncboundmode = {CVAR_SAVE, "cl_nettimesyncboundmode", "6", "method of restricting client time to valid values, 0 = no correction, 1 = tight bounding (jerky with packet loss), 2 = loose bounding (corrects it if out of bounds), 3 = leniant bounding (ignores temporary errors due to varying framerate), 4 = slow adjustment method from Quake3, 5 = slighttly nicer version of Quake3 method, 6 = bounding + Quake3"};
- cvar_t cl_nettimesyncboundtolerance = {CVAR_SAVE, "cl_nettimesyncboundtolerance", "0.25", "how much error is tolerated by bounding check, as a fraction of frametime, 0.25 = up to 25% margin of error tolerated, 1 = use only new time, 0 = use only old time (same effect as setting cl_nettimesyncfactor to 1)"};
- cvar_t cl_iplog_name = {CVAR_SAVE, "cl_iplog_name", "darkplaces_iplog.txt", "name of iplog file containing player addresses for iplog_list command and automatic ip logging when parsing status command"};
- static qboolean QW_CL_CheckOrDownloadFile(const char *filename);
- static void QW_CL_RequestNextDownload(void);
- static void QW_CL_NextUpload(void);
- //static qboolean QW_CL_IsUploading(void);
- static void QW_CL_StopUpload(void);
- /*
- ==================
- CL_ParseStartSoundPacket
- ==================
- */
- static void CL_ParseStartSoundPacket(int largesoundindex)
- {
- vec3_t pos;
- int channel, ent;
- int sound_num;
- int nvolume;
- int field_mask;
- float attenuation;
- float speed;
- int fflags = CHANNELFLAG_NONE;
- if (cls.protocol == PROTOCOL_QUAKEWORLD)
- {
- channel = MSG_ReadShort(&cl_message);
- if (channel & (1<<15))
- nvolume = MSG_ReadByte(&cl_message);
- else
- nvolume = DEFAULT_SOUND_PACKET_VOLUME;
- if (channel & (1<<14))
- attenuation = MSG_ReadByte(&cl_message) / 64.0;
- else
- attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;
- speed = 1.0f;
- ent = (channel>>3)&1023;
- channel &= 7;
- sound_num = MSG_ReadByte(&cl_message);
- }
- else
- {
- field_mask = MSG_ReadByte(&cl_message);
- if (field_mask & SND_VOLUME)
- nvolume = MSG_ReadByte(&cl_message);
- else
- nvolume = DEFAULT_SOUND_PACKET_VOLUME;
- if (field_mask & SND_ATTENUATION)
- attenuation = MSG_ReadByte(&cl_message) / 64.0;
- else
- attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;
- if (field_mask & SND_SPEEDUSHORT4000)
- speed = ((unsigned short)MSG_ReadShort(&cl_message)) / 4000.0f;
- else
- speed = 1.0f;
- if (field_mask & SND_LARGEENTITY)
- {
- ent = (unsigned short) MSG_ReadShort(&cl_message);
- channel = MSG_ReadChar(&cl_message);
- }
- else
- {
- channel = (unsigned short) MSG_ReadShort(&cl_message);
- ent = channel >> 3;
- channel &= 7;
- }
- if (largesoundindex || (field_mask & SND_LARGESOUND) || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3)
- sound_num = (unsigned short) MSG_ReadShort(&cl_message);
- else
- sound_num = MSG_ReadByte(&cl_message);
- }
- channel = CHAN_NET2ENGINE(channel);
- MSG_ReadVector(&cl_message, pos, cls.protocol);
- if (sound_num < 0 || sound_num >= MAX_SOUNDS)
- {
- Con_Printf("CL_ParseStartSoundPacket: sound_num (%i) >= MAX_SOUNDS (%i)\n", sound_num, MAX_SOUNDS);
- return;
- }
- if (ent >= MAX_EDICTS)
- {
- Con_Printf("CL_ParseStartSoundPacket: ent = %i", ent);
- return;
- }
- if (ent >= cl.max_entities)
- CL_ExpandEntities(ent);
- if( !CL_VM_Event_Sound(sound_num, nvolume / 255.0f, channel, attenuation, ent, pos, fflags, speed) )
- S_StartSound_StartPosition_Flags (ent, channel, cl.sound_precache[sound_num], pos, nvolume/255.0f, attenuation, 0, fflags, speed);
- }
- /*
- ==================
- CL_KeepaliveMessage
- When the client is taking a long time to load stuff, send keepalive messages
- so the server doesn't disconnect.
- ==================
- */
- static unsigned char olddata[NET_MAXMESSAGE];
- void CL_KeepaliveMessage (qboolean readmessages)
- {
- static double lastdirtytime = 0;
- static qboolean recursive = false;
- double dirtytime;
- double deltatime;
- static double countdownmsg = 0;
- static double countdownupdate = 0;
- sizebuf_t old;
- qboolean thisrecursive;
- thisrecursive = recursive;
- recursive = true;
- dirtytime = Sys_DirtyTime();
- deltatime = dirtytime - lastdirtytime;
- lastdirtytime = dirtytime;
- if (deltatime <= 0 || deltatime >= 1800.0)
- return;
- countdownmsg -= deltatime;
- countdownupdate -= deltatime;
- if(!thisrecursive)
- {
- if(cls.state != ca_dedicated)
- {
- if(countdownupdate <= 0) // check if time stepped backwards
- {
- SCR_UpdateLoadingScreenIfShown();
- countdownupdate = 2;
- }
- }
- }
- // no need if server is local and definitely not if this is a demo
- if (sv.active || !cls.netcon || cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon >= SIGNONS)
- {
- recursive = thisrecursive;
- return;
- }
- if (readmessages)
- {
- // read messages from server, should just be nops
- old = cl_message;
- memcpy(olddata, cl_message.data, cl_message.cursize);
- NetConn_ClientFrame();
- cl_message = old;
- memcpy(cl_message.data, olddata, cl_message.cursize);
- }
- if (cls.netcon && countdownmsg <= 0) // check if time stepped backwards
- {
- sizebuf_t msg;
- unsigned char buf[4];
- countdownmsg = 5;
- // write out a nop
- // LordHavoc: must use unreliable because reliable could kill the sigon message!
- Con_Print("--> client to server keepalive\n");
- memset(&msg, 0, sizeof(msg));
- msg.data = buf;
- msg.maxsize = sizeof(buf);
- MSG_WriteChar(&msg, clc_nop);
- NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, 0, false);
- }
- recursive = thisrecursive;
- }
- void CL_ParseEntityLump(char *entdata)
- {
- qboolean loadedsky = false;
- const char *data;
- char key[128], value[MAX_INPUTLINE];
- FOG_clear(); // LordHavoc: no fog until set
- // LordHavoc: default to the map's sky (q3 shader parsing sets this)
- R_SetSkyBox(cl.worldmodel->brush.skybox);
- data = entdata;
- if (!data)
- return;
- if (!COM_ParseToken_Simple(&data, false, false, true))
- return; // error
- if (com_token[0] != '{')
- return; // error
- while (1)
- {
- if (!COM_ParseToken_Simple(&data, false, false, true))
- return; // error
- if (com_token[0] == '}')
- break; // end of worldspawn
- if (com_token[0] == '_')
- strlcpy (key, com_token + 1, sizeof (key));
- else
- strlcpy (key, com_token, sizeof (key));
- while (key[strlen(key)-1] == ' ') // remove trailing spaces
- key[strlen(key)-1] = 0;
- if (!COM_ParseToken_Simple(&data, false, false, true))
- return; // error
- strlcpy (value, com_token, sizeof (value));
- if (!strcmp("sky", key))
- {
- loadedsky = true;
- R_SetSkyBox(value);
- }
- else if (!strcmp("skyname", key)) // non-standard, introduced by QuakeForge... sigh.
- {
- loadedsky = true;
- R_SetSkyBox(value);
- }
- else if (!strcmp("qlsky", key)) // non-standard, introduced by QuakeLives (EEK)
- {
- loadedsky = true;
- R_SetSkyBox(value);
- }
- else if (!strcmp("fog", key))
- {
- FOG_clear(); // so missing values get good defaults
- r_refdef.fog_start = 0;
- r_refdef.fog_alpha = 1;
- r_refdef.fog_end = 16384;
- r_refdef.fog_height = 1<<30;
- r_refdef.fog_fadedepth = 128;
- #if _MSC_VER >= 1400
- #define sscanf sscanf_s
- #endif
- sscanf(value, "%f %f %f %f %f %f %f %f %f", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth);
- }
- else if (!strcmp("fog_density", key))
- r_refdef.fog_density = atof(value);
- else if (!strcmp("fog_red", key))
- r_refdef.fog_red = atof(value);
- else if (!strcmp("fog_green", key))
- r_refdef.fog_green = atof(value);
- else if (!strcmp("fog_blue", key))
- r_refdef.fog_blue = atof(value);
- else if (!strcmp("fog_alpha", key))
- r_refdef.fog_alpha = atof(value);
- else if (!strcmp("fog_start", key))
- r_refdef.fog_start = atof(value);
- else if (!strcmp("fog_end", key))
- r_refdef.fog_end = atof(value);
- else if (!strcmp("fog_height", key))
- r_refdef.fog_height = atof(value);
- else if (!strcmp("fog_fadedepth", key))
- r_refdef.fog_fadedepth = atof(value);
- else if (!strcmp("fog_heighttexture", key))
- {
- FOG_clear(); // so missing values get good defaults
- #if _MSC_VER >= 1400
- sscanf_s(value, "%f %f %f %f %f %f %f %f %f %s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename, (unsigned int)sizeof(r_refdef.fog_height_texturename));
- #else
- sscanf(value, "%f %f %f %f %f %f %f %f %f %63s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename);
- #endif
- r_refdef.fog_height_texturename[63] = 0;
- }
- }
- if (!loadedsky && cl.worldmodel->brush.isq2bsp)
- R_SetSkyBox("unit1_");
- }
- static const vec3_t defaultmins = {-4096, -4096, -4096};
- static const vec3_t defaultmaxs = {4096, 4096, 4096};
- static void CL_SetupWorldModel(void)
- {
- prvm_prog_t *prog = CLVM_prog;
- // update the world model
- cl.entities[0].render.model = cl.worldmodel = CL_GetModelByIndex(1);
- CL_UpdateRenderEntity(&cl.entities[0].render);
- // make sure the cl.worldname and related cvars are set up now that we know the world model name
- // set up csqc world for collision culling
- if (cl.worldmodel)
- {
- strlcpy(cl.worldname, cl.worldmodel->name, sizeof(cl.worldname));
- FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension));
- strlcpy(cl.worldbasename, !strncmp(cl.worldnamenoextension, "maps/", 5) ? cl.worldnamenoextension + 5 : cl.worldnamenoextension, sizeof(cl.worldbasename));
- Cvar_SetQuick(&cl_worldmessage, cl.worldmessage);
- Cvar_SetQuick(&cl_worldname, cl.worldname);
- Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension);
- Cvar_SetQuick(&cl_worldbasename, cl.worldbasename);
- World_SetSize(&cl.world, cl.worldname, cl.worldmodel->normalmins, cl.worldmodel->normalmaxs, prog);
- }
- else
- {
- Cvar_SetQuick(&cl_worldmessage, cl.worldmessage);
- Cvar_SetQuick(&cl_worldnamenoextension, "");
- Cvar_SetQuick(&cl_worldbasename, "");
- World_SetSize(&cl.world, "", defaultmins, defaultmaxs, prog);
- }
- World_Start(&cl.world);
- // load or reload .loc file for team chat messages
- CL_Locs_Reload_f();
- // make sure we send enough keepalives
- CL_KeepaliveMessage(false);
- // reset particles and other per-level things
- R_Modules_NewMap();
- // make sure we send enough keepalives
- CL_KeepaliveMessage(false);
- // load the team chat beep if possible
- cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav");
- // check memory integrity
- Mem_CheckSentinelsGlobal();
- #ifdef CONFIG_MENU
- // make menu know
- MR_NewMap();
- #endif
- // load the csqc now
- if (cl.loadcsqc)
- {
- cl.loadcsqc = false;
- CL_VM_Init();
- }
- }
- static qboolean QW_CL_CheckOrDownloadFile(const char *filename)
- {
- qfile_t *file;
- char vabuf[1024];
- // see if the file already exists
- file = FS_OpenVirtualFile(filename, true);
- if (file)
- {
- FS_Close(file);
- return true;
- }
- // download messages in a demo would be bad
- if (cls.demorecording)
- {
- Con_Printf("Unable to download \"%s\" when recording.\n", filename);
- return true;
- }
- // don't try to download when playing a demo
- if (!cls.netcon)
- return true;
- strlcpy(cls.qw_downloadname, filename, sizeof(cls.qw_downloadname));
- Con_Printf("Downloading %s\n", filename);
- if (!cls.qw_downloadmemory)
- {
- cls.qw_downloadmemory = NULL;
- cls.qw_downloadmemorycursize = 0;
- cls.qw_downloadmemorymaxsize = 1024*1024; // start out with a 1MB buffer
- }
- MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
- MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "download %s", filename));
- cls.qw_downloadnumber++;
- cls.qw_downloadpercent = 0;
- cls.qw_downloadmemorycursize = 0;
- return false;
- }
- static void QW_CL_ProcessUserInfo(int slot);
- static void QW_CL_RequestNextDownload(void)
- {
- int i;
- char vabuf[1024];
- // clear name of file that just finished
- cls.qw_downloadname[0] = 0;
- // skip the download fragment if playing a demo
- if (!cls.netcon)
- {
- return;
- }
- switch (cls.qw_downloadtype)
- {
- case dl_single:
- break;
- case dl_skin:
- if (cls.qw_downloadnumber == 0)
- Con_Printf("Checking skins...\n");
- for (;cls.qw_downloadnumber < cl.maxclients;cls.qw_downloadnumber++)
- {
- if (!cl.scores[cls.qw_downloadnumber].name[0])
- continue;
- // check if we need to download the file, and return if so
- if (!QW_CL_CheckOrDownloadFile(va(vabuf, sizeof(vabuf), "skins/%s.pcx", cl.scores[cls.qw_downloadnumber].qw_skin)))
- return;
- }
- cls.qw_downloadtype = dl_none;
- // load any newly downloaded skins
- for (i = 0;i < cl.maxclients;i++)
- QW_CL_ProcessUserInfo(i);
- // if we're still in signon stages, request the next one
- if (cls.signon != SIGNONS)
- {
- cls.signon = SIGNONS-1;
- // we'll go to SIGNONS when the first entity update is received
- MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
- MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "begin %i", cl.qw_servercount));
- }
- break;
- case dl_model:
- if (cls.qw_downloadnumber == 0)
- {
- Con_Printf("Checking models...\n");
- cls.qw_downloadnumber = 1;
- }
- for (;cls.qw_downloadnumber < MAX_MODELS && cl.model_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++)
- {
- // skip submodels
- if (cl.model_name[cls.qw_downloadnumber][0] == '*')
- continue;
- if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/spike.mdl"))
- cl.qw_modelindex_spike = cls.qw_downloadnumber;
- if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/player.mdl"))
- cl.qw_modelindex_player = cls.qw_downloadnumber;
- if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/flag.mdl"))
- cl.qw_modelindex_flag = cls.qw_downloadnumber;
- if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/s_explod.spr"))
- cl.qw_modelindex_s_explod = cls.qw_downloadnumber;
- // check if we need to download the file, and return if so
- if (!QW_CL_CheckOrDownloadFile(cl.model_name[cls.qw_downloadnumber]))
- return;
- }
- cls.qw_downloadtype = dl_none;
- // touch all of the precached models that are still loaded so we can free
- // anything that isn't needed
- if (!sv.active)
- Mod_ClearUsed();
- for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++)
- Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL);
- // precache any models used by the client (this also marks them used)
- cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL);
- cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL);
- cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL);
- cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL);
- // we purge the models and sounds later in CL_SignonReply
- //Mod_PurgeUnused();
- // now we try to load everything that is new
- // world model
- cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, NULL);
- if (cl.model_precache[1]->Draw == NULL)
- Con_Printf("Map %s could not be found or downloaded\n", cl.model_name[1]);
- // normal models
- for (i = 2;i < MAX_MODELS && cl.model_name[i][0];i++)
- if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL))->Draw == NULL)
- Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]);
- // check memory integrity
- Mem_CheckSentinelsGlobal();
- // now that we have a world model, set up the world entity, renderer
- // modules and csqc
- CL_SetupWorldModel();
- // add pmodel/emodel CRCs to userinfo
- CL_SetInfo("pmodel", va(vabuf, sizeof(vabuf), "%i", FS_CRCFile("progs/player.mdl", NULL)), true, true, true, true);
- CL_SetInfo("emodel", va(vabuf, sizeof(vabuf), "%i", FS_CRCFile("progs/eyes.mdl", NULL)), true, true, true, true);
- // done checking sounds and models, send a prespawn command now
- MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
- MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "prespawn %i 0 %i", cl.qw_servercount, cl.model_precache[1]->brush.qw_md4sum2));
- if (cls.qw_downloadmemory)
- {
- Mem_Free(cls.qw_downloadmemory);
- cls.qw_downloadmemory = NULL;
- }
- // done loading
- cl.loadfinished = true;
- break;
- case dl_sound:
- if (cls.qw_downloadnumber == 0)
- {
- Con_Printf("Checking sounds...\n");
- cls.qw_downloadnumber = 1;
- }
- for (;cl.sound_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++)
- {
- // check if we need to download the file, and return if so
- if (!QW_CL_CheckOrDownloadFile(va(vabuf, sizeof(vabuf), "sound/%s", cl.sound_name[cls.qw_downloadnumber])))
- return;
- }
- cls.qw_downloadtype = dl_none;
- // clear sound usage flags for purging of unused sounds
- S_ClearUsed();
- // 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);
- // sounds used by the game
- for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++)
- cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true);
- // we purge the models and sounds later in CL_SignonReply
- //S_PurgeUnused();
- // check memory integrity
- Mem_CheckSentinelsGlobal();
- // done with sound downloads, next we check models
- MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
- MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "modellist %i %i", cl.qw_servercount, 0));
- break;
- case dl_none:
- default:
- Con_Printf("Unknown download type.\n");
- }
- }
- static void QW_CL_ParseDownload(void)
- {
- int size = (signed short)MSG_ReadShort(&cl_message);
- int percent = MSG_ReadByte(&cl_message);
- //Con_Printf("download %i %i%% (%i/%i)\n", size, percent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize);
- // skip the download fragment if playing a demo
- if (!cls.netcon)
- {
- if (size > 0)
- cl_message.readcount += size;
- return;
- }
- if (size == -1)
- {
- Con_Printf("File not found.\n");
- QW_CL_RequestNextDownload();
- return;
- }
- if (cl_message.readcount + (unsigned short)size > cl_message.cursize)
- Host_Error("corrupt download message\n");
- // make sure the buffer is big enough to include this new fragment
- if (!cls.qw_downloadmemory || cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size)
- {
- unsigned char *old;
- while (cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size)
- cls.qw_downloadmemorymaxsize *= 2;
- old = cls.qw_downloadmemory;
- cls.qw_downloadmemory = (unsigned char *)Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize);
- if (old)
- {
- memcpy(cls.qw_downloadmemory, old, cls.qw_downloadmemorycursize);
- Mem_Free(old);
- }
- }
- // read the fragment out of the packet
- MSG_ReadBytes(&cl_message, size, cls.qw_downloadmemory + cls.qw_downloadmemorycursize);
- cls.qw_downloadmemorycursize += size;
- cls.qw_downloadspeedcount += size;
- cls.qw_downloadpercent = percent;
- if (percent != 100)
- {
- // request next fragment
- MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
- MSG_WriteString(&cls.netcon->message, "nextdl");
- }
- else
- {
- // finished file
- Con_Printf("Downloaded \"%s\"\n", cls.qw_downloadname);
- FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
- cls.qw_downloadpercent = 0;
- // start downloading the next file (or join the game)
- QW_CL_RequestNextDownload();
- }
- }
- static void QW_CL_ParseModelList(void)
- {
- int n;
- int nummodels = MSG_ReadByte(&cl_message);
- char *str;
- char vabuf[1024];
- // parse model precache list
- for (;;)
- {
- str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring));
- if (!str[0])
- break;
- nummodels++;
- 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)", (int)strlen(str), MAX_QPATH - 1);
- strlcpy(cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels]));
- }
- n = MSG_ReadByte(&cl_message);
- if (n)
- {
- MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
- MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "modellist %i %i", cl.qw_servercount, n));
- return;
- }
- cls.signon = 2;
- cls.qw_downloadnumber = 0;
- cls.qw_downloadtype = dl_model;
- QW_CL_RequestNextDownload();
- }
- static void QW_CL_ParseSoundList(void)
- {
- int n;
- int numsounds = MSG_ReadByte(&cl_message);
- char *str;
- char vabuf[1024];
- // parse sound precache list
- for (;;)
- {
- str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring));
- if (!str[0])
- break;
- numsounds++;
- 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)", (int)strlen(str), MAX_QPATH - 1);
- strlcpy(cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds]));
- }
- n = MSG_ReadByte(&cl_message);
- if (n)
- {
- MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
- MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "soundlist %i %i", cl.qw_servercount, n));
- return;
- }
- cls.signon = 2;
- cls.qw_downloadnumber = 0;
- cls.qw_downloadtype = dl_sound;
- QW_CL_RequestNextDownload();
- }
- static void QW_CL_Skins_f(void)
- {
- cls.qw_downloadnumber = 0;
- cls.qw_downloadtype = dl_skin;
- QW_CL_RequestNextDownload();
- }
- static void QW_CL_Changing_f(void)
- {
- if (cls.qw_downloadmemory) // don't change when downloading
- return;
- S_StopAllSounds();
- cl.intermission = 0;
- cls.signon = 1; // not active anymore, but not disconnected
- Con_Printf("\nChanging map...\n");
- }
- void QW_CL_NextUpload(void)
- {
- int r, percent, size;
- if (!cls.qw_uploaddata)
- return;
- r = cls.qw_uploadsize - cls.qw_uploadpos;
- if (r > 768)
- r = 768;
- size = min(1, cls.qw_uploadsize);
- percent = (cls.qw_uploadpos+r)*100/size;
- MSG_WriteByte(&cls.netcon->message, qw_clc_upload);
- MSG_WriteShort(&cls.netcon->message, r);
- MSG_WriteByte(&cls.netcon->message, percent);
- SZ_Write(&cls.netcon->message, cls.qw_uploaddata + cls.qw_uploadpos, r);
- Con_DPrintf("UPLOAD: %6d: %d written\n", cls.qw_uploadpos, r);
- cls.qw_uploadpos += r;
- if (cls.qw_uploadpos < cls.qw_uploadsize)
- return;
- Con_Printf("Upload completed\n");
- QW_CL_StopUpload();
- }
- void QW_CL_StartUpload(unsigned char *data, int size)
- {
- // do nothing in demos or if not connected
- if (!cls.netcon)
- return;
- // abort existing upload if in progress
- QW_CL_StopUpload();
- Con_DPrintf("Starting upload of %d bytes...\n", size);
- cls.qw_uploaddata = (unsigned char *)Mem_Alloc(cls.permanentmempool, size);
- memcpy(cls.qw_uploaddata, data, size);
- cls.qw_uploadsize = size;
- cls.qw_uploadpos = 0;
- QW_CL_NextUpload();
- }
- #if 0
- qboolean QW_CL_IsUploading(void)
- {
- return cls.qw_uploaddata != NULL;
- }
- #endif
- void QW_CL_StopUpload(void)
- {
- if (cls.qw_uploaddata)
- Mem_Free(cls.qw_uploaddata);
- cls.qw_uploaddata = NULL;
- cls.qw_uploadsize = 0;
- cls.qw_uploadpos = 0;
- }
- static void QW_CL_ProcessUserInfo(int slot)
- {
- int topcolor, bottomcolor;
- char temp[2048];
- InfoString_GetValue(cl.scores[slot].qw_userinfo, "name", cl.scores[slot].name, sizeof(cl.scores[slot].name));
- InfoString_GetValue(cl.scores[slot].qw_userinfo, "topcolor", temp, sizeof(temp));topcolor = atoi(temp);
- InfoString_GetValue(cl.scores[slot].qw_userinfo, "bottomcolor", temp, sizeof(temp));bottomcolor = atoi(temp);
- cl.scores[slot].colors = topcolor * 16 + bottomcolor;
- InfoString_GetValue(cl.scores[slot].qw_userinfo, "*spectator", temp, sizeof(temp));
- cl.scores[slot].qw_spectator = temp[0] != 0;
- InfoString_GetValue(cl.scores[slot].qw_userinfo, "team", cl.scores[slot].qw_team, sizeof(cl.scores[slot].qw_team));
- InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin));
- if (!cl.scores[slot].qw_skin[0])
- strlcpy(cl.scores[slot].qw_skin, "base", sizeof(cl.scores[slot].qw_skin));
- // TODO: skin cache
- }
- static void QW_CL_UpdateUserInfo(void)
- {
- int slot;
- slot = MSG_ReadByte(&cl_message);
- if (slot >= cl.maxclients)
- {
- Con_Printf("svc_updateuserinfo >= cl.maxclients\n");
- MSG_ReadLong(&cl_message);
- MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring));
- return;
- }
- cl.scores[slot].qw_userid = MSG_ReadLong(&cl_message);
- strlcpy(cl.scores[slot].qw_userinfo, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(cl.scores[slot].qw_userinfo));
- QW_CL_ProcessUserInfo(slot);
- }
- static void QW_CL_SetInfo(void)
- {
- int slot;
- char key[2048];
- char value[2048];
- slot = MSG_ReadByte(&cl_message);
- strlcpy(key, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(key));
- strlcpy(value, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(value));
- if (slot >= cl.maxclients)
- {
- Con_Printf("svc_setinfo >= cl.maxclients\n");
- return;
- }
- InfoString_SetValue(cl.scores[slot].qw_userinfo, sizeof(cl.scores[slot].qw_userinfo), key, value);
- QW_CL_ProcessUserInfo(slot);
- }
- static void QW_CL_ServerInfo(void)
- {
- char key[2048];
- char value[2048];
- char temp[32];
- strlcpy(key, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(key));
- strlcpy(value, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(value));
- Con_DPrintf("SERVERINFO: %s=%s\n", key, value);
- InfoString_SetValue(cl.qw_serverinfo, sizeof(cl.qw_serverinfo), key, value);
- InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
- cl.qw_teamplay = atoi(temp);
- }
- static void QW_CL_ParseNails(void)
- {
- int i, j;
- int numnails = MSG_ReadByte(&cl_message);
- vec_t *v;
- unsigned char bits[6];
- for (i = 0;i < numnails;i++)
- {
- for (j = 0;j < 6;j++)
- bits[j] = MSG_ReadByte(&cl_message);
- if (cl.qw_num_nails >= 255)
- continue;
- v = cl.qw_nails[cl.qw_num_nails++];
- v[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096;
- v[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096;
- v[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096;
- v[3] = -360*(bits[4]>>4)/16;
- v[4] = 360*bits[5]/256;
- v[5] = 0;
- }
- }
- static void CL_UpdateItemsAndWeapon(void)
- {
- int j;
- // check for important changes
- // set flash times
- if (cl.olditems != cl.stats[STAT_ITEMS])
- for (j = 0;j < 32;j++)
- if ((cl.stats[STAT_ITEMS] & (1<<j)) && !(cl.olditems & (1<<j)))
- cl.item_gettime[j] = cl.time;
- cl.olditems = cl.stats[STAT_ITEMS];
- // GAME_NEXUIZ hud needs weapon change time
- if (cl.activeweapon != cl.stats[STAT_ACTIVEWEAPON])
- cl.weapontime = cl.time;
- cl.activeweapon = cl.stats[STAT_ACTIVEWEAPON];
- }
- #define LOADPROGRESSWEIGHT_SOUND 1.0
- #define LOADPROGRESSWEIGHT_MODEL 4.0
- #define LOADPROGRESSWEIGHT_WORLDMODEL 30.0
- #define LOADPROGRESSWEIGHT_WORLDMODEL_INIT 2.0
- static void CL_BeginDownloads(qboolean aborteddownload)
- {
- char vabuf[1024];
- // quakeworld works differently
- if (cls.protocol == PROTOCOL_QUAKEWORLD)
- return;
- // this would be a good place to do curl downloads
- if(Curl_Have_forthismap())
- {
- Curl_Register_predownload(); // come back later
- return;
- }
- // if we got here...
- // curl is done, so let's start with the business
- if(!cl.loadbegun)
- SCR_PushLoadingScreen(false, "Loading precaches", 1);
- cl.loadbegun = true;
- // if already downloading something from the previous level, don't stop it
- if (cls.qw_downloadname[0])
- return;
- if (cl.downloadcsqc)
- {
- size_t progsize;
- cl.downloadcsqc = false;
- if (cls.netcon
- && !sv.active
- && csqc_progname.string
- && csqc_progname.string[0]
- && csqc_progcrc.integer >= 0
- && cl_serverextension_download.integer
- && (FS_CRCFile(csqc_progname.string, &progsize) != csqc_progcrc.integer || ((int)progsize != csqc_progsize.integer && csqc_progsize.integer != -1))
- && !FS_FileExists(va(vabuf, sizeof(vabuf), "dlcache/%s.%i.%i", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer)))
- {
- Con_Printf("Downloading new CSQC code to dlcache/%s.%i.%i\n", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer);
- if(cl_serverextension_download.integer == 2 && FS_HasZlib())
- Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s deflate", csqc_progname.string));
- else
- Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", csqc_progname.string));
- return;
- }
- }
- if (cl.loadmodel_current < cl.loadmodel_total)
- {
- // loading models
- if(cl.loadmodel_current == 1)
- {
- // worldmodel counts as 16 models (15 + world model setup), for better progress bar
- SCR_PushLoadingScreen(false, "Loading precached models",
- (
- (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL_INIT
- ) / (
- (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL_INIT
- + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND
- )
- );
- SCR_BeginLoadingPlaque(false);
- }
- for (;cl.loadmodel_current < cl.loadmodel_total;cl.loadmodel_current++)
- {
- SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current],
- (
- (cl.loadmodel_current == 1) ? LOADPROGRESSWEIGHT_WORLDMODEL : LOADPROGRESSWEIGHT_MODEL
- ) / (
- (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL_INIT
- )
- );
- if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw)
- {
- SCR_PopLoadingScreen(false);
- if(cl.loadmodel_current == 1)
- {
- SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], 1.0 / cl.loadmodel_total);
- SCR_PopLoadingScreen(false);
- }
- continue;
- }
- CL_KeepaliveMessage(true);
- // if running a local game, calling Mod_ForName is a completely wasted effort...
- if (sv.active)
- cl.model_precache[cl.loadmodel_current] = sv.models[cl.loadmodel_current];
- else
- {
- if(cl.loadmodel_current == 1)
- {
- // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3
- Mod_FreeQ3Shaders();
- }
- cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.model_name[cl.loadmodel_current][0] == '*' ? cl.model_name[1] : NULL);
- }
- SCR_PopLoadingScreen(false);
- if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw && cl.loadmodel_current == 1)
- {
- // we now have the worldmodel so we can set up the game world
- SCR_PushLoadingScreen(true, "world model setup",
- (
- LOADPROGRESSWEIGHT_WORLDMODEL_INIT
- ) / (
- (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL_INIT
- )
- );
- CL_SetupWorldModel();
- SCR_PopLoadingScreen(true);
- if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
- {
- cl.loadfinished = true;
- // now issue the spawn to move on to signon 2 like normal
- if (cls.netcon)
- Cmd_ForwardStringToServer("prespawn");
- }
- }
- }
- SCR_PopLoadingScreen(false);
- // finished loading models
- }
- if (cl.loadsound_current < cl.loadsound_total)
- {
- // loading sounds
- if(cl.loadsound_current == 1)
- SCR_PushLoadingScreen(false, "Loading precached sounds",
- (
- cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND
- ) / (
- (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL
- + LOADPROGRESSWEIGHT_WORLDMODEL_INIT
- + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND
- )
- );
- for (;cl.loadsound_current < cl.loadsound_total;cl.loadsound_current++)
- {
- SCR_PushLoadingScreen(false, cl.sound_name[cl.loadsound_current], 1.0 / cl.loadsound_total);
- if (cl.sound_precache[cl.loadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.loadsound_current]))
- {
- SCR_PopLoadingScreen(false);
- continue;
- }
- CL_KeepaliveMessage(true);
- cl.sound_precache[cl.loadsound_current] = S_PrecacheSound(cl.sound_name[cl.loadsound_current], false, true);
- SCR_PopLoadingScreen(false);
- }
- SCR_PopLoadingScreen(false);
- // finished loading sounds
- }
- if(IS_NEXUIZ_DERIVED(gamemode))
- Cvar_SetValueQuick(&cl_serverextension_download, false);
- // in Nexuiz/Xonotic, the built in download protocol is kinda broken (misses lots
- // of dependencies) anyway, and can mess around with the game directory;
- // until this is fixed, only support pk3 downloads via curl, and turn off
- // individual file downloads other than for CSQC
- // on the other end of the download protocol, GAME_NEXUIZ/GAME_XONOTIC enforces writing
- // to dlcache only
- // idea: support download of pk3 files using this protocol later
- // note: the reason these loops skip already-loaded things is that it
- // enables this command to be issued during the game if desired
- if (cl.downloadmodel_current < cl.loadmodel_total)
- {
- // loading models
- for (;cl.downloadmodel_current < cl.loadmodel_total;cl.downloadmodel_current++)
- {
- if (aborteddownload)
- {
- if (cl.downloadmodel_current == 1)
- {
- // the worldmodel failed, but we need to set up anyway
- Mod_FreeQ3Shaders();
- CL_SetupWorldModel();
- if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
- {
- cl.loadfinished = true;
- // now issue the spawn to move on to signon 2 like normal
- if (cls.netcon)
- Cmd_ForwardStringToServer("prespawn");
- }
- }
- aborteddownload = false;
- continue;
- }
- if (cl.model_precache[cl.downloadmodel_current] && cl.model_precache[cl.downloadmodel_current]->Draw)
- continue;
- CL_KeepaliveMessage(true);
- if (cl.model_name[cl.downloadmodel_current][0] != '*' && strcmp(cl.model_name[cl.downloadmodel_current], "null") && !FS_FileExists(cl.model_name[cl.downloadmodel_current]))
- {
- if (cl.downloadmodel_current == 1)
- Con_Printf("Map %s not found\n", cl.model_name[cl.downloadmodel_current]);
- else
- Con_Printf("Model %s not found\n", cl.model_name[cl.downloadmodel_current]);
- // regarding the * check: don't try to download submodels
- if (cl_serverextension_download.integer && cls.netcon && cl.model_name[cl.downloadmodel_current][0] != '*' && !sv.active)
- {
- Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", cl.model_name[cl.downloadmodel_current]));
- // we'll try loading again when the download finishes
- return;
- }
- }
- if(cl.downloadmodel_current == 1)
- {
- // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3
- Mod_FreeQ3Shaders();
- }
- cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, true, cl.model_name[cl.downloadmodel_current][0] == '*' ? cl.model_name[1] : NULL);
- if (cl.downloadmodel_current == 1)
- {
- // we now have the worldmodel so we can set up the game world
- // or maybe we do not have it (cl_serverextension_download 0)
- // then we need to continue loading ANYWAY!
- CL_SetupWorldModel();
- if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
- {
- cl.loadfinished = true;
- // now issue the spawn to move on to signon 2 like normal
- if (cls.netcon)
- Cmd_ForwardStringToServer("prespawn");
- }
- }
- }
- // finished loading models
- }
- if (cl.downloadsound_current < cl.loadsound_total)
- {
- // loading sounds
- for (;cl.downloadsound_current < cl.loadsound_total;cl.downloadsound_current++)
- {
- char soundname[MAX_QPATH];
- if (aborteddownload)
- {
- aborteddownload = false;
- continue;
- }
- if (cl.sound_precache[cl.downloadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.downloadsound_current]))
- continue;
- CL_KeepaliveMessage(true);
- dpsnprintf(soundname, sizeof(soundname), "sound/%s", cl.sound_name[cl.downloadsound_current]);
- if (!FS_FileExists(soundname) && !FS_FileExists(cl.sound_name[cl.downloadsound_current]))
- {
- Con_Printf("Sound %s not found\n", soundname);
- if (cl_serverextension_download.integer && cls.netcon && !sv.active)
- {
- Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", soundname));
- // we'll try loading again when the download finishes
- return;
- }
- }
- cl.sound_precache[cl.downloadsound_current] = S_PrecacheSound(cl.sound_name[cl.downloadsound_current], false, true);
- }
- // finished loading sounds
- }
- SCR_PopLoadingScreen(false);
- if (!cl.loadfinished)
- {
- cl.loadfinished = true;
- // check memory integrity
- Mem_CheckSentinelsGlobal();
- // now issue the spawn to move on to signon 2 like normal
- if (cls.netcon)
- Cmd_ForwardStringToServer("prespawn");
- }
- }
- static void CL_BeginDownloads_f(void)
- {
- // prevent cl_begindownloads from being issued multiple times in one match
- // to prevent accidentally cancelled downloads
- if(cl.loadbegun)
- Con_Printf("cl_begindownloads is only valid once per match\n");
- else
- CL_BeginDownloads(false);
- }
- static void CL_StopDownload(int size, int crc)
- {
- if (cls.qw_downloadmemory && cls.qw_downloadmemorycursize == size && CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize) == crc)
- {
- int existingcrc;
- size_t existingsize;
- const char *extension;
- if(cls.qw_download_deflate)
- {
- unsigned char *out;
- size_t inflated_size;
- out = FS_Inflate(cls.qw_downloadmemory, cls.qw_downloadmemorycursize, &inflated_size, tempmempool);
- Mem_Free(cls.qw_downloadmemory);
- if(out)
- {
- Con_Printf("Inflated download: new size: %u (%g%%)\n", (unsigned)inflated_size, 100.0 - 100.0*(cls.qw_downloadmemorycursize / (float)inflated_size));
- cls.qw_downloadmemory = out;
- cls.qw_downloadmemorycursize = (int)inflated_size;
- }
- else
- {
- cls.qw_downloadmemory = NULL;
- cls.qw_downloadmemorycursize = 0;
- Con_Printf("Cannot inflate download, possibly corrupt or zlib not present\n");
- }
- }
- if(!cls.qw_downloadmemory)
- {
- Con_Printf("Download \"%s\" is corrupt (see above!)\n", cls.qw_downloadname);
- }
- else
- {
- crc = CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
- size = cls.qw_downloadmemorycursize;
- // finished file
- // save to disk only if we don't already have it
- // (this is mainly for playing back demos)
- existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize);
- if (existingsize || IS_NEXUIZ_DERIVED(gamemode) || !strcmp(cls.qw_downloadname, csqc_progname.string))
- // let csprogs ALWAYS go to dlcache, to prevent "viral csprogs"; also, never put files outside dlcache for Nexuiz/Xonotic
- {
- if ((int)existingsize != size || existingcrc != crc)
- {
- // we have a mismatching file, pick another name for it
- char name[MAX_QPATH*2];
- dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc);
- if (!FS_FileExists(name))
- {
- Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc);
- FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
- if(!strcmp(cls.qw_downloadname, csqc_progname.string))
- {
- if(cls.caughtcsprogsdata)
- Mem_Free(cls.caughtcsprogsdata);
- cls.caughtcsprogsdata = (unsigned char *) Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorycursize);
- memcpy(cls.caughtcsprogsdata, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
- cls.caughtcsprogsdatasize = cls.qw_downloadmemorycursize;
- Con_DPrintf("Buffered \"%s\"\n", name);
- }
- }
- }
- }
- else
- {
- // we either don't have it or have a mismatching file...
- // so it's time to accept the file
- // but if we already have a mismatching file we need to rename
- // this new one, and if we already have this file in renamed form,
- // we do nothing
- Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc);
- FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
- extension = FS_FileExtension(cls.qw_downloadname);
- if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3"))
- FS_Rescan();
- }
- }
- }
- else if (cls.qw_downloadmemory && size)
- {
- Con_Printf("Download \"%s\" is corrupt (%i bytes, %i CRC, should be %i bytes, %i CRC), discarding\n", cls.qw_downloadname, size, crc, (int)cls.qw_downloadmemorycursize, (int)CRC_Block(cls.qw_downloadmemory, c…
Large files files are truncated, but you can click here to view the full file