diff --git a/CMakeLists.txt b/CMakeLists.txt index 9500617bf..47efee278 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -509,15 +509,17 @@ SET_TARGET_PROPERTIES(qi PROPERTIES COMPILE_DEFINITIONS "FTEPLUGIN") SET_TARGET_PROPERTIES(qi PROPERTIES PREFIX "fteplug_") #Bullet Physics library plugin -FIND_PACKAGE(Bullet REQUIRED) -ADD_LIBRARY(bullet MODULE - plugins/qvm_api.c - plugins/plugin.c - plugins/bullet/bulletplug.cpp -) -TARGET_INCLUDE_DIRECTORIES(bullet PUBLIC ${BULLET_INCLUDE_DIRS}) -SET_TARGET_PROPERTIES(bullet PROPERTIES COMPILE_DEFINITIONS "FTEPLUGIN") -SET_TARGET_PROPERTIES(bullet PROPERTIES PREFIX "fteplug_") +FIND_PACKAGE(Bullet) +IF (${BULLET_FOUND}) + ADD_LIBRARY(bullet MODULE + plugins/qvm_api.c + plugins/plugin.c + plugins/bullet/bulletplug.cpp + ) + TARGET_INCLUDE_DIRECTORIES(bullet PUBLIC ${BULLET_INCLUDE_DIRS}) + SET_TARGET_PROPERTIES(bullet PROPERTIES COMPILE_DEFINITIONS "FTEPLUGIN") + SET_TARGET_PROPERTIES(bullet PROPERTIES PREFIX "fteplug_") +ENDIF() #Bullet Physics library plugin #FIND_PACKAGE(ode REQUIRED) @@ -569,4 +571,3 @@ ENDIF() #ffmpeg plugin #cef plugin -#bullet plugin diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index 54ff640d3..6e401807a 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -1249,6 +1249,12 @@ void VARGS CL_SendClientCommand(qboolean reliable, char *format, ...) CL_AllowIndependantSendCmd(oldallow); } +//sometimes a server will quickly restart twice. +//connected clients will then receive TWO 'new' commands - both with the same servercount value. +//the connection process then tries to proceed with two sets of commands until it fails catastrophically. +//by attempting to strip out dupe commands we can usually avoid the issue +//note that FTE servers track progress properly, so this is not an issue for us, but in the interests of compat with mvdsv... +//however, FTE servers can send a little faster, so warnings about this can be awkward. int CL_RemoveClientCommands(char *command) { clcmdbuf_t *next, *first; diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index affaae1ad..a0a0a37f3 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -3302,7 +3302,7 @@ static void CLQW_ParseServerData (void) } else { - if (CL_RemoveClientCommands("soundlist")) + if (CL_RemoveClientCommands("soundlist") && !(cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) Con_DPrintf("Multiple soundlists\n"); // ask for the sound list next // CL_SendClientCommand ("soundlist %i 0", cl.servercount); @@ -4068,7 +4068,7 @@ static void CL_ParseSoundlist (qboolean lots) } else { - if (CL_RemoveClientCommands("modellist")) + if (CL_RemoveClientCommands("modellist") && !(cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) Con_DPrintf("Multiple modellists\n"); // CL_SendClientCommand ("modellist %i 0", cl.servercount); CL_SendClientCommand (true, modellist_name, cl.servercount, 0); @@ -5247,9 +5247,9 @@ static void CL_ParseSetInfo (void) player = &cl.players[slot]; if (offset) - Con_DPrintf("SETINFO %s: %s+=%s\n", player->name, key, val); + Con_DLPrintf(2,"SETINFO %s: %s+=%s\n", player->name, key, val); else - Con_DPrintf("SETINFO %s: %s=%s\n", player->name, key, val); + Con_DLPrintf(strcmp(key, "chat")?1:2,"SETINFO %s: %s=%s\n", player->name, key, val); InfoBuf_SyncReceive(&player->userinfo, key, keysize, val, valsize, offset, final); player->userinfovalid = true; diff --git a/engine/client/m_items.c b/engine/client/m_items.c index 0644b5be7..d16956c50 100644 --- a/engine/client/m_items.c +++ b/engine/client/m_items.c @@ -1964,6 +1964,11 @@ void M_Menu_Main_f (void) static menuresel_t resel; int y; +#ifndef SERVERONLY + if (isDedicated || !Renderer_Started()) + return; +#endif + #ifdef CSQC_DAT if (CSQC_ConsoleCommand(-1, va("%s %s", Cmd_Argv(0), Cmd_Args()))) return; @@ -2223,10 +2228,18 @@ int MC_AddBulk(struct menu_s *menu, menuresel_t *resel, menubulk_t *bulk, int xs { //lots of fancy code just to figure out the correct width of the string. yay. :( int px, py; conchar_t buffer[2048], *end; - end = COM_ParseFunString(CON_WHITEMASK, bulk->text, buffer, sizeof(buffer), false); - Font_BeginString(font_default, 0, 0, &px, &py); - px = Font_LineWidth(buffer, end); - Font_EndString(NULL); + if (font_default) + { + end = COM_ParseFunString(CON_WHITEMASK, bulk->text, buffer, sizeof(buffer), false); + Font_BeginString(font_default, 0, 0, &px, &py); + px = Font_LineWidth(buffer, end); + Font_EndString(NULL); + } + else + { + Con_DPrintf("MC_AddBulk: default font not initialised yet\n"); + px = strlen(bulk->text)*8; + } x -= ((float)px * vid.width) / vid.rotpixelwidth; } diff --git a/engine/client/m_single.c b/engine/client/m_single.c index 0b3bc8100..ba3c2faf4 100644 --- a/engine/client/m_single.c +++ b/engine/client/m_single.c @@ -8,8 +8,6 @@ //============================================================================= /* LOAD/SAVE MENU */ -#define FTESAVEGAME_VERSION 25000 - typedef struct { int issave; int cursorpos; @@ -61,7 +59,7 @@ static void M_ScanSave(unsigned int slot, const char *name, qboolean savable) { VFS_GETS(f, line, sizeof(line)); version = atoi(line); - if (version != 5 && version != 6 && (version < FTESAVEGAME_VERSION || version >= FTESAVEGAME_VERSION+GT_MAX)) + if (version != SAVEGAME_VERSION_NQ && version != SAVEGAME_VERSION_QW && version != SAVEGAME_VERSION_FTE_LEG && (version < SAVEGAME_VERSION_FTE_HUB || version >= SAVEGAME_VERSION_FTE_HUB+GT_MAX)) { Q_strncpyz (m_saves[slot].desc, "Incompatible version", sizeof(m_saves[slot].desc)); VFS_CLOSE (f); diff --git a/engine/client/p_script.c b/engine/client/p_script.c index d42514d6b..05659739b 100644 --- a/engine/client/p_script.c +++ b/engine/client/p_script.c @@ -2246,12 +2246,14 @@ parsefluid: if (ptype->scale) { ptype->looks.type = PT_SPARKFAN; - Con_DPrintf("%s.%s: effect lacks a texture. assuming type sparkfan.\n", ptype->config, ptype->name); + if (ptype->countextra || ptype->count || ptype->countrand) + Con_DPrintf("%s.%s: effect lacks a texture. assuming type sparkfan.\n", ptype->config, ptype->name); } else { ptype->looks.type = PT_SPARK; - Con_DPrintf("%s.%s: effect lacks a texture. assuming type spark.\n", ptype->config, ptype->name); + if (ptype->countextra || ptype->count || ptype->countrand) + Con_DPrintf("%s.%s: effect lacks a texture. assuming type spark.\n", ptype->config, ptype->name); } } else if (ptype->looks.type == PT_SPARK) diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index 79440af16..24361b2b6 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -7183,19 +7183,28 @@ pbool PDECL CSQC_CheckHeaderCrc(pubprogfuncs_t *progs, progsnum_t num, int crc) { if (!num) { - if (crc == 22390) - ; //fte's full csqc stuff - else if (crc == 5927) - ; //simple csqc. but only if -#ifndef csqc_isdarkplaces - else if (crc == 52195) + switch(crc) { + case PROGHEADER_CRC_CSQC: + break; //fte's full csqc stuff + case PROGHEADER_CRC_QW: + case PROGHEADER_CRC_NQ: + case PROGHEADER_CRC_PREREL: + case PROGHEADER_CRC_TENEBRAE: + case PROGHEADER_CRC_H2: + case PROGHEADER_CRC_H2MP: + case PROGHEADER_CRC_H2DEMO: + break; //simple csqc. but only if it has the right entry points. +#ifndef csqc_isdarkplaces + case PROGHEADER_CRC_CSQC_DP: csqc_isdarkplaces = true; Con_DPrintf(CON_WARNING "Running darkplaces csprogs.dat version\n"); - } + break; #endif - else + default: Con_Printf(CON_WARNING "Running unknown csprogs.dat version\n"); + break; + } } return true; } @@ -7372,7 +7381,7 @@ qboolean CSQC_Init (qboolean anycsqc, qboolean csdatenabled, unsigned int checks if (csqc_nogameaccess && !PR_FindFunction (csqcprogs, "CSQC_DrawHud", PR_ANY)) { //simple csqc module is not csqc. abort now. CSQC_Shutdown(); - Con_DPrintf("Loaded progs.dat has no CSQC_DrawHud\n"); + Con_DPrintf("progs.dat is not suitable for SimpleCSQC - no CSQC_DrawHud\n"); return false; } else if (csqc_nogameaccess) diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c index 15926bab1..5520689c2 100644 --- a/engine/client/r_surf.c +++ b/engine/client/r_surf.c @@ -1337,10 +1337,7 @@ static void Surf_BuildLightMap (model_t *currentmodel, msurface_t *surf, qbyte * { t = (-1-ambient)*255; for (i=0 ; icached_light[maps] = -1-ambient; @@ -1350,25 +1347,19 @@ static void Surf_BuildLightMap (model_t *currentmodel, msurface_t *surf, qbyte * else if (r_fullbright.value>0) //not qw { for (i=0 ; ilightdata) { /*fullbright if map is not lit. but not overbright*/ for (i=0 ; isamples) { /*no samples, but map is otherwise lit = pure black*/ for (i=0 ; icached_light[0] = 0; surf->cached_colour[0] = 0; } @@ -1472,8 +1463,26 @@ static void Surf_BuildLightMap (model_t *currentmodel, msurface_t *surf, qbyte * else { // set to full bright if no light data - if (r_fullbright.ival || !currentmodel->lightdata) + if (ambient < 0) { + t = (-1-ambient)*255; + for (i=0 ; icached_light[maps] = -1-ambient; + surf->cached_colour[maps] = 0xff; + } + } + else if (r_fullbright.value > 0) + { //r_fullbright is meant to be a scaler. + for (i=0 ; icached_light[0] = d_lightstylevalue[0]; + surf->cached_colour[0] = cl_lightstyle[0].colourkey; + } + else if (!currentmodel->lightdata) + { //no scalers here. for (i=0 ; icached_light[0] = d_lightstylevalue[0]; diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index 2aac2fd25..f2986e822 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -565,7 +565,7 @@ int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char *match, dir = opendir(truepath); if (!dir) { - Con_DPrintf("Failed to open dir %s\n", truepath); + Con_DLPrintf((errno==ENOENT)?2:1, "Failed to open dir %s\n", truepath); return true; } do @@ -664,7 +664,7 @@ dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) lib = lt_dlopenext (name); if (!lib) { - Con_DPrintf("%s: %s\n", name, lt_dlerror()); + Con_DLPrintf(2, "%s: %s\n", name, lt_dlerror()); return NULL; } @@ -711,7 +711,7 @@ dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) lib = dlopen (va("%s.so", name), RTLD_LAZY); if (!lib) { - Con_DPrintf("%s\n", dlerror()); + Con_DLPrintf(2,"%s\n", dlerror()); return NULL; } diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 1a1a586cd..c8a9a0bc0 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -1220,8 +1220,14 @@ STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION = 255, // DP //savegame vars -#define SAVEGAME_COMMENT_LENGTH 39 -#define SAVEGAME_VERSION 667 +#define SAVEGAME_COMMENT_LENGTH 39 +#define SAVEGAME_VERSION_NQ 5 +#define SAVEGAME_VERSION_QW 6 //actually zQuake, but the functional difference is that its qw instead of nq. +#define SAVEGAME_VERSION_FTE_LEG 667 //found in .sav files. this is for legacy-like saved games with multiple players. +#define SAVEGAME_VERSION_FTE_HUB 25000 //found in .fsv files. includes svs.gametype, so bumps should be large. +#define CACHEGAME_VERSION_OLD 513 +#define CACHEGAME_VERSION_VERBOSE 514 +#define CACHEGAME_VERSION_BINARY 515 #define PM_DEFAULTSTEPHEIGHT 18 diff --git a/engine/common/com_mesh.c b/engine/common/com_mesh.c index 62d2d4ea0..8c04f2b75 100644 --- a/engine/common/com_mesh.c +++ b/engine/common/com_mesh.c @@ -3478,12 +3478,12 @@ static void *Q1MDL_LoadSkins_GL (galiasinfo_t *galias, dmdl_t *pq1inmodel, model case TF_H2_T4A4: frames[0].defaultshader = "{\n" + "cull disable\n" "{\n" "map $diffuse\n" "blendfunc gl_one_minus_src_alpha gl_src_alpha\n" "alphagen entity\n" "rgbgen lightingDiffuse\n" - "cull disable\n" "depthwrite\n" "}\n" "}\n"; diff --git a/engine/common/fs.c b/engine/common/fs.c index 2ccc12be1..efc8bc69a 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -3069,7 +3069,7 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths) /*some modern non-compat settings*/ #define DMFCFG "set com_parseutf8 1\npm_airstep 1\nsv_demoExtensions 1\n" /*set some stuff so our regular qw client appears more like hexen2. sv_mintic is required to 'fix' the ravenstaff so that its projectiles don't impact upon each other*/ -#define HEX2CFG "set com_parseutf8 -1\nset gl_font gfx/hexen2\nset in_builtinkeymap 0\nset_calc cl_playerclass int (random * 5) + 1\nset sv_maxspeed 640\ncl_run 0\nset watervis 1\nset r_lavaalpha 1\nset r_lavastyle -2\nset r_wateralpha 0.5\nset sv_pupglow 1\ngl_shaftlight 0.5\nsv_mintic 0.015\nset mod_warnmodels 0\nset cl_model_bobbing 1\nsv_sound_watersplash \"misc/hith2o.wav\"\nsv_sound_land \"fx/thngland.wav\"\n" +#define HEX2CFG "set com_parseutf8 -1\nset gl_font gfx/hexen2\nset in_builtinkeymap 0\nset_calc cl_playerclass int (random * 5) + 1\nset cl_forwardspeed 200\nset cl_backspeed 200\ncl_sidespeed 225\nset sv_maxspeed 640\ncl_run 0\nset watervis 1\nset r_lavaalpha 1\nset r_lavastyle -2\nset r_wateralpha 0.5\nset sv_pupglow 1\ngl_shaftlight 0.5\nsv_mintic 0.015\nset mod_warnmodels 0\nset cl_model_bobbing 1\nsv_sound_watersplash \"misc/hith2o.wav\"\nsv_sound_land \"fx/thngland.wav\"\nset sv_walkpitch 0\n" /*yay q2!*/ #define Q2CFG "set com_parseutf8 0\ncom_nogamedirnativecode 0\nset sv_bigcoords 0\n" /*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/ @@ -4332,10 +4332,11 @@ qboolean Sys_FindGameData(const char *poshname, const char *gamename, char *base if (Sys_SteamHasFile(basepath, basepathlen, "quake 2", "baseq2/pak0.pak")) return true; } - else if (!strcmp(gamename, "hexen2") || !strcmp(gamename, "h2mp")) + else if (!strcmp(gamename, "hexen2") || !strcmp(gamename, "h2mp") || !strcmp(gamename, "portals")) { if (Sys_SteamHasFile(basepath, basepathlen, "hexen 2", "data/pak0.pak")) return true; + gamename = "hexen2"; } s = va("/usr/share/games/%s/", gamename); diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index 432128008..59ffb5fc3 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -88,6 +88,21 @@ void PR_Route_Visualise (void); void PR_Route_Init (void); #endif +//known progs versions... +enum +{ + PROGHEADER_CRC_QW = 54730, + PROGHEADER_CRC_NQ = 5927, + PROGHEADER_CRC_PREREL = 26940, //prerelease + PROGHEADER_CRC_TENEBRAE = 32401, //tenebrae + PROGHEADER_CRC_H2 = 38488, //basic hexen2 + PROGHEADER_CRC_H2MP = 26905, //hexen2 mission pack uses slightly different defs... *sigh*... + PROGHEADER_CRC_H2DEMO = 14046, //I'm guessing this is from the original release or something + PROGHEADER_CRC_CSQC = 22390, + PROGHEADER_CRC_CSQC_DP = 52195, + PROGHEADER_CRC_MENUQC = 10020 +}; + //pr_cmds.c builtins that need to be moved to a common. void VARGS PR_BIError(pubprogfuncs_t *progfuncs, char *format, ...) LIKEPRINTF(2); void QCBUILTIN PF_print (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); diff --git a/engine/common/qvm.c b/engine/common/qvm.c index 78ca5daaa..0bab9a97a 100644 --- a/engine/common/qvm.c +++ b/engine/common/qvm.c @@ -97,6 +97,8 @@ qboolean QVM_LoadDLL(vm_t *vm, const char *name, qboolean binroot, void **vmMain hVM=NULL; *fname = 0; + Con_DPrintf("Attempting to load native library: %s\n", name); + if (binroot) { if (!hVM && FS_NativePath(dllname_arch, FS_BINARYPATH, fname, sizeof(fname))) @@ -115,13 +117,13 @@ qboolean QVM_LoadDLL(vm_t *vm, const char *name, qboolean binroot, void **vmMain { if (!hVM && FS_NativePath(va("%s_%s_"ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, name, gpath), FS_BINARYPATH, fname, sizeof(fname))) { - Con_DPrintf("Loading native: %s\n", fname); + Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } if (!hVM && FS_NativePath(va("%s_%s"ARCH_DL_POSTFIX, name, gpath), FS_BINARYPATH, fname, sizeof(fname))) { - Con_DPrintf("Loading native: %s\n", fname); + Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } } @@ -135,14 +137,14 @@ qboolean QVM_LoadDLL(vm_t *vm, const char *name, qboolean binroot, void **vmMain if (!hVM) { snprintf (fname, sizeof(fname), "%s/%s", gpath, dllname_arch); - Con_DPrintf("Loading native: %s\n", fname); + Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } if (!hVM) { snprintf (fname, sizeof(fname), "%s/%s", gpath, dllname_anycpu); - Con_DPrintf("Loading native: %s\n", fname); + Con_DLPrintf(2, "Loading native: %s\n", fname); hVM = Sys_LoadLibrary(fname, funcs); } } diff --git a/engine/common/sys_linux_threads.c b/engine/common/sys_linux_threads.c index 0300dd96c..66e849ab8 100644 --- a/engine/common/sys_linux_threads.c +++ b/engine/common/sys_linux_threads.c @@ -104,8 +104,7 @@ void *Sys_CreateThread(char *name, int (*func)(void *), void *args, int priority thread = NULL; } pthread_attr_destroy(&attr); - -#ifdef __USE_GNU +#if defined(DEBUG) && defined(__USE_GNU) && __GLIBC_PREREQ(2,12) pthread_setname_np(*thread, name); #endif @@ -133,7 +132,7 @@ void *Sys_CreateThread(char *name, int (*func)(void *), void *args, int priority } pthread_attr_destroy(&attr); -#ifdef __USE_GNU +#if defined(DEBUG) && defined(__USE_GNU) && __GLIBC_PREREQ(2,12) pthread_setname_np(*thread, name); #endif diff --git a/engine/gl/gl_font.c b/engine/gl/gl_font.c index 0d6bfe044..4967e0187 100644 --- a/engine/gl/gl_font.c +++ b/engine/gl/gl_font.c @@ -1950,7 +1950,7 @@ struct font_s *Font_LoadFont(const char *fontfilename, float vheight) size_t lumpsize; qbyte lumptype; unsigned char *w = W_GetLumpName(fontfilename+4, &lumpsize, &lumptype); - if (!w || lumpsize != 5) + if (!w || lumpsize != 32*128 || lumptype != 'D') { Z_Free(f); return NULL; diff --git a/engine/gl/gl_vidlinuxglx.c b/engine/gl/gl_vidlinuxglx.c index 06ce980c3..7225ae9c1 100644 --- a/engine/gl/gl_vidlinuxglx.c +++ b/engine/gl/gl_vidlinuxglx.c @@ -150,6 +150,7 @@ static struct Cursor (*pXCreatePixmapCursor)(Display *display, Pixmap source, Pixmap mask, XColor *foreground_color, XColor *background_color, unsigned int x, unsigned int y); Window (*pXCreateWindow)(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, int depth, unsigned int class, Visual *visual, unsigned long valuemask, XSetWindowAttributes *attributes); int (*pXDefineCursor)(Display *display, Window w, Cursor cursor); + int (*pXDeleteProperty)(Display *display, Window w, Atom property); int (*pXDestroyWindow)(Display *display, Window w); int (*pXFillRectangle)(Display *display, Drawable d, GC gc, int x, int y, unsigned int width, unsigned int height); int (*pXFlush)(Display *display); @@ -213,6 +214,13 @@ static struct qboolean dounicode; XIC unicodecontext; XIM inputmethod; + + struct + { + Window source; //the source window to send dndFinished to. + Atom type; //the type of the data. usually text/uri-list. + Atom myprop; //the property on our window that we're copying the data to. + } dnd; } x11; static int X11_ErrorHandler(Display *dpy, XErrorEvent *e) @@ -237,6 +245,7 @@ static qboolean x11_initlib(void) {(void**)&x11.pXCreatePixmapCursor, "XCreatePixmapCursor"}, {(void**)&x11.pXCreateWindow, "XCreateWindow"}, {(void**)&x11.pXDefineCursor, "XDefineCursor"}, + {(void**)&x11.pXDeleteProperty, "XDeleteProperty"}, {(void**)&x11.pXDestroyWindow, "XDestroyWindow"}, {(void**)&x11.pXFillRectangle, "XFillRectangle"}, {(void**)&x11.pXFlush, "XFlush"}, @@ -2353,26 +2362,150 @@ static void GetEvent(void) Con_DPrintf("Got unknown x11wm message %s\n", protname); x11.pXFree(protname); } +#if 1 + else if (!strcmp(name, "XdndEnter") && event.xclient.format == 32) + { + //check for text/uri-list + int i; + for (i = 2; i < 2+3; i++) + { + if (event.xclient.data.l[i]) + { + char *t = x11.pXGetAtomName(vid_dpy, event.xclient.data.l[i]); + if (!strcmp(t, "text/uri-list")) //file list + x11.dnd.type = event.xclient.data.l[i]; + x11.pXFree(t); + } + } + } + else if (!strcmp(name, "XdndPosition") && event.xclient.format == 32) + { + //Send XdndStatus + XEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = event.xclient.data.l[0]; + xev.xclient.message_type = x11.pXInternAtom(vid_dpy, "XdndStatus", False); + xev.xclient.format = 32; + xev.xclient.data.l[0] = vid_window; //so source can ignore it if stale + xev.xclient.data.l[1] = 1; + xev.xclient.data.l[2] = 0; //(x<<16)|y (should be in root coords) + xev.xclient.data.l[3] = 0; //(w<<16)|h + xev.xclient.data.l[4] = x11.pXInternAtom (vid_dpy, "XdndActionCopy", False); + x11.pXSendEvent(vid_dpy, xev.xclient.window, False, 0, &xev); + } + else if (!strcmp(name, "XdndLeave") && event.xclient.format == 32) + { + if (x11.dnd.source == event.xclient.data.l[0]) + { + x11.dnd.source = None; + x11.dnd.type = None; + } + } + else if (!strcmp(name, "XdndDrop") && event.xclient.format == 32) + { + Atom xa_XdndSelection = x11.pXInternAtom(vid_dpy, "XdndSelection", False); + Time t = CurrentTime;//event.xclient.data.l[2]; + x11.dnd.myprop = x11.pXInternAtom(vid_dpy, "_FTE_dnd", False); + if (x11.pXGetSelectionOwner(vid_dpy, xa_XdndSelection) == event.xclient.data.l[0]) + { + x11.pXDeleteProperty(vid_dpy, vid_window, x11.dnd.myprop); + x11.pXConvertSelection(vid_dpy, xa_XdndSelection, x11.dnd.type, x11.dnd.myprop, vid_window, t); + } + } +#endif else Con_DPrintf("Got unknown x11 message %s\n", name); x11.pXFree(name); } break; - #if 1 - case SelectionRequest: //needed for copy-to-clipboard + case SelectionNotify: + //for drag-n-drop + if (event.xselection.selection == x11.pXInternAtom(vid_dpy, "XdndSelection", False) && x11.dnd.myprop != None) { - Atom xa_string = x11.pXInternAtom(vid_dpy, "UTF8_STRING", false); + qboolean okay = false; + unsigned char *data; + Atom type; + int fmt; + unsigned long nitems; + unsigned long bytesleft; + if (x11.pXGetWindowProperty(vid_dpy, vid_window, x11.dnd.myprop, 0, 65536, False, AnyPropertyType, &type, &fmt, &nitems, &bytesleft, &data) == Success && data) + { + if (type == x11.dnd.type) + { + char *start, *end; + for (start = data; *start; ) + { + for (end = start; *end && *end != '\r'; end++) + ; + if (end != start) + Host_RunFile(start, end-start, NULL); + start = end; + while (*start == '\r' || *start == '\n') + start++; + } + okay = true; + x11.pXFree(data); + } + } + x11.pXDeleteProperty(vid_dpy, vid_window, x11.dnd.myprop); //might be large, so don't force it to hang around. + + //Send XdndFinished now + XEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = x11.dnd.source; + xev.xclient.message_type = x11.pXInternAtom(vid_dpy, "XdndFinished", False); + xev.xclient.format = 32; + xev.xclient.data.l[0] = vid_window; //so source can ignore it if stale + xev.xclient.data.l[1] = (okay?1:0); + xev.xclient.data.l[2] = x11.pXInternAtom (vid_dpy, "XdndActionCopy", False); + x11.pXSendEvent(vid_dpy, xev.xclient.window, False, 0, &xev); + } + break; + + case SelectionRequest: //needed for when another program tries pasting. + { + Atom xa_u8string = x11.pXInternAtom(vid_dpy, "UTF8_STRING", false); //explicitly UTF-8 + Atom xa_l1string = x11.pXInternAtom(vid_dpy, "STRING", false); //explicitly 8859-1 + Atom xa_text = x11.pXInternAtom(vid_dpy, "TEXT", false); //selection owner decides encoding (and we pick UTF-8) + Atom xa_targets = x11.pXInternAtom(vid_dpy, "TARGETS", false); + Atom xa_supportedtargets[] = {xa_u8string, xa_l1string, xa_text, xa_targets/*, xa_multiple, xa_timestamp*/}; memset(&rep, 0, sizeof(rep)); + if (event.xselectionrequest.property == None) event.xselectionrequest.property = x11.pXInternAtom(vid_dpy, "foobar2000", false); - if (event.xselectionrequest.property != None && event.xselectionrequest.target == xa_string) - { + if (event.xselectionrequest.property != None && event.xselectionrequest.target == xa_targets) + { //TARGETS results in a list of accepted target types (atoms) + x11.pXChangeProperty(vid_dpy, event.xselectionrequest.requestor, event.xselectionrequest.property, event.xselectionrequest.target, 32, PropModeReplace, (void*)xa_supportedtargets, countof(xa_supportedtargets)); + rep.xselection.property = event.xselectionrequest.property; + } + else if (event.xselectionrequest.property != None && (event.xselectionrequest.target == xa_u8string || event.xselectionrequest.target == xa_text)) + { //UTF8_STRING or TEXT (which we choose to use utf-8 as our charset) x11.pXChangeProperty(vid_dpy, event.xselectionrequest.requestor, event.xselectionrequest.property, event.xselectionrequest.target, 8, PropModeReplace, (void*)clipboard_buffer, strlen(clipboard_buffer)); rep.xselection.property = event.xselectionrequest.property; } + else if (event.xselectionrequest.property != None && event.xselectionrequest.target == xa_l1string) + { //STRING == latin1. convert as needed. + char latin1[SYS_CLIPBOARD_SIZE]; + char *in = clipboard_buffer; + int c = 0; + int err; + while (*in && c < sizeof(latin1)) + { + int uc =utf8_decode(&err, in, &in); + if ((uc >= 0xe000 && uc <= 0xe100) && (uc&0x7f) >= 32) + uc = uc&0x7f; //don't do c0/c1 glyphs. otherwise treat as ascii. + else if (uc > 255 || err) + uc = '?'; //unsupported char + latin1[c++] = uc; + } + x11.pXChangeProperty(vid_dpy, event.xselectionrequest.requestor, event.xselectionrequest.property, event.xselectionrequest.target, 8, PropModeReplace, (void*)latin1, c); + rep.xselection.property = event.xselectionrequest.property; + } else - { + { //unsupported target. we need to let them know that we don't know what they're asking for. rep.xselection.property = None; } rep.xselection.type = SelectionNotify; @@ -2518,7 +2651,7 @@ static void *X11VID_CreateCursorRGBA(const qbyte *rgbacursor, size_t w, size_t h for (y = 0; y < h; y++) for (x = 0; x < w; x++, rgbacursor+=4) - *dest++ = (rgbacursor[3]<<24)|(rgbacursor[0]<<16)|(rgbacursor[1]<<8)|(rgbacursor[0]<<0); //0xARGB + *dest++ = (rgbacursor[3]<<24)|(rgbacursor[0]<<16)|(rgbacursor[1]<<8)|(rgbacursor[2]<<0); //0xARGB cursor = Z_Malloc(sizeof(*cursor)); *cursor = xcursor.ImageLoadCursor(vid_dpy, img); @@ -2994,6 +3127,10 @@ Window X_CreateWindow(qboolean override, XVisualInfo *visinfo, int x, int y, uns /*make it visible*/ x11.pXMapWindow(vid_dpy, wnd); + //advertise support as a drag+drop target + prots[0] = 5; //version 5 is the most recent. + x11.pXChangeProperty(vid_dpy, wnd, x11.pXInternAtom(vid_dpy, "XdndAware", False), XA_ATOM, 32, PropModeReplace, (void*)prots, 1); + return wnd; } @@ -3656,6 +3793,7 @@ char *Sys_GetClipboard(void) { if(vid_dpy) { + //FIXME: we should query it using TARGETS first to see if UTF8_STRING etc is actually valid. Atom xa_clipboard = x11.pXInternAtom(vid_dpy, "PRIMARY", false); Atom xa_string = x11.pXInternAtom(vid_dpy, "UTF8_STRING", false); Window clipboardowner = x11.pXGetSelectionOwner(vid_dpy, xa_clipboard); @@ -3666,8 +3804,15 @@ char *Sys_GetClipboard(void) unsigned long nitems, bytesleft; unsigned char *data; x11.pXConvertSelection(vid_dpy, xa_clipboard, xa_string, None, vid_window, CurrentTime); + + //FIXME: we should rewrite the clipboard pasting to invoke a callback once its available. x11.pXFlush(vid_dpy); - x11.pXGetWindowProperty(vid_dpy, vid_window, xa_string, 0, 0, False, AnyPropertyType, &type, &fmt, &nitems, &bytesleft, &data); + x11.pXSync(vid_dpy, False); + Sys_Sleep(0.3); + x11.pXSync(vid_dpy, False); + + //and now we can actually read the data. + x11.pXGetWindowProperty(vid_dpy, vid_window, xa_string, 0, 65536, False, AnyPropertyType, &type, &fmt, &nitems, &bytesleft, &data); return data; } @@ -3691,6 +3836,10 @@ void Sys_SaveClipboard(char *text) { Atom xa_clipboard = x11.pXInternAtom(vid_dpy, "PRIMARY", false); x11.pXSetSelectionOwner(vid_dpy, xa_clipboard, vid_window, CurrentTime); + + //Set both clipboards. Because x11 is kinda annoying. + xa_clipboard = x11.pXInternAtom(vid_dpy, "CLIPBOARD", false); + x11.pXSetSelectionOwner(vid_dpy, xa_clipboard, vid_window, CurrentTime); } } #endif diff --git a/engine/gl/gl_warp.c b/engine/gl/gl_warp.c index c875afee3..78378eeec 100644 --- a/engine/gl/gl_warp.c +++ b/engine/gl/gl_warp.c @@ -77,7 +77,7 @@ void R_SetSky(const char *sky) tex.reflectcube = R_LoadHiResTexture(sky, "env:gfx/env", IF_LOADNOW|IF_CUBEMAP|IF_CLAMP); if (tex.reflectcube->width) { - forcedsky = R_RegisterShader(va("skybox_%s", sky), 0, "{\nsort sky\nprogram defaultskybox\n{\nmap \"$cube:$reflectcube\"\ntcgen skybox\n}\nsurfaceparms nodlight\nsurfaceparms sky\n}"); + forcedsky = R_RegisterShader(va("skybox_%s", sky), 0, "{\nsort sky\nprogram defaultskybox\n{\nmap \"$cube:$reflectcube\"\ntcgen skybox\n}\nsurfaceparm nodlight\nsurfaceparm sky\n}"); R_BuildDefaultTexnums(&tex, forcedsky); return; } @@ -85,7 +85,7 @@ void R_SetSky(const char *sky) //crappy old path that I still need to fix up a bit //unlike cubemaps, this works on gl1.1/gles1, and also works with the different faces as different sizes. - forcedsky = R_RegisterShader(shadername, 0, va("{\nsort sky\nskyparms \"%s\" 512 -\nsurfaceparms nodlight\n}", sky)); + forcedsky = R_RegisterShader(shadername, 0, va("{\nsort sky\nskyparms \"%s\" 512 -\nsurfaceparm nodlight\n}", sky)); //check that we actually got some textures. //we accept the skybox if even 1 face is valid. //we ignore the replacement only request if all are invalid. diff --git a/engine/qclib/pr_edict.c b/engine/qclib/pr_edict.c index 725756858..cbd9e4fa1 100644 --- a/engine/qclib/pr_edict.c +++ b/engine/qclib/pr_edict.c @@ -10,6 +10,8 @@ struct edict_s; #define NOENDIAN #endif +#define qcc_iswhite(c) ((c) == ' ' || (c) == '\r' || (c) == '\n' || (c) == '\t' || (c) == '\v') + pbool ED_ParseEpair (progfuncs_t *progfuncs, size_t qcptr, unsigned int fldofs, int fldtype, char *s); /* @@ -1887,7 +1889,7 @@ char *PDECL PR_SaveEnts(pubprogfuncs_t *ppf, char *buf, size_t *bufofs, size_t b int header_crc; //if 'general' block is found, this is a compleate state, otherwise, we should spawn entities like -int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PDECL *callback) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend)) +int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PDECL *entspawned) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend), pbool(PDECL *extendedterm)(pubprogfuncs_t *progfuncs, void *ctx, const char **extline)) { progfuncs_t *progfuncs = (progfuncs_t*)ppf; const char *datastart; @@ -1924,6 +1926,51 @@ int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PD while(1) { datastart = file; + + if (extendedterm) + { + //skip simple leading whitespace + while (qcc_iswhite(*file)) + file++; + if (file[0] == '/' && file[1] == '*') + { //looks like we have a hidden extension. + file+=2; + for(;;) + { + //skip to end of line + if (!*file) + break; //unexpected EOF + else if (file[0] == '*' && file[1] == '/') + { //end of comment + file+=2; + break; + } + else if (*file != '\n') + { + file++; + continue; + } + file++; //skip past the \n + while (*file == ' ' || *file == '\t') + file++; //skip leading indentation + + if (file[0] == '*' && file[1] == '/') + { //end of comment + file+=2; + break; + } + else if (*file == '/') + continue; //embedded comment. ignore the line. not going to do nested comments, because those are not normally valid anyway, just C++-style inside C-style. + else if (extendedterm(ppf, ctx, &file)) + ; //found a term we recognised + else + ; //unknown line, but this is a comment so whatever + + } + continue; + } + } + file = QCC_COM_Parse(file); if (file == NULL) break; //finished reading file @@ -1976,8 +2023,8 @@ int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PD externs->entspawn((struct edict_s *) ed, true); file = ED_ParseEdict(progfuncs, file, ed); - if (callback) - callback(ppf, (struct edict_s *)ed, ctx, datastart, file); + if (entspawned) + entspawned(ppf, (struct edict_s *)ed, ctx, datastart, file); } else if (!strcmp(qcc_token, "progs")) { @@ -2247,8 +2294,11 @@ int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PD externs->entspawn((struct edict_s *) ed, true); file = ED_ParseEdict(progfuncs, file, ed); - callback(ppf, (struct edict_s *)ed, ctx, datastart, file); + if (entspawned) + entspawned(ppf, (struct edict_s *)ed, ctx, datastart, file); } + else if (extendedterm && extendedterm(ppf, ctx, &datastart)) + file = datastart; else Sys_Error("Bad entity lump: '%s' not recognised (last ent was %i)", qcc_token, ed?ed->entnum:0); } diff --git a/engine/qclib/progsint.h b/engine/qclib/progsint.h index 76e48a6ab..df66d34be 100644 --- a/engine/qclib/progsint.h +++ b/engine/qclib/progsint.h @@ -289,7 +289,7 @@ int PDECL Comp_Continue(pubprogfuncs_t *progfuncs); pbool PDECL PR_SetWatchPoint(pubprogfuncs_t *progfuncs, char *key); char *PDECL PR_EvaluateDebugString(pubprogfuncs_t *progfuncs, char *key); char *PDECL PR_SaveEnts(pubprogfuncs_t *progfuncs, char *mem, size_t *size, size_t maxsize, int mode); -int PDECL PR_LoadEnts(pubprogfuncs_t *progfuncs, const char *file, void *ctx, void (PDECL *callback) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend)); +int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PDECL *entspawned) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend), pbool(PDECL *extendedterm)(pubprogfuncs_t *progfuncs, void *ctx, const char **extline)); char *PDECL PR_SaveEnt (pubprogfuncs_t *progfuncs, char *buf, size_t *size, size_t maxsize, struct edict_s *ed); struct edict_s *PDECL PR_RestoreEnt (pubprogfuncs_t *progfuncs, const char *buf, size_t *size, struct edict_s *ed); void PDECL PR_StackTrace (pubprogfuncs_t *progfuncs, int showlocals); diff --git a/engine/qclib/progslib.h b/engine/qclib/progslib.h index 221265733..1ff7ca8b3 100644 --- a/engine/qclib/progslib.h +++ b/engine/qclib/progslib.h @@ -122,7 +122,10 @@ struct pubprogfuncs_s void (PDECL *ED_Print) (pubprogfuncs_t *prinst, struct edict_s *ed); char *(PDECL *save_ents) (pubprogfuncs_t *prinst, char *buf, size_t *size, size_t maxsize, int mode); //dump the entire progs info into one big self allocated string - int (PDECL *load_ents) (pubprogfuncs_t *prinst, const char *s, void *ctx, void (PDECL *callback) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend)); //restore the entire progs state (or just add some more ents) (returns edicts ize) + int (PDECL *load_ents) (pubprogfuncs_t *prinst, const char *s, void *ctx, + void (PDECL *entspawned) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend), + pbool(PDECL *extendedterm)(pubprogfuncs_t *progfuncs, void *ctx, const char **extline) + ); //restore the entire progs state (or just add some more ents) (returns edicts ize) char *(PDECL *saveent) (pubprogfuncs_t *prinst, char *buf, size_t *size, size_t maxsize, struct edict_s *ed); //will save just one entities vars struct edict_s *(PDECL *restoreent) (pubprogfuncs_t *prinst, const char *buf, size_t *size, struct edict_s *ed); //will restore the entity that had it's values saved (can use NULL for ed) @@ -280,7 +283,7 @@ typedef union eval_s #define ED_Free(pf, ed) (*pf->EntFree) (pf, ed) #define ED_Clear(pf, ed) (*pf->EntClear) (pf, ed) -#define PR_LoadEnts(pf, s, ctx, cb) (*pf->load_ents) (pf, s, ctx, cb) +#define PR_LoadEnts(pf, s, ctx, entcb, extcb) (*pf->load_ents) (pf, s, ctx, entcb, extcb) #define PR_SaveEnts(pf, buf, size, maxsize, mode) (*pf->save_ents) (pf, buf, size, maxsize, mode) #if 0//def _DEBUG diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 6f4de123b..24010be13 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -1307,7 +1307,7 @@ static void PR_ApplyCompilation_f (void) PR_RegisterFields(); sv.world.edict_size=PR_InitEnts(svprogfuncs, sv.world.max_edicts); - sv.world.edict_size=svprogfuncs->load_ents(svprogfuncs, s, NULL, NULL); + sv.world.edict_size=svprogfuncs->load_ents(svprogfuncs, s, NULL, NULL, NULL); PR_LoadGlabalStruct(false); @@ -1555,6 +1555,35 @@ static void PR_FallbackSpawn_Misc_Model(pubprogfuncs_t *progfuncs, edict_t *self G_INT(OFS_PARM0) = EDICT_TO_PROG(progfuncs, self); PF_makestatic(progfuncs, pr_globals); } +static void PR_FallbackSpawn_Func_Detail(pubprogfuncs_t *progfuncs, edict_t *self) +{ + void *pr_globals; + eval_t *val; + + if (sv.world.worldmodel && sv.world.worldmodel->type==mod_brush && sv.world.worldmodel->fromgame == fg_quake3) + { //on q3bsp, these are expected to be handled directly by q3map2, but it doesn't always strip it. + ED_Free(progfuncs, self); + return; + } + + if (!self->v->model && (val = progfuncs->GetEdictFieldValue(progfuncs, self, "mdl", ev_string, NULL))) + self->v->model = val->string; + if (!*PR_GetString(progfuncs, self->v->model)) //must have a model, because otherwise various things will assume its not valid at all. + progfuncs->SetStringField(progfuncs, self, &self->v->model, "*null", true); + + self->v->solid = SOLID_BSP; + self->v->movetype = MOVETYPE_PUSH; + + //make sure the model is precached, to avoid errors. + pr_globals = PR_globals(progfuncs, PR_CURRENT); + G_INT(OFS_PARM0) = self->v->model; + PF_precache_model(progfuncs, pr_globals); + + pr_globals = PR_globals(progfuncs, PR_CURRENT); + G_INT(OFS_PARM0) = EDICT_TO_PROG(progfuncs, self); + G_INT(OFS_PARM1) = self->v->model; + PF_setmodel(progfuncs, pr_globals); +} struct spawnents_s { int killonspawnflags; @@ -1672,6 +1701,13 @@ static void PDECL PR_DoSpawnInitialEntity(pubprogfuncs_t *progfuncs, struct edic } else if (!strcmp(eclassname, "misc_model")) PR_FallbackSpawn_Misc_Model(progfuncs, ed); + //func_detail+func_group are for compat with ericw-tools, etc. + else if (!strcmp(eclassname, "func_detail_illusionary")) + PR_FallbackSpawn_Misc_Model(progfuncs, ed); + else if (!strcmp(eclassname, "func_detail") || !strcmp(eclassname, "func_detail_wall") || !strcmp(eclassname, "func_detail_fence")) + PR_FallbackSpawn_Func_Detail(progfuncs, ed); + else if (!strcmp(eclassname, "func_group")) + PR_FallbackSpawn_Func_Detail(progfuncs, ed); else { //only warn on the first occurence of the classname, don't spam. @@ -1757,7 +1793,7 @@ void PR_SpawnInitialEntities(const char *file) ctx.fulldata = PR_FindGlobal(svprogfuncs, "__fullspawndata", PR_ANY, NULL); if (svprogfuncs) - sv.world.edict_size = PR_LoadEnts(svprogfuncs, file, &ctx, PR_DoSpawnInitialEntity); + sv.world.edict_size = PR_LoadEnts(svprogfuncs, file, &ctx, PR_DoSpawnInitialEntity, NULL); else sv.world.edict_size = 0; } @@ -4243,7 +4279,7 @@ void QCBUILTIN PF_sv_particleeffectnum(pubprogfuncs_t *prinst, struct globalvars #ifdef NQPROT //DPP7's network protocol depends upon the ordering of these from an external file. unreliable, but if we're meant to be compatible then we need to at least pretend. - if (!sv.strings.particle_precache[1] && sv_listen_dp.ival) + if (!sv.strings.particle_precache[1] && (sv_listen_dp.ival || !strncmp(s, "effectinfo.", 11))) COM_Effectinfo_Enumerate(SV_ParticlePrecache_Add); #endif @@ -11764,10 +11800,12 @@ void PR_DumpPlatform_f(void) */ {"CSQC_Init", "void(float apilevel, string enginename, float engineversion)", CS, "Called at startup. enginename and engineversion are arbitary hints and can take any form. enginename should be consistant between revisions, but this cannot truely be relied upon."}, - {"CSQC_WorldLoaded", "void()", CS, "Called after model+sound precaches have been executed. Gives a chance for the qc to read the entity lump from the bsp."}, + {"CSQC_WorldLoaded", "void()", CS, "Called after the server's model+sound precaches have been executed. Gives a chance for the qc to read the entity lump from the bsp (via getentitytoken)."}, {"CSQC_Shutdown", "void()", CS, "Specifies that the csqc is going down. Save your persistant settings here."}, {"CSQC_UpdateView", "void(float vwidth, float vheight, float notmenu)", CS, "Called every single video frame. The CSQC is responsible for rendering the entire screen."}, {"CSQC_UpdateViewLoading", "void(float vwidth, float vheight, float notmenu)", CS, "Alternative to CSQC_UpdateView, called when the engine thinks there should be a loading screen. If present, will inhibit the engine's normal loading screen, deferring to qc to draw it."}, + {"CSQC_DrawHud", "void(vector viewsize, float scoresshown)", CS, "Part of simple csqc, called after drawing the 3d view whenever CSQC_UpdateView is not defined."}, + {"CSQC_DrawScores", "void(vector viewsize, float scoresshown)", CS, "Part of simple csqc, called after CSQC_DrawHud whenever CSQC_UpdateView is not defined, and when there are no menus/console active."}, {"CSQC_Parse_StuffCmd", "void(string msg)", CS, "Gives the CSQC a chance to intercept stuffcmds. Use the tokenize builtin to parse the message. Unrecognised commands would normally be localcmded, but its probably better to drop unrecognised stuffcmds completely."}, {"CSQC_Parse_CenterPrint", "float(string msg)", CS, "Gives the CSQC a chance to intercept centerprints. Return true if you wish the engine to otherwise ignore the centerprint."}, {"CSQC_Parse_Damage", "float(float save, float take, vector inflictororg)", CS, "Called as a result of player.dmg_save or player.dmg_take being set on the server.\nReturn true to completely inhibit the engine's colour shift and damage rolls, allowing you to do your own thing.\nYou can use punch_roll += (normalize(inflictororg-player.origin)*v_right)*(take+save)*autocvar_v_kickroll; as a modifier for the roll angle should the player be hit from the side, and slowly fade it away over time."}, diff --git a/engine/server/pr_q1qvm.c b/engine/server/pr_q1qvm.c index efa90f386..5c349570a 100755 --- a/engine/server/pr_q1qvm.c +++ b/engine/server/pr_q1qvm.c @@ -517,7 +517,9 @@ static edict_t *QDECL Q1QVMPF_EntAlloc(pubprogfuncs_t *pf, pbool object, size_t return (struct edict_s *)e; } -static int QDECL Q1QVMPF_LoadEnts(pubprogfuncs_t *pf, const char *mapstring, void *ctx, void (PDECL *callback) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend)) +static int QDECL Q1QVMPF_LoadEnts(pubprogfuncs_t *pf, const char *mapstring, void *ctx, + void (PDECL *ent_callback) (pubprogfuncs_t *progfuncs, struct edict_s *ed, void *ctx, const char *entstart, const char *entend), + pbool (PDECL *ext_callback)(pubprogfuncs_t *pf, void *ctx, const char **str)) { //the qvm calls the spawn functions itself. //no saved-games. diff --git a/engine/server/savegame.c b/engine/server/savegame.c index 20a80787f..37f13202d 100644 --- a/engine/server/savegame.c +++ b/engine/server/savegame.c @@ -2,6 +2,7 @@ #include "pr_common.h" #if !defined(CLIENTONLY) && defined(SAVEDGAMES) +#define CACHEGAME_VERSION_DEFAULT CACHEGAME_VERSION_VERBOSE extern cvar_t skill; extern cvar_t deathmatch; @@ -77,8 +78,94 @@ void SV_SavegameComment (char *text, size_t textsize) } #ifndef QUAKETC + +pbool SV_Legacy_ExtendedSaveData(pubprogfuncs_t *progfuncs, void *loadctx, const char **ptr) +{ + char token[8192]; + com_tokentype_t tt; + const char *l = *ptr; + size_t idx; + if (l[0] == 's' && l[1] == 'v' && l[2] == '.') + l += 3; //DPism + + do + { + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt); + } while(tt == TTP_LINEENDING); + if (tt != TTP_RAWTOKEN)return false; + + if (!strcmp(token, "lightstyle") || !strcmp(token, "lightstyles")) + { //lightstyle N "STYLESTRING" 1 1 1 + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + idx = atoi(token); + if (idx >= countof(sv.strings.lightstyles)) + return false; //unsupported index. + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_STRING)return false; + + if (sv.strings.lightstyles[idx]) + Z_Free((char*)sv.strings.lightstyles[idx]); + sv.strings.lightstyles[idx] = Z_StrDup(token); + sv.lightstylecolours[idx][0] = sv.lightstylecolours[idx][1] = sv.lightstylecolours[idx][2] = 1.0; + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + sv.lightstylecolours[idx][0] = atof(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + sv.lightstylecolours[idx][1] = atof(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + sv.lightstylecolours[idx][2] = atof(token); + } + else if (!strcmp(token, "model_precache") || !strcmp(token, "model")) + { //model_precache N "MODELNAME" + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + idx = atoi(token); + if (!idx || idx >= countof(sv.strings.model_precache)) + return false; //unsupported index. + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_STRING)return false; + sv.strings.model_precache[idx] = PR_AddString(svprogfuncs, token, 0, false); + } + else if (!strcmp(token, "sound_precache") || !strcmp(token, "sound")) + { //sound_precache N "MODELNAME" + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + idx = atoi(token); + if (!idx || idx >= countof(sv.strings.sound_precache)) + return false; //unsupported index. + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_STRING)return false; + sv.strings.sound_precache[idx] = PR_AddString(svprogfuncs, token, 0, false); + } + else if (!strcmp(token, "particle_precache")) + { //particle_precache N "MODELNAME" + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + idx = atoi(token); + if (!idx || idx >= countof(sv.strings.particle_precache)) + return false; //unsupported index. + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_STRING)return false; + sv.strings.particle_precache[idx] = PR_AddString(svprogfuncs, token, 0, false); + } + else if (!strcmp(token, "buffer")) + { + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + //buffer = atoi(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + //count = atoi(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_STRING)return false; + return false; + } + else if (!strcmp(token, "bufstr")) + { + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + //buffer = atoi(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return false; + //idx = atoi(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_STRING)return false; + return false; + } + else + return false; + *ptr = l; + return true; +} + //expects the version to have already been parsed -static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) +static qboolean SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) { //FIXME: Multiplayer save probably won't work with spectators. char mapname[MAX_QPATH]; @@ -101,11 +188,11 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) char *modelnames[MAX_PRECACHE_MODELS]; char *soundnames[MAX_PRECACHE_SOUNDS]; - if (version != 667 && version != 5 && version != 6) //5 for NQ, 6 for ZQ/FQ + if (version != SAVEGAME_VERSION_FTE_LEG && version != SAVEGAME_VERSION_NQ && version != SAVEGAME_VERSION_QW) { VFS_CLOSE (f); Con_TPrintf ("Unable to load savegame of version %i\n", version); - return; + return false; } VFS_GETS(f, str, sizeof(str)); //discard comment. Con_Printf("loading legacy game from %s...\n", filename); @@ -131,7 +218,7 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) } SV_SendMessagesToAll(); - if (version == 5 || version == 6) + if (version == SAVEGAME_VERSION_NQ || version == SAVEGAME_VERSION_QW) { slots = 1; SV_UpdateMaxPlayers(1); @@ -163,7 +250,7 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) { VFS_CLOSE(f); Con_Printf ("Corrupted save game"); - return; + return false; } SV_UpdateMaxPlayers(slots); for (clnum = 0; clnum < sv.allocated_client_slots; clnum++) //work out which players we had when we saved, and hope they accepted the reconnect. @@ -196,7 +283,7 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) } } } - if (version == 5 || version == 6) + if (version == SAVEGAME_VERSION_NQ || version == SAVEGAME_VERSION_QW) { VFS_GETS(f, str, sizeof(str)); Cvar_SetValue (Cvar_FindVar("skill"), atof(str)); @@ -204,7 +291,7 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) Cvar_SetValue (Cvar_FindVar("coop"), 0); Cvar_SetValue (Cvar_FindVar("teamplay"), 0); - if (version == 5) + if (version == SAVEGAME_VERSION_NQ) { progstype = PROG_NQ; Cvar_Set (&pr_ssqc_progs, "progs.dat"); //NQ's progs. @@ -240,7 +327,7 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) { VFS_CLOSE (f); Con_TPrintf ("Couldn't load map\n"); - return; + return false; } sv.allocated_client_slots = slots; @@ -285,7 +372,7 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) // load the edicts out of the savegame file // the rest of the file is sent directly to the progs engine. - if (version == 5 || version == 6) + if (version == SAVEGAME_VERSION_NQ || version == SAVEGAME_VERSION_QW) ;//Q_InitProgs(); //reinitialize progs entirly. else { @@ -322,7 +409,7 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) strcpy(file, "loadgame"); clnum=VFS_READ(f, file+8, filelen); file[filelen+8]='\0'; - sv.world.edict_size=svprogfuncs->load_ents(svprogfuncs, file, NULL, NULL); + sv.world.edict_size=svprogfuncs->load_ents(svprogfuncs, file, NULL, NULL, SV_Legacy_ExtendedSaveData); BZ_Free(file); PR_LoadGlabalStruct(false); @@ -332,7 +419,7 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) VFS_CLOSE(f); - //FIXME: DP saved games have some / *\nkey values\nkey values\n* / thing in them to save precaches and stuff + //FIXME: QSS+DP saved games have some / *\nkey values\nkey values\n* / thing in them to save precaches and stuff World_ClearWorld(&sv.world, true); @@ -362,16 +449,17 @@ static void SV_Loadgame_Legacy(char *filename, vfsfile_t *f, int version) cl->playerclass = 0; #endif } + return true; } -static void SV_LegacySavegame (const char *savename) +static qboolean SV_LegacySavegame (const char *savename) { size_t len; char *s = NULL; client_t *cl; int clnum; - int version = SAVEGAME_VERSION; + int version = SAVEGAME_VERSION_FTE_LEG; char native[MAX_OSPATH]; char name[MAX_QPATH]; @@ -382,27 +470,26 @@ static void SV_LegacySavegame (const char *savename) if (sv.state != ss_active) { Con_TPrintf("Can't apply: Server isn't running or is still loading\n"); - return; + return false; } if (sv.allocated_client_slots != 1 || svs.clients->state != cs_spawned) { //we don't care about fte-format legacy. Con_TPrintf("Unable to use legacy savegame format to save multiplayer games\n"); - SV_Savegame_f(); - return; + return false; } sprintf (name, "%s", savename); COM_RequireExtension (name, ".sav", sizeof(name)); //do NOT allow .pak etc if (!FS_NativePath(name, FS_GAMEONLY, native, sizeof(native))) - return; + return false; Con_TPrintf (U8("Saving game to %s...\n"), native); f = FS_OpenVFS(name, "wbp", FS_GAMEONLY); if (!f) { Con_TPrintf ("ERROR: couldn't open %s.\n", name); - return; + return false; } //if there are 1 of 1 players connected @@ -412,9 +499,9 @@ static void SV_LegacySavegame (const char *savename) if (s) { if (progstype == PROG_QW) - version = 6; + version = SAVEGAME_VERSION_QW; else - version = 5; + version = SAVEGAME_VERSION_NQ; } } @@ -423,7 +510,7 @@ static void SV_LegacySavegame (const char *savename) SV_SavegameComment (comment, sizeof(comment)); VFS_PRINTF(f, "%s\n", comment); - if (version != SAVEGAME_VERSION) + if (version == SAVEGAME_VERSION_NQ || version == SAVEGAME_VERSION_QW) { //only 16 spawn parms. for (i=0; i < 16; i++) @@ -467,31 +554,46 @@ static void SV_LegacySavegame (const char *savename) s = PR_SaveEnts(svprogfuncs, NULL, &len, 0, 1); VFS_PUTS(f, s); VFS_PUTS(f, "\n"); - /* - // DarkPlaces extended savegame - sv.lightstyles %i %s - sv.model_precache %i %s - sv.sound_precache %i %s - sv.buffer %i %i "string" - sv.bufstr %i %i "%s" + +#if 1 + /* Extended save info + ** This should also be compatible with both DP and QSS. + ** WARNING: this does NOT protect against models/sounds being precached in different/random orders (statics/baselines/ambients will be wrong). + ** the only protection we get is from late precaches. + ** theoretically the loader could make it work by rewriting the various tables, but that would not necessarily be reliable. */ + VFS_PUTS(f, "/*\n"); + VFS_PUTS(f, "// FTE extended savegame\n"); + for (i=0 ; i < countof(sv.strings.lightstyles); i++) + { //yes, repeat styles 0-63 again, for some reason, but only list ones that are not empty. + if (sv.strings.lightstyles[i]) + VFS_PRINTF(f, "sv.lightstyles %i %s\n", i, sv.strings.lightstyles[i]); + } + for (i=1 ; i < countof(sv.strings.model_precache); i++) + { + if (sv.strings.model_precache[i]) + VFS_PRINTF(f, "sv.model_precache %i %s\n", i, sv.strings.model_precache[i]); + } + for (i=1 ; i < countof(sv.strings.sound_precache); i++) + { + if (sv.strings.sound_precache[i]) + VFS_PRINTF(f, "sv.lightstyles %i %s\n", i, sv.strings.sound_precache[i]); + } +// sv.buffer %i %i "string" +// sv.bufstr %i %i "%s" + VFS_PUTS(f, "*/\n"); +#endif svprogfuncs->parms->memfree(s); VFS_CLOSE(f); FS_FlushFSHashWritten(name); + + Q_strncpyz(sv.loadgame_on_restart, savename, sizeof(sv.loadgame_on_restart)); + return true; } #endif - - -#define CACHEGAME_VERSION_OLD 513 -#define CACHEGAME_VERSION_VERBOSE 514 -#define CACHEGAME_VERSION_BINARY 515 - - - - void SV_FlushLevelCache(void) { levelcache_t *cache; @@ -701,35 +803,83 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * VFS_GETS(f, str, sizeof(str)); version = atoi(str); - if (version != CACHEGAME_VERSION_OLD) + if (version != CACHEGAME_VERSION_OLD && version != CACHEGAME_VERSION_VERBOSE) { VFS_CLOSE (f); - Con_TPrintf ("Savegame is version %i, not %i\n", version, CACHEGAME_VERSION_OLD); + Con_TPrintf ("Savegame is version %i, not %i\n", version, CACHEGAME_VERSION_DEFAULT); return false; } VFS_GETS(f, str, sizeof(str)); //comment SV_SendMessagesToAll(); - VFS_GETS(f, str, sizeof(str)); - pt = atof(str); + if (version == CACHEGAME_VERSION_OLD) + { + VFS_GETS(f, str, sizeof(str)); + pt = atof(str); -// this silliness is so we can load 1.06 save files, which have float skill values - VFS_GETS(f, str, sizeof(str)); - current_skill = (int)(atof(str) + 0.1); - Cvar_Set (&skill, va("%i", current_skill)); + // this silliness is so we can load 1.06 save files, which have float skill values + VFS_GETS(f, str, sizeof(str)); + current_skill = (int)(atof(str) + 0.1); + Cvar_Set (&skill, va("%i", current_skill)); - VFS_GETS(f, str, sizeof(str)); - Cvar_SetValue (&deathmatch, atof(str)); - VFS_GETS(f, str, sizeof(str)); - Cvar_SetValue (&coop, atof(str)); - VFS_GETS(f, str, sizeof(str)); - Cvar_SetValue (&teamplay, atof(str)); + VFS_GETS(f, str, sizeof(str)); + Cvar_SetValue (&deathmatch, atof(str)); + VFS_GETS(f, str, sizeof(str)); + Cvar_SetValue (&coop, atof(str)); + VFS_GETS(f, str, sizeof(str)); + Cvar_SetValue (&teamplay, atof(str)); - VFS_GETS(f, mapname, sizeof(mapname)); - VFS_GETS(f, str, sizeof(str)); - time = atof(str); + VFS_GETS(f, mapname, sizeof(mapname)); + VFS_GETS(f, str, sizeof(str)); + time = atof(str); + } + else + { + time = 0; + pt = PROG_UNKNOWN; + while (VFS_GETS(f, str, sizeof(str))) + { + char *s = str; + cvar_t *var; + s = COM_Parse(s); + if (!strcmp(com_token, "map")) + { //map "foo": terminates the preamble. + COM_ParseOut(s, mapname, sizeof(mapname)); + break; + } + else if (!strcmp(com_token, "cvar")) + { + s = COM_Parse(s); + var = Cvar_FindVar(com_token); + s = COM_Parse(s); + if (var) + Cvar_Set(var, com_token); + } + else if (!strcmp(com_token, "time")) + { + s = COM_Parse(s); + time = atof(com_token); + } + else if (!strcmp(com_token, "vmmode")) + { + s = COM_Parse(s); + if (!strcmp(com_token, "NONE")) pt = PROG_NONE; + else if (!strcmp(com_token, "QW")) pt = PROG_QW; + else if (!strcmp(com_token, "NQ")) pt = PROG_NQ; + else if (!strcmp(com_token, "H2")) pt = PROG_H2; + else if (!strcmp(com_token, "PREREL")) pt = PROG_PREREL; + else if (!strcmp(com_token, "TENEBRAE")) pt = PROG_TENEBRAE; + else if (!strcmp(com_token, "UNKNOWN")) pt = PROG_UNKNOWN; + else pt = PROG_UNKNOWN; + } + else + Con_TPrintf ("Unknown savegame directive %s\n", com_token); + } + } + //NOTE: This sets up the default baselines+statics+ambients. + //FIXME: if any model names changed, then we're screwed. SV_SpawnServer (mapname, startspot, false, false); sv.time = time; if (svs.gametype != gametype) @@ -744,20 +894,6 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * return false; } -// sv.paused = true; // pause until all clients connect -// sv.loadgame = true; - -// load the light styles - - VFS_GETS(f, str, sizeof(str)); - numstyles = atoi(str); - if (numstyles > MAX_LIGHTSTYLES) - { - VFS_CLOSE (f); - Con_Printf ("load failed - invalid number of lightstyles\n"); - return false; - } - // load the edicts out of the savegame file // the rest of the file is sent directly to the progs engine. @@ -770,25 +906,39 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * PR_InitEnts(svprogfuncs, sv.world.max_edicts); } - for (i = 0; i MAX_LIGHTSTYLES) + { + VFS_CLOSE (f); + Con_Printf ("load failed - invalid number of lightstyles\n"); + return false; + } + for (i = 0; iload_ents(svprogfuncs, file, NULL, NULL); + sv.world.edict_size=svprogfuncs->load_ents(svprogfuncs, file, NULL, NULL, SV_Legacy_ExtendedSaveData); BZ_Free(file); progstype = pt; @@ -807,8 +957,11 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * pr_global_struct->time = sv.time = sv.world.physicstime = time; sv.starttime = Sys_DoubleTime() - sv.time; - VFS_SEEK(f, modelpos); - LoadModelsAndSounds(f); + if (modelpos != 0) + { + VFS_SEEK(f, modelpos); + LoadModelsAndSounds(f); + } VFS_CLOSE(f); @@ -918,7 +1071,7 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) int i; char comment[SAVEGAME_COMMENT_LENGTH+1]; levelcache_t *cache; - int version = CACHEGAME_VERSION_OLD; + int version = CACHEGAME_VERSION_DEFAULT; if (!sv.state) return; @@ -1078,44 +1231,13 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) case PROG_TENEBRAE: mode = "TENEBRAE"; break; case PROG_UNKNOWN: mode = "UNKNOWN"; break; } - VFS_PRINTF (f, "vmmode %s\n", COM_QuotedString(mode, buf, sizeof(buf), false)); - VFS_PRINTF (f, "skill %s\n", COM_QuotedString(skill.string, buf, sizeof(buf), false)); - VFS_PRINTF (f, "deathmatch %s\n", COM_QuotedString(deathmatch.string, buf, sizeof(buf), false)); - VFS_PRINTF (f, "coop %s\n", COM_QuotedString(coop.string, buf, sizeof(buf), false)); - VFS_PRINTF (f, "teamplay %s\n", COM_QuotedString(teamplay.string, buf, sizeof(buf), false)); - VFS_PRINTF (f, "map %s\n", COM_QuotedString(svs.name, buf, sizeof(buf), false)); - VFS_PRINTF (f, "time %f\n", sv.time); - - for (i=0 ; i= CACHEGAME_VERSION_BINARY); - - //FIXME: string buffers - //FIXME: hash tables - //FIXME: skeletal objects? - //FIXME: static entities - //FIXME: midi track - //FIXME: custom temp-ents? - //FIXME: pending uri_gets? (if only just to report fails on load) - //FIXME: routing calls? - //FIXME: sql queries? - //FIXME: frik files? - //FIXME: qc threads? - - VFS_PRINTF (f, "entities\n"); + VFS_PRINTF (f, "vmmode %s\n", COM_QuotedString(mode, buf, sizeof(buf), false)); + VFS_PRINTF (f, "cvar skill %s\n", COM_QuotedString(skill.string, buf, sizeof(buf), false)); + VFS_PRINTF (f, "cvar deathmatch %s\n", COM_QuotedString(deathmatch.string, buf, sizeof(buf), false)); + VFS_PRINTF (f, "cvar coop %s\n", COM_QuotedString(coop.string, buf, sizeof(buf), false)); + VFS_PRINTF (f, "cvar teamplay %s\n", COM_QuotedString(teamplay.string, buf, sizeof(buf), false)); + VFS_PRINTF (f, "time %f\n", sv.time); + VFS_PRINTF (f, "map %s\n", COM_QuotedString(svs.name, buf, sizeof(buf), false)); } else { @@ -1165,6 +1287,43 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) svprogfuncs->parms->memfree(s); } + if (version >= CACHEGAME_VERSION_VERBOSE) + { + char buf[8192]; + for (i=0 ; i= CACHEGAME_VERSION_BINARY); + + //FIXME: string buffers + //FIXME: hash tables + //FIXME: skeletal objects? + //FIXME: static entities + //FIXME: midi track + //FIXME: custom temp-ents? + //FIXME: pending uri_gets? (if only just to report fails on load) + //FIXME: routing calls? + //FIXME: sql queries? + //FIXME: frik files? + //FIXME: qc threads? + + // portalblobsize = CM_WritePortalState(sv.world.worldmodel, &portalblob); + // VFS_WRITE(f, portalblob, portalblobsize); + } + VFS_CLOSE (f); @@ -1180,8 +1339,6 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) FS_FlushFSHashWritten(name); } -#define FTESAVEGAME_VERSION 25000 - //mapchange is true for Q2's map-change autosaves. void SV_Savegame (const char *savename, qboolean mapchange) { @@ -1209,14 +1366,6 @@ void SV_Savegame (const char *savename, qboolean mapchange) levelcache_t *cache; char *savefilename; -#ifndef QUAKETC - if (!sv_savefmt.ival && !mapchange) - { - SV_LegacySavegame(savename); - return; - } -#endif - if (!sv.state || sv.state == ss_clustermode) { Con_Printf("Server is not active - unable to save\n"); @@ -1228,6 +1377,22 @@ void SV_Savegame (const char *savename, qboolean mapchange) return; } +#ifndef QUAKETC + { + int savefmt = sv_savefmt.ival; + if (!*sv_savefmt.string && (svs.gametype != GT_PROGS || progstype == PROG_H2 || svs.levcache)) + savefmt = 1; //hexen2+q2/etc must not use the legacy format by default. can't use it when using any kind of hub system either (harder to detect upfront, which might give confused saved game naming but will at least work). + else + savefmt = sv_savefmt.ival; + if (!savefmt && !mapchange) + { + if (SV_LegacySavegame(savename)) + return; + Con_Printf("Unable to use legacy saved game format\n"); + } + } +#endif + switch(svs.gametype) { default: @@ -1265,7 +1430,7 @@ void SV_Savegame (const char *savename, qboolean mapchange) return; } SV_SavegameComment(comment, sizeof(comment)); - VFS_PRINTF (f, "%d\n", FTESAVEGAME_VERSION+svs.gametype); + VFS_PRINTF (f, "%d\n", SAVEGAME_VERSION_FTE_HUB+svs.gametype); VFS_PRINTF (f, "%s\n", comment); VFS_PRINTF(f, "%i\n", sv.allocated_client_slots); @@ -1285,9 +1450,31 @@ void SV_Savegame (const char *savename, qboolean mapchange) VFS_PRINTF(f, "%s\n", cl->name); if (*cl->name) - for (len = 0; len < NUM_SPAWN_PARMS; len++) - VFS_PRINTF(f, "%i (%f)\n", *(int*)&cl->spawn_parms[len], cl->spawn_parms[len]); //write ints as not everyone passes a float in the parms. - //write floats too so you can use it to debug. + { + if (1) + { + char tmp[65536]; + VFS_PRINTF(f, "{\n"); + for (len = 0; len < NUM_SPAWN_PARMS; len++) + VFS_PRINTF(f, "\tparm%i 0x%x //%.9g\n", len, *(int*)&cl->spawn_parms[len], cl->spawn_parms[len]); //write hex as not everyone passes a float in the parms. + VFS_PRINTF(f, "\tparm_string %s\n", COM_QuotedString(cl->spawn_parmstring?cl->spawn_parmstring:"", tmp, sizeof(tmp), false)); + /*if (cl->spawninfo) + { + VFS_PRINTF(f, "\tspawninfo %s\n", COM_QuotedString(cl->spawninfo, tmp, sizeof(tmp), false)); + VFS_PRINTF(f, "\tspawninfotime %9g\n", cl->spawninfotime); + }*/ + VFS_PRINTF(f, "}\n"); //write ints as not everyone passes a float in the parms. + } + else + { + for (len = 0; len < NUM_SPAWN_PARMS; len++) + VFS_PRINTF(f, "%i (%f)\n", *(int*)&cl->spawn_parms[len], cl->spawn_parms[len]); //write ints as not everyone passes a float in the parms. + //write floats too so you can use it to debug. + //FIXME: spawn_parmstring + //FIXME: spawninfo[time] (for hexen2) + //FIXME: startspot... + } + } } InfoBuf_WriteToFile(f, &svs.info, NULL, 0); @@ -1414,6 +1601,8 @@ void SV_Savegame (const char *savename, qboolean mapchange) //fixme FS_FlushFSHashFull(); } + + Q_strncpyz(sv.loadgame_on_restart, savename, sizeof(sv.loadgame_on_restart)); } @@ -1464,10 +1653,13 @@ void SV_Savegame_f (void) } #ifndef QUAKETC if (!Q_strcasecmp(Cmd_Argv(0), "savegame_legacy")) - SV_LegacySavegame(savename); - else + { + if (SV_LegacySavegame(savename)) + return; + Con_Printf("Unable to use legacy save format\n"); + } #endif - SV_Savegame(savename, false); + SV_Savegame(savename, false); } else Con_Printf("%s: invalid number of arguments\n", Cmd_Argv(0)); @@ -1527,7 +1719,8 @@ void SV_AutoSave(void) #endif } -void SV_Loadgame_f (void) +//Attempts to load a named saved game. +qboolean SV_Loadgame (const char *unsafe_savename) { levelcache_t *cache; unsigned char str[MAX_LOCALINFO_STRING+1], *trim; @@ -1555,16 +1748,7 @@ void SV_Loadgame_f (void) }; int bd,best; -#ifndef SERVERONLY - if (!Renderer_Started() && !isDedicated) - { - Cbuf_AddText(va("wait;%s %s\n", Cmd_Argv(0), Cmd_Args()), Cmd_ExecLevel); - return; - } -#endif - - Q_strncpyz(savename, Cmd_Argv(1), sizeof(savename)); - + Q_strncpyz(savename, unsafe_savename, sizeof(savename)); if (!*savename || strstr(savename, "..")) strcpy(savename, "quick"); @@ -1583,7 +1767,7 @@ void SV_Loadgame_f (void) if (!f) { Con_TPrintf ("ERROR: couldn't open %s.\n", filename); - return; + return false; } #if defined(MENU_DAT) && !defined(SERVERONLY) @@ -1592,18 +1776,23 @@ void SV_Loadgame_f (void) VFS_GETS(f, str, sizeof(str)-1); version = atoi(str); - if (version < FTESAVEGAME_VERSION || version >= FTESAVEGAME_VERSION+GT_MAX) + if (version < SAVEGAME_VERSION_FTE_HUB || version >= SAVEGAME_VERSION_FTE_HUB+GT_MAX) { #ifdef QUAKETC VFS_CLOSE (f); Con_TPrintf ("Unable to load savegame of version %i\n", version); + return false; #else - SV_Loadgame_Legacy(filename, f, version); + if (SV_Loadgame_Legacy(filename, f, version)) + { + Q_strncpyz(sv.loadgame_on_restart, savename, sizeof(sv.loadgame_on_restart)); + return true; + } + return false; #endif - return; } - gametype = version - FTESAVEGAME_VERSION; + gametype = version - SAVEGAME_VERSION_FTE_HUB; VFS_GETS(f, str, sizeof(str)-1); #ifndef SERVERONLY if (!cls.state) @@ -1676,6 +1865,32 @@ void SV_Loadgame_f (void) for (len = 0; len < NUM_SPAWN_PARMS; len++) { VFS_GETS(f, str, sizeof(str)-1); + if (*str == '{') + { + while(VFS_GETS(f, str, sizeof(str)-1)) + { + if (*str == '}') + break; + trim = COM_Parse(str); + if (!strcmp(com_token, "parm_string")) + { + COM_Parse(str); + cl->spawn_parmstring = Z_StrDup(com_token); + } + else if (!strncmp(com_token, "parm", 4) && (unsigned)atoi(com_token+4) < NUM_SPAWN_PARMS) + { + COM_Parse(str); + len = atoi(com_token+4); + if (!strncmp(com_token, "0x", 2)) + *(int*)&cl->spawn_parms[len] = strtoul(com_token, NULL, 16); + else + cl->spawn_parms[len] = strtod(com_token, NULL); + } + else + Con_Printf("Unknown player data: %s\n", com_token); + } + break; + } for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) @@ -1802,5 +2017,21 @@ void SV_Loadgame_f (void) sv.spawned_client_slots += loadzombies; sv.autosave_time = sv.time + sv_autosave.value*60; + + Q_strncpyz(sv.loadgame_on_restart, savename, sizeof(sv.loadgame_on_restart)); + return true; +} + +void SV_Loadgame_f (void) +{ +#ifndef SERVERONLY + if (!Renderer_Started() && !isDedicated) + { + Cbuf_AddText(va("wait;%s %s\n", Cmd_Argv(0), Cmd_Args()), Cmd_ExecLevel); + return; + } +#endif + + SV_Loadgame(Cmd_Argv(1)); } #endif diff --git a/engine/server/server.h b/engine/server/server.h index 30425264d..cd4a28b91 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -140,6 +140,8 @@ typedef struct char mapname[256]; // text description of the map char modelname[MAX_QPATH]; // maps/.bsp, for model_precache[0] + char loadgame_on_restart[MAX_QPATH]; //saved game to load on map_restart + world_t world; union { @@ -866,12 +868,12 @@ typedef struct bannedips_s { typedef enum { GT_PROGS, //q1, qw, h2 are similar enough that we consider it only one game mode. (We don't support the h2 protocol) GT_Q1QVM, -#ifdef VM_LUA - GT_LUA, //for the luls -#endif GT_HALFLIFE, GT_QUAKE2, //q2 servers run from a q2 game dll GT_QUAKE3, //q3 servers run off the q3 qvm api +#ifdef VM_LUA + GT_LUA, //for the luls +#endif GT_MAX } gametype_e; @@ -879,6 +881,7 @@ typedef struct levelcache_s { struct levelcache_s *next; char *mapname; gametype_e gametype; + unsigned char savedplayers[(MAX_CLIENTS+7)>>3]; //bitmask to say which players are actually stored in the cache. so that restarts can restore. } levelcache_t; #ifdef TCPCONNECT @@ -1577,6 +1580,7 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade void SV_Savegame_f (void); void SV_Savegame_c(int argn, const char *partial, struct xcommandargcompletioncb_s *ctx); void SV_Loadgame_f (void); +qboolean SV_Loadgame (const char *unsafe_savename); void SV_AutoSave(void); void SV_FlushLevelCache(void); extern cvar_t sv_autosave; diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 2e0e53e3a..f0392d1fa 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -450,12 +450,29 @@ command from the console or progs. quirks: a leading '*' means new unit, meaning all old map state is flushed regardless of startspot a '+' means 'set nextmap cvar to the following value and otherwise ignore, for q2 compat. only applies if there's also a '.' and the specified bsp doesn't exist, for q1 compat. -just a '.' is taken to mean 'restart'. parms are not changed from their current values, startspot is also unchanged. +just a '.' is taken to mean 'restart'. parms are not changed from their current values, startspot is also unchanged. Loads the last saved game instead when applicable. -'map' will change map, for most games. strips parms+serverflags+cache. note that NQ kicks everyone (NQ expects you to use changelevel for that). +variations: +'map' will change map, for most games. strips parms+serverflags+cache. note that vanilla NQ kicks everyone (NQ expects you to use changelevel for that). 'changelevel' will not flush the level cache, for h2 compat (won't save current level state in such a situation, as nq would prefer not) 'gamemap' will save the game to 'save0' after loading, for q2 compat 'spmap' is for q3 and sets 'gametype' to '2', otherwise identical to 'map'. all other map commands will reset it to '0' if its '2' at the time. +'map_restart' restarts the current map. Name is needed for q3 compat. +'restart' is an alias for 'map_restart'. Exists for NQ compat, but as an alias for QW mods that tried to use it for mod-specific things. + +hexen2 fixme: +'restart restore' restarts the map, reloading from a saved game if applicable. +'restart' forgets the current map (potentially breaking the game). we don't care much for that behaviour (could make it a 'restart unit' I guess). + +quake2: +'gamemap [*]foo.dm2[$spot][+nextserver]' + * == new unit + $ == start spot + + == value for nextserver cvar (used for cinematics). +'map' is always a new unit. + +quake: ++ is used in certain map names. * cannot be, but $ potentially could be. ====================== */ void SV_Map_f (void) @@ -523,7 +540,9 @@ void SV_Map_f (void) sv.mapchangelocked = false; - if (strcmp(level, ".")) //restart current + if (!strcmp(level, ".")) + ;//restart current + else { snprintf (expanded, sizeof(expanded), "maps/%s.bsp", level); // this function and the if statement below, is a quake bugfix which stopped a map called "dm6++.bsp" from loading because of the + sign, quake2 map syntax interprets + character as "intro.cin+base1.bsp", to play a cinematic then load a map after if (!COM_FCheckExists (expanded)) @@ -584,6 +603,14 @@ void SV_Map_f (void) } } +#ifdef SAVEDGAMES + if (isrestart && *sv.loadgame_on_restart && SV_Loadgame(sv.loadgame_on_restart)) + { //we managed to reload a saved game instead! + //this is required in order to keep hub state consistent (dying mid-map would require saved games to store both current and start of map(not to be confused with initial state, which would be trivial)) + return; + } +#endif + // check to make sure the level exists if (*level == '*') { @@ -644,7 +671,7 @@ void SV_Map_f (void) if (!exts[i]) { // FTE is still a Quake engine so report BSP missing - snprintf (expanded, sizeof(expanded), exts[0], level); + snprintf (expanded, sizeof(expanded), exts[1], level); Con_TPrintf ("Can't find %s\n", expanded); #ifndef SERVERONLY SCR_SetLoadingStage(LS_NONE); @@ -785,11 +812,9 @@ void SV_Map_f (void) } SCR_SetLoadingFile("spawnserver"); - if (newunit || !startspot || cinematic #ifdef SAVEDGAMES - || !SV_LoadLevelCache(NULL, level, startspot, false) + if (newunit || !startspot || cinematic || !SV_LoadLevelCache(NULL, level, startspot, false)) #endif - ) { if (waschangelevel && !startspot) startspot = ""; @@ -1951,6 +1976,12 @@ static void SV_Status_f (void) if (!sv.strings.sound_precache[count]) break; Con_Printf("sounds : %i/%i\n", count, MAX_PRECACHE_SOUNDS); + + for (count = 1; count < MAX_SSPARTICLESPRE; count++) + if (!sv.strings.particle_precache[count]) + break; + if (count!=1) + Con_Printf("particles : %i/%i\n", count, MAX_SSPARTICLESPRE); } Con_Printf("gamedir : %s\n", FS_GetGamedir(true)); if (sv.csqcdebug) @@ -2882,25 +2913,38 @@ void SV_ReallyEvilHack_f(void) void SV_PrecacheList_f(void) { unsigned int i; - for (i = 0; i < sizeof(sv.strings.vw_model_precache)/sizeof(sv.strings.vw_model_precache[0]); i++) + char *group = Cmd_Argv(1); + if (!*group || !strncmp(group, "vwep", 4)) { - if (sv.strings.vw_model_precache[i]) - Con_Printf("vweap %u: %s\n", i, sv.strings.vw_model_precache[i]); + for (i = 0; i < sizeof(sv.strings.vw_model_precache)/sizeof(sv.strings.vw_model_precache[0]); i++) + { + if (sv.strings.vw_model_precache[i]) + Con_Printf("vwep %u: %s\n", i, sv.strings.vw_model_precache[i]); + } } - for (i = 0; i < MAX_PRECACHE_MODELS; i++) + if (!*group || !strncmp(group, "model", 5)) { - if (sv.strings.model_precache[i]) - Con_Printf("model %u: %s\n", i, sv.strings.model_precache[i]); + for (i = 0; i < MAX_PRECACHE_MODELS; i++) + { + if (sv.strings.model_precache[i]) + Con_Printf("model %u: %s\n", i, sv.strings.model_precache[i]); + } } - for (i = 0; i < MAX_PRECACHE_SOUNDS; i++) + if (!*group || !strncmp(group, "sound", 5)) { - if (sv.strings.sound_precache[i]) - Con_Printf("sound %u: %s\n", i, sv.strings.sound_precache[i]); + for (i = 0; i < MAX_PRECACHE_SOUNDS; i++) + { + if (sv.strings.sound_precache[i]) + Con_Printf("sound %u: %s\n", i, sv.strings.sound_precache[i]); + } } - for (i = 0; i < MAX_SSPARTICLESPRE; i++) + if (!*group || !strncmp(group, "part", 4)) { - if (sv.strings.particle_precache[i]) - Con_Printf("pticl %u: %s\n", i, sv.strings.particle_precache[i]); + for (i = 0; i < MAX_SSPARTICLESPRE; i++) + { + if (sv.strings.particle_precache[i]) + Con_Printf("part %u: %s\n", i, sv.strings.particle_precache[i]); + } } } diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index 48c2e8022..26a002dba 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -898,8 +898,8 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, #ifndef SERVERONLY cl.worldmodel = NULL; r_worldentity.model = NULL; - if (0) - cls.state = ca_connected; +// if (0) +// cls.state = ca_connected; Surf_PreNewMap(); #ifdef VM_CG CG_Stop(); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index a4a116727..88f67ea35 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -5685,7 +5685,7 @@ void SV_ExecInitialConfigs(char *defaultexec) { Cbuf_AddText("cvar_purgedefaults\n", RESTRICT_LOCAL); //reset cvar defaults to their engine-specified values. the tail end of 'exec default.cfg' will update non-cheat defaults to mod-specified values. Cbuf_AddText("cvarreset *\n", RESTRICT_LOCAL); //reset all cvars to their current (engine) defaults - Cbuf_AddText("alias restart \"changelevel .\"\n",RESTRICT_LOCAL); + Cbuf_AddText("alias restart \"map_restart\"\n",RESTRICT_LOCAL); Cbuf_AddText(va("sv_gamedir \"%s\"\n", FS_GetGamedir(true)), RESTRICT_LOCAL); diff --git a/engine/server/sv_sys_unix.c b/engine/server/sv_sys_unix.c index 785138c0b..08e693385 100644 --- a/engine/server/sv_sys_unix.c +++ b/engine/server/sv_sys_unix.c @@ -1032,7 +1032,7 @@ int Sys_EnumerateFiles2 (const char *truepath, int apathofs, const char *match, dir = opendir(truepath); if (!dir) { - Con_DPrintf("Failed to open dir %s\n", truepath); + Con_DLPrintf((errno==ENOENT)?2:1, "Failed to open dir %s\n", truepath); return true; } do diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index a3ddaa75a..a1bbd0502 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -5925,7 +5925,7 @@ typedef struct { char *name; void (*func) (void); - qboolean noqchandling; + int noqchandling; } ucmd_t; ucmd_t ucmds[] = @@ -5976,8 +5976,8 @@ ucmd_t ucmds[] = {"ptrack", SV_PTrack_f}, //ZOID - used with autocam {"snap", SV_NoSnap_f}, //cheat detection - {"enablecsqc", SV_EnableClientsCSQC, true}, - {"disablecsqc", SV_DisableClientsCSQC, true}, + {"enablecsqc", SV_EnableClientsCSQC, 2}, + {"disablecsqc", SV_DisableClientsCSQC, 2}, {"vote", SV_Vote_f}, @@ -6178,15 +6178,26 @@ void SV_ExecuteUserCommand (const char *s, qboolean fromQC) for ( ; u->name ; u++) if (!strcmp (Cmd_Argv(0), u->name) ) { + if (u->noqchandling==2) + { //issue the command then let the QC handle it too + if (!fromQC) + { + if (u->func) + u->func(); + PR_KrimzonParseCommand(s); + } + host_client = oldhost; + return; + } if (!fromQC && !u->noqchandling) - if (PR_KrimzonParseCommand(s)) //KRIMZON_SV_PARSECLIENTCOMMAND has the opertunity to parse out certain commands. + if (PR_KrimzonParseCommand(s)) //KRIMZON_SV_PARSECLIENTCOMMAND has the opportunity to parse out certain commands. { host_client = oldhost; return; } // SV_BeginRedirect (RD_CLIENT, host_client->language); if (u->func) - u->func (); + u->func(); host_client = oldhost; // SV_EndRedirect (); return; diff --git a/plugins/bullet/bulletplug.cpp b/plugins/bullet/bulletplug.cpp index b16b54c39..5a851af74 100644 --- a/plugins/bullet/bulletplug.cpp +++ b/plugins/bullet/bulletplug.cpp @@ -79,7 +79,7 @@ void World_Bullet_Init(void) physics_bullet_enable = pCvar_GetNVFDG("physics_bullet_enable", "1", 0, "", "Bullet"); physics_bullet_maxiterationsperframe = pCvar_GetNVFDG("physics_bullet_maxiterationsperframe", "10", 0, "FIXME: should be 1 when CCD is working properly.", "Bullet"); physics_bullet_framerate = pCvar_GetNVFDG("physics_bullet_framerate", "60", 0, "", "Bullet"); - pr_meshpitch = pCvar_GetNVFDG("r_meshpitch", "-1", 0, "", "Bullet"); + pr_meshpitch = pCvar_GetNVFDG("r_meshpitch", "-1", 0, "", "Bullet"); } void World_Bullet_Shutdown(void) @@ -1259,9 +1259,10 @@ static void World_Bullet_Frame_BodyFromEntity(world_t *world, wedict_t *ed) ed->rbe.body.body = (void*)body; //motion threshhold should be speed/physicsframerate. + //Threshhold enables CCD when the object moves faster than X //FIXME: recalculate... body->setCcdMotionThreshold((geomsize[0]+geomsize[1]+geomsize[2])*(4/3)); - //radius should be the body's radius + //radius should be the body's radius, or smaller. body->setCcdSweptSphereRadius((geomsize[0]+geomsize[1]+geomsize[2])*(0.5/3)); ctx->dworld->addRigidBody(body, ed->xv->dimension_solid, ed->xv->dimension_hit);