I am going to describe the very few changes I had to make to implement this in the Windows version of ProQuake 3.99, which should be nearly the same as doing so with GLQuake or for that matter WinQuake.
End ResultNotes wrote:Off hand I don't see anything operating system specific here either but haven't tried to compile a Mac OS X version. Curl is known to be multi-platform. When I do the OS X version, I'll write up whatever.
The end result is that connecting to a server, like ThreeWave CTF downloads the maps and model -- but not the sounds -- and does so very rapidly (seems instant even for 5 MB maps on my broadband) and reconnects.
Yes, DarkPlaces does it better but this is still very good but can definitely be improved upon.
But the nicest thing is how simple the modification is.
We begin ...
Requirements for this Tutorial
1. Windows operating system + MSVC6
2. Libcurl.lib (I have no clue which specific version Rook used off the CURL download page).
3. This version of the ProQuake 3.99 source but this should likely work with stock glquake source with Microsoft Visual C++ Express Edition but you'll be on your own but it shouldn't be too hard.
Questions and Miscellaneous Notes
1. The libcurl.lib gets compiled into the binary and the size of the binary is only increased negligibly (a few KB!). Excellent!
2. Does the binary work properly on old Windows 98 or ME? What about Windows 7? Or even Vista? I haven't tried any of those yet with the binary. I'm not going to make the assumption that, say, Windows 98 works with this because I just don't know.
3. The download works super-efficiently and is extremely fast. Far faster than really what you'd expect!
Tutorial
1. Add the following files to your project
You can borrow them from this (download) and be sure to take the libcurl.lib as well.
2. client.hFiles to add:
curl.h
curlver.h
easy.h
multi.h
webdownload.c
webdownload.h
Add the yellow ....
And still in client.h, add the yellow ...typedef enum {
ca_dedicated, // a dedicated server with no ability to start a client
ca_disconnected, // full screen console with no connection
ca_connected // valid netcon, talking to a server
} cactive_t;
typedef struct
{
qboolean web;
char *name;
double percent;
qboolean disconnect; // set when user tries to disconnect, to allow cleaning up webdownload
} download_t;
3. Add COM_GetFolder to common.c// connection information
int signon; // 0 to SIGNONS
struct qsocket_s *netcon;
sizebuf_t message; // writing buffer to send to server
download_t download;
Add the yellow.
4. In common.h, add this anywhere really like at the end of the file/*
============
COM_GetFolder
============
*/
void COM_GetFolder (char *in, char *out)
{
char *last = NULL;
while (*in)
{
if (*in == '/')
last = out;
*out++ = *in++;
}
if (last)
*last = 0;
else
*out = 0;
}
/*
============
COM_FileBase
============
*/
void COM_FileBase (char *in, char *out)
{
char *s, *s2;
s = in + strlen(in) - 1;
while (s != in && *s != '.')
s--;
for (s2 = s ; *s2 && *s2 != '/' ; s2--)
;
if (s-s2 < 2)
strcpy (out,"?model?");
else
{
s--;
strncpy (out,s2+1, s-s2);
out[s-s2] = 0;
}
}
5. In cl_parse.cvoid COM_GetFolder (char *in, char *out);//R00k
Add the yellow.
And further download in cl_parse.c make alike!
/*
=====================
CL_WebDownloadProgress
Callback function for webdownloads.
Since Web_Get only returns once it's done, we have to do various things here:
Update download percent, handle input, redraw UI and send net packets.
=====================
*/
static int CL_WebDownloadProgress( double percent )
{
static double time, oldtime, newtime;
cls.download.percent = percent;
CL_KeepaliveMessage();
newtime = Sys_DoubleTime ();
time = newtime - oldtime;
Host_Frame (time);
oldtime = newtime;
return cls.download.disconnect; // abort if disconnect received
}
/*
==================
CL_ParseServerInfo
==================
*/
void CL_ParseServerInfo (void)
{
char *str, tempname[MAX_QPATH];
int i, nummodels, numsounds;
char model_precache[MAX_MODELS][MAX_QPATH];
char sound_precache[MAX_SOUNDS][MAX_QPATH];
extern cvar_t cl_web_download;
extern cvar_t cl_web_download_url;
extern int Web_Get( const char *url, const char *referer, const char *name, int resume, int max_downloading_time, int timeout, int ( *_progress )(double) );
// now we try to load everything else until a cache allocation fails
for (i=1 ; i<nummodels ; i++)
{
cl.model_precache = Mod_ForName (model_precache, false);
if (cl.model_precache == NULL)
{
if (cl_web_download.value && cl_web_download_url.string)
{
char url[1024];
qboolean success = false;
char download_tempname[MAX_QPATH],download_finalname[MAX_QPATH];
char folder[MAX_QPATH];
char name[MAX_QPATH];
extern char server_name[MAX_QPATH];
extern int net_hostport;
//Create the FULL path where the file should be written
Q_snprintfz (download_tempname, MAX_OSPATH, "%s/%s.tmp", com_gamedir, model_precache);
//determine the proper folder and create it, the OS will ignore if already exsists
COM_GetFolder(model_precache,folder);// "progs/","maps/"
Q_snprintfz (name, sizeof(name), "%s/%s", com_gamedir, folder);
Sys_mkdir (name);
Con_Printf( "Web downloading: %s from %s%s\n", model_precache, cl_web_download_url.string, model_precache);
//assign the url + path + file + extension we want
Q_snprintfz( url, sizeof( url ), "%s%s", cl_web_download_url.string, model_precache);
cls.download.web = true;
cls.download.disconnect = false;
cls.download.percent = 0.0;
//let libCURL do it's magic!!
success = Web_Get(url, NULL, download_tempname, false, 600, 30, CL_WebDownloadProgress);
cls.download.web = false;
free(url);
free(name);
free(folder);
if (success)
{
Con_Printf("Web download succesfull: %s\n", download_tempname);
//Rename the .tmp file to the final precache filename
Q_snprintfz (download_finalname, MAX_OSPATH, "%s/%s", com_gamedir, model_precache);
rename (download_tempname, download_finalname);
free(download_tempname);
free(download_finalname);
Cbuf_AddText (va("connect %s:%u\n",server_name,net_hostport));//reconnect after each success
return;
}
else
{
remove (download_tempname);
Con_Printf( "Web download of %s failed\n", download_tempname );
return;
}
free(download_tempname);
if( cls.download.disconnect )//if the user type disconnect in the middle of the download
{
cls.download.disconnect = false;
CL_Disconnect_f();
return;
}
} else
#endif
{
Con_Printf("Model %s not found\n", model_precache);
return;
}
}
CL_KeepaliveMessage ();
}
S_BeginPrecaching ();
for (i=1 ; i<numsounds ; i++)
{
cl.sound_precache[i] = S_PrecacheSound (sound_precache[i]);
CL_KeepaliveMessage ();
}
S_EndPrecaching ();
6. Now in cl_main.c
*/
// cl_main.c -- client main loop
#include "quakedef.h"
#include "curl.h"
cvar_t cl_web_download = {"cl_web_download", "1", true};
cvar_t cl_web_download_url = {"cl_web_download_url", "http://www.mywebpage.com/maps/", true};
client_static_t cls;
client_state_t cl;
And then ...
void CL_Disconnect (void)
{
// stop sounds (especially looping!)
S_StopAllSounds (true);
// bring the console down and fade the colors back to normal
// SCR_BringDownConsole ();
// We have to shut down webdownloading first
if( cls.download.web )
{
cls.download.disconnect = true;
return;
}
// if running a local server, shut it down
if (cls.demoplayback)
{
CL_StopPlayback ();
}
And then near end of cl_main.c
Cmd_AddCommand ("timedemo", CL_TimeDemo_f);
Cvar_RegisterVariable (&cl_web_download, NULL);
Cvar_RegisterVariable (&cl_web_download_url, NULL);
7. In your project settings, include libcurl.lib to be compiled into the build. In MSVC6, you do this by clicking Project -> Settings and then I select GL Release with ProQuake 3.99 and click the Link Tab and in "Object/Library Modules" I added libcurl.lib to the end.
You are done.
Here is a before and after of the code more or less so someone with WinMerge or whatever can manually examine the differences.
Source code: Before (download) and after (download).