diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 24891d81..99031f77 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -1134,7 +1134,7 @@ void CL_RecordMap_f (void) Q_strncpyz(mapname, Cmd_Argv(2), sizeof(mapname)); CL_Disconnect_f(); - SV_SpawnServer (mapname, NULL, false, false); + SV_SpawnServer (mapname, NULL, false, false, 0); #ifdef MVD_RECORDING COM_DefaultExtension(demoname, ".mvd", sizeof(demoname)); @@ -1785,46 +1785,7 @@ void CL_Record_f (void) } else { //automagically generate a name - if (cl.playerview[0].spectator) - { // FIXME: if tracking a player, use his name - fname = va ("spec_%s_%s", - TP_PlayerName(), - TP_MapName()); - } - else - { // guess game type and write demo name - i = TP_CountPlayers(); - if (cl.teamplay && i >= 3) - { // Teamplay - fname = va ("%s_%s_vs_%s_%s", - TP_PlayerName(), - TP_PlayerTeam(), - TP_EnemyTeam(), - TP_MapName()); - } - else - { - if (i == 2) - { // Duel - fname = va ("%s_vs_%s_%s", - TP_PlayerName(), - TP_EnemyName(), - TP_MapName()); - } - else if (i > 2) - { // FFA - fname = va ("%s_ffa_%s", - TP_PlayerName(), - TP_MapName()); - } - else - { // one player - fname = va ("%s_%s", - TP_PlayerName(), - TP_MapName()); - } - } - } + fname = TP_GenerateDemoName(); } while((p = strstr(fname, ".."))) diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 0f00cd0d..f5ec9105 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -50,6 +50,7 @@ float r_blobshadows; extern cvar_t cl_gibfilter, cl_deadbodyfilter; extern int cl_playerindex; +static qboolean cl_expandvisents; extern world_t csqc_world; @@ -3299,7 +3300,10 @@ void CL_LinkStaticEntities(void *pvs, int *areas) for (i = 0; i < cl.num_statics; i++) { if (cl_numvisedicts == cl_maxvisedicts) + { + cl_expandvisents=true; break; + } stat = &cl_static_entities[i]; clmodel = stat->ent.model; @@ -4078,7 +4082,7 @@ void CL_LinkPacketEntities (void) } // if set to invisible, skip - if (state->modelindex<1) + if (state->modelindex<1 || (state->effects & NQEF_NODRAW)) { if (state->tagindex == 0xffff) { @@ -4730,10 +4734,9 @@ void CLQW_ParsePlayerinfo (void) state->pm_type = PM_NORMAL; +#ifdef QUAKESTATS TP_ParsePlayerInfo(oldstate, state, info); - -#ifdef QUAKESTATS //can't CL_SetStatInt as we don't know if its actually us or not cl.players[num].stats[STAT_WEAPONFRAME] = state->weaponframe; cl.players[num].statsf[STAT_WEAPONFRAME] = state->weaponframe; @@ -4997,9 +5000,9 @@ guess_pm_type: state->pm_type = PM_NORMAL; } +#ifdef QUAKESTATS TP_ParsePlayerInfo(oldstate, state, info); -#ifdef QUAKESTATS //can't CL_SetStatInt as we don't know if its actually us or not for (i = 0; i < cl.splitclients; i++) { @@ -5895,7 +5898,7 @@ Made up of: clients, packet_entities, nails, and tents void CL_ClearEntityLists(void) { cl_framecount++; - if (cl_numvisedicts+128 >= cl_maxvisedicts) + if (cl_expandvisents || cl_numvisedicts+128 >= cl_maxvisedicts) { int newnum = cl_maxvisedicts + 256; entity_t *n = BZ_Realloc(cl_visedicts, newnum * sizeof(*n)); @@ -5904,6 +5907,7 @@ void CL_ClearEntityLists(void) cl_visedicts = n; cl_maxvisedicts = newnum; } + cl_expandvisents = false; } cl_numvisedicts = 0; cl_numstrisidx = 0; diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 0df94154..da92bc7a 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -1609,7 +1609,7 @@ void CL_ResetFog(int ftype) static void CL_ReconfigureCommands(int newgame) { - static int oldgame; + static int oldgame = ~0; extern void SCR_SizeUp_f (void); //cl_screen extern void SCR_SizeDown_f (void); //cl_screen #ifdef QUAKESTATS @@ -2956,6 +2956,25 @@ void CL_Reconnect_f (void) return; } +#if defined(HAVE_SERVER) && defined(SUBSERVERS) + if (sv.state == ss_clustermode) + { //reconnecting while we're a cluster... o.O + char oldguid[sizeof(connectinfo.guid)]; + Q_strncpyz(oldguid, connectinfo.guid, sizeof(oldguid)); + memset(&connectinfo, 0, sizeof(connectinfo)); + connectinfo.istransfer = false; + Q_strncpyz(connectinfo.guid, oldguid, sizeof(oldguid)); //retain the same guid on transfers + + Cvar_Set(&cl_disconnectreason, "Transferring...."); + connectinfo.trying = true; + connectinfo.defaultport = cl_defaultport.value; + connectinfo.protocol = CP_UNKNOWN; + SCR_SetLoadingStage(LS_CONNECTION); + CL_CheckForResend(); + return; + } +#endif + CL_Disconnect(NULL); connectinfo.tries = 0; //re-ensure routes. CL_BeginServerReconnect(); @@ -3557,7 +3576,7 @@ client_connect: //fixme: make function Con_TPrintf ("connection\n"); #ifndef CLIENTONLY - if (sv.state) + if (sv.state && sv.state != ss_clustermode) SV_UnspawnServer(); #endif } @@ -4817,9 +4836,9 @@ void CL_Init (void) Cmd_AddCommandAD ("timedemo", CL_TimeDemo_f, CL_DemoList_c, NULL); #ifdef _DEBUG Cmd_AddCommand ("freespace", CL_FreeSpace_f); -#endif Cmd_AddCommand ("crashme_endgame", CL_CrashMeEndgame_f); Cmd_AddCommand ("crashme_error", CL_CrashMeError_f); +#endif Cmd_AddCommandD ("showpic", SCR_ShowPic_Script_f, "showpic [width] [height] [touchcommand]\nDisplays an image onscreen, that potentially has a key binding attached to it when clicked/touched.\nzone should be one of: TL, TR, BL, BR, MM, TM, BM, ML, MR. This serves as an extra offset to move the image around the screen without any foreknowledge of the screen resolution."); Cmd_AddCommandD ("showpic_removeall", SCR_ShowPic_Remove_f, "removes any pictures inserted with the showpic command."); @@ -5345,15 +5364,15 @@ qboolean Host_BeginFileDownload(struct dl_download *dl, char *mimetype) } return result; } -void Host_RunFilePrompted(void *ctx, int button) +void Host_RunFilePrompted(void *ctx, promptbutton_t button) { hrf_t *f = ctx; switch(button) { - case 0: + case PROMPT_YES: f->flags |= HRF_OVERWRITE; break; - case 1: + case PROMPT_NO: f->flags |= HRF_NOOVERWRITE; break; default: @@ -5519,7 +5538,7 @@ done: else { host_parms.manifest = Z_StrDup(fdata); - man = FS_Manifest_Parse(NULL, fdata); + man = FS_Manifest_ReadMem(NULL, NULL, fdata); if (man) { if (!man->updateurl) @@ -5527,9 +5546,6 @@ done: // if (f->flags & HRF_DOWNLOADED) man->blockupdate = true; BZ_Free(fdata); -#ifdef PACKAGEMANAGER - PM_Shutdown(); -#endif FS_ChangeGame(man, true, true); } else @@ -5946,6 +5962,8 @@ double Host_Frame (double time) SV_Frame(); RSpeedEnd(RSPEED_SERVER); } + else + MSV_PollSlaves(); #endif while(COM_DoWork(0, false)) ; @@ -6070,6 +6088,8 @@ double Host_Frame (double time) RSpeedEnd(RSPEED_SERVER); host_frametime = ohft; } + else + MSV_PollSlaves(); return 0; } #endif @@ -6141,6 +6161,8 @@ double Host_Frame (double time) // if (cls.protocol != CP_QUAKE3 && cls.protocol != CP_QUAKE2) // CL_ReadPackets (); //q3's cgame cannot cope with input commands with the same time as the most recent snapshot value } + else + MSV_PollSlaves(); #endif CL_CalcClientTime(); @@ -6224,7 +6246,9 @@ double Host_Frame (double time) CL_QTVPoll(); +#ifdef QUAKESTATS TP_UpdateAutoStatus(); +#endif host_framecount++; cl.lasttime = cl.time; @@ -6661,10 +6685,6 @@ void Host_Init (quakeparms_t *parms) V_Init (); NET_Init (); -#ifdef PLUGINS - Plug_Initialise(false); -#endif - #if defined(Q2BSPS) || defined(Q3BSPS) CM_Init(); #endif @@ -6694,6 +6714,10 @@ void Host_Init (quakeparms_t *parms) Sbar_Init (); CL_Init (); +#ifdef PLUGINS + Plug_Initialise(false); +#endif + #ifdef TEXTEDITOR Editor_Init(); #endif @@ -6802,7 +6826,7 @@ void Host_Shutdown(void) Cmd_Shutdown(); #ifdef PACKAGEMANAGER - PM_Shutdown(); + PM_Shutdown(false); #endif Key_Unbindall_f(); diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 83ff3485..d3e3d2a5 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -968,11 +968,17 @@ qboolean CL_CheckOrEnqueDownloadFile (const char *filename, const char *localnam if (flags & DLLF_ALLOWWEB) { + const char *sv_dlURL = InfoBuf_ValueForKey(&cl.serverinfo, "sv_dlURL"); flags &= ~DLLF_ALLOWWEB; - if (*cl_download_mapsrc.string) - if (!strcmp(filename, localname)) - if (!strncmp(filename, "maps/", 5)) - if (!strcmp(filename + strlen(filename)-4, ".bsp")) + if (*sv_dlURL && (flags & DLLF_NONGAME) && !strncmp(filename, "package/", 8)) + { + filename = va("%s/%s", cl_download_mapsrc.string, filename+8); + flags |= DLLF_ALLOWWEB; + } + else if (*cl_download_mapsrc.string && + !strcmp(filename, localname) && + !strncmp(filename, "maps/", 5) && + !strcmp(filename + strlen(filename)-4, ".bsp")) { char base[MAX_QPATH]; COM_FileBase(filename, base, sizeof(base)); @@ -1523,7 +1529,7 @@ static int CL_LoadSounds(int stage, qboolean dontactuallyload) void Sound_CheckDownload(const char *s) { -#if !defined(QUAKETC) && defined(AVAIL_OGGVORBIS) +#if defined(HAVE_LEGACY) && defined(AVAIL_OGGVORBIS) char mangled[512]; #endif if (*s == '*') //q2 sexed sound @@ -1536,7 +1542,7 @@ void Sound_CheckDownload(const char *s) if (CL_CheckFile(s)) return; //we have it already -#if !defined(QUAKETC) && defined(AVAIL_OGGVORBIS) +#if defined(HAVE_LEGACY) && defined(AVAIL_OGGVORBIS) //the things I do for nexuiz... *sigh* COM_StripExtension(s, mangled, sizeof(mangled)); COM_DefaultExtension(mangled, ".ogg", sizeof(mangled)); @@ -1550,7 +1556,7 @@ void Sound_CheckDownload(const char *s) if (CL_CheckFile(s)) return; //we have it already -#if !defined(QUAKETC) && defined(AVAIL_OGGVORBIS) +#if defined(HAVE_LEGACY) && defined(AVAIL_OGGVORBIS) //the things I do for nexuiz... *sigh* COM_StripExtension(s, mangled, sizeof(mangled)); COM_DefaultExtension(mangled, ".ogg", sizeof(mangled)); @@ -4857,6 +4863,7 @@ static void CLQW_ParseStartSoundPacket(void) S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, NULL, volume/255.0, attenuation, 0, 0, 0); } +#ifdef QUAKESTATS for (i = 0; i < cl.splitclients; i++) { if (ent == cl.playerview[i].playernum+1) @@ -4866,6 +4873,7 @@ static void CLQW_ParseStartSoundPacket(void) } } TP_CheckPickupSound(cl.sound_name[sound_num], pos, -1); +#endif } #ifdef Q2CLIENT @@ -5053,6 +5061,7 @@ static void CLNQ_ParseStartSoundPacket(void) S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, vel, volume/255.0, attenuation, timeofs, pitchadj, flags); } +#ifdef QUAKESTATS for (i = 0; i < cl.splitclients; i++) { if (ent == cl.playerview[i].playernum+1) @@ -5062,6 +5071,7 @@ static void CLNQ_ParseStartSoundPacket(void) } } TP_CheckPickupSound(cl.sound_name[sound_num], pos, -1); +#endif } #endif @@ -5506,8 +5516,10 @@ static void CL_SetStat_Internal (int pnum, int stat, int ivalue, float fvalue) cl.playerview[pnum].stats[stat] = ivalue; cl.playerview[pnum].statsf[stat] = fvalue; +#ifdef QUAKESTATS if (pnum == 0) TP_StatChanged(stat, ivalue); +#endif } #ifdef NQPROT diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index 6042578c..e1b22781 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -1181,7 +1181,7 @@ void CL_PredictMovePNum (int seat) } if (i == pe->num_entities && pv->nolocalplayer) { - return; //no player, nothing makes sense any more. + //return; //no player, nothing makes sense any more. from.state = &nullstate; nopred = true; } diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index 8f1d4645..127e726c 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -755,16 +755,12 @@ int SCR_DrawCenterString (vrect_t *rect, cprint_t *p, struct font_s *font) if (p->flags & CPRINT_BACKGROUND) { //hexen2 style plaque. - int px, py, pw; - px = rect->x; - py = ( y * vid.height) / (float)vid.pixelheight; - pw = rect->width+8; Font_EndString(font); if (*p->titleimage) R2D_ScalePic (rect->x + ((int)rect->width - pic->width)/2, rect->y + ((int)rect->height - pic->height)/2, pic->width, pic->height, pic); else - Draw_TextBox(px-16, py-8-8, pw/8, linecount+2); + Draw_ApproxTextBox(rect->x, (y * vid.height) / (float)vid.pixelheight, rect->width, linecount*Font_CharVHeight(font)); Font_BeginString(font, rect->x, y, &left, &top); } @@ -913,7 +909,9 @@ int R_DrawTextField(int x, int y, int w, int h, const char *text, unsigned int d cprint_t p; vrect_t r; conchar_t buffer[16384]; //FIXME: make dynamic. + int lines; + p.cursorchar = NULL; p.string = buffer; p.stringbytes = sizeof(buffer); r.x = x; @@ -927,7 +925,11 @@ int R_DrawTextField(int x, int y, int w, int h, const char *text, unsigned int d p.time_start = cl.time; *p.titleimage = 0; - return SCR_DrawCenterString(&r, &p, font); + + lines = SCR_DrawCenterString(&r, &p, font); + +// SCR_CopyCenterPrint(&p); + return lines; } qboolean SCR_HardwareCursorIsActive(void) diff --git a/engine/client/client.h b/engine/client/client.h index e98cd928..e32e9e0d 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -1549,27 +1549,27 @@ void Cam_AutoTrack_Update(const char *mode); //reset autotrack setting (because void CL_Say (qboolean team, char *extra); int TP_CategorizeMessage (char *s, int *offset, player_info_t **plr); -void TP_CheckPickupSound(char *s, vec3_t org, int seat); -qboolean TP_CheckSoundTrigger (char *str); -int TP_CountPlayers (void); -char* TP_EnemyName (void); -char* TP_EnemyTeam (void); -void TP_ExecTrigger (char *s, qboolean indemos); +void TP_ExecTrigger (char *s, qboolean indemos); //executes one of the user's f_foo aliases from some engine-defined event. qboolean TP_FilterMessage (char *s); void TP_Init(void); char* TP_LocationName (const vec3_t location); -char* TP_MapName (void); void TP_NewMap (void); +qboolean TP_CheckSoundTrigger (char *str); //plays sound files when some substring exists in chat. +void TP_SearchForMsgTriggers (char *s, int level); //msg_trigger: executes aliases when a chat message contains some user-defined string. +qboolean TP_SuppressMessage(char *buf); //true when the message contains macro results that the local player isn't meant to see (teamplay messages that contain enemy player counts for instance) +char *TP_GenerateDemoName(void); //makes something up. +#ifdef QUAKESTATS +//hack zone: this stuff makes assumptions about quake-only stats+items+rules and stuff. +void TP_CheckPickupSound(char *s, vec3_t org, int seat); void TP_ParsePlayerInfo(player_state_t *oldstate, player_state_t *state, player_info_t *info); qboolean TP_IsPlayerVisible(vec3_t origin); -char* TP_PlayerName (void); -char* TP_PlayerTeam (void); -void TP_SearchForMsgTriggers (char *s, int level); qboolean TP_SoundTrigger(char *message); void TP_StatChanged (int stat, int value); -qboolean TP_SuppressMessage(char *buf); -colourised_t *TP_FindColours(char *name); void TP_UpdateAutoStatus(void); +#endif +#ifdef QWSKINS +colourised_t *TP_FindColours(char *name); +#endif // // skin.c diff --git a/engine/client/console.c b/engine/client/console.c index 6a57f5a1..c983157a 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -232,6 +232,11 @@ console_t *Con_FindConsole(const char *name) console_t *Con_Create(const char *name, unsigned int flags) { console_t *con, *p; + if (!name) + { + static unsigned long seq; + name = va("c%lu", seq++); + } if (!strcmp(name, "current")) return NULL; if (!strcmp(name, "head")) @@ -532,7 +537,7 @@ void QT_Create(char *command) void Con_QTerm_f(void) { if(Cmd_IsInsecure()) - Con_Printf("Server tried stuffcmding a restricted commandqterm %s\n", Cmd_Args()); + Con_Printf("Server tried stuffcmding a restricted command: qterm %s\n", Cmd_Args()); else QT_Create(Cmd_Args()); } diff --git a/engine/client/image.c b/engine/client/image.c index 022ab3f6..97a29c6e 100644 --- a/engine/client/image.c +++ b/engine/client/image.c @@ -5498,6 +5498,7 @@ static struct pendingtextureinfo *Image_ReadDDSFile(unsigned int flags, const ch memcpy(&fmtheader, filedata+4, sizeof(fmtheader)); if (fmtheader.dwSize != sizeof(fmtheader)) return NULL; //corrupt/different version + fmtheader.dwSize += 4; memset(&fmt10header, 0, sizeof(fmt10header)); fmt10header.arraysize = (fmtheader.ddsCaps[1] & 0x200)?6:1; //cubemaps need 6 faces... @@ -5505,6 +5506,8 @@ static struct pendingtextureinfo *Image_ReadDDSFile(unsigned int flags, const ch nummips = fmtheader.dwMipMapCount; if (nummips < 1) nummips = 1; + if (nummips > countof(mips->mip)) + return NULL; if (!(fmtheader.ddpfPixelFormat.dwFlags & 4)) { @@ -5591,7 +5594,7 @@ static struct pendingtextureinfo *Image_ReadDDSFile(unsigned int flags, const ch else if (*(int*)&fmtheader.ddpfPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('1'<<16)|('0'<<24))) { //this has some weird extra header with dxgi format types. - memcpy(&fmt10header, filedata+4+fmtheader.dwSize, sizeof(fmt10header)); + memcpy(&fmt10header, filedata+fmtheader.dwSize, sizeof(fmt10header)); fmtheader.dwSize += sizeof(fmt10header); switch(fmt10header.dxgiformat) { @@ -5802,11 +5805,11 @@ static struct pendingtextureinfo *Image_ReadDDSFile(unsigned int flags, const ch mips->extrafree = filedata; mips->encoding = encoding; - filedata += 4+fmtheader.dwSize; + filedata += fmtheader.dwSize; - w = fmtheader.dwWidth; - h = fmtheader.dwHeight; - d = fmtheader.dwDepth; + w = max(1, fmtheader.dwWidth); + h = max(1, fmtheader.dwHeight); + d = max(1, fmtheader.dwDepth); if (layers == 1) { //can just use the data without copying. @@ -5819,13 +5822,13 @@ static struct pendingtextureinfo *Image_ReadDDSFile(unsigned int flags, const ch mips->mip[mipnum].width = w; mips->mip[mipnum].height = h; mips->mip[mipnum].depth = d; - mips->mipcount++; filedata += datasize; w = max(1, w>>1); h = max(1, h>>1); d = max(1, d>>1); } + mips->mipcount = mipnum; } else { //we need to copy stuff in order to pack it properly. :( @@ -11057,10 +11060,10 @@ static qboolean Image_DecompressFormat(struct pendingtextureinfo *mips, const ch rcoding = PTI_RGBX8; break; #else - case PTI_BC4_R8_SNORM: - case PTI_BC4_R8: - case PTI_BC5_RG8_SNORM: - case PTI_BC5_RG8: + case PTI_BC4_R_SNORM: + case PTI_BC4_R: + case PTI_BC5_RG_SNORM: + case PTI_BC5_RG: Con_ThrottlePrintf(&throttle, 0, "Fallback BC4/BC5 decompression is not supported in this build\n"); break; #endif diff --git a/engine/client/keys.c b/engine/client/keys.c index c3296f69..34fbbdc4 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -889,6 +889,8 @@ void Key_DefaultLinkClicked(console_t *con, char *text, char *info) } return; } + if (!con) + return; //can't do footers Con_Footerf(con, false, "^m#^m ^[%s\\player\\%i^]: %if %ims", cl.players[player].name, player, cl.players[player].frags, cl.players[player].ping); @@ -1058,6 +1060,14 @@ void Key_DefaultLinkClicked(console_t *con, char *text, char *info) Cbuf_AddText(va("\nedit \"%s\"\n", c), RESTRICT_LOCAL); return; } +#endif +#ifdef SUBSERVERS + c = Info_ValueForKey(info, "ssv"); + if (*c && !strchr(c, ';') && !strchr(c, '\n')) + { + Cbuf_AddText(va("\nssv \"%s\"\n", c), RESTRICT_LOCAL); + return; + } #endif c = Info_ValueForKey(info, "impulse"); if (*c && !strchr(c, ';') && !strchr(c, '\n')) @@ -1082,7 +1092,8 @@ void Key_DefaultLinkClicked(console_t *con, char *text, char *info) c = Info_ValueForKey(info, "desc"); if (*c) { - Con_Footerf(con, false, "%s", c); + if (con) + Con_Footerf(con, false, "%s", c); return; } @@ -1133,8 +1144,8 @@ void Key_HandleConsoleLink(console_t *con, char *buffer) { //okay, its a valid link that they clicked *end = 0; -#ifdef PLUGINS - if (!Plug_ConsoleLink(buffer+2, info, con->name)) +#ifdef PLUGINS //plugins can use these window things like popup menus. + if (con && !Plug_ConsoleLink(buffer+2, info, con->name)) #endif #ifdef CSQC_DAT if (!CSQC_ConsoleLink(buffer+2, info)) diff --git a/engine/client/m_download.c b/engine/client/m_download.c index 57983622..4cf23590 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -12,6 +12,8 @@ #include "shader.h" #include "netinc.h" +#define ENABLEPLUGINSBYDEFAULT //auto-enable plugins that the user installs. this risks other programs installing dlls (but we can't really protect our listing file so this is probably not any worse in practise). + #ifdef PACKAGEMANAGER #if !defined(NOBUILTINMENUS) && !defined(SERVERONLY) #define DOWNLOADMENU @@ -35,7 +37,13 @@ -extern cvar_t pm_downloads_url; +cvar_t pkg_downloads_url = CVARFD("pkg_downloads_url", NULL, CVAR_NOTFROMSERVER|CVAR_NOSAVE|CVAR_NOSET, "The URL of a package updates list."); //read from the default.fmf +#ifdef ENABLEPLUGINSBYDEFAULT +cvar_t pkg_autoupdate = CVARFD("pkg_autoupdate", "1", CVAR_NOTFROMSERVER|CVAR_NOSAVE|CVAR_NOSET, "Controls autoupdates, can only be changed via the downloads menu.\n0: off.\n1: enabled (stable only).\n2: enabled (unstable).\nNote that autoupdate will still prompt the user to actually apply the changes."); //read from the package list only. +#else +cvar_t pkg_autoupdate = CVARFD("pkg_autoupdate", "-1", CVAR_NOTFROMSERVER|CVAR_NOSAVE|CVAR_NOSET, "Controls autoupdates, can only be changed via the downloads menu.\n0: off.\n1: enabled (stable only).\n2: enabled (unstable).\nNote that autoupdate will still prompt the user to actually apply the changes."); //read from the package list only. +#endif + #define INSTALLEDFILES "installed.lst" //the file that resides in the quakedir (saying what's installed). //installed native okay [previously manually installed, or has no a qhash] @@ -60,22 +68,26 @@ extern cvar_t pm_downloads_url; #define DPF_USERMARKED (1u<<4) //user selected it #define DPF_AUTOMARKED (1u<<5) //selected only to satisfy a dependancy -#define DPF_DISPLAYVERSION (1u<<6) //some sort of conflict, the package is listed twice, so show versions so the user knows what's old. -#define DPF_FORGETONUNINSTALL (1u<<7) //for previously installed packages, remove them from the list if there's no current version any more (should really be automatic if there's no known mirrors) -#define DPF_HIDDEN (1u<<8) //wrong arch, file conflicts, etc. still listed if actually installed. -#define DPF_PURGE (1u<<9) //package should be completely removed (ie: the dlcache dir too). if its still marked then it should be reinstalled anew. available on cached or corrupt packages, implied by native. -#define DPF_MANIFEST (1u<<10) //package was named by the manifest, and should only be uninstalled after a warning. -#define DPF_TESTING (1u<<11) //package is provided on a testing/trial basis, and will only be selected/listed if autoupdates are configured to allow it. +#define DPF_MANIMARKED (1u<<6) //legacy. selected to satisfy packages listed directly in manifests. the filesystem code will load the packages itself, we just need to download (but not enable). +#define DPF_DISPLAYVERSION (1u<<7) //some sort of conflict, the package is listed twice, so show versions so the user knows what's old. -#define DPF_ENGINE (1u<<12) //engine update. replaces old autoupdate mechanism -#define DPF_PLUGIN (1u<<13) //this is a plugin package, with a dll +#define DPF_FORGETONUNINSTALL (1u<<8) //for previously installed packages, remove them from the list if there's no current version any more (should really be automatic if there's no known mirrors) +#define DPF_HIDDEN (1u<<9) //wrong arch, file conflicts, etc. still listed if actually installed. +#define DPF_PURGE (1u<<10) //package should be completely removed (ie: the dlcache dir too). if its still marked then it should be reinstalled anew. available on cached or corrupt packages, implied by native. +#define DPF_MANIFEST (1u<<11) //package was named by the manifest, and should only be uninstalled after a warning. -#define DPF_TRUSTED (1u<<14) //flag used when parsing package lists. if not set then packages will be ignored if they are anything but paks/pk3s -#define DPF_SIGNATUREREJECTED (1u<<15) //signature is bad -#define DPF_SIGNATUREACCEPTED (1u<<16) //signature is good (required for dll/so/exe files) -#define DPF_SIGNATUREUNKNOWN (1u<<17) //signature is unknown +#define DPF_TESTING (1u<<12) //package is provided on a testing/trial basis, and will only be selected/listed if autoupdates are configured to allow it. +#define DPF_GUESSED (1u<<13) //package data was guessed from basically just filename+qhash+url. merge aggressively. +#define DPF_ENGINE (1u<<14) //engine update. replaces old autoupdate mechanism +#define DPF_PLUGIN (1u<<15) //this is a plugin package, with a dll -#define DPF_MARKED (DPF_USERMARKED|DPF_AUTOMARKED) +#define DPF_TRUSTED (1u<<16) //flag used when parsing package lists. if not set then packages will be ignored if they are anything but paks/pk3s +#define DPF_SIGNATUREREJECTED (1u<<17) //signature is bad +#define DPF_SIGNATUREACCEPTED (1u<<18) //signature is good (required for dll/so/exe files) +#define DPF_SIGNATUREUNKNOWN (1u<<19) //signature is unknown + +#define DPF_MARKED (DPF_USERMARKED|DPF_AUTOMARKED) //flags that will enable it +#define DPF_ALLMARKED (DPF_USERMARKED|DPF_AUTOMARKED|DPF_MANIMARKED) //flags that will download it without necessarily enabling it. #define DPF_PRESENT (DPF_NATIVE|DPF_CACHED) #define DPF_DISABLEDINSTALLED (DPF_ENGINE|DPF_PLUGIN) //engines+plugins can be installed without being enabled. //pak.lst @@ -144,7 +156,8 @@ typedef struct package_s { EXTRACT_COPY, //just copy the download over EXTRACT_XZ, //give the download code a write filter so that it automatically decompresses on the fly EXTRACT_GZ, //give the download code a write filter so that it automatically decompresses on the fly - EXTRACT_ZIP //extract stuff once it completes. kinda sucky. + EXTRACT_EXPLICITZIP,//extract an explicit file list once it completes. kinda sucky. + EXTRACT_ZIP, //extract stuff once it completes. kinda sucky. } extract; struct packagedep_s @@ -162,6 +175,7 @@ typedef struct package_s { // DEP_MIRROR, // DEP_FAILEDMIRROR, + DEP_EXTRACTNAME, //a file that will be installed DEP_FILE //a file that will be installed } dtype; char name[1]; @@ -237,6 +251,12 @@ static void PM_FreePackage(package_t *p) } } + if (p->download) + { //if its currently downloading, cancel it. + DL_Close(p->download); + p->download = NULL; + } + //free its data. while(p->deps) { @@ -270,7 +290,7 @@ static qboolean PM_PurgeOnDisable(package_t *p) if (p->flags & DPF_DISABLEDINSTALLED) return false; //hashed packages can also be present and not enabled, but only if they're in the cache and not native - if (*p->gamedir && p->qhash && (p->flags & DPF_PRESENT)) + if (*p->gamedir && (p->flags & DPF_CACHED)) return false; //all other packages must be deleted to disable them return true; @@ -364,12 +384,26 @@ void PM_ValidateAuthenticity(package_t *p) p->flags |= DPF_SIGNATUREUNKNOWN; } +static qboolean PM_TryGenCachedName(const char *pname, package_t *p, char *local, int llen) +{ + if (!*p->gamedir) + return false; + if (!p->qhash) + { //we'll throw paks+pk3s into the cache dir even without a qhash + const char *ext = COM_GetFileExtension(pname, NULL); + if (strcmp(ext, ".pak") && strcmp(ext, ".pk3")) + return false; + } + return FS_GenCachedPakName(pname, p->qhash, local, llen); +} + //checks the status of each package static void PM_ValidatePackage(package_t *p) { package_t *o; struct packagedep_s *dep; vfsfile_t *pf; + char temp[MAX_OSPATH]; p->flags &=~ (DPF_NATIVE|DPF_CACHED|DPF_CORRUPT); if (p->flags & DPF_ENABLED) { @@ -388,17 +422,13 @@ static void PM_ValidatePackage(package_t *p) VFS_CLOSE(pf); p->flags |= DPF_NATIVE; } - else if (*p->gamedir && p->qhash) + else if (PM_TryGenCachedName(n, p, temp, sizeof(temp))) { - char temp[MAX_OSPATH]; - if (FS_GenCachedPakName(n, p->qhash, temp, sizeof(temp))) + pf = FS_OpenVFS(temp, "rb", p->fsroot); + if (pf) { - pf = FS_OpenVFS(temp, "rb", p->fsroot); - if (pf) - { - VFS_CLOSE(pf); - p->flags |= DPF_CACHED; - } + VFS_CLOSE(pf); + p->flags |= DPF_CACHED; } } if (!(p->flags & (DPF_NATIVE|DPF_CACHED))) @@ -419,14 +449,10 @@ static void PM_ValidatePackage(package_t *p) else n = dep->name; pf = FS_OpenVFS(n, "rb", p->fsroot); - if (!pf && *p->gamedir && p->qhash) + if (!pf && PM_TryGenCachedName(n, p, temp, sizeof(temp))) { - char temp[MAX_OSPATH]; - if (FS_GenCachedPakName(n, p->qhash, temp, sizeof(temp))) - { - pf = FS_OpenVFS(temp, "rb", p->fsroot); - fl = DPF_CACHED; - } + pf = FS_OpenVFS(temp, "rb", p->fsroot); + fl = DPF_CACHED; //fixme: skip any archive checks } @@ -455,7 +481,7 @@ static void PM_ValidatePackage(package_t *p) p->flags |= DPF_CACHED; else if (!o) { - if (!PM_PurgeOnDisable(p)) + if (!PM_PurgeOnDisable(p) || !strcmp(p->gamedir, "downloads")) { p->flags |= fl; VFS_CLOSE(pf); @@ -567,20 +593,32 @@ static qboolean PM_MergePackage(package_t *oldp, package_t *newp) if (newp->description){Z_Free(oldp->description); oldp->description = Z_StrDup(newp->description);} if (newp->license){Z_Free(oldp->license); oldp->license = Z_StrDup(newp->license);} if (newp->author){Z_Free(oldp->author); oldp->author = Z_StrDup(newp->author);} + if (newp->website){Z_Free(oldp->website); oldp->website = Z_StrDup(newp->website);} if (newp->previewimage){Z_Free(oldp->previewimage); oldp->previewimage = Z_StrDup(newp->previewimage);} oldp->priority = newp->priority; if (nm) { //copy over the mirrors oldp->extract = newp->extract; - for (; nm --> 0 && om < countof(oldp->mirror); om++) + for (; nm --> 0 && om < countof(oldp->mirror); ) { - oldp->mirror[om] = newp->mirror[nm]; + //skip it if this mirror was already known. + unsigned int u; + for (u = 0; u < om; u++) + { + if (!strcmp(oldp->mirror[u], newp->mirror[nm])) + break; + } + if (u < om) + continue; + + //new mirror! copy it over + oldp->mirror[om++] = newp->mirror[nm]; newp->mirror[nm] = NULL; } } //these flags should only remain set if set in both. - oldp->flags &= ~(DPF_FORGETONUNINSTALL|DPF_TESTING) | (newp->flags & (DPF_FORGETONUNINSTALL|DPF_TESTING)); + oldp->flags &= ~(DPF_FORGETONUNINSTALL|DPF_TESTING|DPF_MANIFEST) | (newp->flags & (DPF_FORGETONUNINSTALL|DPF_TESTING|DPF_MANIFEST)); PM_FreePackage(newp); return true; @@ -588,34 +626,69 @@ static qboolean PM_MergePackage(package_t *oldp, package_t *newp) return false; } -static void PM_InsertPackage(package_t *p) +static package_t *PM_InsertPackage(package_t *p) { package_t **link; + int v; for (link = &availablepackages; *link; link = &(*link)->next) { package_t *prev = *link; - int v = strcmp(prev->name, p->name); + if (((prev->flags|p->flags) & DPF_GUESSED) && prev->extract == p->extract && !strcmp(prev->gamedir, p->gamedir) && prev->fsroot == p->fsroot && !strcmp(prev->qhash?prev->qhash:"", p->qhash?p->qhash:"")) + { //if one of the packages was guessed then match according to the qhash and file names. + struct packagedep_s *a = prev->deps, *b = p->deps; + qboolean differs = false; + for (;;) + { + while (a && a->dtype != DEP_FILE) + a = a->next; + while (b && b->dtype != DEP_FILE) + b = b->next; + if (!a && !b) + break; + if (a && b && !strcmp(a->name, b->name)) + { + a = a->next; + b = b->next; + continue; //matches... + } + differs = true; + break; + } + if (differs) + continue; + else + { + if (p->flags & DPF_GUESSED) + { //the new one was guessed. just return the existing package instead. + PM_FreePackage(p); + return prev; + } + + //FIXME: replace prev... + } + } + v = strcmp(prev->name, p->name); if (v > 0) break; //insert before this one else if (v == 0) { //name matches. //if (!strcmp(p->fullname),prev->fullname) - if (!strcmp(p->version, prev->version)) + if (!strcmp(p->version, prev->version) && !strcmp(prev->qhash?prev->qhash:"", p->qhash?p->qhash:"")) if (!strcmp(p->gamedir, prev->gamedir)) if (!strcmp(p->arch?p->arch:"", prev->arch?prev->arch:"")) { /*package matches, merge them somehow, don't add*/ package_t *a; if (PM_MergePackage(prev, p)) - return; + return prev; for (a = p->alternative; a; a = a->next) { if (PM_MergePackage(a, p)) - return; + return prev; } p->next = prev->alternative; prev->alternative = p; p->link = &prev->alternative; - return; + return prev; } //something major differs, display both independantly. @@ -623,11 +696,27 @@ static void PM_InsertPackage(package_t *p) prev->flags |= DPF_DISPLAYVERSION; } } + PM_ValidatePackage(p); + + if (p->flags & DPF_MANIFEST) + if (!(p->flags & (DPF_PRESENT|DPF_CACHED))) + { //if a manifest wants it then there's only any point listing it if there's an actual mirror listed. otherwise its a hint to the filesystem for ordering and not something that's actually present. + int i; + for (i = 0; i < countof(p->mirror); i++) + if (p->mirror[i]) + break; + if (i == countof(p->mirror)) + { + PM_FreePackage(p); + return NULL; + } + } + p->next = *link; p->link = link; *link = p; - PM_ValidatePackage(p); numpackages++; + return p; } static qboolean PM_CheckFeature(const char *feature, const char **featurename, const char **concommand) @@ -747,12 +836,19 @@ static void PM_AddSubList(const char *url, const char *prefix, qboolean save, qb static void PM_RemSubList(const char *url) { int i; - for (i = 0; i < numdownloadablelists; i++) + for (i = 0; i < numdownloadablelists; ) { if (!strcmp(downloadablelist[i].url, url)) { - downloadablelist[i].save = false; + if (downloadablelist[i].curdl) + DL_Close(downloadablelist[i].curdl); + Z_Free(downloadablelist[i].url); + Z_Free(downloadablelist[i].prefix); + memmove(&downloadablelist[i], &downloadablelist[i+1], sizeof(*downloadablelist)*(--numdownloadablelists-i)); + break; } + else + i++; } } #endif @@ -1050,6 +1146,8 @@ static qboolean PM_ParsePackageList(const char *f, int parseflags, const char *u extract = EXTRACT_GZ; else if (!strcmp(val, "zip")) extract = EXTRACT_ZIP; + else if (!strcmp(val, "zip_explicit")) + extract = EXTRACT_EXPLICITZIP; else Con_Printf("Unknown decompression method: %s\n", val); } @@ -1069,6 +1167,8 @@ static qboolean PM_ParsePackageList(const char *f, int parseflags, const char *u PM_AddDep(p, DEP_NEEDFEATURE, val); else if (!strcmp(key, "test")) flags |= DPF_TESTING; + else if (!strcmp(key, "guessed")) + flags |= DPF_GUESSED; else if (!strcmp(key, "stale") && version==2) flags &= ~DPF_ENABLED; //known about, (probably) cached, but not actually enabled. else if (!strcmp(key, "installed") && version>2) @@ -1156,7 +1256,7 @@ static qboolean PM_ParsePackageList(const char *f, int parseflags, const char *u ext = ".xz"; else if (extract == EXTRACT_GZ) ext = ".gz"; - else if (extract == EXTRACT_ZIP) + else if (extract == EXTRACT_ZIP || extract == EXTRACT_EXPLICITZIP) ext = ".zip"; relurl = file; } @@ -1246,6 +1346,7 @@ void PM_EnumeratePlugins(void (*callback)(const char *name)) #endif #ifdef PLUGINS +static package_t *PM_FindExactPackage(const char *packagename, const char *arch, const char *version, unsigned int flags); static package_t *PM_FindPackage(const char *packagename); static int QDECL PM_EnumeratedPlugin (const char *name, qofs_t size, time_t mtime, void *param, searchpathfuncs_t *spath) { @@ -1260,6 +1361,7 @@ static int QDECL PM_EnumeratedPlugin (const char *name, qofs_t size, time_t mtim char vmname[MAX_QPATH]; int len, l, a; char *dot; + const char *synthver = "??""??"; if (!strncmp(name, PLUGINPREFIX, strlen(PLUGINPREFIX))) Q_strncpyz(vmname, name+strlen(PLUGINPREFIX), sizeof(vmname)); else @@ -1304,7 +1406,7 @@ static int QDECL PM_EnumeratedPlugin (const char *name, qofs_t size, time_t mtim } } - if (PM_FindPackage(vmname)) + if (PM_FindExactPackage(vmname, NULL, NULL, 0)) return true; //don't include it if its a dupe anyway. p = Z_Malloc(sizeof(*p)); @@ -1317,18 +1419,22 @@ static int QDECL PM_EnumeratedPlugin (const char *name, qofs_t size, time_t mtim p->category = Z_StrDup("Plugins/"); p->priority = PM_DEFAULTPRIORITY; p->fsroot = FS_BINARYPATH; - strcpy(p->version, "??""??"); + strcpy(p->version, synthver); p->flags = DPF_PLUGIN|DPF_NATIVE|DPF_FORGETONUNINSTALL; - PM_InsertPackage(p); - +#ifdef ENABLEPLUGINSBYDEFAULT + p->flags |= DPF_USERMARKED|DPF_ENABLED; +#else *(int*)param = true; +#endif + PM_InsertPackage(p); return true; } #ifndef SERVERONLY void PM_PluginDetected(void *ctx, int status) { - PM_WriteInstalledPackages(); + if (status != -1) + PM_WriteInstalledPackages(); if (status == 0) { Cmd_ExecuteString("menu_download\n", RESTRICT_LOCAL); @@ -1339,11 +1445,11 @@ void PM_PluginDetected(void *ctx, int status) #endif #ifndef SERVERONLY -void PM_AutoUpdateQuery(void *ctx, int status) +void PM_AutoUpdateQuery(void *ctx, promptbutton_t status) { - if (status == -1) + if (status == PROMPT_CANCEL) return; //'Later' - if (status == 1) + if (status == PROMPT_NO) Cvar_ForceSet(&pkg_autoupdate, "0"); //'Disable' else Cvar_ForceSet(&pkg_autoupdate, "1"); //'Enable' @@ -1355,7 +1461,7 @@ void PM_AutoUpdateQuery(void *ctx, int status) static void PM_PreparePackageList(void) { //figure out what we've previously installed. - if (!loadedinstalled) + if (fs_manifest && !loadedinstalled) { qofs_t sz = 0; char *f = FS_MallocFile(INSTALLEDFILES, FS_ROOT, &sz); @@ -1374,13 +1480,15 @@ static void PM_PreparePackageList(void) FS_NativePath("", FS_BINARYPATH, nat, sizeof(nat)); Con_DPrintf("Loading plugins from \"%s\"\n", nat); Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_DL_POSTFIX, PM_EnumeratedPlugin, &foundone, NULL); +#ifndef ENABLEPLUGINSBYDEFAULT if (foundone && !pluginpromptshown) { pluginpromptshown = true; #ifndef SERVERONLY - Menu_Prompt(PM_PluginDetected, NULL, "Plugin(s) appears to have\nbeen installed externally.\nUse the updates menu\nto enable them.", "View", NULL, "Disable"); + Menu_Prompt(PM_PluginDetected, NULL, "Plugin(s) appears to have\nbeen installed externally.\nUse the updates menu\nto enable them.", "View", "Disable", "Later..."); #endif } +#endif } #endif if (!pluginpromptshown && pkg_autoupdate.ival < 0 && numdownloadablelists) @@ -1431,7 +1539,7 @@ void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const cha } while (pri < maxpri); } -void PM_Shutdown(void) +void PM_Shutdown(qboolean soft) { //free everything... pkg_downloads_url.modified = false; @@ -1456,41 +1564,86 @@ void PM_Shutdown(void) downloadablelist[numdownloadablelists].prefix = NULL; } - while (availablepackages) - PM_FreePackage(availablepackages); + if (!soft) + { + while (availablepackages) + PM_FreePackage(availablepackages); + } loadedinstalled = false; } //finds the newest version -static package_t *PM_FindPackage(const char *packagename) +static package_t *PM_FindExactPackage(const char *packagename, const char *arch, const char *version, unsigned int flags) { package_t *p, *r = NULL; - - //fixme: NAME (>VER) - //fixme: NAME (VER) + if (arch && !*arch) arch = NULL; + if (version && !*version) version = NULL; for (p = availablepackages; p; p = p->next) { if (!strcmp(p->name, packagename)) { + if (arch && (!p->arch||strcmp(p->arch, arch))) + continue; //wrong arch. + if (!arch && p->arch && *p->arch && strcmp(p->arch, THISARCH)) + continue; //wrong arch. + if (flags && !(p->flags & flags)) + continue; + if (version) + { //versions are a bit more complex. + if (*version == '=' && strcmp(p->version, version+1)) + continue; + if (*version == '>' && strcmp(p->version, version+1)<=0) + continue; + if (*version == '<' && strcmp(p->version, version+1)>=0) + continue; + } if (!r || strcmp(r->version, p->version)>0) r = p; } } return r; } +static package_t *PM_FindPackage(const char *packagename) +{ + char *t = strcpy(alloca(strlen(packagename)+2), packagename); + char *arch = strchr(t, ':'); + char *ver = strchr(t, '='); + if (!ver) + ver = strchr(t, '>'); + if (!ver) + ver = strchr(t, '<'); + if (arch) + *arch++ = 0; + if (ver) + { + *ver = 0; + return PM_FindExactPackage(t, arch, packagename + (ver-t), 0); //weirdness is because the leading char of the version is important. + } + else + return PM_FindExactPackage(t, arch, NULL, 0); +} //returns the marked version of a package, if any. static package_t *PM_MarkedPackage(const char *packagename, int markflag) { - package_t *p; - for (p = availablepackages; p; p = p->next) + char *t = strcpy(alloca(strlen(packagename)+2), packagename); + char *arch = strchr(t, ':'); + char *ver = strchr(t, '='); + if (!markflag) + return NULL; + if (!ver) + ver = strchr(t, '>'); + if (!ver) + ver = strchr(t, '<'); + if (arch) + *arch++ = 0; + if (ver) { - if (p->flags & markflag) - if (!strcmp(p->name, packagename)) - return p; + *ver = 0; + return PM_FindExactPackage(t, arch, packagename + (ver-t), markflag); //weirdness is because the leading char of the version is important. } - return NULL; + else + return PM_FindExactPackage(t, arch, NULL, markflag); } //just resets all actions, so that a following apply won't do anything. @@ -1991,10 +2144,10 @@ static void PM_ListDownloaded(struct dl_download *dl) #endif #if defined(HAVE_CLIENT) && defined(WEBCLIENT) static void PM_UpdatePackageList(qboolean autoupdate, int retry); -static void PM_AllowPackageListQuery_Callback(void *ctx, int opt) +static void PM_AllowPackageListQuery_Callback(void *ctx, promptbutton_t opt) { unsigned int i; - allowphonehome = (opt==0); + allowphonehome = (opt==PROMPT_YES); //something changed, let it download now. for (i = 0; i < numdownloadablelists; i++) @@ -2011,7 +2164,7 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) unsigned int i; if (retry>1 || pkg_downloads_url.modified) - PM_Shutdown(); + PM_Shutdown(true); PM_PreparePackageList(); @@ -2153,6 +2306,11 @@ static void PM_WriteInstalledPackages(void) Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("stale=1"), buf, sizeof(buf)); } + if (p->flags & DPF_GUESSED) + { + Q_strncatz(buf, " ", sizeof(buf)); + COM_QuotedConcat(va("guessed=1"), buf, sizeof(buf)); + } if (*p->title && strcmp(p->title, p->name)) { Q_strncatz(buf, " ", sizeof(buf)); @@ -2378,6 +2536,7 @@ static void PM_Download_Got(struct dl_download *dl) qboolean successful = dl->status == DL_FINISHED; package_t *p; char *tempname = dl->user_ctx; + const enum fs_relative temproot = dl->user_num; for (p = availablepackages; p ; p=p->next) { @@ -2398,13 +2557,13 @@ static void PM_Download_Got(struct dl_download *dl) { char ext[8]; char *destname; - struct packagedep_s *dep; + struct packagedep_s *dep, *srcname = p->deps; p->download = NULL; if (!successful) { Con_Printf("Couldn't download %s (from %s)\n", p->name, dl->url); - FS_Remove (tempname, p->fsroot); + FS_Remove (tempname, temproot); Z_Free(tempname); PM_StartADownload(); return; @@ -2412,44 +2571,61 @@ static void PM_Download_Got(struct dl_download *dl) if (p->extract == EXTRACT_ZIP) { + searchpathfuncs_t *archive = NULL; #ifdef PACKAGE_PK3 - vfsfile_t *f = FS_OpenVFS(tempname, "rb", p->fsroot); + vfsfile_t *f = FS_OpenVFS(tempname, "rb", temproot); if (f) { - searchpathfuncs_t *archive = FSZIP_LoadArchive(f, NULL, tempname, tempname, NULL); - if (archive) - { - p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_ENABLED); - archive->EnumerateFiles(archive, "*", PM_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*", PM_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*", PM_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*", PM_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*", PM_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*/*", PM_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*/*/*", PM_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*/*/*/*", PM_ExtractFiles, p); - archive->EnumerateFiles(archive, "*/*/*/*/*/*/*/*/*", PM_ExtractFiles, p); - archive->ClosePath(archive); - - PM_WriteInstalledPackages(); - -// if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) -// FS_ReloadPackFiles(); - } - else + archive = FSZIP_LoadArchive(f, NULL, tempname, tempname, NULL); + if (!archive) VFS_CLOSE(f); } - PM_ValidatePackage(p); #else Con_Printf("zip format not supported in this build - %s (from %s)\n", p->name, dl->url); #endif - FS_Remove (tempname, p->fsroot); + if (archive) + { + p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_ENABLED); + archive->EnumerateFiles(archive, "*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*/*/*/*", PM_ExtractFiles, p); + archive->EnumerateFiles(archive, "*/*/*/*/*/*/*/*/*", PM_ExtractFiles, p); + archive->ClosePath(archive); + + PM_WriteInstalledPackages(); + +// if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) +// FS_ReloadPackFiles(); + } + PM_ValidatePackage(p); + FS_Remove (tempname, temproot); Z_Free(tempname); + p->trymirrors = 0; //we're done... don't download it again! PM_StartADownload(); return; } else { + qboolean success = false; + searchpathfuncs_t *archive = NULL; +#ifdef PACKAGE_PK3 + if (p->extract == EXTRACT_EXPLICITZIP) + { + vfsfile_t *f = FS_OpenVFS(tempname, "rb", temproot); + if (f) + { + archive = FSZIP_LoadArchive(f, NULL, tempname, tempname, NULL); + if (!archive) + VFS_CLOSE(f); + } + } +#endif + for (dep = p->deps; dep; dep = dep->next) { unsigned int nfl; @@ -2469,7 +2645,7 @@ static void PM_Download_Got(struct dl_download *dl) { char temp[MAX_OSPATH]; destname = va("%s/%s", p->gamedir, dep->name); - if (p->qhash && FS_GenCachedPakName(destname, p->qhash, temp, sizeof(temp))) + if (PM_TryGenCachedName(destname, p, temp, sizeof(temp))) { nfl = DPF_CACHED; destname = va("%s", temp); @@ -2477,32 +2653,74 @@ static void PM_Download_Got(struct dl_download *dl) } else destname = dep->name; - nfl |= DPF_ENABLED | (p->flags & ~(DPF_CACHED|DPF_NATIVE|DPF_CORRUPT)); + if (p->flags & DPF_MARKED) + nfl |= DPF_ENABLED; + nfl |= (p->flags & ~(DPF_CACHED|DPF_NATIVE|DPF_CORRUPT)); FS_CreatePath(destname, p->fsroot); if (FS_Remove(destname, p->fsroot)) ; - if (!FS_Rename2(tempname, destname, p->fsroot, p->fsroot)) + if (p->extract == EXTRACT_EXPLICITZIP) + { + while (srcname && srcname->dtype != DEP_EXTRACTNAME) + srcname = srcname->next; + if (archive) + { + flocation_t loc; + + if (archive->FindFile(archive, &loc, srcname->name, NULL)==FF_FOUND && loc.len < 0x80000000u) + { + char *f = malloc(loc.len); + if (f) + { + archive->ReadFile(archive, &loc, f); + if (FS_WriteFile(destname, f, loc.len, p->fsroot)) + { + p->flags = nfl; + success = true; + continue; + } + } + } + } + + if (!FS_NativePath(destname, p->fsroot, native, sizeof(native))) + Q_strncpyz(native, destname, sizeof(native)); + Con_Printf("Couldn't extract %s/%s to %s. Removed instead.\n", tempname, dep->name, native); + FS_Remove (tempname, temproot); + } + else if (!FS_Rename2(tempname, destname, temproot, p->fsroot)) { //error! if (!FS_NativePath(destname, p->fsroot, native, sizeof(native))) Q_strncpyz(native, destname, sizeof(native)); Con_Printf("Couldn't rename %s to %s. Removed instead.\n", tempname, native); - FS_Remove (tempname, p->fsroot); + FS_Remove (tempname, temproot); } else { //success! if (!FS_NativePath(destname, p->fsroot, native, sizeof(native))) Q_strncpyz(native, destname, sizeof(native)); Con_Printf("Downloaded %s (to %s)\n", p->name, native); + p->flags = nfl; - PM_WriteInstalledPackages(); } + success = true; + } + if (archive) + archive->ClosePath(archive); + if (p->extract == EXTRACT_EXPLICITZIP) + FS_Remove (tempname, temproot); + if (success) + { PM_ValidatePackage(p); PM_PackageEnabled(p); + PM_WriteInstalledPackages(); Z_Free(tempname); + + p->trymirrors = 0; //we're done with this one... don't download it from another mirror! PM_StartADownload(); return; } @@ -2512,7 +2730,7 @@ static void PM_Download_Got(struct dl_download *dl) else Con_Printf("menu_download: Can't figure out where %s came from (url: %s)\n", dl->localname, dl->url); - FS_Remove (tempname, FS_GAMEONLY); + FS_Remove (tempname, temproot); Z_Free(tempname); PM_StartADownload(); } @@ -2583,9 +2801,9 @@ typedef struct { qofs_t sz; qofs_t needsize; qboolean fail; - qbyte need[256]; - qbyte ctx[1]; + qbyte need[DIGEST_MAXSIZE]; char *fname; + qbyte ctx[1]; } hashfile_t; static int QDECL SHA1File_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite) { @@ -2642,13 +2860,16 @@ static vfsfile_t *FS_Sha1_ValidateWrites(vfsfile_t *f, const char *fname, qofs_t n->needsize = needsize; Base16_DecodeBlock(hash, n->need, sizeof(n->need)); n->fail = false; - f = &n->pub; n->hashfunc->init(&n->ctx); + + f = &n->pub; } return f; } +//function that returns true if the package doesn't look exploity. +//so either its a versioned package, or its got a trusted signature. static qboolean PM_SignatureOkay(package_t *p) { struct packagedep_s *dep; @@ -2672,7 +2893,7 @@ static qboolean PM_SignatureOkay(package_t *p) continue; COM_FileExtension(dep->name, ext, sizeof(ext)); - if ((!stricmp(ext, "pak") || !stricmp(ext, "pk3")) && p->qhash) + if ((!stricmp(ext, "pak") || !stricmp(ext, "pk3") || !stricmp(ext, "zip")) && (p->qhash || (p->flags&DPF_MANIFEST))) ; else return false; @@ -2736,6 +2957,7 @@ static void PM_StartADownload(void) { vfsfile_t *tmpfile; char *temp; + enum fs_relative temproot; package_t *p; const int simultaneous = PM_IsApplying(true)?1:2; int i; @@ -2745,8 +2967,7 @@ static void PM_StartADownload(void) { if (p->download) downloading = true; - - if (p->trymirrors) + else if (p->trymirrors) { //flagged for a (re?)download char *mirror = NULL; for (i = 0; i < countof(p->mirror); i++) @@ -2770,7 +2991,8 @@ static void PM_StartADownload(void) //just directly install it. //FIXME: make sure there's no files... p->flags &= ~(DPF_NATIVE|DPF_CACHED|DPF_CORRUPT); - p->flags |= DPF_ENABLED; + if (p->flags & DPF_MARKED) + p->flags |= DPF_ENABLED; Con_Printf("Enabled meta package %s\n", p->name); PM_WriteInstalledPackages(); @@ -2782,7 +3004,8 @@ static void PM_StartADownload(void) if ((p->flags & DPF_PRESENT) && !PM_PurgeOnDisable(p)) { //its in our cache directory, so lets just use that p->trymirrors = 0; - p->flags |= DPF_ENABLED; + if (p->flags & DPF_MARKED) + p->flags |= DPF_ENABLED; Con_Printf("Enabled cached package %s\n", p->name); PM_WriteInstalledPackages(); @@ -2797,21 +3020,23 @@ static void PM_StartADownload(void) } temp = PM_GetTempName(p); + temproot = p->fsroot; //FIXME: we should lock in the temp path, in case the user foolishly tries to change gamedirs. - FS_CreatePath(temp, p->fsroot); + FS_CreatePath(temp, temproot); switch (p->extract) { case EXTRACT_ZIP: + case EXTRACT_EXPLICITZIP: case EXTRACT_COPY: - tmpfile = FS_OpenVFS(temp, "wb", p->fsroot); + tmpfile = FS_OpenVFS(temp, "wb", temproot); break; #ifdef AVAIL_XZDEC case EXTRACT_XZ: { vfsfile_t *raw; - raw = FS_OpenVFS(temp, "wb", p->fsroot); + raw = FS_OpenVFS(temp, "wb", temproot); tmpfile = FS_XZ_DecompressWriteFilter(raw); if (!tmpfile) VFS_CLOSE(raw); @@ -2822,7 +3047,7 @@ static void PM_StartADownload(void) case EXTRACT_GZ: { vfsfile_t *raw; - raw = FS_OpenVFS(temp, "wb", p->fsroot); + raw = FS_OpenVFS(temp, "wb", temproot); tmpfile = FS_GZ_WriteFilter(raw, true, false); if (!tmpfile) VFS_CLOSE(raw); @@ -2848,7 +3073,7 @@ static void PM_StartADownload(void) else { char syspath[MAX_OSPATH]; - FS_NativePath(temp, p->fsroot, syspath, sizeof(syspath)); + FS_NativePath(temp, temproot, syspath, sizeof(syspath)); Con_Printf("Unable to write %s. Fix permissions before trying to download %s\n", syspath, p->name); } if (p->download) @@ -2856,6 +3081,7 @@ static void PM_StartADownload(void) Con_Printf("Downloading %s\n", p->name); p->download->file = tmpfile; p->download->user_ctx = temp; + p->download->user_num = temproot; DL_CreateThread(p->download, NULL, NULL); downloading = true; @@ -2865,7 +3091,7 @@ static void PM_StartADownload(void) p->flags &= ~DPF_MARKED; //can't do it. if (tmpfile) VFS_CLOSE(tmpfile); - FS_Remove(temp, p->fsroot); + FS_Remove(temp, temproot); } } } @@ -2930,7 +3156,7 @@ void PM_ApplyChanges(void) if (*p->gamedir) { char *f = va("%s/%s", p->gamedir, dep->name); - if (p->qhash && FS_GenCachedPakName(f, p->qhash, temp, sizeof(temp)) && PM_CheckFile(temp, p->fsroot)) + if (PM_TryGenCachedName(f, p, temp, sizeof(temp)) && PM_CheckFile(temp, p->fsroot)) { if (!FS_Remove(temp, p->fsroot)) p->flags |= DPF_CACHED; @@ -2952,7 +3178,7 @@ void PM_ApplyChanges(void) if (*p->gamedir) { char *f = va("%s/%s", p->gamedir, dep->name); - if ((p->flags & DPF_NATIVE) && p->qhash && FS_GenCachedPakName(f, p->qhash, temp, sizeof(temp))) + if ((p->flags & DPF_NATIVE) && PM_TryGenCachedName(f, p, temp, sizeof(temp))) FS_Rename(f, temp, p->fsroot); } } @@ -3014,7 +3240,7 @@ void PM_ApplyChanges(void) //and flag any new/updated ones for a download for (p = availablepackages; p ; p=p->next) { - if ((p->flags&DPF_MARKED) && !(p->flags&DPF_ENABLED) && !p->download) + if ((p->flags&DPF_ALLMARKED) && !(p->flags&DPF_ENABLED) && !p->download) p->trymirrors = ~0u; } PM_StartADownload(); //and try to do those downloads. @@ -3127,21 +3353,21 @@ static qboolean PM_DeclinedPackages(char *out, size_t outsize) PM_WriteInstalledPackages(); return ret; } -static void PM_PromptApplyChanges_Callback(void *ctx, int opt) +static void PM_PromptApplyChanges_Callback(void *ctx, promptbutton_t opt) { #ifdef WEBCLIENT pkg_updating = false; #endif - if (opt == 0) + if (opt == PROMPT_YES) PM_ApplyChanges(); } static void PM_PromptApplyChanges(void); -static void PM_PromptApplyDecline_Callback(void *ctx, int opt) +static void PM_PromptApplyDecline_Callback(void *ctx, promptbutton_t opt) { #ifdef WEBCLIENT pkg_updating = false; #endif - if (opt == 1) + if (opt == PROMPT_NO) { PM_DeclinedPackages(NULL, 0); PM_PromptApplyChanges(); @@ -3186,7 +3412,7 @@ void PM_ManifestPackage(const char *metaname, int security) { domanifestinstall = security; Z_Free(manifestpackages); - if (metaname && security) + if (metaname) { manifestpackages = Z_StrDup(metaname); // PM_UpdatePackageList(false, false); @@ -3208,11 +3434,31 @@ qboolean PM_CanInstall(const char *packagename) return false; } +static int QDECL sortpackages(const void *l, const void *r) +{ + const package_t *a=*(package_t*const*)l, *b=*(package_t*const*)r; + const char *ac, *bc; + int order; + + //sort by categories + ac = a->category?a->category:""; + bc = b->category?b->category:""; + order = strcmp(ac,bc); + if (order) + return order; + + //otherwise sort by title. + ac = a->title?a->title:a->name; + bc = b->title?b->title:b->name; + order = strcmp(ac,bc); + return order; +} void PM_Command_f(void) { package_t *p; const char *act = Cmd_Argv(1); const char *key; + qboolean quiet = false; if (Cmd_FromGamecode()) { @@ -3220,6 +3466,12 @@ void PM_Command_f(void) return; } + if (!strncmp(act, "quiet_", 6)) + { + quiet = true; //don't spam so much. for menus to (ab)use. + act += 6; + } + if (!strcmp(act, "sources") || !strcmp(act, "addsource")) { #ifdef WEBCLIENT @@ -3247,242 +3499,298 @@ void PM_Command_f(void) else { - if (!loadedinstalled) - PM_UpdatePackageList(false, false); + if (!loadedinstalled) + PM_UpdatePackageList(false, false); - if (!strcmp(act, "list")) - { - for (p = availablepackages; p; p=p->next) + if (!strcmp(act, "list")) { - char quoted[8192]; - const char *status; - char *markup; - if ((p->flags & DPF_HIDDEN) && !(p->flags & (DPF_MARKED|DPF_ENABLED|DPF_PURGE|DPF_CACHED))) - continue; - if (p->flags & DPF_ENABLED) - markup = S_COLOR_GREEN; - else if (p->flags & DPF_CORRUPT) - markup = S_COLOR_RED; - else if (p->flags & (DPF_CACHED)) - markup = S_COLOR_YELLOW; //downloaded but not active - else - markup = S_COLOR_WHITE; - - if (!(p->flags & DPF_MARKED) != !(p->flags & DPF_ENABLED) || (p->flags & DPF_PURGE)) + int i, count; + package_t **sorted; + const char *category = "", *newcat; + for (count = 0, p = availablepackages; p; p=p->next) + count++; + sorted = Z_Malloc(sizeof(*sorted)*count); + for (count = 0, p = availablepackages; p; p=p->next) { + if ((p->flags & DPF_HIDDEN) && !(p->flags & (DPF_MARKED|DPF_ENABLED|DPF_PURGE|DPF_CACHED))) + continue; + sorted[count++] = p; + } + qsort(sorted, count, sizeof(*sorted), sortpackages); + for (i = 0; i < count; i++) + { + char quoted[8192]; + const char *status; + char *markup; + p = sorted[i]; + + if (p->flags & DPF_ENABLED) + markup = S_COLOR_GREEN; + else if (p->flags & DPF_CORRUPT) + markup = S_COLOR_RED; + else if (p->flags & (DPF_CACHED)) + markup = S_COLOR_YELLOW; //downloaded but not active + else + markup = S_COLOR_WHITE; + + if (!(p->flags & DPF_MARKED) != !(p->flags & DPF_ENABLED) || (p->flags & DPF_PURGE)) + { + if (p->flags & DPF_MARKED) + { + if (p->flags & DPF_PURGE) + status = S_COLOR_CYAN""; + else + status = S_COLOR_CYAN""; + } + else if ((p->flags & DPF_PURGE) || !(p->qhash && (p->flags & DPF_CACHED))) + status = S_COLOR_CYAN""; + else + status = S_COLOR_CYAN""; + } + else if ((p->flags & (DPF_ENABLED|DPF_CACHED)) == DPF_CACHED) + status = S_COLOR_CYAN""; + else if (p->flags & DPF_USERMARKED) + status = S_COLOR_GRAY""; + else if (p->flags & DPF_AUTOMARKED) + status = S_COLOR_GRAY""; + else + status = ""; + + //show category banner + newcat = p->category?p->category:""; + if (strcmp(category, newcat)) + { + category = newcat; + Con_Printf("%s\n", category); + } + + //show quick status display + if (p->flags & DPF_ENABLED) + Con_Printf("^&02 "); + else if (p->flags & DPF_PRESENT) + Con_Printf("^&0E "); + else + Con_Printf("^&04 "); + if (p->flags & DPF_MARKED) + Con_Printf("^&02 "); + else if (!(p->flags & DPF_PURGE) && (p->flags&DPF_PRESENT)) + Con_Printf("^&0E "); + else + Con_Printf("^&04 "); + + //show the package details. + Con_Printf("\t^["S_COLOR_GRAY"%s%s%s%s^] %s"S_COLOR_GRAY" %s (%s%s)", markup, p->name, p->arch?":":"", p->arch?p->arch:"", status, strcmp(p->name, p->title)?p->title:"", p->version, (p->flags&DPF_TESTING)?"-testing":""); + + if (!(p->flags&DPF_MARKED) && p == PM_FindPackage(p->name)) + Con_Printf(" ^[[Add]\\type\\pkg add %s;pkg apply^]", COM_QuotedString(p->name, quoted, sizeof(quoted), false)); + if ((p->flags&DPF_MARKED) && p == PM_MarkedPackage(p->name, DPF_MARKED)) + Con_Printf(" ^[[Remove]\\type\\pkg rem %s;pkg apply^]", COM_QuotedString(p->name, quoted, sizeof(quoted), false)); + Con_Printf("\n"); + } + Z_Free(sorted); + Con_Printf("\n"); + } + else if (!strcmp(act, "show")) + { + key = Cmd_Argv(2); + p = PM_FindPackage(key); + if (p) + { + if (p->previewimage) + Con_Printf("^[%s (%s)\\tipimg\\%s\\tip\\%s^]\n", p->name, p->version, p->previewimage, ""); + else + Con_Printf("%s (%s)\n", p->name, p->version); + if (p->title) + Con_Printf(" title: %s\n", p->title); + if (p->license) + Con_Printf(" license: %s\n", p->license); + if (p->author) + Con_Printf(" author: %s\n", p->author); + if (p->website) + Con_Printf(" website: %s\n", p->website); + if (p->description) + Con_Printf("%s\n", p->description); + if (p->flags & DPF_MARKED) { - if (p->flags & DPF_PURGE) - status = S_COLOR_CYAN""; + if (p->flags & DPF_ENABLED) + { + if (p->flags & DPF_PURGE) + Con_Printf(" package is flagged to be re-installed\n"); + else + Con_Printf(" package is currently installed\n"); + } else - status = S_COLOR_CYAN""; + Con_Printf(" package is flagged to be installed\n"); } - else if ((p->flags & DPF_PURGE) || !(p->qhash && (p->flags & DPF_CACHED))) - status = S_COLOR_CYAN""; else - status = S_COLOR_CYAN""; - } - else if ((p->flags & (DPF_ENABLED|DPF_CACHED)) == DPF_CACHED) - status = S_COLOR_CYAN""; - else if (p->flags & DPF_USERMARKED) - status = S_COLOR_GRAY""; - else if (p->flags & DPF_AUTOMARKED) - status = S_COLOR_GRAY""; - else - status = ""; - - Con_Printf(" ^["S_COLOR_GRAY"%s%s%s%s%s^] %s"S_COLOR_GRAY" %s (%s%s)", p->category?p->category:"", markup, p->name, p->arch?":":"", p->arch?p->arch:"", status, strcmp(p->name, p->title)?p->title:"", p->version, (p->flags&DPF_TESTING)?"-testing":""); - - if (!(p->flags&DPF_MARKED) && p == PM_FindPackage(p->name)) - Con_Printf(" ^[[Add]\\type\\pkg add %s;pkg apply^]", COM_QuotedString(p->name, quoted, sizeof(quoted), false)); - if ((p->flags&DPF_MARKED) && p == PM_MarkedPackage(p->name, DPF_MARKED)) - Con_Printf(" ^[[Remove]\\type\\pkg rem %s;pkg apply^]", COM_QuotedString(p->name, quoted, sizeof(quoted), false)); - Con_Printf("\n"); - } - Con_Printf("\n"); - } - else if (!strcmp(act, "show")) - { - key = Cmd_Argv(2); - p = PM_FindPackage(key); - if (p) - { - if (p->previewimage) - Con_Printf("^[%s (%s)\\tipimg\\%s\\tip\\%s^]\n", p->name, p->version, p->previewimage, ""); - else - Con_Printf("%s (%s)\n", p->name, p->version); - if (p->title) - Con_Printf(" title: %s\n", p->title); - if (p->license) - Con_Printf(" license: %s\n", p->license); - if (p->author) - Con_Printf(" author: %s\n", p->author); - if (p->website) - Con_Printf(" website: %s\n", p->website); - if (p->description) - Con_Printf("%s\n", p->description); - - if (p->flags & DPF_MARKED) - { - if (p->flags & DPF_ENABLED) { - if (p->flags & DPF_PURGE) - Con_Printf(" package is flagged to be re-installed\n"); + if (p->flags & DPF_ENABLED) + { + if (p->flags & DPF_PURGE) + Con_Printf(" package is flagged to be purged\n"); + else + Con_Printf(" package is flagged to be disabled\n"); + } else - Con_Printf(" package is currently installed\n"); + Con_Printf(" package is not installed\n"); } - else - Con_Printf(" package is flagged to be installed\n"); + if (p->flags & DPF_NATIVE) + Con_Printf(" package is native\n"); + if (p->flags & DPF_CACHED) + Con_Printf(" package is cached\n"); + if (p->flags & DPF_CORRUPT) + Con_Printf(" package is corrupt\n"); + if (p->flags & DPF_DISPLAYVERSION) + Con_Printf(" package has a version conflict\n"); + if (p->flags & DPF_FORGETONUNINSTALL) + Con_Printf(" package is obsolete\n"); + if (p->flags & DPF_HIDDEN) + Con_Printf(" package is hidden\n"); + if (p->flags & DPF_ENGINE) + Con_Printf(" package is an engine update\n"); + if (p->flags & DPF_TESTING) + Con_Printf(" package is untested\n"); + return; } - else - { - if (p->flags & DPF_ENABLED) - { - if (p->flags & DPF_PURGE) - Con_Printf(" package is flagged to be purged\n"); - else - Con_Printf(" package is flagged to be disabled\n"); - } - else - Con_Printf(" package is not installed\n"); - } - if (p->flags & DPF_NATIVE) - Con_Printf(" package is native\n"); - if (p->flags & DPF_CACHED) - Con_Printf(" package is cached\n"); - if (p->flags & DPF_CORRUPT) - Con_Printf(" package is corrupt\n"); - if (p->flags & DPF_DISPLAYVERSION) - Con_Printf(" package has a version conflict\n"); - if (p->flags & DPF_FORGETONUNINSTALL) - Con_Printf(" package is obsolete\n"); - if (p->flags & DPF_HIDDEN) - Con_Printf(" package is hidden\n"); - if (p->flags & DPF_ENGINE) - Con_Printf(" package is an engine update\n"); - if (p->flags & DPF_TESTING) - Con_Printf(" package is untested\n"); - return; + Con_Printf("\n"); } - Con_Printf("\n"); - } - else if (!strcmp(act, "search") || !strcmp(act, "find")) - { - const char *key = Cmd_Argv(2); - for (p = availablepackages; p; p=p->next) + else if (!strcmp(act, "search") || !strcmp(act, "find")) { - if (Q_strcasestr(p->name, key) || (p->title && Q_strcasestr(p->title, key)) || (p->description && Q_strcasestr(p->description, key))) + const char *key = Cmd_Argv(2); + for (p = availablepackages; p; p=p->next) { - Con_Printf("%s\n", p->name); + if (Q_strcasestr(p->name, key) || (p->title && Q_strcasestr(p->title, key)) || (p->description && Q_strcasestr(p->description, key))) + { + Con_Printf("%s\n", p->name); + } } + Con_Printf("\n"); + } + else if (!strcmp(act, "apply")) + { + Con_Printf("Applying package changes\n"); + if (qrenderer != QR_NONE) + PM_PromptApplyChanges(); + else if (Cmd_ExecLevel == RESTRICT_LOCAL) + PM_ApplyChanges(); + } + else if (!strcmp(act, "changes")) + { + PM_PrintChanges(); + } + else if (!strcmp(act, "reset") || !strcmp(act, "revert")) + { + PM_RevertChanges(); } - Con_Printf("\n"); - } - else if (!strcmp(act, "apply")) - { - Con_Printf("Applying package changes\n"); - if (qrenderer != QR_NONE) - PM_PromptApplyChanges(); - else if (Cmd_ExecLevel == RESTRICT_LOCAL) - PM_ApplyChanges(); - } - else if (!strcmp(act, "changes")) - { - PM_PrintChanges(); - } - else if (!strcmp(act, "reset") || !strcmp(act, "revert")) - { - PM_RevertChanges(); - } #ifdef WEBCLIENT - else if (!strcmp(act, "update")) - { //flush package cache, make a new request. - int i; - for (i = 0; i < numdownloadablelists; i++) - downloadablelist[i].received = 0; - } - else if (!strcmp(act, "upgrade")) - { //auto-mark any updated packages. - unsigned int changes = PM_MarkUpdates(); - if (changes) + else if (!strcmp(act, "update")) + { //query the servers if we've not already done so. + //FIXME: regrab if more than an hour ago? + if (!allowphonehome) + allowphonehome = -1; //trigger a prompt, instead of ignoring it. + PM_UpdatePackageList(false, 0); + } + else if (!strcmp(act, "refresh")) + { //flush package cache, make a new request even if we already got a response from the server. + int i; + for (i = 0; i < numdownloadablelists; i++) + downloadablelist[i].received = 0; + if (!allowphonehome) + allowphonehome = -1; //trigger a prompt, instead of ignoring it. + PM_UpdatePackageList(false, 0); + } + else if (!strcmp(act, "upgrade")) + { //auto-mark any updated packages. + unsigned int changes = PM_MarkUpdates(); + if (changes) + { + if (!quiet) + Con_Printf("%u packages flagged\n", changes); + PM_PromptApplyChanges(); + } + else if (!quiet) + Con_Printf("Already using latest versions of all packages\n"); + } +#endif + else if (!strcmp(act, "add") || !strcmp(act, "get") || !strcmp(act, "install") || !strcmp(act, "enable")) + { //FIXME: make sure this updates. + int arg = 2; + for (arg = 2; arg < Cmd_Argc(); arg++) + { + const char *key = Cmd_Argv(arg); + p = PM_FindPackage(key); + if (p) + { + PM_MarkPackage(p, DPF_USERMARKED); + p->flags &= ~DPF_PURGE; + } + else + Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); + } + if (!quiet) + PM_PrintChanges(); + } +#ifdef WEBCLIENT + else if (!strcmp(act, "reinstall")) + { //fixme: favour the current verson. + int arg = 2; + for (arg = 2; arg < Cmd_Argc(); arg++) + { + const char *key = Cmd_Argv(arg); + p = PM_FindPackage(key); + if (p) + { + PM_MarkPackage(p, DPF_USERMARKED); + p->flags |= DPF_PURGE; + } + else + Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); + } + if (!quiet) + PM_PrintChanges(); + } +#endif + else if (!strcmp(act, "disable") || !strcmp(act, "rem") || !strcmp(act, "remove")) { - Con_Printf("%u packages flagged\n", changes); - PM_PromptApplyChanges(); + int arg = 2; + for (arg = 2; arg < Cmd_Argc(); arg++) + { + const char *key = Cmd_Argv(arg); + p = PM_MarkedPackage(key, DPF_MARKED); + if (!p) + p = PM_FindPackage(key); + if (p) + PM_UnmarkPackage(p, DPF_MARKED); + else + Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); + } + if (!quiet) + PM_PrintChanges(); + } + else if (!strcmp(act, "del") || !strcmp(act, "purge") || !strcmp(act, "delete") || !strcmp(act, "uninstall")) + { + int arg = 2; + for (arg = 2; arg < Cmd_Argc(); arg++) + { + const char *key = Cmd_Argv(arg); + p = PM_MarkedPackage(key, DPF_MARKED); + if (!p) + p = PM_FindPackage(key); + if (p) + { + PM_UnmarkPackage(p, DPF_MARKED); + if (p->flags & (DPF_PRESENT|DPF_CORRUPT)) + p->flags |= DPF_PURGE; + } + else + Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); + } + if (!quiet) + PM_PrintChanges(); } else - Con_Printf("Already using latest versions of all packages\n"); - } -#endif - else if (!strcmp(act, "add") || !strcmp(act, "get") || !strcmp(act, "install") || !strcmp(act, "enable")) - { //FIXME: make sure this updates. - int arg = 2; - for (arg = 2; arg < Cmd_Argc(); arg++) - { - const char *key = Cmd_Argv(arg); - p = PM_FindPackage(key); - if (p) - PM_MarkPackage(p, DPF_USERMARKED); - else - Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); - } - PM_PrintChanges(); - } -#ifdef WEBCLIENT - else if (!strcmp(act, "reinstall")) - { //fixme: favour the current verson. - int arg = 2; - for (arg = 2; arg < Cmd_Argc(); arg++) - { - const char *key = Cmd_Argv(arg); - p = PM_FindPackage(key); - if (p) - { - PM_MarkPackage(p, DPF_USERMARKED); - p->flags |= DPF_PURGE; - } - else - Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); - } - PM_PrintChanges(); - } -#endif - else if (!strcmp(act, "disable") || !strcmp(act, "rem") || !strcmp(act, "remove")) - { - int arg = 2; - for (arg = 2; arg < Cmd_Argc(); arg++) - { - const char *key = Cmd_Argv(arg); - p = PM_MarkedPackage(key, DPF_MARKED); - if (!p) - p = PM_FindPackage(key); - if (p) - PM_UnmarkPackage(p, DPF_MARKED); - else - Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); - } - PM_PrintChanges(); - } - else if (!strcmp(act, "del") || !strcmp(act, "purge") || !strcmp(act, "delete") || !strcmp(act, "uninstall")) - { - int arg = 2; - for (arg = 2; arg < Cmd_Argc(); arg++) - { - const char *key = Cmd_Argv(arg); - p = PM_MarkedPackage(key, DPF_MARKED); - if (!p) - p = PM_FindPackage(key); - if (p) - { - PM_UnmarkPackage(p, DPF_MARKED); - p->flags |= DPF_PURGE; - } - else - Con_Printf("%s: package %s not known\n", Cmd_Argv(0), key); - } - PM_PrintChanges(); - } - else - Con_Printf("%s: Unknown action %s\nShould be one of list, show, search, upgrade, revert, add, rem, del, changes, apply, sources, addsource, remsource\n", Cmd_Argv(0), act); + Con_Printf("%s: Unknown action %s\nShould be one of list, show, search, upgrade, revert, add, rem, del, changes, apply, sources, addsource, remsource\n", Cmd_Argv(0), act); } } @@ -3527,6 +3835,262 @@ qboolean PM_FindUpdatedEngine(char *syspath, size_t syspathsize) return false; } + + + + + +//called by the filesystem code to make sure needed packages are in the updates system +static const char *FS_RelativeURL(const char *base, const char *file, char *buffer, int bufferlen) +{ + //fixme: cope with windows paths + qboolean baseisurl = base?!!strchr(base, ':'):false; + qboolean fileisurl = !!strchr(file, ':'); + //qboolean baseisabsolute = (*base == '/' || *base == '\\'); + qboolean fileisabsolute = (*file == '/' || *file == '\\'); + const char *ebase; + + if (fileisurl || !base) + return file; + if (fileisabsolute) + { + if (baseisurl) + { + ebase = strchr(base, ':'); + ebase++; + while(*ebase == '/') + ebase++; + while(*ebase && *ebase != '/') + ebase++; + } + else + ebase = base; + } + else + ebase = COM_SkipPath(base); + memcpy(buffer, base, ebase-base); + strcpy(buffer+(ebase-base), file); + + return buffer; +} + +//this function is called by the filesystem code to start downloading the packages listed by each manifest. +void PM_AddManifestPackages(ftemanifest_t *man) +{ + package_t *p, *m; + size_t i; + const char *path; + + char buffer[MAX_OSPATH], *url; + int idx; + struct manpack_s *pack; + const char *baseurl = man->updateurl; + + for (p = availablepackages; p; p = p->next) + p->flags &= ~DPF_MANIMARKED; + + PM_RevertChanges(); + + for (idx = 0; idx < countof(man->package); idx++) + { + pack = &man->package[idx]; + if (!pack->type) + continue; + + //check this package's conditional + if (pack->condition) + { + if (!If_EvaluateBoolean(pack->condition, RESTRICT_LOCAL)) + continue; //ignore it + } + + p = Z_Malloc(sizeof(*p)); + p->name = Z_StrDup(pack->path); + p->title = Z_StrDup(pack->path); + p->category = Z_StrDup(va("%s/", man->formalname)); + p->priority = PM_DEFAULTPRIORITY; + p->fsroot = FS_ROOT; + strcpy(p->version, ""); + p->flags = DPF_FORGETONUNINSTALL|DPF_MANIFEST|DPF_GUESSED; + p->qhash = pack->crcknown?Z_StrDup(va("%#x", pack->crc)):NULL; + + { + char *c = p->name; + for (c=p->name; *c; c++) //don't get confused. + if (*c == '/') *c = '_'; + } + + path = pack->path; + if (pack->type != mdt_installation) + { + char *s = strchr(path, '/'); + if (!s) + { + PM_FreePackage(p); + continue; + } + *s = 0; + Q_strncpyz(p->gamedir, path, sizeof(p->gamedir)); + *s = '/'; + path=s+1; + } + + p->extract = EXTRACT_COPY; + for (i = 0; i < countof(pack->mirrors) && i < countof(p->mirror); i++) + if (pack->mirrors[i]) + { + if (pack->mirrors[i]) + { + url = pack->mirrors[i]; + if (!strncmp(url, "gz:", 3)) + { + url+=3; + p->extract = EXTRACT_GZ; + } + else if (!strncmp(url, "xz:", 3)) + { + url+=3; + p->extract = EXTRACT_XZ; + } + else if (!strncmp(url, "unzip:", 6)) + { + char *comma; + url+=6; + comma = strchr(url, ','); + if (comma) + { + p->extract = EXTRACT_EXPLICITZIP; + *comma = 0; + PM_AddDep(p, DEP_EXTRACTNAME, url); + *comma = ','; + url = comma+1; + } + else + p->extract = EXTRACT_ZIP; + } + /*else if (!strncmp(url, "prompt:", 7)) + { + url+=7; + fspdl_extracttype = X_COPY; + }*/ + + p->mirror[i] = Z_StrDup(FS_RelativeURL(baseurl, url, buffer, sizeof(buffer))); + } + } + PM_AddDep(p, DEP_FILE, path); + + m = PM_InsertPackage(p); + if (!m) + continue; + + PM_MarkPackage(m, DPF_MANIMARKED); + +/* //okay, so we merged it into m. I guess we already had a copy! + if (!(m->flags & DPF_PRESENT)) + if (PM_SignatureOkay(m)) + m->trymirrors = ~0; //FIXME: we should probably mark+prompt... +*/ + continue; + } + + PM_ApplyChanges(); +} + +#ifdef HAVE_CLIENT +#include "pr_common.h" +void QCBUILTIN PF_cl_getpackagemanagerinfo(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + int packageidx = G_INT(OFS_PARM0); + enum packagemanagerinfo_e fieldidx = G_INT(OFS_PARM1); + package_t *p; + G_INT(OFS_RETURN) = 0; + for (p = availablepackages; p; p = p->next) + { + if ((p->flags & DPF_HIDDEN) && !(p->flags & (DPF_MARKED|DPF_ENABLED|DPF_PURGE|DPF_CACHED))) + continue; + if (packageidx--) + continue; + switch(fieldidx) + { + case GPMI_NAME: + if (p->arch) + RETURN_TSTRING(va("%s:%s=%s", p->name, p->arch, p->version)); + else + RETURN_TSTRING(va("%s=%s", p->name, p->version)); + break; + case GPMI_CATEGORY: + RETURN_TSTRING(p->category); + break; + case GPMI_TITLE: + if (p->flags & DPF_DISPLAYVERSION) + RETURN_TSTRING(va("%s (%s)", p->title, p->version)); + else + RETURN_TSTRING(p->title); + break; + case GPMI_VERSION: + RETURN_TSTRING(p->version); + break; + case GPMI_DESCRIPTION: + RETURN_TSTRING(p->description); + break; + case GPMI_LICENSE: + RETURN_TSTRING(p->license); + break; + case GPMI_AUTHOR: + RETURN_TSTRING(p->author); + break; + case GPMI_WEBSITE: + RETURN_TSTRING(p->website); + break; + case GPMI_FILESIZE: + if (p->filesize) + RETURN_TSTRING(va("%lu", (unsigned long)p->filesize)); + break; + case GPMI_AVAILABLE: + if (PM_SignatureOkay(p)) + RETURN_TSTRING("1"); + break; + + case GPMI_INSTALLED: + if (p->flags & DPF_CORRUPT) + RETURN_TSTRING("corrupt"); //some sort of error + else if (p->flags & DPF_ENABLED) + RETURN_TSTRING("enabled"); //its there (and in use) + else if (p->flags & DPF_PRESENT) + RETURN_TSTRING("present"); //its there (but ignored) + else if (p->download) + RETURN_TSTRING(va("%i%%", (int)p->download->qdownload.percent)); //we're downloading it. + else if (p->trymirrors) + RETURN_TSTRING("pending"); //its queued. + break; + case GPMI_GAMEDIR: + RETURN_TSTRING(p->gamedir); + break; + + case GPMI_ACTION: + if (p->flags & DPF_PURGE) + { + if (p->flags & DPF_MARKED) + RETURN_TSTRING("reinstall"); //user wants to install it + else + RETURN_TSTRING("purge"); //wiping it out + } + else if (p->flags & DPF_USERMARKED) + RETURN_TSTRING("user"); //user wants to install it + else if (p->flags & (DPF_AUTOMARKED|DPF_MANIMARKED)) + RETURN_TSTRING("auto"); //enabled to satisfy a dependancy + else if (p->flags & DPF_ENABLED) + RETURN_TSTRING("disable"); //change from enabled to cached. + else if (p->flags & DPF_PRESENT) + RETURN_TSTRING("retain"); //keep it in cache + //else not installed and don't want it. + break; + } + return; + } +} +#endif + #else qboolean PM_CanInstall(const char *packagename) { @@ -3597,8 +4161,9 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct emenu_s *m) Draw_FunStringWidth (x, y, "?""?""?", 48, 2, false); else { - Draw_FunStringWidth (x, y, "^Ue080^Ue082", 48, 2, false); - Draw_FunStringWidth (x, y, "^Ue083", 48, 2, false); + Draw_FunStringWidth (x, y, "^&02 ", 48, 2, false); //green +// Draw_FunStringWidth (x, y, "^Ue080^Ue082", 48, 2, false); +// Draw_FunStringWidth (x, y, "^Ue083", 48, 2, false); } } } @@ -3621,8 +4186,9 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct emenu_s *m) Draw_FunStringWidth (x, y, "?""?""?", 48, 2, false); else { - Draw_FunStringWidth (x, y, "^Ue080^Ue082", 48, 2, false); - Draw_FunStringWidth (x, y, "^Ue083", 48, 2, false); + Draw_FunStringWidth (x, y, "^&02 ", 48, 2, false); //green +// Draw_FunStringWidth (x, y, "^Ue080^Ue082", 48, 2, false); +// Draw_FunStringWidth (x, y, "^Ue083", 48, 2, false); } } } @@ -3638,10 +4204,15 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct emenu_s *m) Draw_FunStringWidth (x, y, "!!!", 48, 2, false); else { - Draw_FunStringWidth (x, y, "^Ue080^Ue082", 48, 2, false); - Draw_FunStringWidth (x, y, "^Ue081", 48, 2, false); if (p->flags & DPF_PRESENT) - Draw_FunStringWidth (x, y, "-", 48, 2, false); + Draw_FunStringWidth (x, y, "^&0E ", 48, 2, false); //yellow + else + Draw_FunStringWidth (x, y, "^&04 ", 48, 2, false); //red + +// Draw_FunStringWidth (x, y, "^Ue080^Ue082", 48, 2, false); +// Draw_FunStringWidth (x, y, "^Ue081", 48, 2, false); +// if (p->flags & DPF_PRESENT) +// Draw_FunStringWidth (x, y, "-", 48, 2, false); } } else diff --git a/engine/client/m_items.c b/engine/client/m_items.c index f6fcdeef..86850663 100644 --- a/engine/client/m_items.c +++ b/engine/client/m_items.c @@ -3,25 +3,37 @@ #include "quakedef.h" #include "shader.h" -void Draw_TextBox (int x, int y, int width, int lines) +//draws the size specified, plus a little extra border (about 8 pixels in each direction, could be more though). +//size is in vpixels. +void Draw_ApproxTextBox (float x, float y, float width, float height) { mpic_t *p; - int cx, cy; - int n; + float cx, cy; + int n, lines, columns; + + x -= 8; + y -= 8; - // draw left side - cx = x; - cy = y; p = R2D_SafeCachePic ("gfx/box_tl.lmp"); - if (R_GetShaderSizes(p, NULL, NULL, false) != true) //assume none exist - { - R2D_ImageColours(0.0, 0.0, 0.0, 0.5); - R2D_FillBlock(x, y, width*8 + 16, 8 * (2 + lines)); + { //simple fill. + R2D_ImageColours(0.1, 0.1, 0.1, 0.9); + R2D_FillBlock(x, y, width + 16, height + 16); R2D_ImageColours(1.0, 1.0, 1.0, 1.0); return; } + //okay, we're drawing it with pics. + //expand the border to keep things centred. + lines = ceil(height/8); + y -= (lines*8-height)/2; + + columns = ceil(width/16)*2; //columns must be a multiple of 2. + x -= (columns*8-width)/2; + + // draw left side + cx = x; + cy = y; if (p) R2D_ScalePic (cx, cy, 8, 8, p); p = R2D_SafeCachePic ("gfx/box_ml.lmp"); @@ -37,7 +49,7 @@ void Draw_TextBox (int x, int y, int width, int lines) // draw middle cx += 8; - while (width > 0) + while (columns > 0) { cy = y; p = R2D_SafeCachePic ("gfx/box_tm.lmp"); @@ -55,7 +67,7 @@ void Draw_TextBox (int x, int y, int width, int lines) p = R2D_SafeCachePic ("gfx/box_bm.lmp"); if (p) R2D_ScalePic (cx, cy+8, 16, 8, p); - width -= 2; + columns -= 2; cx += 16; } @@ -469,8 +481,8 @@ static void MenuDrawItems(int xpos, int ypos, menuoption_t *option, emenu_t *men int framescroll = 0; if (option && option->common.type == mt_box && !option->common.ishidden) - { - Draw_TextBox(xpos+option->common.posx, ypos+option->common.posy, option->box.width, option->box.height); + { //FIXME: why is this here? why is this special? + Draw_ApproxTextBox(xpos+option->common.posx, ypos+option->common.posy, option->box.width, option->box.height); option = option->common.next; } @@ -600,7 +612,7 @@ static void MenuDrawItems(int xpos, int ypos, menuoption_t *option, emenu_t *men } break; case mt_box: - Draw_TextBox(xpos+option->common.posx, ypos+option->common.posy, option->box.width, option->box.height); + Draw_ApproxTextBox(xpos+option->common.posx, ypos+option->common.posy, option->box.width, option->box.height); break; case mt_slider: if (option->slider.var) @@ -687,7 +699,7 @@ static void MenuDrawItems(int xpos, int ypos, menuoption_t *option, emenu_t *men if (option->edit.slim) x += 8; // more space for cursor else - Draw_TextBox(x-8, y-8, 16, 1); + Draw_ApproxTextBox(x, y, 16*8, 8); Draw_FunString(x, y, option->edit.text); if (menu->selecteditem == option && (int)(realtime*4) & 1) @@ -779,7 +791,7 @@ static void MenuDraw(emenu_t *menu) // if (omousey > menu->ypos+option->common.posy && omousey < menu->ypos+option->common.posy+option->common.height) { int x = omousex+8; - int y = omousey; + int y = omousey+8; int w; int h; int l, lines; @@ -792,11 +804,11 @@ static void MenuDraw(emenu_t *menu) Font_EndString(font_default); //figure out how wide that makes the tip - w = 16; - h = (lines+2)*8; + w = 0; + h = lines*8; for (l = 0; l < lines; l++) { - int lw = 16+Font_LineWidth(line_start[l], line_end[l])*vid.width/vid.pixelwidth; + int lw = Font_LineWidth(line_start[l], line_end[l])*vid.width/vid.pixelwidth; if (w < lw) w = lw; } @@ -808,9 +820,7 @@ static void MenuDraw(emenu_t *menu) y -= h; // draw the background - Draw_TextBox(x, y, (w-16)/8, lines); - x += 8; - y += 8; + Draw_ApproxTextBox(x, y, w, lines*8); //draw the text Font_BeginString(font_default, x, y, &x, &y); @@ -2133,8 +2143,12 @@ static int M_Main_AddExtraOptions(emenu_t *mainm, int y) #ifdef WEBCLIENT MC_AddConsoleCommandQBigFont(mainm, 72, y, "Updates ", "menu_download\n"); y += 20; #else - MC_AddConsoleCommandQBigFont(mainm, 72, y, "Packages ", "menu_download\n"); + MC_AddConsoleCommandQBigFont(mainm, 72, y, "Packages ", "menu_download\n"); y += 20; #endif + } + if (Cmd_Exists("menu_mods")) + { + MC_AddConsoleCommandQBigFont(mainm, 72, y, "Mods ", "menu_mods\n"); y += 20; y += 20; } @@ -2253,6 +2267,7 @@ void M_Menu_Main_f (void) p = R2D_SafeCachePic("gfx/menu/title0.lmp"); if (R_GetShaderSizes(p, NULL, NULL, true) > 0) { + int y = 64; mainm = M_CreateMenu(0); mainm->key = MC_Main_Key; @@ -2260,29 +2275,40 @@ void M_Menu_Main_f (void) MC_AddCenterPicture(mainm, 0, 60, "gfx/menu/title0.lmp"); #ifndef CLIENTONLY - b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, 64, "Single Player", "menu_single\n"); + b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, y, "Single Player", "menu_single\n"); mainm->selecteditem = (menuoption_t *)b; b->common.width = 12*20; b->common.height = 20; + y += 20; #endif - b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, 64+20, "MultiPlayer", "menu_multi\n"); + b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, y, "MultiPlayer", "menu_multi\n"); #ifdef CLIENTONLY mainm->selecteditem = (menuoption_t *)b; #endif b->common.width = 12*20; b->common.height = 20; - b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, 64+40, "Options", "menu_options\n"); + y += 20; + b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, y, "Options", "menu_options\n"); b->common.width = 12*20; b->common.height = 20; + y += 20; if (m_helpismedia.value) - b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, 64+60, "Media", "menu_media\n"); + b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, y, "Media", "menu_media\n"); else - b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, 64+60, "Help", "help\n"); + b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, y, "Help", "help\n"); b->common.width = 12*20; b->common.height = 20; - b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, 64+80, "Quit", "menu_quit\n"); + y += 20; + + b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, y, "Mods", "menu_modshelp\n"); b->common.width = 12*20; b->common.height = 20; + y += 20; + + b=MC_AddConsoleCommandHexen2BigFont (mainm, 80, y, "Quit", "menu_quit\n"); + b->common.width = 12*20; + b->common.height = 20; + y += 20; mainm->cursoritem = (menuoption_t *)MC_AddCursor(mainm, &resel, 56, mainm->selecteditem->common.posy); } diff --git a/engine/client/m_master.c b/engine/client/m_master.c index 6d661228..f4cdd2b2 100644 --- a/engine/client/m_master.c +++ b/engine/client/m_master.c @@ -521,7 +521,7 @@ static void SL_PostDraw (emenu_t *menu) h += 4; h *= 8; - Draw_TextBox(vid.width/2 - w/2-12, vid.height/2 - h/2 - 8-8, w/8+1, h/8+1); + Draw_ApproxTextBox(vid.width/2.0f - w/2-4, vid.height/2.0f - h/2 - 8, w+8, h+8); lx = vid.width/2 - w/2; y = vid.height/2 - h/2 - 4; @@ -647,7 +647,7 @@ static void SL_PostDraw (emenu_t *menu) } else { - Draw_TextBox(vid.width/2 - 100-12, vid.height/2 - 32, 200/8, 64/8); + Draw_ApproxTextBox(vid.width/2 - 100, vid.height/2 - 16, 200, 16*3); Draw_FunStringWidth(vid.width/2 - 100, vid.height/2 - 8, "Querying server", 200, 2, false); Draw_FunStringWidth(vid.width/2 - 100, vid.height/2 + 0, "Please wait", 200, 2, false); } @@ -670,9 +670,8 @@ static void SL_PostDraw (emenu_t *menu) specbutton.width = bw + 16; specbutton.height = bh + 16; R2D_ImageColours(1,1,1,1); - Draw_TextBox(lx, y, bw/8, bh/8); y += 8; - lx += 8; + Draw_ApproxTextBox(lx, y, bw, bh); if (mousecursor_x >= specbutton.x && mousecursor_x < specbutton.x+specbutton.width) if (mousecursor_y >= specbutton.y && mousecursor_y < specbutton.y+specbutton.height) @@ -698,9 +697,9 @@ static void SL_PostDraw (emenu_t *menu) joinbutton.width = bw + 16; joinbutton.height = bh + 16; R2D_ImageColours(1,1,1,1); - Draw_TextBox(lx, y, bw/8, bh/8); y += 8; lx += 8; + Draw_ApproxTextBox(lx, y, bw, bh); if (mousecursor_x >= joinbutton.x && mousecursor_x < joinbutton.x+joinbutton.width) if (mousecursor_y >= joinbutton.y && mousecursor_y < joinbutton.y+joinbutton.height) @@ -712,7 +711,7 @@ static void SL_PostDraw (emenu_t *menu) else if (isrefreshing) { R2D_ImageColours(1,1,1,1); - Draw_TextBox(vid.width/2 - 100-12, vid.height/2 - 32, 200/8, 64/8); + Draw_ApproxTextBox(vid.width/2 - 100-4, vid.height/2 - 24, 200, 64); Draw_FunStringWidth(vid.width/2 - 100, vid.height/2 - 8, "Refreshing, please wait", 200, 2, false); Draw_FunStringWidth(vid.width/2 - 100, vid.height/2 + 0, va("%i of %i", Master_NumPolled(), Master_TotalCount()), 200, 2, false); } diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 21ca334a..d75dc1e9 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -895,6 +895,7 @@ const char *presetexec[] = "seta r_drawflame 0;" "seta r_waterstyle 1;" "seta r_lavastyle 1;" //defer to water + "seta r_fog_cullentities 2;" // "seta r_slimestyle \"\";" //defer to water "seta r_coronas 0;" "seta r_shadow_realtime_dlight 0;" @@ -907,6 +908,7 @@ const char *presetexec[] = "seta r_replacemodels \"\";" "seta r_waterwarp 0;" "seta r_lightstylesmooth 0;" + "seta r_lightstylespeed 0;" "seta r_part_density 0.25;" "seta cl_nolerp 1;" "seta r_lerpmuzzlehack 0;" @@ -957,6 +959,8 @@ const char *presetexec[] = "v_gunkick 1;" "cl_rollangle 2.0;" "cl_bob 0.02;" + "r_fog_cullentities 1;" + "r_lightstylespeed 10;" "vid_hardwaregamma 1;" //auto hardware gamma, for fast fullscreen and usable windowed. "r_part_classic_expgrav 1;" //vanillaery "r_part_classic_opaque 1;" @@ -1144,7 +1148,22 @@ void FPS_Preset_f (void) , RESTRICT_LOCAL, false); return; } - + if (!stricmp("shib", arg)) + { + Cbuf_InsertText( + "if r_dynamic >= 1\n" + "{\n" //fake it anyway. + "set r_shadow_realtime_dlight 1\n" + "set r_shadow_realtime_dlight_shadows 0\n" + "set r_dynamic 0\n" + "}\n" + "set r_temporalscenecache 1\n" //the main speedup. + "set r_lightstylespeed 0\n" //FIXME: we shouldn't need this, but its too stuttery without. + "set sv_autooffload 1\n" //Needs polish still. + "set gl_pbolightmaps 1\n" //FIXME: this needs to be the default eventually. + , RESTRICT_LOCAL, false); + return; + } if (!stricmp("qw", arg)) { //enable qwisms Cbuf_InsertText( @@ -1380,26 +1399,27 @@ void M_Menu_Render_f (void) { MB_REDTEXT("Rendering Options", true), MB_TEXT("^Ue080^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue082", true), - MB_CHECKBOXCVAR("Graphics", r_graphics, 0), //graphics on / off. Its a general dig at modern games not having any real options. - MB_CHECKBOXCVAR("Disable VIS", r_novis, 0), - MB_CHECKBOXCVAR("Fast Sky", r_fastsky, 0), + MB_CHECKBOXCVARTIP("Graphics", r_graphics, 0, "The only option console games have. Yes, this is a joke cvar. Try toggling it!"), //graphics on / off. Its a general dig at modern games not having any real options. + MB_CHECKBOXCVARTIP("Disable VIS", r_novis, 0, "You shouldn't normally set this. Its better to use vispatches for your maps in order to support transparent water properly."), + MB_CHECKBOXCVARTIP("Fast Sky", r_fastsky, 0, "Use black skies. On modern machines this is more of a stylistic choice that a perfomance helper."), + MB_CHECKBOXCVAR("Lerp Images", gl_lerpimages, 0), MB_CHECKBOXCVAR("Disable Model Lerp", r_nolerp, 0), MB_CHECKBOXCVAR("Disable Framegroup Lerp", r_noframegrouplerp, 0), - MB_CHECKBOXCVAR("Lerp Images", gl_lerpimages, 0), + MB_CHECKBOXCVAR("Model Bobbing", cl_item_bobbing, 0), MB_COMBOCVAR("Water Warp", r_waterwarp, warpopts, warpvalues, NULL), MB_SLIDER("Water Alpha", r_wateralpha, 0, 1, 0.1, NULL), MB_SLIDER("Viewmodel Alpha", r_drawviewmodel, 0, 1, 0.1, NULL), MB_COMBOCVAR("Screen Tints", gl_cshiftenabled, cshiftopts, cshiftvalues, "Changes how screen flashes should be displayed (otherwise known as polyblends)."), #ifdef QWSKINS - MB_CHECKBOXCVAR("Disable Colormap", gl_nocolors, 0), + MB_CHECKBOXCVAR("Ignore Player Colors", gl_nocolors, 0), #endif MB_COMBOCVAR("Log Centerprints", scr_logcenterprint, logcenteropts, logcentervalues, "Display centerprints in the console also."), MB_CHECKBOXCVAR("FXAA", r_fxaa, 0), #ifdef GLQUAKE MB_CHECKBOXCVAR("Bloom", r_bloom, 0), #endif - MB_CHECKBOXCVAR("HDR", r_hdr_irisadaptation, 0), - MB_CHECKBOXCVAR("Model Bobbing", cl_item_bobbing, 0), + MB_CHECKBOXCVARTIP("HDR", r_hdr_irisadaptation, 0, "Adjust scene brightness to compensate for lighting levels."), + MB_CHECKBOXCVARTIP("Temporal Scene Cache", r_temporalscenecache, 0, "Cache scene data to optimise complex scenes or unvised maps."), MB_END() }; menu = M_Options_Title(&y, 0); @@ -4057,23 +4077,14 @@ void M_Menu_ModelViewer_f(void) } #endif -typedef struct -{ - struct - { - ftemanifest_t *manifest; - char *gamedir; - } *mod; - size_t nummods; -} modmenu_t; - +#include "fs.h" static void Mods_Draw(int x, int y, struct menucustom_s *c, struct emenu_s *m) { - modmenu_t *mods = c->dptr; int i = c->dint; + struct modlist_s *mod = Mods_GetMod(i); c->common.width = vid.width - x - 16; - if (!mods->nummods && !i) + if (!mod && !i) { float scale[] = {8,8}; R_DrawTextField(0, y, vid.width, vid.height - y, @@ -4091,139 +4102,55 @@ static void Mods_Draw(int x, int y, struct menucustom_s *c, struct emenu_s *m) return; } - if (i < 0 || i > mods->nummods) + if (!mod) return; - if (mods->mod[i].manifest) + if (mod->manifest) { if (mousecursor_y >= y && mousecursor_y < y+8) - Draw_AltFunString(x, y, mods->mod[i].manifest->formalname); + Draw_AltFunString(x, y, mod->manifest->formalname); else - Draw_FunString(x, y, mods->mod[i].manifest->formalname); + Draw_FunString(x, y, mod->manifest->formalname); } else { if (mousecursor_y >= y && mousecursor_y < y+8) - Draw_AltFunString(x, y, mods->mod[i].gamedir); + Draw_AltFunString(x, y, mod->gamedir); else - Draw_FunString(x, y, mods->mod[i].gamedir); + Draw_FunString(x, y, mod->gamedir); } } static qboolean Mods_Key(struct menucustom_s *c, struct emenu_s *m, int key, unsigned int unicode) { - modmenu_t *mods = c->dptr; - int i; - ftemanifest_t *man; + int gameidx = c->dint; if (key == K_MOUSE1 || key == K_ENTER || key == K_GP_A) { qboolean wasgameless = !*FS_GetGamedir(false); - i = c->dint; - if (i < 0 || i > mods->nummods) + if (!Mods_GetMod(c->dint)) return false; - man = mods->mod[i].manifest; - mods->mod[i].manifest = NULL; //make sure the manifest survives the menu being closed. M_RemoveMenu(m); - FS_ChangeGame(man, true, true); + Cbuf_AddText(va("\nfs_changegame %u\n", gameidx+1), RESTRICT_LOCAL); if (wasgameless && !!*FS_GetGamedir(false)) { //starting to a blank state generally means that the current(engine-default) config settings are utterly useless and windowed by default. //so generally when switching to a *real* game, we want to restart video just so things like fullscreen etc are saved+used properly. Cbuf_AddText("\nvid_restart\n", RESTRICT_LOCAL); } - else - { - //if we're already running a game, this should probably just be a vid_reload instead to ensure that the conback etc is reloaded. - //(a full restart is annoying) - Cbuf_AddText("\nvid_reload\n", RESTRICT_LOCAL); - } return true; } return false; } -static void Mods_Remove (struct emenu_s *m) -{ - modmenu_t *mods = m->data; - int i; - - for (i = 0; i < mods->nummods; i++) - { - if (mods->mod[i].manifest) - FS_Manifest_Free(mods->mod[i].manifest); - Z_Free(mods->mod[i].gamedir); - } - Z_Free(mods->mod); - mods->mod = NULL; -} - -static qboolean Mods_AddManifest(void *usr, ftemanifest_t *man) -{ - modmenu_t *mods = usr; - int i = mods->nummods; - mods->mod = BZ_Realloc(mods->mod, (i+1) * sizeof(*mods->mod)); - mods->mod[i].manifest = man; - mods->mod[i].gamedir = NULL; - mods->nummods = i+1; - return true; -} -static int QDECL Mods_AddGamedir(const char *fname, qofs_t fsize, time_t mtime, void *usr, searchpathfuncs_t *spath) -{ - modmenu_t *mods = usr; - size_t l = strlen(fname); - int i, p; - char gamedir[MAX_QPATH]; - if (l && fname[l-1] == '/' && l < countof(gamedir)) - { - l--; - memcpy(gamedir, fname, l); - gamedir[l] = 0; - for (i = 0; i < mods->nummods; i++) - { - //don't add dupes (can happen from gamedir+homedir) - //if the gamedir was already included in one of the manifests, don't bother including it again. - //this generally removes id1. - if (mods->mod[i].manifest) - { - for (p = 0; p < countof(fs_manifest->gamepath); p++) - if (mods->mod[i].manifest->gamepath[p].path) - if (!Q_strcasecmp(mods->mod[i].manifest->gamepath[p].path, gamedir)) - return true; - } - else if (mods->mod[i].gamedir) - { - if (!Q_strcasecmp(mods->mod[i].gamedir, gamedir)) - return true; - } - } - mods->mod = BZ_Realloc(mods->mod, (i+1) * sizeof(*mods->mod)); - mods->mod[i].manifest = NULL; - mods->mod[i].gamedir = Z_StrDup(gamedir); - mods->nummods = i+1; - } - return true; -} - -#include "fs.h" void M_Menu_Mods_f (void) { - modmenu_t mods; menucustom_t *c; emenu_t *menu; size_t i; - extern qboolean com_homepathenabled; - - memset(&mods, 0, sizeof(mods)); - FS_EnumerateKnownGames(Mods_AddManifest, &mods); - - if (com_homepathenabled) - Sys_EnumerateFiles(com_homepath, "*", Mods_AddGamedir, &mods, NULL); - Sys_EnumerateFiles(com_gamepath, "*", Mods_AddGamedir, &mods, NULL); //FIXME: sort by mtime? - menu = M_CreateMenu(sizeof(modmenu_t)); - *(modmenu_t*)menu->data = mods; + menu = M_CreateMenu(0); if (COM_FCheckExists("gfx/p_option.lmp")) { MC_AddPicture(menu, 16, 4, 32, 144, "gfx/qplaque.lmp"); @@ -4231,7 +4158,7 @@ void M_Menu_Mods_f (void) } MC_AddFrameStart(menu, 32); - for (i = 0; i < mods.nummods || i<1; i++) + for (i = 0; i<1 || Mods_GetMod(i); i++) { c = MC_AddCustom(menu, 64, 32+i*8, menu->data, i, NULL); if (!menu->cursoritem) @@ -4239,9 +4166,8 @@ void M_Menu_Mods_f (void) c->common.height = 8; c->draw = Mods_Draw; c->key = Mods_Key; - menu->remove = Mods_Remove; } - MC_AddFrameEnd(menu, 32+i*8); + MC_AddFrameEnd(menu, 32); } #if 0 diff --git a/engine/client/m_script.c b/engine/client/m_script.c index 3a91cae3..897ef714 100644 --- a/engine/client/m_script.c +++ b/engine/client/m_script.c @@ -136,7 +136,7 @@ void M_MenuS_Box_f (void) return; } - MC_AddBox(menu_script, x, y, width/8, height/8); + MC_AddBox(menu_script, x, y, width, height); } void M_MenuS_CheckBox_f (void) diff --git a/engine/client/m_single.c b/engine/client/m_single.c index 3589ab0d..693cf19f 100644 --- a/engine/client/m_single.c +++ b/engine/client/m_single.c @@ -351,7 +351,7 @@ void M_Menu_SinglePlayer_f (void) MC_AddWhiteText(menu, 84, 0, 12*8, "This build is unable", false); MC_AddWhiteText(menu, 84, 0, 13*8, "to start a local game", false); - MC_AddBox (menu, 60, 10*8, 25, 4); + MC_AddBox (menu, 60, 11*8, 25*8, 4*8); #else switch(M_GameType()) @@ -540,7 +540,7 @@ void M_Menu_SinglePlayer_f (void) p = R2D_SafeCachePic("gfx/sp_menu.lmp"); if (!p) { - MC_AddBox (menu, 60, 10*8, 23, 4); + MC_AddBox (menu, 60, 10*8, 23*8, 4*8); MC_AddWhiteText(menu, 92, 0, 12*8, "Couldn't find file", false); MC_AddWhiteText(menu, 92, 0, 13*8, "gfx/sp_menu.lmp", false); @@ -619,6 +619,10 @@ static void M_DemoDraw(int x, int y, menucustom_t *control, emenu_t *menu) demoitem_t *item, *lostit; int ty; + char syspath[MAX_OSPATH]; + if (FS_NativePath(info->fs->path, (info->fs->fsroot==FS_GAME)?FS_GAMEONLY:info->fs->fsroot, syspath, sizeof(syspath))) + Draw_FunString(x, y-16, syspath); + ty = vid.height-24; item = info->selected; while(item) @@ -954,16 +958,25 @@ static void ShowDemoMenu (emenu_t *menu, const char *path) if (path != info->fs->path) { - if (*path == '/') + if (*path == '/' && info->fs->fsroot != FS_SYSTEM) path++; Q_strncpyz(info->fs->path, path, sizeof(info->fs->path)); } if (info->fs->fsroot == FS_GAME) + { + if (!strcmp(path, "../")) + { + Q_strncpyz(info->fs->path, "", sizeof(info->fs->path)); + info->fs->fsroot = FS_ROOT; + } + } + else if (info->fs->fsroot == FS_ROOT) { if (!strcmp(path, "../")) { FS_NativePath("", FS_ROOT, info->fs->path, sizeof(info->fs->path)); + Q_strncatz(info->fs->path, "../", sizeof(info->fs->path)); info->fs->fsroot = FS_SYSTEM; while((s = strchr(info->fs->path, '\\'))) *s = '/'; @@ -1003,7 +1016,7 @@ static void ShowDemoMenu (emenu_t *menu, const char *path) Q_snprintfz(match, sizeof(match), "%s../", info->fs->path); DemoAddItem(match, 0, 0, info, NULL); } - else if (info->fs->fsroot == FS_GAME) + else if (info->fs->fsroot == FS_GAME || info->fs->fsroot == FS_ROOT) { Q_snprintfz(match, sizeof(match), "../"); DemoAddItem(match, 0, 0, info, NULL); @@ -1016,6 +1029,13 @@ static void ShowDemoMenu (emenu_t *menu, const char *path) Q_snprintfz(match, sizeof(match), "/*"); Sys_EnumerateFiles("", match, DemoAddItem, info, NULL); } + else if (info->fs->fsroot == FS_ROOT) + { + Q_snprintfz(match, sizeof(match), "%s*", info->fs->path); + if (*com_homepath) + Sys_EnumerateFiles(com_homepath, match, DemoAddItem, info, NULL); + Sys_EnumerateFiles(com_gamepath, match, DemoAddItem, info, NULL); + } else { Q_snprintfz(match, sizeof(match), "%s*", info->fs->path); diff --git a/engine/client/menu.c b/engine/client/menu.c index a0350cac..21624368 100644 --- a/engine/client/menu.c +++ b/engine/client/menu.c @@ -415,29 +415,43 @@ void M_Restart_f(void) typedef struct { menu_t m; - void (*callback)(void *, int); + void (*callback)(void *, promptbutton_t); void *ctx; int lines; const char *messages; const char *buttons[3]; int kbutton, mbutton; + qboolean mousedown; } promptmenu_t; static qboolean Prompt_MenuKeyEvent(struct menu_s *gm, qboolean isdown, unsigned int devid, int key, int unicode) { promptmenu_t *m = (promptmenu_t*)gm; - int action; - void (*callback)(void *, int) = m->callback; + promptbutton_t action; + void (*callback)(void *, promptbutton_t) = m->callback; void *ctx = m->ctx; extern qboolean keydown[]; - if ((!isdown) != (key==K_MOUSE1)) - return false; //don't care about releases, unless mouse1. + if (key == K_MOUSE1) + { //mouse events fire their action on release. + if (isdown) + { + m->mousedown = true; //so we don't respond to stray release events. + return true; + } + else if (!m->mousedown) + return false; //looks like a stray release event. ignore it. + } + else + { //keyboard events fire on press. + if (!isdown) + return false; + } if (key == 'n' || key == 'N') - action = 1; + action = PROMPT_NO; else if (key == 'y' || key == 'Y') - action = 0; + action = PROMPT_YES; else if (key==K_RIGHTARROW || key==K_GP_DPAD_RIGHT || key==K_DOWNARROW || key==K_GP_DPAD_DOWN || (key == K_TAB && !keydown[K_LSHIFT] && !keydown[K_RSHIFT])) { for(;;) @@ -463,18 +477,29 @@ static qboolean Prompt_MenuKeyEvent(struct menu_s *gm, qboolean isdown, unsigned return true; } else if (key == K_ESCAPE || key == K_GP_BACK || key == K_MOUSE2) - action = -1; + action = PROMPT_CANCEL; else if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1 || key == K_GP_A) { + int button; if (key == K_MOUSE1) - action = m->mbutton; + button = m->mbutton; else - action = m->kbutton; + button = m->kbutton; - if (action == -1) //nothing focused - return false; - if (action == 2) //convert buttons to actions... - action = -1; + switch(button) + { + case 0: + action = PROMPT_YES; + break; + case 1: + action = PROMPT_NO; + break; + case 2: + action = PROMPT_CANCEL; + break; + default: + return false; //nothing focused. + } } else return false; // no idea what that is @@ -491,28 +516,29 @@ static void Prompt_Draw(struct menu_s *g) promptmenu_t *m = (promptmenu_t*)g; int x = 64; int y = 76; - int w = 224; - int h = m->lines*8+16; + float scale = Font_CharVHeight(font_console); + int w = 224*scale/8; + int h = (m->lines+3)*scale; int i; const char *msg = m->messages; int bx[4]; x = ((vid.width-w)>>1); - Draw_TextBox(x-8, y, w/8, h/8); - y+=8; + Draw_ApproxTextBox(x, y, w, h); + y+=scale; for (i = 0; i < m->lines; i++, msg = msg+strlen(msg)+1) { - Draw_FunStringWidth(x, y, msg, w, 2, false); - y+=8; + Draw_FunStringWidthFont(font_console, x, y, msg, w, 2, false); + y+=scale; } - y+=8; + y+=scale; m->mbutton = -1; bx[0] = x; bx[1] = x+w/3; bx[2] = x+w-w/3; bx[3] = x+w; - if (mousecursor_y >= y && mousecursor_y <= y+8) + if (mousecursor_y >= y && mousecursor_y <= y+scale) { for (i = 0; i < 3; i++) { @@ -528,25 +554,25 @@ static void Prompt_Draw(struct menu_s *g) { float alphamax = 0.5, alphamin = 0.2; R2D_ImageColours(.5,.4,0,(sin(realtime*2)+1)*0.5*(alphamax-alphamin)+alphamin); - R2D_FillBlock(bx[i], y, bx[i+1]-bx[i], 8); + R2D_FillBlock(bx[i], y, bx[i+1]-bx[i], scale); R2D_ImageColours(1,1,1,1); } - Draw_FunStringWidth(bx[i], y, m->buttons[i], bx[i+1]-bx[i], 2, m->kbutton==i); + Draw_FunStringWidthFont(font_console, bx[i], y, m->buttons[i], bx[i+1]-bx[i], 2, m->kbutton==i); } } } static void Prompt_Release(struct menu_s *gm) { promptmenu_t *m = (promptmenu_t*)gm; - void (*callback)(void *, int) = m->callback; + void (*callback)(void *, promptbutton_t) = m->callback; void *ctx = m->ctx; m->callback = NULL; if (callback) - callback(ctx, -1); + callback(ctx, PROMPT_CANCEL); Z_Free(m); } -void Menu_Prompt (void (*callback)(void *, int), void *ctx, const char *messages, char *optionyes, char *optionno, char *optioncancel) +void Menu_Prompt (void (*callback)(void *, promptbutton_t), void *ctx, const char *messages, char *optionyes, char *optionno, char *optioncancel) { promptmenu_t *m; char *t; @@ -1150,20 +1176,20 @@ static char *quitMessage [] = NULL };*/ void Cmd_WriteConfig_f(void); -static void M_Menu_DoQuit(void *ctx, int option) +static void M_Menu_DoQuit(void *ctx, promptbutton_t option) { - if (option == 0) //'yes - quit' + if (option == PROMPT_YES) //'yes - quit' Cmd_ExecuteString("menu_quit force\n", RESTRICT_LOCAL); -// else if (option == 1) //'no - don't quit' -// else if (option == -1) //'cancel - don't quit' +// else if (option == PROMPT_NO) //'no - don't quit' +// else if (option == PROMPT_CANCEL) //'cancel - don't quit' } -static void M_Menu_DoQuitSave(void *ctx, int option) +static void M_Menu_DoQuitSave(void *ctx, promptbutton_t option) { - if (option == 0) //'yes - save-and-quit' + if (option == PROMPT_YES) //'yes - save-and-quit' Cmd_ExecuteString("menu_quit forcesave\n", RESTRICT_LOCAL); - else if (option == 1) //'no - nosave-and-quit' + else if (option == PROMPT_NO) //'no - nosave-and-quit' Cmd_ExecuteString("menu_quit force\n", RESTRICT_LOCAL); -// else if (option == -1) //'cancel - don't quit' +// else if (option == PROMPT_CANCEL) //'cancel - don't quit' } //quit menu diff --git a/engine/client/menu.h b/engine/client/menu.h index eca73d2e..1f534c1c 100644 --- a/engine/client/menu.h +++ b/engine/client/menu.h @@ -121,7 +121,13 @@ void Menu_Unlink(menu_t *menu); void Menu_Push(menu_t *menu, qboolean prompt); menu_t *Menu_FindContext(void *ctx); -void Menu_Prompt (void (*callback)(void *, int), void *ctx, const char *messages, char *optionyes, char *optionno, char *optioncancel); +typedef enum +{ + PROMPT_YES = 0, + PROMPT_NO = 1, + PROMPT_CANCEL = -1, +} promptbutton_t; +void Menu_Prompt (void (*callback)(void *, promptbutton_t), void *ctx, const char *messages, char *optionyes, char *optionno, char *optioncancel); #ifndef NOBUILTINMENUS @@ -491,6 +497,7 @@ void MP_CvarChanged(cvar_t *var); qboolean MP_Init (void); void MP_Shutdown (void); qboolean MP_Toggle(int mode); +void MP_RendererRestarted(void); void MP_Draw(void); qboolean MP_UsingGamecodeLoadingScreen(void); void MP_RegisterCvarsAndCmds(void); diff --git a/engine/client/merged.h b/engine/client/merged.h index 56b3cd96..73a2c5af 100644 --- a/engine/client/merged.h +++ b/engine/client/merged.h @@ -208,7 +208,8 @@ const char *Mod_GetBoneName(struct model_s *model, int bonenum); void Draw_FunString(float x, float y, const void *str); void Draw_AltFunString(float x, float y, const void *str); -void Draw_FunStringWidth(float x, float y, const void *str, int width, int rightalign, qboolean highlight); +void Draw_FunStringWidthFont(struct font_s *font, float x, float y, const void *str, int width, int rightalign, qboolean highlight); +#define Draw_FunStringWidth(x,y,str,width,rightalign,highlight) Draw_FunStringWidthFont(font_default,x,y,str,width,rightalign,highlight) extern int r_regsequence; diff --git a/engine/client/pr_clcmd.c b/engine/client/pr_clcmd.c index 50f85a92..47314a1d 100644 --- a/engine/client/pr_clcmd.c +++ b/engine/client/pr_clcmd.c @@ -1084,104 +1084,129 @@ void QCBUILTIN PF_cl_setlocaluserinfo (pubprogfuncs_t *prinst, struct globalvars } #include "fs.h" -static struct modlist_s -{ - ftemanifest_t *manifest; - char *gamedir; - char *description; -} *modlist; -static size_t nummods; -static qboolean modsinited; - -static qboolean Mods_AddManifest(void *usr, ftemanifest_t *man) -{ - int i = nummods; - modlist = BZ_Realloc(modlist, (i+1) * sizeof(*modlist)); - modlist[i].manifest = man; - modlist[i].gamedir = man->updatefile; - modlist[i].description = man->formalname; - nummods = i+1; - return true; -} -static int QDECL Mods_AddGamedir(const char *fname, qofs_t fsize, time_t mtime, void *usr, searchpathfuncs_t *spath) -{ - char *f; - size_t l = strlen(fname); - int i, p; - char gamedir[MAX_QPATH]; - if (l && fname[l-1] == '/' && l < countof(gamedir)) - { - l--; - memcpy(gamedir, fname, l); - gamedir[l] = 0; - for (i = 0; i < nummods; i++) - { - //don't add dupes (can happen from basedir+homedir) - //if the gamedir was already included in one of the manifests, don't bother including it again. - //this generally removes id1. - if (modlist[i].manifest) - { - for (p = 0; p < countof(fs_manifest->gamepath); p++) - if (modlist[i].manifest->gamepath[p].path) - if (!Q_strcasecmp(modlist[i].manifest->gamepath[p].path, gamedir)) - return true; - } - else if (modlist[i].gamedir) - { - if (!Q_strcasecmp(modlist[i].gamedir, gamedir)) - return true; - } - } - f = FS_MallocFile(va("%s%s/modinfo.txt", (const char*)usr, gamedir), FS_SYSTEM, NULL); - if (f) - { - modlist = BZ_Realloc(modlist, (i+1) * sizeof(*modlist)); - modlist[i].manifest = NULL; - modlist[i].gamedir = Z_StrDup(gamedir); - modlist[i].description = f; - nummods = i+1; - } - } - return true; -} -static void Mods_InitModList (void) -{ - extern qboolean com_homepathenabled; - - FS_EnumerateKnownGames(Mods_AddManifest, NULL); - - if (com_homepathenabled) - Sys_EnumerateFiles(com_homepath, "*", Mods_AddGamedir, com_homepath, NULL); - Sys_EnumerateFiles(com_gamepath, "*", Mods_AddGamedir, com_gamepath, NULL); -} - void QCBUILTIN PF_cl_getgamedirinfo(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { size_t diridx = G_FLOAT(OFS_PARM0); - int propidx = G_FLOAT(OFS_PARM1); + enum getgamedirinfo_e propidx = G_FLOAT(OFS_PARM1); - if (!modsinited) + struct modlist_s *mod, current; + if (G_FLOAT(OFS_PARM0) == -1) { - modsinited = true; - Mods_InitModList(); + current.description = fs_manifest->formalname; + current.gamedir = FS_GetGamedir(true); + current.manifest = fs_manifest; + mod = ¤t; } + else + mod = Mods_GetMod(diridx); G_INT(OFS_RETURN) = 0; - if (diridx < nummods) + if (mod) { switch(propidx) { - case 1: //description (contents of modinfo.txt) - if (modlist[diridx].description) - RETURN_TSTRING(modlist[diridx].description); + case GGDI_GAMEDIR: //name + RETURN_TSTRING(mod->gamedir); break; - case 2: //cvars - if (modlist[diridx].manifest) - if (modlist[diridx].manifest->defaultexec) - RETURN_TSTRING(modlist[diridx].manifest->defaultexec); + case GGDI_ALLGAMEDIRS: + { + char *dirs = NULL; + size_t d; + ftemanifest_t *man = mod->manifest?mod->manifest:fs_manifest; + //the basedirs + for (d = 0; d < countof(man->gamepath); d++) + { + if (man->gamepath[d].path && man->gamepath[d].flags&GAMEDIR_BASEGAME) + { + if (dirs) + Z_StrCat(&dirs, ";"); + Z_StrCat(&dirs, man->gamepath[d].path); + } + } + if (mod->manifest) + { //the manifest's mod dirs + for (d = 0; d < countof(man->gamepath); d++) + { + if (man->gamepath[d].path && !(man->gamepath[d].flags&GAMEDIR_BASEGAME)) + { + if (dirs) + Z_StrCat(&dirs, ";"); + Z_StrCat(&dirs, man->gamepath[d].path); + } + } + } + else //the specified gamedir + { + if (dirs) + Z_StrCat(&dirs, ";"); + Z_StrCat(&dirs, mod->gamedir); + } + RETURN_TSTRING(dirs?dirs:""); + Z_Free(dirs); + } break; - case 0: //name - RETURN_TSTRING(modlist[diridx].gamedir); + case GGDI_DESCRIPTION: //description (contents of modinfo.txt) + if (mod->description) + RETURN_TSTRING(mod->description); + break; + case GGDI_OVERRIDES: //cvars + if (mod->manifest) + if (mod->manifest->defaultexec) + RETURN_TSTRING(mod->manifest->defaultexec); + break; + case GGDI_LOADCOMMAND: //load command + RETURN_TSTRING(va("fs_changegame %u", (unsigned int)diridx+1u)); + break; + case GGDI_ICON: //icon + { + char iname[MAX_QPATH]; + shader_t *shader; + Q_snprintfz(iname, sizeof(iname), "gamedir/%u", (unsigned) diridx); + shader = R_RegisterShader(iname, SUF_2D, + "{\n" + "affine\n" + "nomipmaps\n" + "program default2d#PREMUL\n" + "{\n" + "clampmap $diffuse\n" + "blendfunc gl_one gl_one_minus_src_alpha\n" + "}\n" + "sort additive\n" + "}\n" + ); + if (shader && !shader->defaulttextures->base) + { //no textures yet? do something about it! + void *data = NULL; + size_t i; + qofs_t sz; + const char *extensions[] = { +#ifdef IMAGEFMT_PNG + ".png", +#endif + ".tga", +#ifdef IMAGEFMT_BMP + ".ico", +#endif + }; + if (mod->manifest && mod->manifest->iconname) + { + for (i = 0; i < countof(extensions) && !data; i++) + { + COM_StripExtension(mod->manifest->filename, iname, sizeof(iname)); + COM_RequireExtension(iname, extensions[i], sizeof(iname)); + data = FS_MallocFile(iname, FS_SYSTEM, &sz); + } + } + for (i = 0; i < countof(extensions) && !data; i++) + data = FS_MallocFile(va("%s/icon%s", mod->gamedir, extensions[i]), FS_ROOT, &sz); + Q_snprintfz(iname, sizeof(iname), "gamedir/%u", (unsigned) diridx); + shader->defaulttextures->base = Image_CreateTexture(iname, NULL, IF_PREMULTIPLYALPHA); + if (data) + Image_LoadTextureFromMemory(shader->defaulttextures->base, shader->defaulttextures->base->flags, iname, iname, data, sz); + } + if (shader && TEXLOADED(shader->defaulttextures->base)) + RETURN_TSTRING(shader->name); + } break; } } diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index 50820523..1b0568e5 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -984,7 +984,9 @@ static qboolean CopyCSQCEdictToEntity(csqcedict_t *fte_restrict in, entity_t *ft out->flags |= RF_DEPTHHACK|RF_WEAPONMODEL; out->pvscache = p->pvsinfo; //for the areas. out->pvscache.num_leafs = -1; //make visible globally +#if defined(Q2BSPS) || defined(Q3BSPS) || defined(TERRAIN) out->pvscache.headnode = 0; +#endif #endif } @@ -2176,6 +2178,66 @@ void QCBUILTIN PF_R_GetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_ r[1] = 0; r[2] = 0; +#ifdef HAVE_LEGACY + if (csqc_isdarkplaces && prinst == csqc_world.progs) + { + switch(parametertype) + { + case VF_VIEWPORT: + r[0] = r_refdef.grect.width * (float)vid.pixelwidth / vid.width;; + r[1] = r_refdef.grect.height * (float)vid.pixelheight / vid.height; + return; + case VF_SIZE_X: + *r = r_refdef.grect.width * (float)vid.pixelwidth / vid.width; + return; + case VF_SIZE_Y: + *r = r_refdef.grect.height * (float)vid.pixelheight / vid.height; + return; + case VF_SIZE: + r[0] = r_refdef.grect.width * (float)vid.pixelwidth / vid.width;; + r[1] = r_refdef.grect.height * (float)vid.pixelheight / vid.height; + r[2] = 0; + return; + case VF_DP_MAINVIEW: + r[0] = 1; + return; + case VF_DP_MINFPS_QUALITY: + r[0] = 1; + return; + case VF_DP_CLEARSCREEN: + r[0] = 0; + return; + case VF_DP_FOG_DENSITY: + r[0] = r_refdef.globalfog.density; + return; + case VF_DP_FOG_COLOR: + r[0] = r_refdef.globalfog.colour[0]; + r[1] = r_refdef.globalfog.colour[1]; + r[2] = r_refdef.globalfog.colour[2]; + return; + case VF_DP_FOG_COLOR_R: + r[0] = r_refdef.globalfog.colour[0]; + return; + case VF_DP_FOG_COLOR_G: + r[0] = r_refdef.globalfog.colour[1]; + return; + case VF_DP_FOG_COLOR_B: + r[0] = r_refdef.globalfog.colour[2]; + return; + case VF_DP_FOG_ALPHA: + r[0] = r_refdef.globalfog.alpha; + return; + case VF_DP_FOG_START: + case VF_DP_FOG_END: + case VF_DP_FOG_HEIGHT: + case VF_DP_FOG_FADEDEPTH: + return; + default: + break; + } + } +#endif + switch(parametertype) { nogameaccess: @@ -2264,24 +2326,14 @@ nogameaccess: case VF_SIZE_X: *r = r_refdef.grect.width; - if (csqc_isdarkplaces && prinst == csqc_world.progs) - *r *= (float)vid.pixelwidth / vid.width; break; case VF_SIZE_Y: *r = r_refdef.grect.height; - if (csqc_isdarkplaces && prinst == csqc_world.progs) - *r *= (float)vid.pixelheight / vid.height; break; case VF_SIZE: r[0] = r_refdef.grect.width; r[1] = r_refdef.grect.height; r[2] = 0; - - if (csqc_isdarkplaces && prinst == csqc_world.progs) - { - r[0] *= (float)vid.pixelwidth / vid.width; - r[1] *= (float)vid.pixelheight / vid.height; - } break; case VF_MIN_X: @@ -2376,8 +2428,54 @@ void QCBUILTIN PF_R_SetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_ R2D_Flush(); csqc_rebuildmatricies = true; - G_FLOAT(OFS_RETURN) = 1; + +#ifdef HAVE_LEGACY + if (csqc_isdarkplaces && prinst == csqc_world.progs) + { + switch(parametertype) + { + case VF_VIEWPORT: + r_refdef.grect.x = p[0] * (float)vid.width / vid.pixelwidth; + r_refdef.grect.y = p[1] * (float)vid.height / vid.pixelheight; + p = G_VECTOR(OFS_PARM2); + r_refdef.grect.width = p[0] * (float)vid.width / vid.pixelwidth; + r_refdef.grect.height = p[1] * (float)vid.height / vid.pixelheight; + r_refdef.dirty |= RDFD_FOV; + return; + case VF_SIZE_X: + r_refdef.grect.width = *p * (float)vid.width / vid.pixelwidth; + r_refdef.dirty |= RDFD_FOV; + return; + case VF_SIZE_Y: + r_refdef.grect.height = *p * (float)vid.height / vid.pixelheight; + r_refdef.dirty |= RDFD_FOV; + return; + case VF_SIZE: + r_refdef.grect.width = p[0] * (float)vid.width / vid.pixelwidth; + r_refdef.grect.height = p[1] * (float)vid.height / vid.pixelheight; + r_refdef.dirty |= RDFD_FOV; + return; + case VF_DP_MAINVIEW: + case VF_DP_MINFPS_QUALITY: + case VF_DP_CLEARSCREEN: + case VF_DP_FOG_DENSITY: + case VF_DP_FOG_COLOR: + case VF_DP_FOG_COLOR_R: + case VF_DP_FOG_COLOR_G: + case VF_DP_FOG_COLOR_B: + case VF_DP_FOG_ALPHA: + case VF_DP_FOG_START: + case VF_DP_FOG_END: + case VF_DP_FOG_HEIGHT: + case VF_DP_FOG_FADEDEPTH: + return; + default: + break; + } + } +#endif + switch(parametertype) { case VF_ACTIVESEAT: @@ -2497,12 +2595,6 @@ void QCBUILTIN PF_R_SetViewFlag(pubprogfuncs_t *prinst, struct globalvars_s *pr_ r_refdef.grect.width = p[0]; r_refdef.grect.height = p[1]; r_refdef.dirty |= RDFD_FOV; - - if (csqc_isdarkplaces) - { - r_refdef.grect.width *= (float)vid.width / vid.pixelwidth; - r_refdef.grect.height *= (float)vid.height / vid.pixelheight; - } break; case VF_MIN_X: @@ -3740,6 +3832,38 @@ static void QCBUILTIN PF_cl_setpause (pubprogfuncs_t *prinst, struct globalvars_ cl.implicitpause = !!G_FLOAT(OFS_PARM0); } +static void QCBUILTIN PF_cl_RotateMoves (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ //changes the past... + float *angles = G_VECTOR(OFS_PARM0); + int frame; + vec3_t of,ou, nf,nu; + vec4_t mat[3]; + vec3_t a; + int seat = ((prinst->callargc>1)?G_FLOAT(OFS_PARM1):csqc_playerseat); + if (seat < 0 || seat >= MAX_SPLITS) + { + G_FLOAT(OFS_RETURN) = false; + return; + } + AngleVectorsFLU(angles, mat[0], mat[1], mat[2]); + mat[0][3] = mat[1][3] = mat[2][3] = 0; + for (frame = 0; frame < countof(cl.outframes); frame++) + { + if (cl.outframes[frame].cmd_sequence > cl.ackedmovesequence) + { + a[0] = SHORT2ANGLE(cl.outframes[frame].cmd[seat].angles[0]); + a[1] = SHORT2ANGLE(cl.outframes[frame].cmd[seat].angles[1]); + a[2] = SHORT2ANGLE(cl.outframes[frame].cmd[seat].angles[2]); + AngleVectors(a, of, NULL, ou); + VectorTransform(of, mat, nf); + VectorTransform(ou, mat, nu); + VectorAngles(nf, nu, a, false); + cl.outframes[frame].cmd[seat].angles[0] = ANGLE2SHORT(a[0]); + cl.outframes[frame].cmd[seat].angles[1] = ANGLE2SHORT(a[1]); + cl.outframes[frame].cmd[seat].angles[2] = ANGLE2SHORT(a[2]); + } + } +} //get the input commands, and stuff them into some globals. static void QCBUILTIN PF_cs_getinputstate (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -6094,6 +6218,12 @@ static void QCBUILTIN PF_V_CalcRefdef(pubprogfuncs_t *prinst, struct globalvars_ VectorCopy(savedvel, csqc_playerview->simvel); } +static void QCBUILTIN PF_getlocationname(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + float *loc = G_VECTOR(OFS_PARM0); + RETURN_TSTRING(TP_LocationName(loc)); +} + #if 1 //static void QCBUILTIN PF_ReadServerEntityState(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) //{ @@ -6641,6 +6771,7 @@ static struct { {"processmodelevents", PF_processmodelevents, 0}, {"getnextmodelevent", PF_getnextmodelevent, 0}, {"getmodeleventidx", PF_getmodeleventidx, 0}, + {"getlocationname", PF_getlocationname, 0}, {"crossproduct", PF_crossproduct, 0}, {"pushmove", PF_pushmove, 0}, @@ -7085,6 +7216,9 @@ static struct { #endif {"netaddress_resolve", PF_netaddress_resolve, 625}, {"getgamedirinfo", PF_cl_getgamedirinfo, 626}, +#ifdef PACKAGEMANAGER + {"getpackagemanagerinfo", PF_cl_getpackagemanagerinfo,0}, +#endif {"sprintf", PF_sprintf, 627}, {"getsurfacenumtriangles", PF_getsurfacenumtriangles, 628}, {"getsurfacetriangle", PF_getsurfacetriangle, 629}, @@ -7097,7 +7231,7 @@ static struct { // {NULL, PF_Fixme, 645}, // {NULL, PF_Fixme, 646}, // {NULL, PF_Fixme, 647}, -// {NULL, PF_Fixme, 648}, + {"CL_RotateMoves", PF_cl_RotateMoves, 648}, {"digest_hex", PF_digest_hex, 639}, {"digest_ptr", PF_digest_ptr, 0}, {"V_CalcRefdef", PF_V_CalcRefdef, 640}, diff --git a/engine/client/pr_menu.c b/engine/client/pr_menu.c index b3512ae8..f18aa28a 100644 --- a/engine/client/pr_menu.c +++ b/engine/client/pr_menu.c @@ -1372,6 +1372,7 @@ static struct func_t toggle; func_t consolecommand; func_t gethostcachecategory; + func_t rendererrestarted; } mpfuncs; jmp_buf mp_abort; @@ -1974,6 +1975,12 @@ void QCBUILTIN PF_crypto_getidfp(pubprogfuncs_t *prinst, struct globalvars_s *pr //not supported. G_INT(OFS_RETURN) = 0; } +//float(string serveraddress) crypto_getidstatus +void QCBUILTIN PF_crypto_getidstatus(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + //not supported. + G_INT(OFS_RETURN) = 0; +} //string(string serveraddress) crypto_getencryptlevel void QCBUILTIN PF_crypto_getencryptlevel(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -1992,6 +1999,12 @@ void QCBUILTIN PF_crypto_getmyidfp(pubprogfuncs_t *prinst, struct globalvars_s * //not supported. G_INT(OFS_RETURN) = 0; } +//float(float i) PF_crypto_getmyidstatus +void QCBUILTIN PF_crypto_getmyidstatus(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + //not supported. + G_INT(OFS_RETURN) = 0; +} static void QCBUILTIN PF_m_precache_model(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -2328,7 +2341,7 @@ static struct { {"altstr_ins", PF_altstr_ins, 86}, {"findflags", PF_FindFlags, 87}, {"findchainflags", PF_menu_findchainflags, 88}, - {"mcvar_defstring", PF_cvar_defstring, 89}, + {"cvar_defstring", PF_cvar_defstring, 89}, {"setmodel", PF_m_setmodel, 90}, {"precache_model", PF_m_precache_model, 91}, {"setorigin", PF_m_setorigin, 92}, @@ -2489,7 +2502,7 @@ static struct { {"tokenizebyseparator", PF_tokenizebyseparator, 479}, {"strtolower", PF_strtolower, 480}, {"strtoupper", PF_strtoupper, 481}, - {"cvar_defstring", PF_cvar_defstring, 482}, + {"csqc_cvar_defstring", PF_cvar_defstring, 482}, // {NULL, PF_Fixme, 483}, {"strreplace", PF_strreplace, 484}, {"strireplace", PF_strireplace, 485}, @@ -2567,6 +2580,9 @@ static struct { #endif {"netaddress_resolve", PF_netaddress_resolve, 625}, {"getgamedirinfo", PF_cl_getgamedirinfo, 626}, +#ifdef PACKAGEMANAGER + {"getpackagemanagerinfo", PF_cl_getpackagemanagerinfo,0}, +#endif {"sprintf", PF_sprintf, 627}, // {NULL, PF_Fixme, 628}, // {NULL, PF_Fixme, 629}, @@ -2582,9 +2598,9 @@ static struct { {"digest_hex", PF_digest_hex, 639}, {"digest_ptr", PF_digest_ptr, 0}, // {NULL, PF_Fixme, 640}, - {"crypto_getmyidstatus", PF_crypto_getmyidfp, 641}, -// {NULL, PF_Fixme, 642}, -// {NULL, PF_Fixme, 643}, + {"crypto_getmyidstatus", PF_crypto_getmyidstatus, 641}, +// {"coverage", PF_Fixme, 642}, + {"crypto_getidstatus", PF_crypto_getidstatus, 643}, // {NULL, PF_Fixme, 644}, // {NULL, PF_Fixme, 645}, // {NULL, PF_Fixme, 646}, @@ -3059,6 +3075,7 @@ qboolean MP_Init (void) mpfuncs.toggle = PR_FindFunction(menu_world.progs, "m_toggle", PR_ANY); mpfuncs.consolecommand = PR_FindFunction(menu_world.progs, "m_consolecommand", PR_ANY); mpfuncs.gethostcachecategory = PR_FindFunction(menu_world.progs, "m_gethostcachecategory", PR_ANY); + mpfuncs.rendererrestarted = PR_FindFunction(menu_world.progs, "Menu_RendererRestarted", PR_ANY); if (mpfuncs.init) PR_ExecuteProgram(menu_world.progs, mpfuncs.init); inmenuprogs--; @@ -3203,6 +3220,38 @@ int MP_GetServerCategory(int index) return category; } +void MP_RendererRestarted(void) +{ + int i; + if (!menu_world.progs) + return; + + menu_world.worldmodel = cl.worldmodel; + + for (i = 0; i < MAX_CSMODELS; i++) + { + cl.model_csqcprecache[i] = NULL; + } + + //FIXME: registered shaders + + //let the csqc know that its rendertargets got purged + if (mpfuncs.rendererrestarted) + { + void *pr_globals = PR_globals(menu_world.progs, PR_CURRENT); + (((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(menu_world.progs, rf->description)); + PR_ExecuteProgram(menu_world.progs, mpfuncs.rendererrestarted); + } + //in case it drew to any render targets. + if (R2D_Flush) + R2D_Flush(); + if (*r_refdef.rt_destcolour[0].texname) + { + Q_strncpyz(r_refdef.rt_destcolour[0].texname, "", sizeof(r_refdef.rt_destcolour[0].texname)); + BE_RenderToTextureUpdate2d(true); + } +} + void MP_Draw(void) { extern qboolean scr_drawloading; diff --git a/engine/client/quakedef.h b/engine/client/quakedef.h index 2e962d92..8c542b34 100644 --- a/engine/client/quakedef.h +++ b/engine/client/quakedef.h @@ -314,8 +314,10 @@ extern qboolean noclip_anglehack; extern quakeparms_t host_parms; extern cvar_t fs_gamename; +#ifdef PACKAGEMANAGER extern cvar_t pkg_downloads_url; extern cvar_t pkg_autoupdate; +#endif extern cvar_t com_protocolname; extern cvar_t com_protocolversion; extern cvar_t com_nogamedirnativecode; diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c index cf2ee6ed..f2b6527a 100644 --- a/engine/client/r_surf.c +++ b/engine/client/r_surf.c @@ -57,6 +57,8 @@ extern cvar_t r_stainfadeammount; extern cvar_t r_lightmap_nearest; extern cvar_t r_lightmap_format; +static void Surf_FreeLightmap(lightmapinfo_t *lm); + static int lightmap_shift; int Surf_LightmapShift (model_t *model) { @@ -2808,9 +2810,10 @@ void Surf_SetupFrame(void) r_refdef.playerview->audio.entnum = r_refdef.playerview->viewentity; VectorCopy(r_refdef.vieworg, r_refdef.playerview->audio.origin); AngleVectors(r_refdef.viewangles, r_refdef.playerview->audio.forward,r_refdef.playerview->audio.right, r_refdef.playerview->audio.up); - if (r_viewcontents & FTECONTENTS_FLUID) - r_refdef.playerview->audio.reverbtype = 1; - else +// I'm fed up of openal users getting audio bugs when underwater. +// if (r_viewcontents & FTECONTENTS_FLUID) +// r_refdef.playerview->audio.reverbtype = 1; +// else r_refdef.playerview->audio.reverbtype = 0; VectorCopy(r_refdef.playerview->simvel, r_refdef.playerview->audio.velocity); } @@ -2852,7 +2855,7 @@ void Surf_GenBrushBatches(batch_t **batches, entity_t *ent) // calculate dynamic lighting for bmodel if it's not an // instanced model - if (model->fromgame != fg_quake3 && model->fromgame != fg_doom3 && lightmap) + if (model->fromgame != fg_quake3 && model->fromgame != fg_doom3 && lightmap && !r_temporalscenecache.ival) { int k; @@ -2926,9 +2929,22 @@ void Surf_GenBrushBatches(batch_t **batches, entity_t *ent) if (!b) continue; *b = *ob; + if (b->vbo && b->maxmeshes) + { + b->meshbuf = *b->mesh[0]; + b->meshbuf.numindexes = b->mesh[b->maxmeshes-1]->indexes+b->mesh[b->maxmeshes-1]->numindexes-b->mesh[0]->indexes; + b->meshbuf.numvertexes = b->mesh[b->maxmeshes-1]->xyz_array+b->mesh[b->maxmeshes-1]->numvertexes-b->mesh[0]->xyz_array; + + b->mesh = &b->meshptr; + b->meshptr = &b->meshbuf; + b->meshes = b->maxmeshes = 1; + } + else + { // if (b->texture) // b->shader = R_TextureAnimation(ent->framestate.g[FS_REG].frame[0], b->texture)->shader; - b->meshes = b->maxmeshes; + b->meshes = b->maxmeshes; + } b->ent = ent; b->flags = bef; @@ -2961,6 +2977,7 @@ struct webostate_s { char dbgid[12]; struct webostate_s *next; + int lastvalid; //keyed to cls.framecount, for cleaning up. model_t *wmodel; int cluster[2]; qboolean generating; @@ -2971,6 +2988,8 @@ struct webostate_s int numbatches; int lightstylevalues[MAX_NET_LIGHTSTYLES]; //when using workers that only reprocessing lighting at 10fps, things get too ugly when things go out of sync +//TODO qbyte *bakedsubmodels; //flags saying whether each submodel was baked or not. baked submodels need to be untinted uncaled unrotated at origin etc + vec3_t lastpos; batch_t *rbatches[SHADER_SORT_COUNT]; @@ -3019,6 +3038,8 @@ void R_GeneratedWorldEBO(void *ctx, void *data, size_t a_, size_t b_) webogeneratingstate = 0; mod = webostate->wmodel; + webostate->lastvalid = cls.framecount; + for (i = 0, idxcount = 0; i < webostate->numbatches; i++) idxcount += webostate->batches[i].numidx; #ifdef GLQUAKE @@ -3116,6 +3137,9 @@ static void Surf_SimpleWorld_Q1BSP(struct webostate_s *es, qbyte *pvs) model_t *wmodel = es->wmodel; int l = wmodel->numclusters; int fc = -r_framecount; + int i; +// int s, f, lastface; + struct wesbatch_s *eb; for (leaf = wmodel->leafs+l; l-- > 0; leaf--) { if ((pvs[l>>3] & (1u<<(l&7))) && leaf->nummarksurfaces) @@ -3127,8 +3151,6 @@ static void Surf_SimpleWorld_Q1BSP(struct webostate_s *es, qbyte *pvs) surf = *mark++; if (surf->visframe != fc) { - int i; - struct wesbatch_s *eb; surf->visframe = fc; Surf_RenderDynamicLightmaps_Worker (wmodel, surf, es->lightstylevalues); @@ -3147,6 +3169,32 @@ static void Surf_SimpleWorld_Q1BSP(struct webostate_s *es, qbyte *pvs) } } } + +/*TODO for (s = 0; s < wmodel->numsubmodels; s++) + { + if (!es->bakedsubmodels[s]) + continue; //not baking this one (not currently visible or something) + //FIXME: pvscull it here? + lastface = wmodel->submodels[s].firstface + wmodel->submodels[s].numfaces; + for (f = wmodel->submodels[s].firstface; f < lastface; f++) + { + surf = wmodel->surfaces; + + Surf_RenderDynamicLightmaps_Worker (wmodel, surf, es->lightstylevalues); + + mesh = surf->mesh; + eb = &es->batches[surf->sbatch->webobatch]; + if (eb->maxidx < eb->numidx + mesh->numindexes) + { + //FIXME: pre-allocate + eb->maxidx = eb->numidx + surf->mesh->numindexes + 512; + eb->idxbuffer = BZ_Realloc(eb->idxbuffer, eb->maxidx * sizeof(index_t)); + } + for (i = 0; i < mesh->numindexes; i++) + eb->idxbuffer[eb->numidx+i] = mesh->indexes[i] + mesh->vbofirstvert; + eb->numidx += mesh->numindexes; + } + }*/ } #endif #if defined(Q2BSPS) || defined(Q3BSPS) @@ -3272,14 +3320,22 @@ void Surf_DrawWorld (void) Surf_LightmapShift(currentmodel); #ifdef THREADEDWORLD - if ((r_dynamic.ival < 0 || currentmodel->numbatches) && !r_refdef.recurse && currentmodel->type == mod_brush) + if ((r_temporalscenecache.ival || currentmodel->numbatches) && !r_refdef.recurse && currentmodel->type == mod_brush) { - struct webostate_s *webostate, *best = NULL; + struct webostate_s *webostate, *best = NULL, *kill; vec_t bestdist = FLT_MAX; for (webostate = webostates; webostate; webostate = webostate->next) { if (webostate->wmodel != currentmodel) continue; + + kill = webostate->next; + if (kill && kill->lastvalid < cls.framecount-5) + { + webostate->next = kill->next; + R_DestroyWorldEBO(kill); + } + if (webostate->cluster[0] == r_viewcluster && webostate->cluster[1] == r_viewcluster2) { best = webostate; @@ -3331,6 +3387,7 @@ void Surf_DrawWorld (void) batch->ebobatch = currentmodel->numbatches; currentmodel->numbatches++; } + /*TODO submodels too*/ } webogeneratingstate = true; webogenerating = BZ_Malloc(sizeof(*webogenerating) + sizeof(webogenerating->batches[0]) * (currentmodel->numbatches-1) + currentmodel->pvsbytes); @@ -3387,6 +3444,10 @@ void Surf_DrawWorld (void) { entvis = surfvis = webostate->pvs.buffer; + webostate->lastvalid = cls.framecount; + + r_dynamic.ival = -1; //don't waste time on dlighting models. + RSpeedEnd(RSPEED_WORLDNODE); areas[0] = 1; @@ -3565,11 +3626,7 @@ void Surf_DeInit(void) for (i = 0; i < numlightmaps; i++) { - if (!lightmap[i]) - continue; - if (!lightmap[i]->external) - Image_DestroyTexture(lightmap[i]->lightmap_texture); - BZ_Free(lightmap[i]); + Surf_FreeLightmap(lightmap[i]); lightmap[i] = NULL; } @@ -3738,6 +3795,25 @@ uploadfmt_t Surf_LightmapMode(model_t *model) return fmt; } +static void Surf_FreeLightmap(lightmapinfo_t *lm) +{ + if (lm) + { +#ifdef GLQUAKE + if (lm->pbo_handle) + { + qglBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, lm->pbo_handle); + qglUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); + qglBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + qglDeleteBuffersARB(1, &lm->pbo_handle); + } +#endif + if (!lm->external) + Image_DestroyTexture(lm->lightmap_texture); + BZ_Free(lm); + } +} + //needs to be followed by a BE_UploadAllLightmaps at some point int Surf_NewLightmaps(int count, int width, int height, uploadfmt_t fmt, qboolean deluxe) { @@ -3774,31 +3850,69 @@ int Surf_NewLightmaps(int count, int width, int height, uploadfmt_t fmt, qboolea i = numlightmaps + count; lightmap = BZ_Realloc(lightmap, sizeof(*lightmap)*(i)); - while(i > first) + while(i --> first) { - i--; +#ifdef GLQUAKE + extern cvar_t gl_pbolightmaps; + //we might as well use a pbo for our staging memory. + if (qrenderer == QR_OPENGL && qglBufferStorage && qglMapBufferRange && gl_pbolightmaps.ival && Sys_IsMainThread()) + { //glBufferStorage and GL_MAP_PERSISTENT_BIT generally means gl4.4+ + //pbos are 2.1 + if (deluxe && ((i - numlightmaps)&1)) + { + lightmap[i] = Z_Malloc(sizeof(*lightmap[i])); + lightmap[i]->width = width; + lightmap[i]->height = height; + lightmap[i]->lightmaps = NULL; + lightmap[i]->stainmaps = NULL; + lightmap[i]->hasdeluxe = false; + lightmap[i]->pixbytes = dpixbytes; + lightmap[i]->fmt = dfmt; + } + else + { + lightmap[i] = Z_Malloc(sizeof(*lightmap[i]) + (sizeof(stmap)*3)*width*height); + lightmap[i]->width = width; + lightmap[i]->height = height; + lightmap[i]->lightmaps = NULL; + lightmap[i]->stainmaps = (qbyte*)(lightmap[i]+1); + lightmap[i]->hasdeluxe = deluxe; + lightmap[i]->pixbytes = pixbytes; + lightmap[i]->fmt = fmt; + } - if (deluxe && ((i - numlightmaps)&1)) - { //deluxemaps always use a specific format. - lightmap[i] = Z_Malloc(sizeof(*lightmap[i]) + (sizeof(qbyte)*dpixbytes)*width*height); - lightmap[i]->width = width; - lightmap[i]->height = height; - lightmap[i]->lightmaps = (qbyte*)(lightmap[i]+1); - lightmap[i]->stainmaps = NULL; - lightmap[i]->hasdeluxe = false; - lightmap[i]->pixbytes = dpixbytes; - lightmap[i]->fmt = dfmt; + qglGenBuffersARB(1, &lightmap[i]->pbo_handle); + qglBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, lightmap[i]->pbo_handle); + //note: we only write the memory. the pbo would normally be in system memory anyway so there shouldn't be too much cost from coherent mappings. + qglBufferStorage(GL_PIXEL_UNPACK_BUFFER_ARB, lightmap[i]->pixbytes*width*height, NULL, GL_MAP_WRITE_BIT|GL_MAP_PERSISTENT_BIT|GL_MAP_COHERENT_BIT); + lightmap[i]->lightmaps = qglMapBufferRange(GL_PIXEL_UNPACK_BUFFER_ARB, 0, lightmap[i]->pixbytes*width*height, GL_MAP_WRITE_BIT|GL_MAP_PERSISTENT_BIT|GL_MAP_COHERENT_BIT); + qglBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0); } else +#endif { - lightmap[i] = Z_Malloc(sizeof(*lightmap[i]) + (sizeof(qbyte)*pixbytes + sizeof(stmap)*3)*width*height); - lightmap[i]->width = width; - lightmap[i]->height = height; - lightmap[i]->lightmaps = (qbyte*)(lightmap[i]+1); - lightmap[i]->stainmaps = (stmap*)(lightmap[i]->lightmaps+pixbytes*width*height); - lightmap[i]->hasdeluxe = deluxe; - lightmap[i]->pixbytes = pixbytes; - lightmap[i]->fmt = fmt; + if (deluxe && ((i - numlightmaps)&1)) + { //deluxemaps always use a specific format. + lightmap[i] = Z_Malloc(sizeof(*lightmap[i]) + (sizeof(qbyte)*dpixbytes)*width*height); + lightmap[i]->width = width; + lightmap[i]->height = height; + lightmap[i]->lightmaps = (qbyte*)(lightmap[i]+1); + lightmap[i]->stainmaps = NULL; + lightmap[i]->hasdeluxe = false; + lightmap[i]->pixbytes = dpixbytes; + lightmap[i]->fmt = dfmt; + } + else + { + lightmap[i] = Z_Malloc(sizeof(*lightmap[i]) + (sizeof(qbyte)*pixbytes + sizeof(stmap)*3)*width*height); + lightmap[i]->width = width; + lightmap[i]->height = height; + lightmap[i]->lightmaps = (qbyte*)(lightmap[i]+1); + lightmap[i]->stainmaps = (stmap*)(lightmap[i]->lightmaps+pixbytes*width*height); + lightmap[i]->hasdeluxe = deluxe; + lightmap[i]->pixbytes = pixbytes; + lightmap[i]->fmt = fmt; + } } lightmap[i]->rectchange.l = 0; @@ -4141,12 +4255,7 @@ void Surf_BuildLightmaps (void) while(numlightmaps > 0) { numlightmaps--; - if (!lightmap[numlightmaps]) - continue; - - if (!lightmap[numlightmaps]->external) - Image_DestroyTexture(lightmap[numlightmaps]->lightmap_texture); - BZ_Free(lightmap[numlightmaps]); + Surf_FreeLightmap(lightmap[numlightmaps]); lightmap[numlightmaps] = NULL; } @@ -4199,7 +4308,7 @@ void Surf_NewMap (void) if (cl.worldmodel) - COM_StripExtension(COM_SkipPath(cl.worldmodel->name), namebuf, sizeof(namebuf)); + COM_FileBase(cl.worldmodel->name, namebuf, sizeof(namebuf)); else *namebuf = '\0'; Cvar_Set(&host_mapname, namebuf); diff --git a/engine/client/render.h b/engine/client/render.h index 13a0a40f..31a60f2a 100644 --- a/engine/client/render.h +++ b/engine/client/render.h @@ -378,6 +378,9 @@ typedef struct { glRect_t rectchange; qbyte *lightmaps; //[pixbytes*LMBLOCK_WIDTH*LMBLOCK_HEIGHT]; stmap *stainmaps; //[3*LMBLOCK_WIDTH*LMBLOCK_HEIGHT]; //rgb no a. added to lightmap for added (hopefully) speed. +#ifdef GLQUAKE + int pbo_handle; //when set, lightmaps is a persistently mapped write-only pbo for us to scribble data into, ready to be copied to the actual texture without waiting for glTexSubImage to complete. +#endif } lightmapinfo_t; extern lightmapinfo_t **lightmap; extern int numlightmaps; @@ -387,6 +390,7 @@ void QDECL Surf_RebuildLightmap_Callback (struct cvar_s *var, char *oldvalue); void R_SkyShutdown(void); void R_SetSky(const char *skyname); +texid_t R_GetDefaultEnvmap(void); #if defined(GLQUAKE) void GLR_Init (void); @@ -653,6 +657,7 @@ extern cvar_t r_lavastyle; extern cvar_t r_slimestyle; extern cvar_t r_telestyle; extern cvar_t r_dynamic; +extern cvar_t r_temporalscenecache; extern cvar_t r_novis; extern cvar_t r_netgraph; extern cvar_t r_deluxemapping_cvar; @@ -672,6 +677,7 @@ extern cvar_t r_xflip; extern cvar_t gl_mindist, gl_maxdist; extern cvar_t r_clear; +extern cvar_t r_clearcolour; extern cvar_t gl_poly; extern cvar_t gl_affinemodels; extern cvar_t r_renderscale; diff --git a/engine/client/renderer.c b/engine/client/renderer.c index cc346106..d8b99873 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -27,6 +27,7 @@ int rquant[RQUANT_MAX]; void R_InitParticleTexture (void); void R_RestartRenderer (rendererstate_t *newr); +static void R_UpdateRendererOpts(void); qboolean vid_isfullscreen; @@ -112,7 +113,7 @@ cvar_t cl_cursorbiasx = CVAR ("cl_cursor_bias_x", "0.0"); cvar_t cl_cursorbiasy = CVAR ("cl_cursor_bias_y", "0.0"); #ifdef QWSKINS -cvar_t gl_nocolors = CVARF ("gl_nocolors", "0", CVAR_ARCHIVE); +cvar_t gl_nocolors = CVARFD ("gl_nocolors", "0", CVAR_ARCHIVE, "Ignores player colours and skins, reducing texture memory usage at the cost of not knowing whether you're killing your team mates."); #endif cvar_t gl_part_flame = CVARFD ("gl_part_flame", "1", CVAR_ARCHIVE, "Enable particle emitting from models. Mainly used for torch and flame effects."); @@ -141,7 +142,7 @@ cvar_t r_bloodstains = CVARF ("r_bloodstains", "1", CVAR_ARCHIVE); cvar_t r_bouncysparks = CVARFD ("r_bouncysparks", "1", CVAR_ARCHIVE, "Enables particle interaction with world surfaces, allowing for bouncy particles, stains, and decals."); -cvar_t r_drawentities = CVAR ("r_drawentities", "1"); +cvar_t r_drawentities = CVARFD ("r_drawentities", "1", CVAR_CHEAT, "Controls whether to draw entities or not.\n0: Draw no entities.\n1: Draw everything as normal.\n2: Draw everything but bmodels.\n3: Draw bmodels only."); cvar_t r_max_gpu_bones = CVARD ("r_max_gpu_bones", "", "Specifies the maximum number of bones that can be handled on the GPU. If empty, will guess."); cvar_t r_drawflat = CVARAF ("r_drawflat", "0", "gl_textureless", CVAR_ARCHIVE | CVAR_SEMICHEAT | CVAR_RENDERERCALLBACK | CVAR_SHADERSYSTEM); @@ -157,7 +158,8 @@ cvar_t r_refractreflect_scale = CVARD ("r_refractreflect_scale", "0.5", "Use cvar_t r_drawviewmodel = CVARF ("r_drawviewmodel", "1", CVAR_ARCHIVE); cvar_t r_drawviewmodelinvis = CVAR ("r_drawviewmodelinvis", "0"); cvar_t r_dynamic = CVARFD ("r_dynamic", IFMINIMAL("0","1"), - CVAR_ARCHIVE, "-1: the engine will use only pvs to determine which surfaces are visible. This can significantly reduce CPU time, but only if there are many surfaces with few textures visible from the camera.\n0: no standard dlights at all.\n1: coloured dlights will be used, they may show through walls. These are not realtime things.\n2: The dlights will be forced to monochrome (this does not affect coronas/flashblends/rtlights attached to the same light)."); + CVAR_ARCHIVE, "0: no standard dlights at all.\n1: coloured dlights will be used, they may show through walls. These are not realtime things.\n2: The dlights will be forced to monochrome (this does not affect coronas/flashblends/rtlights attached to the same light)."); +cvar_t r_temporalscenecache = CVARFD ("r_temporalscenecache", "0", CVAR_ARCHIVE, "Controls whether to generate+reuse a scene cache over multiple frames. This is generated on a separate thread to avoid any associated costs. This can significantly boost framerates on complex maps, but can also stress the gpu more (performance tradeoff that varies per map). An outdated cache may be used if the cache takes too long to build (eg: lightmap animations), which could cause the odd glitch when moving fast (but retain more consistent framerates - another tradeoff).\n0: Tranditional quake rendering.\n1: Generate+Use the scene cache."); cvar_t r_fastturb = CVARF ("r_fastturb", "0", CVAR_SHADERSYSTEM); cvar_t r_fastsky = CVARF ("r_fastsky", "0", @@ -296,7 +298,7 @@ cvar_t vid_conwidth = CVARF ("vid_conwidth", "0", //see R_RestartRenderer_f for the effective default 'if (newr.renderer == -1)'. cvar_t vid_renderer = CVARFD ("vid_renderer", "", CVAR_ARCHIVE | CVAR_VIDEOLATCH, "Specifies which backend is used. Values that might work are: sv (dedicated server), headless (null renderer), vk (vulkan), gl (opengl), egl (opengl es), d3d9 (direct3d 9), d3d11 (direct3d 11, with default hardware rendering), d3d11 warp (direct3d 11, with software rendering)."); -cvar_t vid_renderer_opts = CVARFD ("_vid_renderer_opts", "", CVAR_NOSET, "The possible video renderer apis, in \"value\" \"description\" pairs, for gamecode to read."); +cvar_t vid_renderer_opts = CVARFD ("_vid_renderer_opts", NULL, CVAR_NOSET, "The possible video renderer apis, in \"value\" \"description\" pairs, for gamecode to read."); cvar_t vid_bpp = CVARFD ("vid_bpp", "0", CVAR_ARCHIVE | CVAR_VIDEOLATCH, "The number of colour bits to request from the renedering context"); @@ -369,8 +371,9 @@ cvar_t vid_gl_context_robustness = CVARD ("vid_gl_context_robustness", "1", "A cvar_t vid_gl_context_selfreset = CVARD ("vid_gl_context_selfreset", "1", "Upon hardware failure, have the engine create a new context instead of depending on the drivers to restore everything. This can help to avoid graphics drivers randomly killing your game, and can help reduce memory requirements."); cvar_t vid_gl_context_noerror = CVARD ("vid_gl_context_noerror", "", "Disables OpenGL's error checks for a small performance speedup. May cause segfaults if stuff wasn't properly implemented/tested."); -cvar_t gl_immutable_textures = CVARD ("gl_immutable_textures", "1", "Controls whether to use immutable GPU memory allocations for OpenGL textures. This potentially means less work for the drivers and thus higher framerates."); -cvar_t gl_immutable_buffers = CVARD ("gl_immutable_buffers", "1", "Controls whether to use immutable GPU memory allocations for static OpenGL vertex buffers. This potentially means less work for the drivers and thus higher framerates."); +cvar_t gl_immutable_textures = CVARFD ("gl_immutable_textures", "1", CVAR_VIDEOLATCH, "Controls whether to use immutable GPU memory allocations for OpenGL textures. This potentially means less work for the drivers and thus higher framerates."); +cvar_t gl_immutable_buffers = CVARFD ("gl_immutable_buffers", "1", CVAR_VIDEOLATCH, "Controls whether to use immutable GPU memory allocations for static OpenGL vertex buffers. This potentially means less work for the drivers and thus higher framerates."); +cvar_t gl_pbolightmaps = CVARFD ("gl_pbolightmaps", "1", CVAR_RENDERERLATCH, "Controls whether to use PBOs for streaming lightmap updates. This prevents CPU stalls while the driver reads out the lightmap data (lightmap updates are still not free though)."); #endif #if 1 @@ -409,6 +412,7 @@ cvar_t gl_load24bit = CVARF ("gl_load24bit", "1", cvar_t r_clear = CVARAF("r_clear","0", "gl_clear", 0); +cvar_t r_clearcolour = CVARAF("r_clearcolour", "0.12 0.12 0.12", "r_clearcolor"/*american spelling*/, 0); cvar_t gl_max_size = CVARFD ("gl_max_size", "8192", CVAR_RENDERERLATCH, "Specifies the maximum texture size that the engine may use. Textures larger than this will be downsized. Clamped by the value the driver supports."); cvar_t gl_menutint_shader = CVARD ("gl_menutint_shader", "1", "Controls the use of GLSL to desaturate the background when drawing the menu, like quake's dos software renderer used to do before the ugly dithering of winquake."); @@ -506,7 +510,7 @@ cvar_t vid_hardwaregamma = CVARFD ("vid_hardwaregamma", "1", cvar_t vid_desktopgamma = CVARFD ("vid_desktopgamma", "0", CVAR_ARCHIVE | CVAR_RENDERERLATCH, "Apply gamma ramps upon the desktop rather than the window."); -cvar_t r_fog_cullentities = CVARD ("r_fog_cullentities", "1", "Automatically cull entities according to fog."); +cvar_t r_fog_cullentities = CVARD ("r_fog_cullentities", "1", "0: Never cull entities by fog...\n1: Automatically cull entities according to fog.\n2: Force fog culling regardless "); cvar_t r_fog_exp2 = CVARD ("r_fog_exp2", "1", "Expresses how fog fades with distance. 0 (matching DarkPlaces's default) is typically more realistic, while 1 (matching FitzQuake and others) is more common."); cvar_t r_fog_permutation = CVARFD ("r_fog_permutation", "1", CVAR_SHADERSYSTEM, "Renders fog using a material permutation. 0 plays nicer with q3 shaders, but 1 is otherwise a better choice."); @@ -545,7 +549,7 @@ void GLRenderer_Init(void) Cvar_Register (&gl_immutable_textures, GLRENDEREROPTIONS); Cvar_Register (&gl_immutable_buffers, GLRENDEREROPTIONS); - + Cvar_Register (&gl_pbolightmaps, GLRENDEREROPTIONS); //renderer Cvar_Register (&gl_affinemodels, GLRENDEREROPTIONS); @@ -806,26 +810,9 @@ void Renderer_Init(void) #endif Cvar_Register (&in_windowed_mouse, VIDCOMMANDGROUP); Cvar_Register (&vid_renderer, VIDCOMMANDGROUP); - vid_renderer_opts.enginevalue = -#ifdef GLQUAKE - "gl \"OpenGL\" " -#endif -#ifdef VKQUAKE - "vk \"Vulkan\" " -#endif -#ifdef D3D8QUAKE -// "d3d8 \"Direct3D 8\" " -#endif -#ifdef D3D9QUAKE - "d3d9 \"Direct3D 9\" " -#endif -#ifdef D3D11QUAKE - "d3d11 \"Direct3D 11\" " -#endif -#ifdef SWQUAKE - "sw \"Software Rendering\" " -#endif - ""; + + R_UpdateRendererOpts(); + Cvar_Register (&vid_renderer_opts, VIDCOMMANDGROUP); Cvar_Register (&vid_fullscreen, VIDCOMMANDGROUP); @@ -986,6 +973,7 @@ void Renderer_Init(void) Cvar_Register (&r_speeds, SCREENOPTIONS); Cvar_Register (&r_netgraph, SCREENOPTIONS); + Cvar_Register (&r_temporalscenecache, GRAPHICALNICETIES); Cvar_Register (&r_dynamic, GRAPHICALNICETIES); Cvar_Register (&r_lightmap_saturation, GRAPHICALNICETIES); @@ -1006,6 +994,7 @@ void Renderer_Init(void) Cvar_Register (&gl_blendsprites, GLRENDEREROPTIONS); Cvar_Register (&r_clear, GLRENDEREROPTIONS); + Cvar_Register (&r_clearcolour, GLRENDEREROPTIONS); Cvar_Register (&gl_max_size, GLRENDEREROPTIONS); Cvar_Register (&gl_maxdist, GLRENDEREROPTIONS); Cvar_Register (&gl_texturemode, GLRENDEREROPTIONS); @@ -1871,6 +1860,9 @@ TRACE(("dbg: R_ApplyRenderer: efrags\n")); Shader_DoReload(); CSQC_RendererRestarted(); #endif +#ifdef MENU_DAT + MP_RendererRestarted(); +#endif if (newr && qrenderer != QR_NONE) { @@ -2335,12 +2327,35 @@ void R_SetRenderer_f (void) R_RestartRenderer(&newr); } +static void R_UpdateRendererOpts(void) +{ + char *v = NULL; + size_t i; + struct sortedrenderers_s sorted[countof(rendererinfo)]; + for (i = 0; i < countof(sorted); i++) + { + sorted[i].index = i; + sorted[i].r = rendererinfo[i]; + sorted[i].pri = R_PriorityForRenderer(sorted[i].r); + } + qsort(sorted, countof(sorted), sizeof(sorted[0]), R_SortRenderers); + v = NULL; + for (i = 0; i < countof(rendererinfo); i++) + { + rendererinfo_t *r = sorted[i].r; + if (r && r->description) + { + if (r->rtype == QR_HEADLESS || r->rtype == QR_NONE) + continue; //skip these, they're kinda dangerous. + Z_StrCat(&v, va("%s \"%s\" ", r->name[0], r->description)); + } + } - - - + Z_Free(vid_renderer_opts.enginevalue); + vid_renderer_opts.enginevalue = v; +} @@ -2552,6 +2567,7 @@ texture_t *R_TextureAnimation_Q2 (texture_t *base) unsigned int r_viewcontents; //mleaf_t *r_viewleaf, *r_oldviewleaf; //mleaf_t *r_viewleaf2, *r_oldviewleaf2; +int r_viewarea; int r_viewcluster, r_viewcluster2, r_oldviewcluster, r_oldviewcluster2; int r_visframecount; mleaf_t *r_vischain; // linked list of visible leafs @@ -3057,7 +3073,7 @@ void R_SetFrustum (float projmat[16], float viewmat[16]) //do far plane //fog will logically not actually reach 0, though precision issues will force it. we cut off at an exponant of -500 - if (r_refdef.globalfog.density && r_refdef.globalfog.alpha>=1 && (r_fog_cullentities.ival&&r_skyfog.value>=1) && !r_refdef.globalfog.depthbias) + if (r_refdef.globalfog.density && r_refdef.globalfog.alpha>=1 && (r_fog_cullentities.ival==2||(r_fog_cullentities.ival&&r_skyfog.value>=1)) && !r_refdef.globalfog.depthbias) { float culldist; float fog; diff --git a/engine/client/sbar.c b/engine/client/sbar.c index 17ed8a69..547940e9 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -208,7 +208,7 @@ void Draw_AltFunString(float x, float y, const void *str) } //Draws a marked up string no wider than $width virtual pixels. -void Draw_FunStringWidth(float x, float y, const void *str, int width, int rightalign, qboolean highlight) +void Draw_FunStringWidthFont(struct font_s *font, float x, float y, const void *str, int width, int rightalign, qboolean highlight) { conchar_t buffer[2048]; conchar_t *w; @@ -224,7 +224,7 @@ void Draw_FunStringWidth(float x, float y, const void *str, int width, int right codeflags |= CON_BLINKTEXT; COM_ParseFunString(codeflags, str, buffer, sizeof(buffer), false); - Font_BeginString(font_default, x, y, &px, &py); + Font_BeginString(font, x, y, &px, &py); if (rightalign) { for (w = buffer; *w; ) @@ -258,7 +258,7 @@ void Draw_FunStringWidth(float x, float y, const void *str, int width, int right return; px = Font_DrawChar(px, py, codeflags, codepoint); } - Font_EndString(font_default); + Font_EndString(font); } #ifdef QUAKEHUD diff --git a/engine/client/screen.h b/engine/client/screen.h index f3de64bd..70a0327b 100644 --- a/engine/client/screen.h +++ b/engine/client/screen.h @@ -80,6 +80,7 @@ void SCR_ShowPic_Remove_f(void); //a header is better than none... void Draw_TextBox (int x, int y, int width, int lines); +void Draw_ApproxTextBox (float x, float y, float width, float height); enum fs_relative; diff --git a/engine/client/snd_al.c b/engine/client/snd_al.c index 4d370a18..a3f440c5 100644 --- a/engine/client/snd_al.c +++ b/engine/client/snd_al.c @@ -33,11 +33,16 @@ We also have no doppler with WebAudio. //emscripten provides an openal -> webaudio wrapper. its not the best, but does get the job done. #define OPENAL_STATIC //our javascript port doesn't support dynamic linking (bss+data segments get too messy). #define SDRVNAME "WebAudio" //IE doesn't support webaudio, resulting in noticable error messages about no openal, which is technically incorrect. So lets be clear about this. - qboolean firefoxstaticsounds; //FireFox bugs out with static sounds. they all end up full volume AND THIS IS REALLY LOUD AND REALLY ANNOYING. + #define SDRVNAMEDESC "WebAudio:" #else #define SDRVNAME "OpenAL" + #define SDRVNAMEDESC "OAL:" #define USEEFX #endif +#ifndef HAVE_MIXER + #undef SDRVNAMEDESC + #define SDRVNAMEDESC "" //remove the prefixes in user-visible desciptions when there's (probably) no other devices anyway +#endif #ifdef OPENAL_STATIC #include //output @@ -1532,11 +1537,7 @@ static qboolean QDECL OpenAL_Enumerate(void (QDECL *callback)(const char *driver devnames = palcGetString(NULL, ALC_DEVICE_SPECIFIER); while(*devnames) { -#ifdef FTE_TARGET_WEB - callback(SDRVNAME, devnames, va("WebAudio:%s", devnames)); -#else - callback(SDRVNAME, devnames, va("OAL:%s", devnames)); -#endif + callback(SDRVNAME, devnames, va(SDRVNAMEDESC"%s", devnames)); devnames += strlen(devnames)+1; } return true; @@ -1586,7 +1587,7 @@ static qboolean QDECL OPENAL_Capture_Enumerate (void (QDECL *callback) (const ch devnames = palcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); while(*devnames) { - callback(SDRVNAME, devnames, va("OAL:%s", devnames)); + callback(SDRVNAME, devnames, va(SDRVNAMEDESC"%s", devnames)); devnames += strlen(devnames)+1; } return true; diff --git a/engine/client/snd_alsa.c b/engine/client/snd_alsa.c index 9d956cde..b7490226 100755 --- a/engine/client/snd_alsa.c +++ b/engine/client/snd_alsa.c @@ -540,7 +540,7 @@ static qboolean QDECL ALSA_Enumerate(void (QDECL *cb) (const char *drivername, c { char *d = psnd_device_name_get_hint(hints[i], "DESC"); if (d) - cb(SDRVNAME, n, va("ALSA (%s)", d)); + cb(SDRVNAME, n, va("ALSA:%s", d)); else cb(SDRVNAME, n, n); free(d); diff --git a/engine/client/snd_mem.c b/engine/client/snd_mem.c index 67e5e73a..8ad12a5c 100644 --- a/engine/client/snd_mem.c +++ b/engine/client/snd_mem.c @@ -746,11 +746,11 @@ static qboolean QDECL S_LoadWavSound (sfx_t *s, qbyte *data, size_t datalen, int return false; } - if (info.format == 1 && info.width == 1) + if (info.format == 1 && info.width == 1) //unsigned bytes COM_CharBias(data + info.dataofs, info.samples*info.numchannels); - else if (info.format == 1 && info.width == 2) + else if (info.format == 1 && info.width == 2) //signed shorts COM_SwapLittleShortBlock((short *)(data + info.dataofs), info.samples*info.numchannels); - else if (info.format == 3 && info.width == 4) + else if (info.format == 3 && info.width == 4) //signed floats { S_ShortedLittleFloats(data + info.dataofs, info.samples*info.numchannels); info.width = 2; @@ -760,11 +760,12 @@ static qboolean QDECL S_LoadWavSound (sfx_t *s, qbyte *data, size_t datalen, int s->loadstate = SLS_FAILED; switch(info.format) { - case 1: - case 3: Con_Printf ("%s has an unsupported width (%i bits).\n", s->name, info.width*8); break; - case 6: Con_Printf ("%s uses unsupported a-law format.\n", s->name); break; - case 7: Con_Printf ("%s uses unsupported mu-law format.\n", s->name); break; - default: Con_Printf ("%s has an unsupported format.\n", s->name); break; + case 1/*WAVE_FORMAT_PCM*/: + case 3/*WAVE_FORMAT_IEEE_FLOAT*/: Con_Printf ("%s has an unsupported width (%i bits).\n", s->name, info.width*8); break; + case 6/*WAVE_FORMAT_ALAW*/: Con_Printf ("%s uses unsupported a-law format.\n", s->name); break; + case 7/*WAVE_FORMAT_MULAW*/: Con_Printf ("%s uses unsupported mu-law format.\n", s->name); break; + case 0xfffe/*WAVE_FORMAT_EXTENSIBLE*/: + default: Con_Printf ("%s has an unsupported format (%#x).\n", s->name, info.format); break; } return false; } diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index ba470ede..48dc662f 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -170,6 +170,17 @@ void Sys_Printf (char *fmt, ...) unsigned int codeflags, codepoint; FILE *out = stdout; +#ifdef SUBSERVERS + if (SSV_IsSubServer()) + { + va_start (argptr,fmt); + vsnprintf (text,sizeof(text)-1, fmt,argptr); + va_end (argptr); + SSV_PrintToMaster(text); + return; + } +#endif + if (nostdout) { #ifdef _DEBUG @@ -1269,7 +1280,7 @@ void Sys_Clipboard_PasteText(clipboardtype_t cbt, void (*callback)(void *cb, cha callback(ctx, clipboard_buffer); } -void Sys_SaveClipboard(clipboardtype_t cbt, char *text) { +void Sys_SaveClipboard(clipboardtype_t cbt, const char *text) { Q_strncpyz(clipboard_buffer, text, SYS_CLIPBOARD_SIZE); } #endif diff --git a/engine/client/textedit.c b/engine/client/textedit.c index 989a96b9..0e04f117 100644 --- a/engine/client/textedit.c +++ b/engine/client/textedit.c @@ -832,16 +832,16 @@ qboolean Con_Editor_Key(console_t *con, unsigned int unicode, int key) Q_snprintfz(con->title, sizeof(con->title), "MODIFIED: %s", con->name); return true; } -void Con_Editor_CloseCallback(void *ctx, int op) +void Con_Editor_CloseCallback(void *ctx, promptbutton_t op) { console_t *con = ctx; if (con != con_curwindow) //ensure that it still exists (lame only-active-window check) return; - if (op == 0) + if (op == PROMPT_YES) Con_Editor_Save(con); - if (op != -1) //-1 == cancel + if (op != PROMPT_CANCEL) Con_Destroy(con); } qboolean Con_Editor_Close(console_t *con, qboolean force) diff --git a/engine/client/zqtp.c b/engine/client/zqtp.c index 972be50b..fd1457bb 100644 --- a/engine/client/zqtp.c +++ b/engine/client/zqtp.c @@ -45,8 +45,8 @@ typedef qboolean qbool; #define strlcpy Q_strncpyz #define strlcat Q_strncatz -#define Q_stricmp stricmp -#define Q_strnicmp strnicmp +#define Q_stricmp strcasecmp +#define Q_strnicmp strncasecmp extern int cl_spikeindex, cl_playerindex, cl_h_playerindex, cl_flagindex, cl_rocketindex, cl_grenadeindex, cl_gib1index, cl_gib2index, cl_gib3index; @@ -82,20 +82,8 @@ static void QDECL TP_EnemyColor_CB (struct cvar_s *var, char *oldvalue); #define TP_SKIN_CVARS #endif - -//a list of all the cvars -//this is down to the fact that I keep defining them but forgetting to register. :/ -#define TP_CVARS \ - TP_SKIN_CVARS \ - TP_CVAR(cl_fakename, ""); \ - TP_CVAR(cl_parseSay, "1"); \ - TP_CVAR(cl_parseFunChars, "1"); \ - TP_CVAR(cl_triggers, "1"); \ - TP_CVAR(tp_autostatus, ""); /* things which will not always change, but are useful */ \ - TP_CVAR(tp_forceTriggers, "0"); \ - TP_CVAR(tp_loadlocs, "1"); \ - TP_CVAR(tp_soundtrigger, "~"); \ - \ +#ifdef QUAKESTATS +#define TP_NAME_CVARS \ TP_CVAR(tp_name_none, ""); \ TP_CVAR(tp_name_axe, "axe"); \ TP_CVAR(tp_name_sg, "sg"); \ @@ -121,7 +109,6 @@ static void QDECL TP_EnemyColor_CB (struct cvar_s *var, char *oldvalue); TP_CVAR(tp_name_backpack, "pack"); \ TP_CVAR(tp_name_flag, "flag"); \ TP_CVAR(tp_name_nothing, "nothing"); \ - TP_CVAR(tp_name_someplace, "someplace"); \ TP_CVAR(tp_name_at, "at"); \ TP_CVAR(tp_need_ra, "50"); \ TP_CVAR(tp_need_ya, "50"); \ @@ -173,7 +160,26 @@ static void QDECL TP_EnemyColor_CB (struct cvar_s *var, char *oldvalue); TP_CVAR(loc_name_quad, "quad"); \ TP_CVAR(loc_name_pent, "pent"); \ TP_CVAR(loc_name_ring, "ring"); \ - TP_CVAR(loc_name_suit, "suit") + TP_CVAR(loc_name_suit, "suit"); +#else + #define TP_NAME_CVARS +#endif + +//a list of all the cvars +//this is down to the fact that I keep defining them but forgetting to register. :/ +#define TP_CVARS \ + TP_SKIN_CVARS \ + TP_NAME_CVARS \ + TP_CVAR(cl_fakename, ""); \ + TP_CVAR(cl_parseSay, "1"); \ + TP_CVAR(cl_parseFunChars, "1"); \ + TP_CVAR(cl_triggers, "1"); \ + TP_CVAR(tp_autostatus, ""); /* things which will not always change, but are useful */ \ + TP_CVAR(tp_forceTriggers, "0"); \ + TP_CVAR(tp_loadlocs, "1"); \ + TP_CVAR(tp_soundtrigger, "~"); \ + \ + TP_CVAR(tp_name_someplace, "someplace") //create the globals for all the TP cvars. #define TP_CVAR(name,def) cvar_t name = CVAR(#name, def) @@ -186,13 +192,12 @@ TP_CVARS; extern cvar_t host_mapname; -void TP_UpdateAutoStatus(void); +#define MAX_LOC_NAME 48 + +#ifdef QUAKESTATS static void TP_FindModelNumbers (void); static void TP_FindPoint (void); - -#define MAX_LOC_NAME 48 - // this structure is cleared after entering a new map typedef struct tvars_s { char autoteamstatus[256]; @@ -229,6 +234,8 @@ typedef struct tvars_s { float lastdrop_time; + char lasttrigger_match[256]; + enum { POINT_TYPE_ENEMY, POINT_TYPE_TEAMMATE, @@ -238,7 +245,8 @@ typedef struct tvars_s { float pointtime; } tvars_t; -tvars_t vars; +static tvars_t vars; +#endif typedef struct item_vis_s { @@ -298,6 +306,120 @@ void TP_ExecTrigger (char *s, qboolean indemos) } } +/* +========================================================================== + HELPER FUNCTIONS +========================================================================== +*/ +static int TP_CountPlayers (void) +{ + int i, count; + + count = 0; + for (i = 0; i < cl.allocated_client_slots ; i++) { + if (cl.players[i].name[0] && !cl.players[i].spectator) + count++; + } + + return count; +} + +static char *TP_PlayerTeam (void) +{ + return cl.players[cl.playerview[SP].playernum].team; +} + +static char *TP_EnemyTeam (void) +{ + int i; + static char enemyteam[MAX_INFO_KEY]; + char *myteam = TP_PlayerTeam(); + + for (i = 0; i < cl.allocated_client_slots ; i++) { + if (cl.players[i].name[0] && !cl.players[i].spectator) + { + strcpy (enemyteam, cl.players[i].team); + if (strcmp(myteam, cl.players[i].team) != 0) + return enemyteam; + } + } + return ""; +} + +static char *TP_PlayerName (void) +{ + return cl.players[cl.playerview[SP].playernum].name; +} + + +static char *TP_EnemyName (void) +{ + int i; + char *myname; + static char enemyname[MAX_SCOREBOARDNAME]; + + myname = TP_PlayerName (); + + for (i = 0; i < cl.allocated_client_slots ; i++) { + if (cl.players[i].name[0] && !cl.players[i].spectator) + { + strcpy (enemyname, cl.players[i].name); + if (!strcmp(enemyname, myname)) + return enemyname; + } + } + return ""; +} + +static char *TP_MapName (void) +{ + return host_mapname.string; +} + +char *TP_GenerateDemoName(void) +{ + if (cl.playerview[SP].spectator) + { // FIXME: if tracking a player, use his name + return va ("spec_%s_%s", + TP_PlayerName(), + TP_MapName()); + } + else + { // guess game type and write demo name + int i = TP_CountPlayers(); + if (cl.teamplay && i >= 3) + { // Teamplay + return va ("%s_%s_vs_%s_%s", + TP_PlayerName(), + TP_PlayerTeam(), + TP_EnemyTeam(), + TP_MapName()); + } + else + { + if (i == 2) + { // Duel + return va ("%s_vs_%s_%s", + TP_PlayerName(), + TP_EnemyName(), + TP_MapName()); + } + else if (i > 2) + { // FFA + return va ("%s_ffa_%s", + TP_PlayerName(), + TP_MapName()); + } + else + { // one player + return va ("%s_%s", + TP_PlayerName(), + TP_MapName()); + } + } + } +} + /* ========================================================================== @@ -308,7 +430,7 @@ void TP_ExecTrigger (char *s, qboolean indemos) #define MAX_MACRO_VALUE 256 static char macro_buf[MAX_MACRO_VALUE] = ""; -#ifndef QUAKETC +#ifdef QUAKESTATS // buffer-size-safe helper functions //static void MacroBuf_strcat (char *str) { // strlcat (macro_buf, str, sizeof(macro_buf)); @@ -557,6 +679,7 @@ static char *Macro_Location (void) return TP_LocationName (cl.playerview[SP].simorg); } +#ifdef QUAKESTATS static char *Macro_LastDeath (void) { if (vars.deathtrigger_time) @@ -637,7 +760,7 @@ static char *Macro_PointNameAtLocation (void) return vars.pointname; } -#ifdef QUAKESTATS + static char *Macro_Need (void) { int i, weapon; @@ -713,7 +836,6 @@ done: return macro_buf; } -#endif static char *Skin_To_TFSkin (char *myskin) { @@ -853,7 +975,6 @@ static char *Macro_Point_LED(void) return macro_buf; } -#ifdef QUAKESTATS static char *Macro_MyStatus_LED(void) { int count; @@ -886,7 +1007,6 @@ static char *Macro_MyStatus_LED(void) return macro_buf; } -#endif static void CountNearbyPlayers(qboolean dead) { @@ -1042,7 +1162,7 @@ static char *Macro_LastSeenPowerup(void) return macro_buf; } -char *Macro_LastDrop (void) +static char *Macro_LastDrop (void) { if (vars.lastdrop_time) return vars.lastdroploc; @@ -1050,7 +1170,7 @@ char *Macro_LastDrop (void) return tp_name_someplace.string; } -char *Macro_LastDropTime (void) +static char *Macro_LastDropTime (void) { if (vars.lastdrop_time) Q_snprintfz (macro_buf, 32, "%d", (int) (realtime - vars.lastdrop_time)); @@ -1059,8 +1179,7 @@ char *Macro_LastDropTime (void) return macro_buf; } -#ifdef QUAKESTATS -char *Macro_CombinedHealth(void) +static char *Macro_CombinedHealth(void) { float h; float t, a, m; @@ -1091,7 +1210,7 @@ char *Macro_CombinedHealth(void) return macro_buf; } -char *Macro_Coloured_Armour(void) +static char *Macro_Coloured_Armour(void) { if (cl.playerview[SP].stats[STAT_ITEMS] & IT_ARMOR3) return "{^s^xe00%%a^r}"; @@ -1103,7 +1222,7 @@ char *Macro_Coloured_Armour(void) return "{0}"; } -char *Macro_Coloured_Powerups(void) +static char *Macro_Coloured_Powerups(void) { char *quad, *pent, *ring; quad = (cl.playerview[SP].stats[STAT_ITEMS] & IT_QUAD) ?va("^x03f%s", tp_name_quad.string):""; @@ -1118,7 +1237,7 @@ char *Macro_Coloured_Powerups(void) else return ""; } -char *Macro_Coloured_Short_Powerups(void) +static char *Macro_Coloured_Short_Powerups(void) { char *quad, *pent, *ring; quad = (cl.playerview[SP].stats[STAT_ITEMS] & IT_QUAD) ?"^x03fq":""; @@ -1133,21 +1252,36 @@ char *Macro_Coloured_Short_Powerups(void) else return ""; } -char *Macro_LastIP(void) +static char *Macro_Match_Status(void) { + if (cls.state == ca_disconnected) + return "disconnected"; + if (cls.state < ca_active) + return "connecting"; + switch(cl.matchstate) + { + case MATCH_DONTKNOW: + case MATCH_INPROGRESS: + default: + return "normal"; + case MATCH_COUNTDOWN: + return "countdown"; + case MATCH_STANDBY: + return "standby"; + } +} +/*static char *Macro_LastIP(void) +{ //report the last ip that someone said in chat. return "---"; } -char *Macro_MP3Info(void) -{ +static char *Macro_MP3Info(void) +{ //for people trying to be cool but really just annoying everyone return "---"; } -char *Macro_Match_Status(void) -{ - return "---"; -} -char *Macro_LastTrigger_Match(void) -{ - return "---"; +*/ +static char *Macro_LastTrigger_Match(void) +{ //returns the last line that triggered a msg_trigger + return vars.lasttrigger_match; } #endif @@ -1179,6 +1313,7 @@ $triggermatch is the last chat message that exec'd a msg_trigger. static void TP_InitMacros(void) { Cmd_AddMacro("latency", Macro_Latency, false); + Cmd_AddMacro("location", Macro_Location, false); #ifdef QUAKESTATS Cmd_AddMacro("health", Macro_Health, true); Cmd_AddMacro("armortype", Macro_ArmorType, true); @@ -1195,13 +1330,12 @@ static void TP_InitMacros(void) Cmd_AddMacro("bestammo", Macro_BestAmmo, true); Cmd_AddMacro("powerups", Macro_Powerups, true); Cmd_AddMacro("droppedweapon", Macro_DroppedWeapon, true); -#endif - Cmd_AddMacro("location", Macro_Location, false); + Cmd_AddMacro("tf_skin", Macro_TF_Skin, true); + Cmd_AddMacro("deathloc", Macro_LastDeath, true); Cmd_AddMacro("tookatloc", Macro_TookAtLoc, true); Cmd_AddMacro("tookloc", Macro_TookLoc, true); Cmd_AddMacro("took", Macro_Took, true); - Cmd_AddMacro("tf_skin", Macro_TF_Skin, true); //ones added by Spike, for fuhquake compatability Cmd_AddMacro("connectiontype", Macro_ConnectionType, false); @@ -1211,16 +1345,16 @@ static void TP_InitMacros(void) Cmd_AddMacro("pointloc", Macro_PointLocation, true); Cmd_AddMacro("matchname", Macro_Match_Name, false); Cmd_AddMacro("matchtype", Macro_Match_Type, false); -#ifdef QUAKESTATS Cmd_AddMacro("need", Macro_Need, true); Cmd_AddMacro("ledstatus", Macro_MyStatus_LED, true); -#endif Cmd_AddMacro("ledpoint", Macro_Point_LED, true); Cmd_AddMacro("droploc", Macro_LastDrop, true); Cmd_AddMacro("droptime", Macro_LastDropTime, true); -// Cmd_AddMacro("matchstatus", Macro_Match_Status, false); + + Cmd_AddMacro("matchstatus", Macro_Match_Status, false); + Cmd_AddMacro("triggermatch", Macro_LastTrigger_Match, false); +#endif // Cmd_AddMacro("mp3info", Macro_MP3Info, false); -// Cmd_AddMacro("triggermatch", Macro_LastTrigger_Match, false); //new, fte only (at least when first implemented) #ifdef QUAKESTATS @@ -1234,10 +1368,10 @@ static void TP_InitMacros(void) Cmd_AddMacro("colored_armor", Macro_Coloured_Armour, true); //*shudder* Cmd_AddMacro("colored_powerups", Macro_Coloured_Powerups, true); Cmd_AddMacro("colored_short_powerups", Macro_Coloured_Short_Powerups, true); -#endif - Cmd_AddMacro("gamedir", Macro_Gamedir, false); Cmd_AddMacro("lastloc", Macro_Last_Location, true); Cmd_AddMacro("lastpowerup", Macro_LastSeenPowerup, true); +#endif + Cmd_AddMacro("gamedir", Macro_Gamedir, false); } #define MAX_MACRO_STRING 1024 @@ -1321,17 +1455,16 @@ static char *TP_ParseMacroString (char *s) case 'A': macro_string = Macro_ArmorType(); break; case 'b': macro_string = Macro_BestWeaponAndAmmo(); break; case 'c': macro_string = Macro_Cells(); break; -#endif case 'd': macro_string = Macro_LastDeath(); break; // case 'D': -#ifdef QUAKESTATS case 'h': macro_string = Macro_Health(); break; -#endif case 'i': macro_string = Macro_TookAtLoc(); break; case 'j': macro_string = Macro_LastPointAtLoc(); break; case 'k': macro_string = Macro_LastTookOrPointed(); break; - case 'l': macro_string = Macro_Location(); break; case 'L': macro_string = Macro_Last_Location(); break; +#endif + case 'l': macro_string = Macro_Location(); break; +#ifdef QUAKESTATS case 'm': macro_string = Macro_LastTookOrPointed(); break; case 'o': macro_string = Macro_CountNearbyFriendlyPlayers(); break; @@ -1340,22 +1473,19 @@ static char *TP_ParseMacroString (char *s) case 'E': macro_string = Macro_Count_Last_NearbyEnemyPlayers(); break; case 'P': -#ifdef QUAKESTATS case 'p': macro_string = Macro_Powerups(); break; -#endif case 'q': macro_string = Macro_LastSeenPowerup(); break; // case 'r': macro_string = Macro_LastReportedLoc(); break; case 's': macro_string = Macro_EnemyStatus_LED(); break; case 'S': macro_string = Macro_TF_Skin(); break; case 't': macro_string = Macro_PointNameAtLocation(); break; -#ifdef QUAKESTATS case 'u': macro_string = Macro_Need(); break; case 'w': macro_string = Macro_WeaponAndAmmo(); break; -#endif case 'x': macro_string = Macro_PointName(); break; case 'X': macro_string = Macro_Took(); break; case 'y': macro_string = Macro_PointLocation(); break; case 'Y': macro_string = Macro_TookLoc(); break; +#endif case 'n': //vicinity case 'N': //hides from you @@ -1477,10 +1607,9 @@ typedef struct locdata_s { char name[MAX_LOC_NAME]; } locdata_t; -#define MAX_LOC_ENTRIES 4096 - -locdata_t locdata[MAX_LOC_ENTRIES]; // FIXME: allocate dynamically? -int loc_numentries; +static locdata_t *locdata; // FIXME: allocate dynamically? +static size_t loc_numentries; +static size_t loc_maxentries; static void TP_LoadLocFile (char *filename, qbool quiet) @@ -1563,8 +1692,8 @@ static void TP_LoadLocFile (char *filename, qbool quiet) max[2] = strtod(comma, &comma); if (*comma++ == ',') { - if (loc_numentries >= MAX_LOC_ENTRIES) - continue; + if (loc_numentries == loc_maxentries) + Z_ReallocElements((void**)&locdata, &loc_maxentries, loc_numentries+64, sizeof(*locdata)); loc = &locdata[loc_numentries]; loc_numentries++; @@ -1618,9 +1747,8 @@ static void TP_LoadLocFile (char *filename, qbool quiet) continue; } - if (loc_numentries >= MAX_LOC_ENTRIES) - continue; - + if (loc_numentries == loc_maxentries) + Z_ReallocElements((void**)&locdata, &loc_maxentries, loc_numentries+64, sizeof(*locdata)); loc = &locdata[loc_numentries]; loc_numentries++; @@ -1628,18 +1756,17 @@ static void TP_LoadLocFile (char *filename, qbool quiet) loc->min[i] = loc->max[i] = atoi(Cmd_Argv(i)) / 8.0; loc->name[0] = 0; - loc->name[sizeof(loc->name)-1] = 0; // can't rely on strncat for (i = 3; i < argc; i++) { if (i != 3) - strncat (loc->name, " ", sizeof(loc->name)-1); - strncat (loc->name, Cmd_Argv(i), sizeof(loc->name)-1); + Q_strncatz (loc->name, " ", sizeof(loc->name)); + Q_strncatz (loc->name, Cmd_Argv(i), sizeof(loc->name)); } } } if (!quiet) - Com_Printf ("Loaded %s (%i points)\n", fullpath, loc_numentries); + Com_Printf ("Loaded %s (%lu points)\n", fullpath, (unsigned long)loc_numentries); } static void TP_LoadLocFile_f (void) @@ -1820,6 +1947,9 @@ void TP_SearchForMsgTriggers (char *s, int level) string = Cmd_AliasExist (t->name, RESTRICT_LOCAL); if (string) { +#ifdef QUAKESTATS + Q_strncpyz(vars.lasttrigger_match, s, sizeof (vars.lasttrigger_match)); +#endif Cbuf_AddText (string, RESTRICT_LOCAL); Cbuf_AddText ("\n", RESTRICT_LOCAL); // Cbuf_ExecuteLevel (RESTRICT_LOCAL); @@ -1868,72 +1998,6 @@ ok: } }*/ - -int TP_CountPlayers (void) -{ - int i, count; - - count = 0; - for (i = 0; i < cl.allocated_client_slots ; i++) { - if (cl.players[i].name[0] && !cl.players[i].spectator) - count++; - } - - return count; -} - -char *TP_PlayerTeam (void) -{ - return cl.players[cl.playerview[SP].playernum].team; -} - -char *TP_EnemyTeam (void) -{ - int i; - static char enemyteam[MAX_INFO_KEY]; - char *myteam = TP_PlayerTeam(); - - for (i = 0; i < cl.allocated_client_slots ; i++) { - if (cl.players[i].name[0] && !cl.players[i].spectator) - { - strcpy (enemyteam, cl.players[i].team); - if (strcmp(myteam, cl.players[i].team) != 0) - return enemyteam; - } - } - return ""; -} - -char *TP_PlayerName (void) -{ - return cl.players[cl.playerview[SP].playernum].name; -} - - -char *TP_EnemyName (void) -{ - int i; - char *myname; - static char enemyname[MAX_SCOREBOARDNAME]; - - myname = TP_PlayerName (); - - for (i = 0; i < cl.allocated_client_slots ; i++) { - if (cl.players[i].name[0] && !cl.players[i].spectator) - { - strcpy (enemyname, cl.players[i].name); - if (!strcmp(enemyname, myname)) - return enemyname; - } - } - return ""; -} - -char *TP_MapName (void) -{ - return host_mapname.string; -} - #ifdef QWSKINS /* ============================================================================= @@ -2124,8 +2188,10 @@ void TP_NewMap (void) static char last_map[MAX_QPATH]; char locname[MAX_QPATH]; +#ifdef QUAKESTATS memset (&vars, 0, sizeof(vars)); TP_FindModelNumbers (); +#endif // FIXME, just try to load the loc file no matter what? if (strcmp(host_mapname.string, last_map)) @@ -2142,7 +2208,9 @@ void TP_NewMap (void) strlcpy (last_map, "", sizeof(last_map)); } +#ifdef QUAKESTATS TP_UpdateAutoStatus(); +#endif TP_ExecTrigger ("f_newmap", false); } @@ -2351,6 +2419,7 @@ int TP_CategorizeMessage (char *s, int *offset, player_info_t **plr) return flags; } +#ifdef QUAKESTATS //=================================================================== // Pickup triggers // @@ -2420,8 +2489,8 @@ static void FlagCommand (int *flags, int defaultflags) { for (i = 0 ; i < NUM_ITEMFLAGS ; i++) if (*flags & (1 << i)) { if (*str) - strncat (str, " ", sizeof(str) - strlen(str) - 1); - strncat (str, pknames[i], sizeof(str) - strlen(str) - 1); + Q_strncatz (str, " ", sizeof(str)); + Q_strncatz (str, pknames[i], sizeof(str)); } Com_Printf ("%s\n", str); return; @@ -2624,7 +2693,7 @@ static item_t tp_items[] = { { it_flag, &tp_name_flag, "progs/tf_stan.mdl", {0, 0, 45}, 40, 0 }, - { it_ra|it_ya|it_ga, NULL, "progs/armor.mdl", + { it_ra|it_ya|it_ga, &tp_name_armor, "progs/armor.mdl", //generic armour, used only when the skin number if invalid for the other types. {0, 0, 24}, 22, 0 }, { it_ga, &tp_name_ga, "progs/armor.mdl", @@ -2701,7 +2770,6 @@ static void TP_FindModelNumbers (void) } } -#ifndef QUAKETC // on success, result is non-zero // on failure, result is zero // for armors, returns skinnum+1 on success @@ -2875,11 +2943,9 @@ static void TP_ItemTaken (char *s, int flag, vec3_t org, int entnum, item_t *ite } */ } -#endif void TP_ParsePlayerInfo(player_state_t *oldstate, player_state_t *state, player_info_t *info) { -#ifndef QUAKETC playerview_t *pv = &cl.playerview[SP]; // if (TP_NeedRefreshSkins()) // { @@ -2918,12 +2984,10 @@ void TP_ParsePlayerInfo(player_state_t *oldstate, player_state_t *state, player_ strcpy (vars.lastdroploc, Macro_Location()); } } -#endif } void TP_CheckPickupSound (char *s, vec3_t org, int seat) { -#ifndef QUAKETC int entnum; item_t *item; playerview_t *pv = &cl.playerview[seat]; @@ -3031,7 +3095,6 @@ more: return; TP_ItemTaken (item->cvar->string, item->itemflag, org, entnum, item, seat); } -#endif } qboolean R_CullSphere (vec3_t org, float radius); @@ -3165,7 +3228,7 @@ static char *Utils_TF_ColorToTeam_Failsafe(int color) return (best == -1) ? "" : teams[best]; } -char *Utils_TF_ColorToTeam(int color) +static char *Utils_TF_ColorToTeam(int color) { char *s; @@ -3193,7 +3256,6 @@ char *Utils_TF_ColorToTeam(int color) return Utils_TF_ColorToTeam_Failsafe(color); } - static void TP_FindPoint (void) { packet_entities_t *pak; @@ -3377,7 +3439,6 @@ nothing: pmove.skipent = oldskip; } - void TP_UpdateAutoStatus(void) { char newstatusbuf[sizeof(vars.autoteamstatus)]; @@ -3420,7 +3481,6 @@ void TP_UpdateAutoStatus(void) void TP_StatChanged (int stat, int value) { -#ifdef QUAKESTATS playerview_t *pv = &cl.playerview[SP]; int i; if (stat == STAT_HEALTH) @@ -3486,11 +3546,11 @@ void TP_StatChanged (int stat, int value) TP_ExecTrigger ("f_weaponchange", false); vars.activeweapon = pv->stats[STAT_ACTIVEWEAPON]; } -#endif vars.stat_framecounts[stat] = cls.framecount; TP_UpdateAutoStatus(); } +#endif /* @@ -3569,8 +3629,9 @@ qbool TP_CheckSoundTrigger (char *str) } +#define MAX_FILTERS 8 #define MAX_FILTER_LENGTH 4 -static char filter_strings[8][MAX_FILTER_LENGTH+1]; +static char filter_strings[MAX_FILTERS][MAX_FILTER_LENGTH+1]; static int num_filters = 0; /* @@ -3659,7 +3720,7 @@ static void TP_MsgFilter_f (void) } strlcpy (filter_strings[num_filters], s+1, sizeof(filter_strings[0])); num_filters++; - if (num_filters >= 8) + if (num_filters >= countof(filter_strings)) break; } } @@ -3685,9 +3746,11 @@ void TP_Init (void) Cmd_AddCommand ("colorize", TP_Colourise_f); //us //Cmd_AddCommand ("colorise", TP_Colourise_f); //piss off both. #endif +#ifdef QUAKESTATS Cmd_AddCommand ("tp_took", TP_Took_f); Cmd_AddCommand ("tp_pickup", TP_Pickup_f); Cmd_AddCommand ("tp_point", TP_Point_f); +#endif TP_InitMacros(); } @@ -3698,7 +3761,10 @@ void TP_Init (void) qboolean TP_SuppressMessage(char *buf) { char *s; - playerview_t *pv = &cl.playerview[SP]; + unsigned int seat; + + if (cls.demoplayback) + return false; for (s = buf; *s && *s != 0x7f; s++) ; @@ -3707,7 +3773,10 @@ qboolean TP_SuppressMessage(char *buf) { *s++ = '\n'; *s++ = 0; - return (!cls.demoplayback && !pv->spectator && *s - 'A' == pv->playernum); + for (seat = 0; seat < cl.splitclients; seat++) + if (!cl.playerview[seat].spectator && *s - 'A' == cl.playerview[seat].playernum) + return true; + } return false; } @@ -3718,7 +3787,7 @@ void CL_Say (qboolean team, char *extra) { extern cvar_t cl_fakename; char text[2048], sendtext[2048], *s; - playerview_t *pv = &cl.playerview[SP]; + playerview_t *pv = &cl.playerview[CL_TargettedSplit(false)]; if (Cmd_Argc() < 2) { @@ -3863,7 +3932,9 @@ void CL_SayMe_f (void) void CL_SayTeam_f (void) { +#ifdef QUAKESTATS vars.autoteamstatus_time = realtime + 3; +#endif CL_Say (true, NULL); } diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 6a67612a..9da0edeb 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -81,7 +81,7 @@ cvar_t tp_disputablemacros = CVARF("tp_disputablemacros", "1", CVAR_SEMICHEAT); -#define MAX_MACROS 64 +#define MAX_MACROS 70 typedef struct { char name[32]; @@ -104,9 +104,9 @@ void Cmd_AddMacro(char *s, char *(*f)(void), int disputableintentions) if (i == MAX_MACROS) Sys_Error("Cmd_AddMacro: macro_count == MAX_MACROS"); - Q_strncpyz(macro_commands[macro_count].name, s, sizeof(macro_commands[macro_count].name)); - macro_commands[macro_count].func = f; - macro_commands[macro_count].disputableintentions = disputableintentions; + Q_strncpyz(macro_commands[i].name, s, sizeof(macro_commands[macro_count].name)); + macro_commands[i].func = f; + macro_commands[i].disputableintentions = disputableintentions; if (i == macro_count) macro_count++; @@ -4251,9 +4251,6 @@ void Cmd_Init (void) Cmd_AddCommandAD ("showalias", Cmd_ShowAlias_f, Key_Alias_c, NULL); -// Cmd_AddCommand ("msg_trigger", Cmd_Msg_Trigger_f); -// Cmd_AddCommand ("filter", Cmd_Msg_Filter_f); - Cmd_AddCommandAD ("toggle", Cmd_toggle_f, Cmd_Set_c, "Toggles a cvar between two values\ntoggle CVARNAME [newval [altval]]"); Cmd_AddCommandAD ("set", Cmd_set_f, Cmd_Set_c, "Changes the current value of the named cvar, creating it if it doesn't yet exist."); Cmd_AddCommandAD ("setfl", Cmd_set_f, Cmd_Set_c, "Changes the current value of the named cvar, creating it if it doesn't yet exist. The third arg allows setting cvar flags and should be u, s, or a. This command should normally be used only inside default.cfg."); diff --git a/engine/common/com_phys_ode.c b/engine/common/com_phys_ode.c index f70be232..74464525 100644 --- a/engine/common/com_phys_ode.c +++ b/engine/common/com_phys_ode.c @@ -2835,12 +2835,13 @@ qboolean Plug_Init(void) CHECKBUILTIN(Sys_CloseLibrary); #endif - rbefuncs = plugfuncs->GetEngineInterface("RBE", sizeof(rbeplugfuncs_t)); - if (rbefuncs && rbefuncs->version < RBEPLUGFUNCS_VERSION) + rbefuncs = plugfuncs->GetEngineInterface("RBE", sizeof(*rbefuncs)); + if (rbefuncs && ( rbefuncs->version < RBEPLUGFUNCS_VERSION || + rbefuncs->wedictsize != sizeof(wedict_t))) rbefuncs = NULL; if (!rbefuncs) { - Con_Printf("ODE plugin failed: Engine does not support external rigid body engines.\n"); + Con_Printf("ODE plugin failed: Engine is incompatible.\n"); return false; } #ifndef ODE_STATIC diff --git a/engine/common/common.c b/engine/common/common.c index b23c7edb..22f1f3df 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -103,8 +103,6 @@ cvar_t ezcompat_markup = CVARD("ezcompat_markup", "1", "Attempt compatibility wi cvar_t com_highlightcolor = CVARD("com_highlightcolor", STRINGIFY(COLOR_RED), "ANSI colour to be used for highlighted text, used when com_parseutf8 is active."); cvar_t com_nogamedirnativecode = CVARFD("com_nogamedirnativecode", "1", CVAR_NOTFROMSERVER, FULLENGINENAME" blocks all downloads of files with a .dll or .so extension, however other engines (eg: ezquake and fodquake) do not - this omission can be used to trigger delayed eremote exploits in any engine (including "DISTRIBUTION") which is later run from the same gamedir.\nQuake2, Quake3(when debugging), and KTX typically run native gamecode from within gamedirs, so if you wish to run any of these games you will need to ensure this cvar is changed to 0, as well as ensure that you don't run unsafe clients."); cvar_t sys_platform = CVAR("sys_platform", PLATFORM); -cvar_t pkg_downloads_url = CVARFD("pkg_downloads_url", NULL, CVAR_NOTFROMSERVER|CVAR_NOSAVE|CVAR_NOSET, "The URL of a package updates list."); //read from the default.fmf -cvar_t pkg_autoupdate = CVARFD("pkg_autoupdate", "-1", CVAR_NOTFROMSERVER|CVAR_NOSAVE|CVAR_NOSET, "Controls autoupdates, can only be changed via the downloads menu.\n0: off.\n1: enabled (stable only).\n2: enabled (unstable).\nNote that autoupdate will still prompt the user to actually apply the changes."); //read from the package list only. #ifdef HAVE_LEGACY cvar_t pm_noround = CVARD("pm_noround", "0", "Disables player prediction snapping, in a way that cannot be reliably predicted but may be needed to avoid map bugs."); #endif @@ -4288,6 +4286,12 @@ skipwhite: len++; } } + if (c == '\\' && data[1] == '\"') + { + if (tokentype) + *tokentype = TTP_STRING; + return COM_ParseCString(data+1, token, tokenlen, NULL); + } // parse single characters if (strchr(punctuation, c)) diff --git a/engine/common/common.h b/engine/common/common.h index 13365588..7777287f 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -583,9 +583,10 @@ char *VFS_GETS(vfsfile_t *vf, char *buffer, size_t buflen); void VARGS VFS_PRINTF(vfsfile_t *vf, const char *fmt, ...) LIKEPRINTF(2); enum fs_relative{ - FS_BINARYPATH, //for dlls and stuff + //note that many of theses paths can map to multiple system locations. FS_NativePath can vary somewhat in terms of what it returns, generally favouring writable locations rather then the path that actually contains a file. + FS_BINARYPATH, //where the 'exe' is located. we'll check here for dlls too. FS_LIBRARYPATH, //for system dlls and stuff - FS_ROOT, //./ (effective -homedir if enabled, otherwise effective -basedir arg) + FS_ROOT, //either homedir or basedir, FS_SYSTEM, //a system path. absolute paths are explicitly allowed and expected, but not required. //after this point, all types must be relative to a gamedir @@ -605,6 +606,7 @@ void FS_CreatePath(const char *pname, enum fs_relative relativeto); qboolean FS_Rename(const char *oldf, const char *newf, enum fs_relative relativeto); //0 on success, non-0 on error qboolean FS_Rename2(const char *oldf, const char *newf, enum fs_relative oldrelativeto, enum fs_relative newrelativeto); qboolean FS_Remove(const char *fname, enum fs_relative relativeto); //0 on success, non-0 on error +qboolean FS_RemoveTree(searchpathfuncs_t *pathhandle, const char *fname); qboolean FS_Copy(const char *source, const char *dest, enum fs_relative relativesource, enum fs_relative relativedest); qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out, int outlen); //if you really need to fopen yourself qboolean FS_WriteFile (const char *filename, const void *data, int len, enum fs_relative relativeto); @@ -656,7 +658,7 @@ enum manifestdeptype_e }; typedef struct { - qboolean blockupdate; //set to block the updateurl from being used this session. this avoids recursive updates when manifests contain the same update url. + char *filename; //filename the manifest was read from. not necessarily writable... NULL when the manifest is synthesised or from http. enum { MANIFEST_SECURITY_NOT, //don't trust it, don't even allow downloadsurl. @@ -679,17 +681,20 @@ typedef struct } homedirtype; char *mainconfig; //eg "fte.cfg", reducing conflicts with other engines, but can be other values... char *updateurl; //url to download an updated manifest file from. - char *updatefile; //this is the file that needs to be written to update the manifest. + qboolean blockupdate; //set to block the updateurl from being used this session. this avoids recursive updates when manifests contain the same update url. char *installation; //optional hardcoded commercial name, used for scanning the registry to find existing installs. char *formalname; //the commercial name of the game. you'll get FULLENGINENAME otherwise. +#ifdef PACKAGEMANAGER char *downloadsurl; //optional installable files (menu) char *installupd; //which download/updated package to install. +#endif char *protocolname; //the name used for purposes of dpmaster char *defaultexec; //execed after cvars are reset, to give game-specific engine-defaults. char *defaultoverrides; //execed after default.cfg, to give usable defaults even when the mod the user is running is shit. char *eula; //when running as an installer, the user will be presented with this as a prompt char *rtcbroker; //the broker to use for webrtc connections. char *basedir; //this is where we expect to find the data. + char *iconname; //path we can find the icon (relative to the fmf's location) struct { enum @@ -705,7 +710,7 @@ typedef struct } flags; char *path; } gamepath[8]; - struct manpack_s + struct manpack_s //FIXME: this struct should be replaced with packagemanager info instead. { int type; char *path; //the 'pure' name @@ -719,8 +724,9 @@ typedef struct } ftemanifest_t; extern ftemanifest_t *fs_manifest; //currently active manifest. void FS_Manifest_Free(ftemanifest_t *man); -ftemanifest_t *FS_Manifest_Parse(const char *fname, const char *data); -void PM_Shutdown(void); +ftemanifest_t *FS_Manifest_ReadMem(const char *fname, const char *basedir, const char *data); +ftemanifest_t *FS_Manifest_ReadSystem(const char *fname, const char *basedir); +void PM_Shutdown(qboolean soft); void PM_Command_f(void); qboolean PM_CanInstall(const char *packagename); @@ -737,6 +743,7 @@ struct gamepacks char *subpath; //within the package (for zips) }; void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths); +qboolean FS_GamedirIsOkay(const char *path); char *FS_GetGamedir(qboolean publicpathonly); char *FS_GetManifestArgs(void); int FS_GetManifestArgv(char **argv, int maxargs); @@ -853,6 +860,7 @@ size_t Base64_DecodeBlock(const char *in, const char *in_end, qbyte *out, size_t size_t Base16_EncodeBlock(const char *in, size_t length, qbyte *out, size_t outsize); size_t Base16_DecodeBlock(const char *in, qbyte *out, size_t outsize); +#define DIGEST_MAXSIZE (512/8) //largest valid digest size, in bytes typedef struct { unsigned int digestsize; diff --git a/engine/common/config_fteqw.h b/engine/common/config_fteqw.h index b1fb68ab..1417df40 100644 --- a/engine/common/config_fteqw.h +++ b/engine/common/config_fteqw.h @@ -181,7 +181,7 @@ // Features required by vanilla quake/quakeworld... //#define QUAKETC #define QUAKESTATS //defines STAT_HEALTH etc. if omitted, you'll need to provide that functionality yourself. -#define QUAKEHUD //support for drawing the vanilla hud. +#define QUAKEHUD //support for drawing the vanilla hud. disable this if you're always going to be using csqc (or equivelent) #define QWSKINS //disabling this means no qw .pcx skins nor enemy/team skin/colour forcing //#define NOBUILTINMENUS //#define NOLEGACY //just spike trying to kill off crappy crap... diff --git a/engine/common/fs.c b/engine/common/fs.c index 95256eb9..7b40a01d 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -27,7 +27,7 @@ float fs_accessed_time; //timestamp of read (does not include flocates, which sh cvar_t com_fs_cache = CVARF("fs_cache", IFMINIMAL("2","1"), CVAR_ARCHIVE); cvar_t fs_noreexec = CVARD("fs_noreexec", "0", "Disables automatic re-execing configs on gamedir switches.\nThis means your cvar defaults etc may be from the wrong mod, and cfg_save will leave that stuff corrupted!"); cvar_t cfg_reload_on_gamedir = CVAR("cfg_reload_on_gamedir", "1"); -cvar_t fs_game = CVARFCD("game", "", CVAR_NOSAVE|CVAR_NORESET, fs_game_callback, "Provided for Q2 compat."); +cvar_t fs_game = CVARAFCD("fs_game"/*q3*/, "", "game"/*q2/qs*/, CVAR_NOSAVE|CVAR_NORESET, fs_game_callback, "Provided for Q2 compat."); cvar_t fs_gamedir = CVARFD("fs_gamedir", "", CVAR_NOUNSAFEEXPAND|CVAR_NOSET|CVAR_NOSAVE, "Provided for Q2 compat."); cvar_t fs_basedir = CVARFD("fs_basedir", "", CVAR_NOUNSAFEEXPAND|CVAR_NOSET|CVAR_NOSAVE, "Provided for Q2 compat."); cvar_t dpcompat_ignoremodificationtimes = CVARAFD("fs_packageprioritisation", "1", "dpcompat_ignoremodificationtimes", CVAR_NOUNSAFEEXPAND|CVAR_NOSAVE, "Favours the package that is:\n0: Most recently modified\n1: Is alphabetically last (favour z over a, 9 over 0)."); @@ -35,6 +35,7 @@ int active_fs_cachetype; static int fs_referencetype; int fs_finds; void COM_CheckRegistered (void); +void Mods_FlushModList(void); static qboolean Sys_SteamHasFile(char *basepath, int basepathlen, char *steamdir, char *fname); static void QDECL fs_game_callback(cvar_t *var, char *oldvalue) @@ -207,18 +208,21 @@ void FS_Manifest_Free(ftemanifest_t *man) int i, j; if (!man) return; + Z_Free(man->filename); Z_Free(man->updateurl); - Z_Free(man->updatefile); Z_Free(man->installation); Z_Free(man->formalname); +#ifdef PACKAGEMANAGER Z_Free(man->downloadsurl); Z_Free(man->installupd); +#endif Z_Free(man->protocolname); Z_Free(man->eula); Z_Free(man->defaultexec); Z_Free(man->defaultoverrides); Z_Free(man->rtcbroker); Z_Free(man->basedir); + Z_Free(man->iconname); for (i = 0; i < sizeof(man->gamepath) / sizeof(man->gamepath[0]); i++) { Z_Free(man->gamepath[i].path); @@ -246,10 +250,12 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm) newm->installation = Z_StrDup(oldm->installation); if (oldm->formalname) newm->formalname = Z_StrDup(oldm->formalname); +#ifdef PACKAGEMANAGER if (oldm->downloadsurl) newm->downloadsurl = Z_StrDup(oldm->downloadsurl); if (oldm->installupd) newm->installupd = Z_StrDup(oldm->installupd); +#endif if (oldm->protocolname) newm->protocolname = Z_StrDup(oldm->protocolname); if (oldm->eula) @@ -260,6 +266,8 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm) newm->defaultoverrides = Z_StrDup(oldm->defaultoverrides); if (oldm->rtcbroker) newm->rtcbroker = Z_StrDup(oldm->rtcbroker); + if (oldm->iconname) + newm->iconname = Z_StrDup(oldm->iconname); if (oldm->basedir) newm->basedir = Z_StrDup(oldm->basedir); if (oldm->mainconfig) @@ -302,10 +310,12 @@ static void FS_Manifest_Print(ftemanifest_t *man) Con_Printf("name %s\n", COM_QuotedString(man->formalname, buffer, sizeof(buffer), false)); if (man->mainconfig) Con_Printf("mainconfig %s\n", COM_QuotedString(man->mainconfig, buffer, sizeof(buffer), false)); +#ifdef PACKAGEMANAGER if (man->downloadsurl) Con_Printf("downloadsurl %s\n", COM_QuotedString(man->downloadsurl, buffer, sizeof(buffer), false)); if (man->installupd) Con_Printf("install %s\n", COM_QuotedString(man->installupd, buffer, sizeof(buffer), false)); +#endif if (man->protocolname) Con_Printf("protocolname %s\n", COM_QuotedString(man->protocolname, buffer, sizeof(buffer), false)); if (man->defaultexec) @@ -314,6 +324,8 @@ static void FS_Manifest_Print(ftemanifest_t *man) Con_Printf("%s", man->defaultoverrides); if (man->rtcbroker) Con_Printf("rtcbroker %s\n", COM_QuotedString(man->rtcbroker, buffer, sizeof(buffer), false)); + if (man->iconname) + Con_Printf("icon %s\n", COM_QuotedString(man->iconname, buffer, sizeof(buffer), false)); if (man->basedir) Con_Printf("basedir %s\n", COM_QuotedString(man->basedir, buffer, sizeof(buffer), false)); @@ -361,6 +373,10 @@ static void FS_Manifest_Print(ftemanifest_t *man) static void FS_Manifest_PurgeGamedirs(ftemanifest_t *man) { int i; + if (man->filename) + Z_Free(man->filename); + man->filename = NULL; + for (i = 0; i < sizeof(man->gamepath) / sizeof(man->gamepath[0]); i++) { if (man->gamepath[i].path && !(man->gamepath[i].flags&GAMEDIR_BASEGAME)) @@ -374,7 +390,7 @@ static void FS_Manifest_PurgeGamedirs(ftemanifest_t *man) } //create a new empty manifest with default values. -static ftemanifest_t *FS_Manifest_Create(const char *syspath) +static ftemanifest_t *FS_Manifest_Create(const char *syspath, const char *basedir) { ftemanifest_t *man = Z_Malloc(sizeof(*man)); @@ -385,7 +401,9 @@ static ftemanifest_t *FS_Manifest_Create(const char *syspath) #endif if (syspath) - man->updatefile = Z_StrDup(syspath); //this should be a system path. + man->filename = Z_StrDup(syspath); //this should be a system path. + if (basedir) + man->basedir = Z_StrDup(basedir); //this should be a system path. #ifdef QUAKETC man->mainconfig = Z_StrDup("config.cfg"); @@ -510,7 +528,7 @@ mirror: return false; } -static qboolean FS_GamedirIsOkay(const char *path) +qboolean FS_GamedirIsOkay(const char *path) { if (!*path || strchr(path, '\n') || strchr(path, '\r') || !strcmp(path, ".") || !strcmp(path, "..") || strchr(path, ':') || strchr(path, '/') || strchr(path, '\\') || strchr(path, '$')) { @@ -577,6 +595,7 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) Z_Free(man->eula); man->eula = Z_StrDup(Cmd_Argv(1)); } +#ifdef PACKAGEMANAGER else if (!Q_strcasecmp(cmd, "downloadsurl")) { Z_Free(man->downloadsurl); @@ -589,6 +608,7 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) else man->installupd = Z_StrDup(Cmd_Argv(1)); } +#endif else if (!Q_strcasecmp(cmd, "protocolname")) { Z_Free(man->protocolname); @@ -622,6 +642,11 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) Z_Free(man->updateurl); man->updateurl = Z_StrDup(Cmd_Argv(1)); } + else if (!Q_strcasecmp(cmd, "icon")) //relative path to an icon image (typically png) + { + Z_Free(man->iconname); + man->iconname = Z_StrDup(Cmd_Argv(1)); + } else if (!Q_strcasecmp(cmd, "disablehomedir") || !Q_strcasecmp(cmd, "homedirmode")) { char *arg = Cmd_Argv(1); @@ -696,7 +721,11 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) else if (!Q_strcasecmp(cmd, "package") || !Q_strcasecmp(cmd, "archivedpackage")) FS_Manifest_ParsePackage(man, mdt_singlepackage); else if (!Q_strcasecmp(cmd, "basedir")) - ; + { //allow explicit basedirs when this is an actual file on the user's system, and we don't have an explicit one. + //this should only happen from parsing /etc/fte/*.fmf + if (!man->basedir && man->filename) + man->basedir = Z_StrDup(Cmd_Argv(1)); + } else { Con_Printf("Unknown token: %s\n", cmd); @@ -705,7 +734,7 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) return result; } //read a manifest file -ftemanifest_t *FS_Manifest_Parse(const char *fname, const char *data) +ftemanifest_t *FS_Manifest_ReadMem(const char *fname, const char *basedir, const char *data) { ftemanifest_t *man; if (!data) @@ -715,11 +744,11 @@ ftemanifest_t *FS_Manifest_Parse(const char *fname, const char *data) if (!*data) return NULL; - man = FS_Manifest_Create(fname); + man = FS_Manifest_Create(fname, basedir); while (data && *data) { - data = Cmd_TokenizeString((char*)data, false, false); + data = Cmd_TokenizeString(data, false, false); if (!FS_Manifest_ParseTokens(man) && man->parsever <= 1) { FS_Manifest_Free(man); @@ -752,6 +781,58 @@ ftemanifest_t *FS_Manifest_Parse(const char *fname, const char *data) return man; } +ftemanifest_t *FS_Manifest_ReadSystem(const char *fname, const char *basedir) +{ + ftemanifest_t *man = NULL; + vfsfile_t *f; + f = VFSOS_Open(fname, "rb"); + if (f) + { + size_t len = VFS_GETLEN(f); + char *fdata = BZ_Malloc(len+1); + if (fdata) + { + VFS_READ(f, fdata, len); + fdata[len] = 0; + man = FS_Manifest_ReadMem(fname, basedir, fdata); + if (man) + man->security = MANIFEST_SECURITY_DEFAULT; + BZ_Free(fdata); + } + VFS_CLOSE(f); + } + return man; +} +//reads eg $homedir/$moddir.fmf or $basedir/$moddir.fmf as appropriate. +ftemanifest_t *FS_Manifest_ReadMod(const char *moddir) +{ + ftemanifest_t *man = NULL; + char path[MAX_OSPATH]; + if (*moddir) + { + //check the homedir, which is a little messy when manifests might disallow themselves... + if (!man && com_homepathusable) + { + Q_snprintfz(path, sizeof(path), "%s%s", com_homepath, moddir); + COM_RequireExtension(path, ".fmf", sizeof(path)); + man = FS_Manifest_ReadSystem(path, com_gamepath); + if (man && man->homedirtype == MANIFEST_NOHOMEDIR) + { + FS_Manifest_Free(man); //manifest doesn't like itself... pretend to not find it. + man = NULL; + } + } + + if (!man) + { + Q_snprintfz(path, sizeof(path), "%s%s", com_gamepath, moddir); + COM_RequireExtension(path, ".fmf", sizeof(path)); + man = FS_Manifest_ReadSystem(path, com_gamepath); + } + } + return man; +} + //====================================================================================================== @@ -1861,7 +1942,7 @@ qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out { //this is sometimes used to query the actual path. //don't alow it for other stuff though. - if (relativeto != FS_ROOT && relativeto != FS_BINARYPATH) + if (relativeto != FS_ROOT && relativeto != FS_BINARYPATH && relativeto != FS_GAMEONLY) return false; } else @@ -2240,6 +2321,34 @@ qboolean FS_Remove(const char *fname, enum fs_relative relativeto) } return false; } +static int QDECL FS_RemoveTreeCallback(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath) +{ + char fullname[MAX_OSPATH]; + if (*fname && fname[strlen(fname)-1] == '/') + { + Q_snprintfz(fullname, sizeof(fullname), "%s*", fname); + if (!spath->EnumerateFiles(spath, fullname, FS_RemoveTreeCallback, NULL)) + return false; + } + if (!spath->RemoveFile) + return false; //can't remove... + + if (!spath->RemoveFile(spath, fname)) + { + Con_Printf("Unable to delete %s\n", fname); + return false; //remove failed + } + FS_RebuildFSHash_Update(fname); + return true; +} +qboolean FS_RemoveTree(searchpathfuncs_t *pathhandle, const char *fname) +{ //this requires that the searchpath a) supports remove. b) supports listing directories... + //path is expected to have a trailing / + if (FS_RemoveTreeCallback(fname, 0, 0, NULL, pathhandle)) + return true; + return false; +} + //create a path for the given filename (dir-only must have trailing slash) void FS_CreatePath(const char *pname, enum fs_relative relativeto) { @@ -3167,31 +3276,40 @@ char *FS_GetGamedir(qboolean publicpathonly) char *FS_GetManifestArgs(void) { char *homearg = com_homepathenabled?"-usehome ":"-nohome "; - if (fs_manifest->updatefile) - return va("%s-manifest %s -basedir %s", homearg, fs_manifest->updatefile, com_gamepath); + if (fs_manifest->filename) + return va("%s-manifest %s -basedir %s", homearg, fs_manifest->filename, com_gamepath); return va("%s-game %s -basedir %s", homearg, pubgamedirfile, com_gamepath); } +#ifdef SUBSERVERS int FS_GetManifestArgv(char **argv, int maxargs) { int c = 0; if (maxargs < 5) return c; argv[c++] = com_homepathenabled?"-usehome ":"-nohome "; - if (fs_manifest->updatefile) + if (fs_manifest->filename) { argv[c++] = "-manifest"; - argv[c++] = fs_manifest->updatefile; + argv[c++] = fs_manifest->filename; } else { argv[c++] = "-game"; argv[c++] = pubgamedirfile; } + argv[c++] = "-basedir"; argv[c++] = com_gamepath; + + argv[c++] = "+deathmatch"; + argv[c++] = *deathmatch.string?deathmatch.string:"0"; + + argv[c++] = "+coop"; + argv[c++] = *coop.string?coop.string:"0"; return c; } +#endif /* //given a 'c:/foo/bar/' path, will extract 'bar'. @@ -3285,24 +3403,7 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths) return; } - man = NULL; - if (!man) - { - vfsfile_t *f = *dir?VFSOS_Open(va("%s%s.fmf", com_gamepath, dir), "rb"):NULL; - if (f) - { - size_t len = VFS_GETLEN(f); - char *fdata = BZ_Malloc(len+1); - if (fdata) - { - VFS_READ(f, fdata, len); - fdata[len] = 0; - man = FS_Manifest_Parse(NULL, fdata); - BZ_Free(fdata); - } - VFS_CLOSE(f); - } - } + man = FS_Manifest_ReadMod(dir); if (!man) { @@ -3434,13 +3535,15 @@ const gamemode_info_t gamemode_info[] = { //standard quake {"-quake", "q1", "FTE-Quake DarkPlaces-Quake",{"id1/pak0.pak", "id1/quake.rc"},QCFG,{"id1", "qw", "*fte"}, "Quake", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, //alternative name, because fmf file install names are messy when a single name is used for registry install path. - {"-afterquake", NULL, "FTE-Quake",{"id1/pak0.pak", "id1/quake.rc"}, QCFG,{"id1", "qw", "*fte"}, "Quake", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, + {"-afterquake", NULL, "FTE-Quake",{"id1/pak0.pak", "id1/quake.rc"}, QCFG,{"id1", "qw", "*fte"}, "AfterQuake", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, //netquake-specific quake that avoids qw/ with its nquake fuckups, and disables nqisms {"-netquake", "nq", "FTE-Quake DarkPlaces-Quake",{"id1/pak0.pak", "id1/quake.rc"},NQCFG,{"id1"}, "NetQuake", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, - //because we can. quakespasm is hopefully close enough... - {"-fitz", NULL, "FTE-Quake DarkPlaces-Quake",{"id1/pak0.pak", "id1/quake.rc"},NQCFG"fps_preset spasm\n",{"id1"}, "NetQuake", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, + //blurgh + {"-spasm", NULL, "FTE-Quake DarkPlaces-Quake",{"quakespasm.pak"}, NQCFG"fps_preset spasm\n",{"id1", "*"}, "FauxSpasm", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, + //because we can. 'fps_preset spasm' is hopefully close enough... + {"-fitz", NULL, "FTE-Quake DarkPlaces-Quake",{"quakespasm.pak"}, NQCFG"fps_preset spasm\n",{"id1"}, "FauxFitz", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, //because we can - {"-tenebrae", NULL, "FTE-Quake DarkPlaces-Quake",{"id1/pak0.pak", "id1/quake.rc"},NQCFG"fps_preset tenebrae\n",{"id1","qw","*fte"},"Tenebrae", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, + {"-tenebrae", NULL, "FTE-Quake DarkPlaces-Quake",{"id1/pak0.pak", "id1/quake.rc"},NQCFG"fps_preset tenebrae\n",{"id1","tenebrae"},"FauxTenebrae", UPDATEURL(Q1) /*,"id1/pak0.pak|http://quakeservers.nquake.com/qsw106.zip|http://nquake.localghost.net/qsw106.zip|http://qw.quakephil.com/nquake/qsw106.zip|http://fnu.nquake.com/qsw106.zip"*/}, //quake's mission packs should not be favoured over the base game nor autodetected //third part mods also tend to depend upon the mission packs for their huds, even if they don't use any other content. @@ -3560,7 +3663,7 @@ qboolean FS_GenCachedPakName(const char *pname, const char *crc, char *local, in if (crc && *crc) { Q_strncatz(local, ".", llen); - snprintf(hex, sizeof(hex), "%x", (unsigned int)strtoul(crc, NULL, 0)); + snprintf(hex, sizeof(hex), "%0x", (unsigned int)strtoul(crc, NULL, 0)); Q_strncatz(local, hex, llen); } return true; @@ -4768,6 +4871,8 @@ void FS_Shutdown(void) if (!fs_thread_mutex) return; + Mods_FlushModList(); + #ifdef PACKAGEMANAGER PM_ManifestPackage(NULL, false); #endif @@ -4776,7 +4881,9 @@ void FS_Shutdown(void) fs_thread_mutex = NULL; Cvar_SetEngineDefault(&fs_gamename, NULL); +#ifdef PACKAGEMANAGER Cvar_SetEngineDefault(&pkg_downloads_url, NULL); +#endif Cvar_SetEngineDefault(&com_protocolname, NULL); } @@ -4895,16 +5002,16 @@ static int FS_IdentifyDefaultGame(char *newbase, int sizeof_newbase, qboolean fi return gamenum; } -//allowed to modify newbasedir if fixedbasedir isn't set -static ftemanifest_t *FS_GenerateLegacyManifest(char *newbasedir, int sizeof_newbasedir, qboolean fixedbasedir, int game) +static ftemanifest_t *FS_GenerateLegacyManifest(int game, const char *basedir) { ftemanifest_t *man; + size_t j; if (gamemode_info[game].manifestfile) - man = FS_Manifest_Parse(NULL, gamemode_info[game].manifestfile); + man = FS_Manifest_ReadMem(NULL, basedir, gamemode_info[game].manifestfile); else { - man = FS_Manifest_Create(NULL); + man = FS_Manifest_Create(NULL, basedir); if (gamemode_info[game].customexec && !strncmp(gamemode_info[game].customexec, "//-nohome\n", 10)) { @@ -4919,6 +5026,22 @@ static ftemanifest_t *FS_GenerateLegacyManifest(char *newbasedir, int sizeof_new Cmd_TokenizeString(va("name \"%s\"", gamemode_info[game].poshname), false, false); FS_Manifest_ParseTokens(man); } + + //if there's no base dirs, edit the manifest to give it its default ones. + for (j = 0; j < sizeof(man->gamepath) / sizeof(man->gamepath[0]); j++) + { + if (man->gamepath[j].path && (man->gamepath[j].flags&GAMEDIR_BASEGAME)) + break; + } + if (j == sizeof(man->gamepath) / sizeof(man->gamepath[0])) + { + for (j = 0; j < countof(gamemode_info[game].dir); j++) + if (gamemode_info[game].dir[j]) + { + Cmd_TokenizeString(va("basegame \"%s\"", gamemode_info[game].dir[j]), false, false); + FS_Manifest_ParseTokens(man); + } + } } man->security = MANIFEST_SECURITY_INSTALLER; return man; @@ -4934,6 +5057,9 @@ static void FS_AppendManifestGameArguments(ftemanifest_t *man) i = COM_CheckParm ("-basegame"); if (i) { + if (man->filename) + Z_Free(man->filename); + man->filename = NULL; do { Cmd_TokenizeString(va("basegame \"%s\"", com_argv[i+1]), false, false); @@ -4947,6 +5073,9 @@ static void FS_AppendManifestGameArguments(ftemanifest_t *man) i = COM_CheckParm ("-game"); if (i) { + if (man->filename) + Z_Free(man->filename); + man->filename = NULL; do { Cmd_TokenizeString(va("gamedir \"%s\"", com_argv[i+1]), false, false); @@ -4960,6 +5089,9 @@ static void FS_AppendManifestGameArguments(ftemanifest_t *man) i = COM_CheckParm ("+gamedir"); if (i) { + if (man->filename) + Z_Free(man->filename); + man->filename = NULL; do { Cmd_TokenizeString(va("gamedir \"%s\"", com_argv[i+1]), false, false); @@ -4972,518 +5104,40 @@ static void FS_AppendManifestGameArguments(ftemanifest_t *man) } #ifdef MANIFESTDOWNLOADS -static char *FS_RelativeURL(char *base, char *file, char *buffer, int bufferlen) -{ - //fixme: cope with windows paths - qboolean baseisurl = base?!!strchr(base, ':'):false; - qboolean fileisurl = !!strchr(file, ':'); - //qboolean baseisabsolute = (*base == '/' || *base == '\\'); - qboolean fileisabsolute = (*file == '/' || *file == '\\'); - char *ebase; - - if (fileisurl || !base) - return file; - if (fileisabsolute) - { - if (baseisurl) - { - ebase = strchr(base, ':'); - ebase++; - while(*ebase == '/') - ebase++; - while(*ebase && *ebase != '/') - ebase++; - } - else - ebase = base; - } - else - ebase = COM_SkipPath(base); - memcpy(buffer, base, ebase-base); - strcpy(buffer+(ebase-base), file); - - return buffer; -} - static struct dl_download *curpackagedownload; -static enum manifestdeptype_e fspdl_type; -static enum { - X_DLONLY, //simple pk3 file - X_COPY, //we copy it from an existing install (ie: special install path for total conversion) - X_MULTIUNZIP, //zip with multiple files that need extracting - X_UNZIP, //pull a single file from a zip - X_GZ, //dlonly+ungzip - X_XZ //dlonly+unxzip -} fspdl_extracttype; -static char fspdl_internalname[MAX_QPATH]; -static char fspdl_temppath[MAX_OSPATH]; -static char fspdl_finalpath[MAX_OSPATH]; -static void FS_BeginNextPackageDownload(void); qboolean FS_DownloadingPackage(void) { if (PM_IsApplying(false)) return true; return !fs_manifest || !!curpackagedownload; } -//vfsfile_t *FS_DecompressXZip(vfsfile_t *infile, vfsfile_t *outfile); -static void FS_ExtractPackage(searchpathfuncs_t *archive, flocation_t *loc, const char *origname, const char *finalname) -{ - vfsfile_t *in = archive->OpenVFS(archive, loc, "rb"); - if (in) - { - vfsfile_t *out = VFSOS_Open(finalname, "wb"); - qofs_t remaining = VFS_GETLEN(in); - size_t count; - if (out) - { - char buf[65536]; - while (remaining) - { - if (remaining < sizeof(buf)) - count = remaining; - else - count = sizeof(buf); - VFS_READ(in, buf, count); - VFS_WRITE(out, buf, count); - remaining -= count; - } - VFS_CLOSE(out); - } - else - Con_Printf("Unable to write %s\n", finalname); - VFS_CLOSE(in); - } - else - Con_Printf("Unable to read %s\n", origname); -} -static int QDECL FS_ExtractAllPackages(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath) -{ - //okay, this package naming thing is getting stupid. - flocation_t loc; - int status = FF_NOTFOUND; - status = spath->FindFile(spath, &loc, fname, NULL); - //FIXME: symlinks? - if (status == FF_FOUND) - FS_ExtractPackage(spath, &loc, fname, va("%s%s", (const char*)parm, fname)); - return 1; -} -static void FS_PackagePrompt(char *finalpath, char *filename, char *game) -{ - static char existingbase[MAX_OSPATH]; - static char prevgame[64]; - vfsfile_t *in = NULL; - const char *posh = NULL; - int i; - if (game) - { - for (i = 0; i < sizeof(gamemode_info) / sizeof(gamemode_info[0]); i++) - { - if (!Q_strcasecmp(gamemode_info[i].poshname, gamemode_info[i].argname+1)) - { - posh = gamemode_info[i].poshname; - break; - } - } - if (*existingbase && !strcmp(prevgame, game)) - { - in = VFSOS_Open(va("%s/%s", existingbase, filename), "rb"); - if (!in) - in = VFSOS_Open(va("%s/%s", existingbase, COM_SkipPath(filename)), "rb"); - } - Q_strncpyz(prevgame, game, sizeof(prevgame)); - if (!posh) - posh = game; - else if (!in && Sys_FindGameData(NULL, game, existingbase, sizeof(existingbase), false)) - { - in = VFSOS_Open(va("%s/%s", existingbase, filename), "rb"); - if (!in) - in = VFSOS_Open(va("%s/%s", existingbase, COM_SkipPath(filename)), "rb"); - } - } - while(!in && Sys_DoDirectoryPrompt(existingbase, sizeof(existingbase), posh, NULL)) - { - in = VFSOS_Open(va("%s/%s", existingbase, filename), "rb"); - if (!in) - in = VFSOS_Open(va("%s/%s", existingbase, COM_SkipPath(filename)), "rb"); - } - if (in) - { - vfsfile_t *out = VFSOS_Open(finalpath, "wb"); - qofs_t remaining = VFS_GETLEN(in); - size_t count; - if (out) - { - char buf[65536]; - while (remaining) - { - if (remaining < sizeof(buf)) - count = remaining; - else - count = sizeof(buf); - VFS_READ(in, buf, count); - VFS_WRITE(out, buf, count); - remaining -= count; - } - VFS_CLOSE(out); - } - else - Con_Printf("Unable to write %s\n", finalpath); - VFS_CLOSE(in); - } - else - Con_Printf("Unable to read %s%s\n", existingbase, filename); -} -static void FS_PackageDownloaded(struct dl_download *dl) -{ - curpackagedownload = NULL; - - if (dl->file) - { - VFS_CLOSE(dl->file); - dl->file = NULL; - } - if (dl->status == DL_FAILED) - Con_Printf("download for %s:%s failed\n", fspdl_finalpath, fspdl_internalname); - - if (dl->status == DL_FINISHED) - { - //rename the file as needed. - COM_CreatePath(fspdl_finalpath); - - if (fspdl_extracttype == X_UNZIP || fspdl_extracttype == X_MULTIUNZIP) //if zip... - { //archive -#ifdef PACKAGE_PK3 - searchpathfuncs_t *archive = FSZIP_LoadArchive(VFSOS_Open(fspdl_temppath, "rb"), NULL, dl->url, dl->url, ""); -#else - searchpathfuncs_t *archive = NULL; -#endif - if (archive) - { - flocation_t loc; - if (fspdl_extracttype == X_MULTIUNZIP) - { - char *f = fspdl_internalname; - char *e; - for (f = fspdl_internalname; *f; f = e) - { - e = strchr(f, ':'); - if (e) - *e++ = 0; - else - e = f + strlen(f); - - if (strchr(f, '*')) - archive->EnumerateFiles(archive, f, FS_ExtractAllPackages, fspdl_finalpath); - else - { - int status = archive->FindFile(archive, &loc, f, NULL); - if (status == FF_FOUND) - FS_ExtractPackage(archive, &loc, f, va("%s%s", fspdl_finalpath, f)); - } - } - } - else - { - flocation_t loc; - int status = FF_NOTFOUND; -//FIXME: loop through all other packages to extract all of them as appropriate - if (status == FF_NOTFOUND) - status = archive->FindFile(archive, &loc, fspdl_internalname, NULL); - if (status == FF_NOTFOUND) - status = archive->FindFile(archive, &loc, COM_SkipPath(fspdl_internalname), NULL); - - if (status == FF_FOUND) - FS_ExtractPackage(archive, &loc, fspdl_internalname, fspdl_finalpath); - else - { - Con_Printf("Unable to find %s in %s\n", fspdl_internalname, fspdl_temppath); - } - } - archive->ClosePath(archive); - } - } - else - { - //direct file - if (!Sys_Rename(fspdl_temppath, fspdl_finalpath)) - { - Con_Printf("Unable to rename \"%s\" to \"%s\"\n", fspdl_temppath, fspdl_finalpath); - } -// else -// PM_AddDownloadedPackage(fspdl_finalpath); - } - } - Sys_remove (fspdl_temppath); - - fs_restarts++; - FS_ChangeGame(fs_manifest, true, false); - - FS_BeginNextPackageDownload(); -} - -static qboolean FS_BeginPackageDownload(struct manpack_s *pack, char *baseurl, qboolean allownoncache) -{ - char *crcstr = ""; - vfsfile_t *check; - vfsfile_t *tmpf; - - char buffer[MAX_OSPATH], *url; - qboolean multiex = false; - - //check this package's conditional - if (pack->condition) - { - if (!If_EvaluateBoolean(pack->condition, RESTRICT_LOCAL)) - return false; - } - - if (pack->type == mdt_installation) - { //libraries are in the root directory. we allow extracting multiple of them from a zip, etc. - //they are not packages and thus do NOT support crcs. - char *s, *e; - if (!allownoncache) //don't even THINK about allowing the download unless its part of an initial install. - return false; - for (s = pack->path; *s; s = e) - { - while(*s == ':') - s++; - e = strchr(s, ':'); - if (!e) - e = s+strlen(s); - if (e-s >= sizeof(buffer)) - continue; - memcpy(buffer, s, e-s); - buffer[e-s] = 0; - - check = FS_OpenVFS(buffer, "rb", FS_ROOT); - if (check) - { - VFS_CLOSE(check); - continue; - } - break; - } - if (!*s) //all files were already present, apparently - return false; - } - else - { - //check if we already have a version of the pak. if the user installed one, don't corrupt it with some unwanted pak. this may cause problems but whatever, user versitility wins. - //this matches the rules for loading packs too. double is utterly pointless. - check = FS_OpenVFS(pack->path, "rb", FS_ROOT); - if (check) - { - VFS_CLOSE(check); - return false; - } - - //figure out what the cached name should be and see if we already have that or not - if (pack->crcknown) - crcstr = va("%#x", pack->crc); - if (!pack->crcknown && allownoncache) - Q_strncpyz(buffer, pack->path, sizeof(buffer)); - else if (!FS_GenCachedPakName(pack->path, crcstr, buffer, sizeof(buffer))) - return false; - check = FS_OpenVFS(buffer, "rb", FS_ROOT); - if (check) - { - VFS_CLOSE(check); - return false; - } - } - - if (pack->type == mdt_installation) - { - if (!strchr(pack->path, ':')) - { - if (!FS_NativePath(pack->path, FS_ROOT, fspdl_finalpath, sizeof(fspdl_finalpath)) || - !FS_NativePath(va("%s.tmp", pack->path), FS_ROOT, fspdl_temppath, sizeof(fspdl_temppath))) - return false; - } - else - { - if (!FS_NativePath("", FS_ROOT, fspdl_finalpath, sizeof(fspdl_finalpath)) || - !FS_NativePath(va("%s.tmp", fs_manifest->installation), FS_ROOT, fspdl_temppath, sizeof(fspdl_temppath))) - return false; - multiex = true; - } - } - else - { - //figure out a temp name and figure out where we're going to get it from. - if (!FS_NativePath(buffer, FS_ROOT, fspdl_finalpath, sizeof(fspdl_finalpath))) - return false; - if (!pack->crcknown && allownoncache) - Q_strncpyz(buffer, va("%s.tmp", pack->path), sizeof(buffer)); - else if (!FS_GenCachedPakName(va("%s.tmp", pack->path), crcstr, buffer, sizeof(buffer))) - return false; - if (!FS_NativePath(buffer, FS_ROOT, fspdl_temppath, sizeof(fspdl_temppath))) - return false; - } - - url = NULL; - while(!url) - { - //ran out of mirrors? - if (pack->mirrornum == (sizeof(pack->mirrors) / sizeof(pack->mirrors[0]))) - break; - - if (pack->mirrors[pack->mirrornum]) - url = FS_RelativeURL(baseurl, pack->mirrors[pack->mirrornum], buffer, sizeof(buffer)); - pack->mirrornum++; - } - //no valid mirrors - if (!url) - return false; - - fspdl_extracttype = X_DLONLY; - if (!strncmp(url, "gz:", 3)) - { - url+=3; - fspdl_extracttype = X_GZ; - } - else if (!strncmp(url, "xz:", 3)) - { - url+=3; - fspdl_extracttype = X_XZ; - } - else if (!strncmp(url, "unzip:", 6)) - { - url+=6; - fspdl_extracttype = X_UNZIP; - } - else if (!strncmp(url, "prompt:", 7)) - { - url+=7; - fspdl_extracttype = X_COPY; - } - else - fspdl_extracttype = X_DLONLY; - - if (fspdl_extracttype == X_UNZIP || fspdl_extracttype == X_COPY) - { - char *o = fspdl_internalname; - while(o+1 < fspdl_internalname+sizeof(fspdl_internalname) && *url) - { - if (*url == ',') - { - url++; - break; - } - *o++ = *url++; - } - *o = 0; - } - else - *fspdl_internalname = 0; - - if (multiex) - { - if (fspdl_extracttype != X_UNZIP && fspdl_extracttype != X_DLONLY) - return false; //multiunzip is only supported with unzip urls... (or assumed if its a direct download - fspdl_extracttype = X_MULTIUNZIP; - - if (!*fspdl_internalname) - Q_strncpyz(fspdl_internalname, pack->path, sizeof(fspdl_internalname)); - } - - fspdl_type = pack->type; - - if (fspdl_extracttype == X_COPY) - { - FS_PackagePrompt(fspdl_finalpath, url, fspdl_internalname); - return false; - } - - COM_CreatePath(fspdl_temppath); - tmpf = VFSOS_Open(fspdl_temppath, "wb"); - - if (tmpf) - { - switch(fspdl_extracttype) - { - case X_XZ: -#ifdef AVAIL_XZDEC - tmpf = FS_XZ_DecompressWriteFilter(tmpf); -#else - VFS_CLOSE(tmpf); - tmpf = NULL; -#endif - break; - case X_GZ: -#ifdef AVAIL_GZDEC - tmpf = FS_GZ_WriteFilter(tmpf, true, false); -#else - VFS_CLOSE(tmpf); - tmpf = NULL; -#endif - break; - default: - break; - } - - if (!tmpf) - Sys_remove (fspdl_temppath); - } - if (tmpf) - { - Con_Printf("Downloading %s from %s\n", fspdl_finalpath, url); - curpackagedownload = HTTP_CL_Get(url, NULL, FS_PackageDownloaded); - if (curpackagedownload) - { - curpackagedownload->file = tmpf; -#ifdef MULTITHREAD - DL_CreateThread(curpackagedownload, NULL, NULL); -#endif - return true; - } - VFS_CLOSE(tmpf); - Sys_remove (fspdl_temppath); - } - return false; -} static void FS_ManifestUpdated(struct dl_download *dl); -static void FS_BeginNextPackageDownload(void) +static void FS_BeginNextPackageDownload(ftemanifest_t *man) { - int j; - ftemanifest_t *man = fs_manifest; if (curpackagedownload || !man || com_installer) return; - if (man->security != MANIFEST_SECURITY_NOT) - { - for (j = 0; j < sizeof(fs_manifest->package) / sizeof(fs_manifest->package[0]); j++) - { - if (man->package[j].type != mdt_installation) - continue; - - if (FS_BeginPackageDownload(&man->package[j], man->updateurl, true)) - return; - } - } - - if (man->updateurl && !man->blockupdate) + if (man == fs_manifest && man->updateurl && !man->blockupdate) { + vfsfile_t *f = FS_OpenVFS(man->filename, "ab", FS_SYSTEM); //this is JUST to make sure its writable. don't bother updating it if it isn't. man->blockupdate = true; - Con_Printf("Updating manifest from %s\n", man->updateurl); - waitingformanifest++; - curpackagedownload = HTTP_CL_Get(man->updateurl, NULL, FS_ManifestUpdated); - if (curpackagedownload) + if (f) { - curpackagedownload->user_ctx = man; - return; + VFS_CLOSE(f); + + Con_Printf("Updating manifest from %s\n", man->updateurl); + waitingformanifest++; + curpackagedownload = HTTP_CL_Get(man->updateurl, NULL, FS_ManifestUpdated); + if (curpackagedownload) + { + curpackagedownload->user_ctx = man; + return; + } } } - for (j = 0; j < sizeof(fs_manifest->package) / sizeof(fs_manifest->package[0]); j++) - { - if (man->package[j].type != mdt_singlepackage) - continue; - - if (FS_BeginPackageDownload(&man->package[j], man->updateurl, false)) - return; - } + PM_AddManifestPackages(man); } static void FS_ManifestUpdated(struct dl_download *dl) { @@ -5502,17 +5156,18 @@ static void FS_ManifestUpdated(struct dl_download *dl) { VFS_READ(dl->file, fdata, len); fdata[len] = 0; - man = FS_Manifest_Parse(fs_manifest->updatefile, fdata); + man = FS_Manifest_ReadMem(fs_manifest->filename, fs_manifest->basedir, fdata); if (man) { //the updateurl MUST match the current one in order for the local version of the manifest to be saved (to avoid extra updates, and so it appears in the menu_mods) //this is a paranoia measure to avoid too much damage from buggy/malicious proxies that return empty pages or whatever. - if (man->updateurl && fs_manifest->updateurl && !strcmp(man->updateurl, fs_manifest->updateurl)) + if (man->updateurl && fs_manifest->updateurl && !strcmp(man->updateurl, fs_manifest->updateurl) && + man->basedir && fs_manifest->basedir && !strcmp(man->basedir, fs_manifest->basedir)) //basedir must match too... ie: not be overridden. { man->blockupdate = true; //don't download it multiple times. that's just crazy. - if (man->updatefile) + if (man->filename) { - vfsfile_t *f2 = FS_OpenVFS(fs_manifest->updatefile, "rb", FS_SYSTEM); + vfsfile_t *f2 = FS_OpenVFS(fs_manifest->filename, "rb", FS_SYSTEM); if (f2) { len2 = VFS_GETLEN(f2); @@ -5536,7 +5191,7 @@ static void FS_ManifestUpdated(struct dl_download *dl) BZ_Free(fdata2); } if (man) - FS_WriteFile(man->updatefile, fdata, len, FS_SYSTEM); + FS_WriteFile(man->filename, fdata, len, FS_SYSTEM); } if (man) FS_ChangeGame(man, true, false); @@ -5552,7 +5207,7 @@ static void FS_ManifestUpdated(struct dl_download *dl) dl->file = NULL; } - FS_BeginNextPackageDownload(); + FS_BeginNextPackageDownload(fs_manifest); } void FS_BeginManifestUpdates(void) { @@ -5562,7 +5217,7 @@ void FS_BeginManifestUpdates(void) return; if (!curpackagedownload) - FS_BeginNextPackageDownload(); + FS_BeginNextPackageDownload(man); } #else qboolean FS_DownloadingPackage(void) @@ -5621,31 +5276,16 @@ ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedirsize, q exename[strlen(exename)] = 0; //and hopefully we now have something consistent that we can try to use. - f = VFSOS_Open(va("%s%s.fmf", newbasedir, exename), "rb"); + if (!man) + man = FS_Manifest_ReadSystem(va("%s%s.fmf", newbasedir, exename), newbasedir); #ifdef BRANDING_NAME - if (!f) - f = VFSOS_Open(va("%s"STRINGIFY(BRANDING_NAME)".fmf", newbasedir), "rb"); + if (!man) + man = FS_Manifest_ReadSystem(va("%s"STRINGIFY(BRANDING_NAME)".fmf", newbasedir), newbasedir); #endif - if (!f) - f = VFSOS_Open(va("%sdefault.fmf", newbasedir), "rb"); - if (f) - { - size_t len = VFS_GETLEN(f); - char *fdata = BZ_Malloc(len+1); - if (fdata) - { - VFS_READ(f, fdata, len); - fdata[len] = 0; - man = FS_Manifest_Parse(NULL, fdata); - if (man) - { - man->security = MANIFEST_SECURITY_DEFAULT; - man->basedir = Z_StrDup(newbasedir); - } - BZ_Free(fdata); - } - VFS_CLOSE(f); - } + if (!man) + man = FS_Manifest_ReadSystem(va("%sdefault.fmf", newbasedir), newbasedir); + if (man) + man->security = MANIFEST_SECURITY_DEFAULT; } //-basepack is primarily an android feature @@ -5670,8 +5310,9 @@ ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedirsize, q { VFS_READ(f, fdata, len); fdata[len] = 0; - man = FS_Manifest_Parse(NULL, fdata); - man->security = MANIFEST_SECURITY_DEFAULT; + man = FS_Manifest_ReadMem(NULL, NULL, fdata); + if (man) + man->security = MANIFEST_SECURITY_DEFAULT; BZ_Free(fdata); } VFS_CLOSE(f); @@ -5685,7 +5326,7 @@ ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedirsize, q if (!man && game == -1 && host_parms.manifest) { - man = FS_Manifest_Parse(va("%sdefault.fmf", newbasedir), host_parms.manifest); + man = FS_Manifest_ReadMem(NULL, newbasedir, host_parms.manifest); if (man) man->security = MANIFEST_SECURITY_INSTALLER; } @@ -5695,7 +5336,7 @@ ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedirsize, q if (game == -1) game = FS_IdentifyDefaultGame(newbasedir, newbasedirsize, fixedbasedir); if (game != -1) - man = FS_GenerateLegacyManifest(newbasedir, newbasedirsize, fixedbasedir, game); + man = FS_GenerateLegacyManifest(game, fixedbasedir?newbasedir:NULL); } FS_AppendManifestGameArguments(man); @@ -5727,7 +5368,9 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean int i, j; char realpath[MAX_OSPATH-1]; char newbasedir[MAX_OSPATH]; +#ifdef PACKAGEMANAGER char *olddownloadsurl; +#endif qboolean fixedbasedir; qboolean reloadconfigs = false; qboolean builtingame = false; @@ -5820,26 +5463,32 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean if (!man) { #ifdef _WIN32 + //quit straight out on windows. this prevents shitty sandboxed malware scanners from seeing bugs in opengl drivers and blaming us for it. if (!fixedbasedir) Sys_Error("No recognised game data found in working directory:\n%s", com_gamepath); #endif - man = FS_Manifest_Parse(NULL, + man = FS_Manifest_ReadMem(NULL, NULL, "FTEManifestVer 1\n" "game \"\"\n" "name \"" FULLENGINENAME "\"\n" - "defaultexec \\\"vid_fullscreen 0; gl_font cour;vid_width 640; vid_height 480\"\n" + "-set vid_fullscreen 0\n" + "-set gl_font cour\n" + "-set vid_width 640\n" + "-set vid_height 480\n" ); } if (!man) Sys_Error("couldn't generate dataless manifest\n"); } +#ifdef PACKAGEMANAGER if (fs_manifest && fs_manifest->downloadsurl) olddownloadsurl = Z_StrDup(fs_manifest->downloadsurl); else if (!fs_manifest && man->downloadsurl) olddownloadsurl = Z_StrDup(man->downloadsurl); else olddownloadsurl = NULL; +#endif if (man == fs_manifest) { @@ -5859,6 +5508,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean } fs_manifest = man; +#ifdef PACKAGEMANAGER if (man->security == MANIFEST_SECURITY_NOT && strcmp(man->downloadsurl?man->downloadsurl:"", olddownloadsurl?olddownloadsurl:"")) { //make sure we only fuck over the user if this is a 'secure' manifest, and not hacked in some way. Z_Free(man->downloadsurl); @@ -5866,6 +5516,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean } else Z_Free(olddownloadsurl); +#endif if (man->installation && *man->installation) { @@ -5889,6 +5540,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean } } +#ifdef PACKAGEMANAGER if (!man->downloadsurl && gamemode_info[i].downloadsurl) { #ifndef FTE_TARGET_WEB @@ -5912,6 +5564,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean Cmd_TokenizeString(va("downloadsurl \"%s\"", gamemode_info[i].downloadsurl), false, false); FS_Manifest_ParseTokens(man); } +#endif if (!man->protocolname) { Cmd_TokenizeString(va("protocolname \"%s\"", gamemode_info[i].protocolname), false, false); @@ -5944,9 +5597,9 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean Q_strncpyz (newbasedir, realpath, sizeof(newbasedir)); #ifdef HAVE_CLIENT else - { - Z_Free(man->updatefile); - man->updatefile = NULL; + { //no basedir known... switch to installer mode and ask the user where they want it (at least on windows) + Z_Free(man->filename); + man->filename = NULL; com_installer = true; } #endif @@ -5957,7 +5610,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean if (strcmp(com_gamepath, newbasedir)) { #ifdef PACKAGEMANAGER - PM_Shutdown(); + PM_Shutdown(false); #endif Q_strncpyz (com_gamepath, newbasedir, sizeof(com_gamepath)); } @@ -6015,7 +5668,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean FS_BeginManifestUpdates(); #ifdef MANIFESTDOWNLOADS - if (curpackagedownload && fs_loadedcommand) + if (FS_DownloadingPackage() && fs_loadedcommand) allowreloadconfigs = false; #endif @@ -6053,11 +5706,15 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean if (reloadconfigs) { Cvar_SetEngineDefault(&fs_gamename, man->formalname?man->formalname:"FTE"); +#ifdef PACKAGEMANAGER Cvar_SetEngineDefault(&pkg_downloads_url, man->downloadsurl?man->downloadsurl:""); +#endif Cvar_SetEngineDefault(&com_protocolname, man->protocolname?man->protocolname:"FTE"); //FIXME: flag this instead and do it after a delay? Cvar_ForceSet(&fs_gamename, fs_gamename.enginevalue); +#ifdef PACKAGEMANAGER Cvar_ForceSet(&pkg_downloads_url, pkg_downloads_url.enginevalue); +#endif Cvar_ForceSet(&com_protocolname, com_protocolname.enginevalue); #ifdef HAVE_CLIENT vidrestart = false; @@ -6117,6 +5774,8 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean Cvar_ForceSet(&fs_basedir, com_gamepath); #endif + Mods_FlushModList(); + return true; } @@ -6145,49 +5804,50 @@ void FS_CreateBasedir(const char *path) typedef struct { + qboolean anygamedir; + const char *basedir; int found; qboolean (*callback)(void *usr, ftemanifest_t *man); void *usr; } fmfenums_t; -static int QDECL FS_EnumerateFMFs(const char *fname, qofs_t fsize, time_t mtime, void *inf, searchpathfuncs_t *spath) +static int QDECL FS_EnumeratedFMF(const char *fname, qofs_t fsize, time_t mtime, void *inf, searchpathfuncs_t *spath) { + ftemanifest_t *man = NULL; fmfenums_t *e = inf; - vfsfile_t *f = NULL; - char *homem = va("%s%s", com_homepathenabled?com_homepath:com_gamepath, COM_SkipPath(fname)); - if (!f) //always try the homedir first, because that can be updated automagically. - f = VFSOS_Open(fname, "rb"); - if (!f) - { //*then* try in packages or basedir etc. - if (spath) - { - flocation_t loc; - if (spath->FindFile(spath, &loc, fname, NULL)) - f = spath->OpenVFS(spath, &loc, "rb"); - } - else - f = VFSOS_Open(fname, "rb"); - } - if (f) + vfsfile_t *f = NULL; + if (spath) { - size_t l = VFS_GETLEN(f); - char *data = Z_Malloc(l+1); - if (data) + flocation_t loc; + if (spath->FindFile(spath, &loc, fname, NULL)) { - ftemanifest_t *man; - VFS_READ(f, data, l); - data[l] = 0; //just in case. - - man = FS_Manifest_Parse(homem, data); - if (man) + f = spath->OpenVFS(spath, &loc, "rb"); + if (f) { - if (e->callback(e->usr, man)) - e->found++; - else - FS_Manifest_Free(man); + size_t l = VFS_GETLEN(f); + char *data = Z_Malloc(l+1); + if (data) + { + VFS_READ(f, data, l); + data[l] = 0; //just in case. + + man = FS_Manifest_ReadMem(NULL, e->basedir, data); + Z_Free(data); + } + VFS_CLOSE(f); } - Z_Free(data); } - VFS_CLOSE(f); + } + else if (e->basedir) + man = FS_Manifest_ReadMod(fname); + else + man = FS_Manifest_ReadSystem(fname, NULL); + + if (man) + { + if (e->callback(e->usr, man)) + e->found++; + else + FS_Manifest_Free(man); } return true; @@ -6199,12 +5859,43 @@ int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), int i; char basedir[MAX_OSPATH]; fmfenums_t e; + ftemanifest_t *man; + e.anygamedir = !fs_manifest || !*fs_manifest->installation; e.found = 0; e.callback = callback; e.usr = usr; + if (e.anygamedir) + { + e.basedir = com_gamepath; + man = FS_ReadDefaultManifest(com_gamepath, 0, true); + if (man) + { + if (e.callback(e.usr, man)) + e.found++; + else + FS_Manifest_Free(man); + } + } + + //okay, no manifests in the basepack, try looking in the basedir. + //this defaults to the working directory. perhaps try the exe's location instead? + e.basedir = com_gamepath; + Sys_EnumerateFiles(com_gamepath, "*.fmf", FS_EnumeratedFMF, &e, NULL); + if (*com_homepath) + Sys_EnumerateFiles(com_homepath, "*.fmf", FS_EnumeratedFMF, &e, NULL); + + if (e.anygamedir) + { +#ifdef __linux__ + e.basedir = NULL; + Sys_EnumerateFiles(NULL, "/etc/fte/*.fmf", FS_EnumeratedFMF, &e, NULL); +#endif + } + //-basepack is primarily an android feature, where the apk file is specified. //this allows custom mods purely by customising the apk + e.basedir = host_parms.basedir; i = COM_CheckParm ("-basepack"); while (i && i < com_argc-1) { @@ -6214,40 +5905,26 @@ int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), pak = FS_OpenPackByExtension(vfs, NULL, pakname, pakname); if (pak) { - pak->EnumerateFiles(pak, "*.fmf", FS_EnumerateFMFs, &e); + pak->EnumerateFiles(pak, "*.fmf", FS_EnumeratedFMF, &e); pak->ClosePath(pak); } i = COM_CheckNextParm ("-basepack", i); } - //okay, no manifests in the basepack, try looking in the basedir. - //this defaults to the working directory. perhaps try the exe's location instead? - if (!e.found) - Sys_EnumerateFiles(host_parms.basedir, "*.fmf", FS_EnumerateFMFs, &e, NULL); - - if (!e.found) - { - if (*com_homepath) - Sys_EnumerateFiles(NULL, va("%s/*.fmf", com_homepath), FS_EnumerateFMFs, &e, NULL); -#ifdef __linux__ - Sys_EnumerateFiles(NULL, "/etc/fte/*.fmf", FS_EnumerateFMFs, &e, NULL); -#endif - } - //right, no fmf files anywhere. //just make stuff up from whatever games they may have installed on their system. - if (!e.found) + for (i = 0; gamemode_info[i].argname; i++) { - for (i = 0; gamemode_info[i].argname; i++) + Q_strncpyz(basedir, com_gamepath, sizeof(basedir)); + if (gamemode_info[i].manifestfile || + ((gamemode_info[i].exename || (i>0&&gamemode_info[i].customexec&&gamemode_info[i-1].customexec&&strcmp(gamemode_info[i].customexec,gamemode_info[i-1].customexec))) && FS_DirHasGame(com_gamepath, i)) || + (e.anygamedir&&Sys_FindGameData(NULL, gamemode_info[i].argname+1, basedir, sizeof(basedir), true))) { - if (gamemode_info[i].manifestfile || Sys_FindGameData(NULL, gamemode_info[i].argname+1, basedir, sizeof(basedir), true)) - { - ftemanifest_t *man = FS_GenerateLegacyManifest(NULL, 0, true, i); - if (e.callback(e.usr, man)) - e.found++; - else - FS_Manifest_Free(man); - } + man = FS_GenerateLegacyManifest(i, basedir); + if (e.callback(e.usr, man)) + e.found++; + else + FS_Manifest_Free(man); } } return e.found; @@ -6309,7 +5986,7 @@ qboolean FS_FixupGamedirForExternalFile(char *input, char *filename, size_t fnam Con_Printf("switching basedir+game to %s for %s\n", filename, input); Q_strncpyz(newbase, filename, sizeof(newbase)); host_parms.basedir = newbase; - FS_ChangeGame(FS_GenerateLegacyManifest(NULL, 0, true, game), true, true); + FS_ChangeGame(FS_GenerateLegacyManifest(game, newbase), true, true); } *sep = '/'; sep = NULL; @@ -6360,36 +6037,330 @@ qboolean FS_FixupGamedirForExternalFile(char *input, char *filename, size_t fnam } + +/*mod listing management*/ +static struct modlist_s *modlist; +static size_t nummods; +static qboolean modsinited; +void Mods_FlushModList(void) +{ + while (nummods) + { + nummods--; + if (modlist[nummods].manifest) + FS_Manifest_Free(modlist[nummods].manifest); + if (modlist[nummods].description) + Z_Free(modlist[nummods].description); + if (modlist[nummods].gamedir) + Z_Free(modlist[nummods].gamedir); + } + if (modlist) + Z_Free(modlist); + modlist = NULL; + modsinited = false; +} + +static qboolean Mods_AddManifest(void *usr, ftemanifest_t *man) +{ + int p, best = -1; + int i = nummods; + + for (p = 0; p < countof(man->gamepath); p++) + if (man->gamepath[p].path) + { + if (man->gamepath[p].flags & (GAMEDIR_PRIVATE|GAMEDIR_STEAMGAME)) + continue; //don't pick paths that don't make sense to others. + if (!(man->gamepath[p].flags & GAMEDIR_BASEGAME) || (best<0||(man->gamepath[best].flags & GAMEDIR_BASEGAME))) + best = p; + } + if (best < 0) + return false; //no gamedirs? wut? + + modlist = BZ_Realloc(modlist, (i+1) * sizeof(*modlist)); + modlist[i].manifest = man; + modlist[i].gamedir = Z_StrDup(man->gamepath[best].path); + modlist[i].description = man->formalname?Z_StrDup(man->formalname):NULL; + nummods = i+1; + return true; +} +static int Mods_WasPackageOrDatFound(const char *fname, qofs_t ofs, time_t modtime, void *usr, searchpathfuncs_t *spath) +{ //we check for *.dat too, because we care about [qw]progs.dat/menu.dat/csprogs.dat or possibly addons. hopefully we can get away with such a generic extension. + const char *ext = COM_GetFileExtension(fname, NULL); + if (!strcasecmp(ext, ".pk3") || !strcasecmp(ext, ".pak") || !strcasecmp(ext, ".dat")) + return false; //found one, can stop searching now + //FIXME: pk3dir + return true; //keep looking for one +} +static int Mods_WasMapFound(const char *fname, qofs_t ofs, time_t modtime, void *usr, searchpathfuncs_t *spath) +{ + const char *ext = COM_GetFileExtension(fname, NULL); + //don't bother looking for .map + if (!strcasecmp(ext, ".bsp") || !strcasecmp(ext, ".hmp")) + return false; //found one, can stop searching now + return true; //keep looking for one +} +static int QDECL Mods_AddGamedir(const char *fname, qofs_t fsize, time_t mtime, void *usr, searchpathfuncs_t *spath) +{ + char *desc; + size_t l = strlen(fname); + int i, p; + char gamedir[MAX_QPATH]; + const char *basedir = usr; + if (l && fname[l-1] == '/' && l < countof(gamedir)) + { + l--; + memcpy(gamedir, fname, l); + gamedir[l] = 0; + for (i = 0; i < nummods; i++) + { + //don't add dupes (can happen from basedir+homedir) + //if the gamedir was already included in one of the manifests, don't bother including it again. + //this generally removes id1. + if (modlist[i].manifest) + { + for (p = 0; p < countof(fs_manifest->gamepath); p++) + if (modlist[i].manifest->gamepath[p].path) + if (!Q_strcasecmp(modlist[i].manifest->gamepath[p].path, gamedir)) + return true; + } + else if (modlist[i].gamedir) + { + if (!Q_strcasecmp(modlist[i].gamedir, gamedir)) + return true; + } + } + + if ((desc = FS_MallocFile(va("%s%s/modinfo.txt", basedir, gamedir), FS_SYSTEM, NULL))) + ; //dp's modinfo.txt thing (which no mod seems to use anyway) + else if ((desc = FS_MallocFile(va("%s%s/description.txt", basedir, gamedir), FS_SYSTEM, NULL))) + ; //quake3's description stuff + else if ((desc = FS_MallocFile(va("%s%s/liblist.gam", basedir, gamedir), FS_SYSTEM, NULL))) + { //halflifeisms? o.O mneh why not + size_t u; + Cmd_TokenizeString(desc, false, false); + FS_FreeFile(desc); + desc = NULL; + for (u = 0; u < Cmd_Argc(); u+=2) + { + if (!strcasecmp(Cmd_Argv(u), "game")) + desc = Cmd_Argv(u+1); + } + if (desc) + desc = Z_StrDup(desc); + } + //we don't really know what it is. probably some useless subdir. report it only if it looks like there's something actually interesting in there + else if (!Sys_EnumerateFiles(va("%s%s/", basedir, gamedir), "*.*", Mods_WasPackageOrDatFound, NULL, NULL) || + !Sys_EnumerateFiles(va("%s%s/maps/", basedir, gamedir), "*.*", Mods_WasMapFound, NULL, NULL)) + ; //stopped early means we found a file. + else + return true; //nothing interesting there... don't bother to list it + + if (strchr(gamedir, ';') || !FS_GamedirIsOkay(gamedir)) + { + Z_Free(gamedir); + return true; //don't list it if we can't use it anyway + } + + modlist = BZ_Realloc(modlist, (i+1) * sizeof(*modlist)); + modlist[i].manifest = NULL; + modlist[i].gamedir = Z_StrDup(gamedir); + modlist[i].description = desc; + nummods = i+1; + } + return true; +} + +static int QDECL Mods_SortMod(const void *first, const void *second) +{ + const struct modlist_s *a = first; + const struct modlist_s *b = second; + return strcmp(a->gamedir, b->gamedir); +} +struct modlist_s *Mods_GetMod(size_t diridx) +{ + if (!modsinited) + { + int mancount; + modsinited = true; + FS_EnumerateKnownGames(Mods_AddManifest, NULL); + mancount = nummods; + if (*fs_manifest->installation) + { + if (com_homepathenabled) + Sys_EnumerateFiles(com_homepath, "*", Mods_AddGamedir, com_homepath, NULL); + Sys_EnumerateFiles(com_gamepath, "*", Mods_AddGamedir, com_gamepath, NULL); + } + qsort(modlist+mancount, nummods-mancount, sizeof(*modlist), Mods_SortMod); + } + if (diridx < nummods) + return &modlist[diridx]; + return NULL; +} + + + +#if defined(HAVE_CLIENT) && defined(WEBCLIENT) +typedef struct +{ + char *manifestname; //manifest getting written + char *url; //url to get the manifest from. + char *mantext; //contents of downloaded manifest... + int mansize; + ftemanifest_t *man; +} modinstall_t; +static void FS_ModInstallConfirmed(void *vctx, promptbutton_t button) +{ + modinstall_t *ctx = vctx; + if (button == PROMPT_YES) + { + vfsfile_t *out = FS_OpenVFS(ctx->manifestname, "wb", FS_SYSTEM); + if (out) + { + VFS_WRITE(out, ctx->mantext, ctx->mansize); + VFS_CLOSE(out); + + FS_ChangeGame(ctx->man, true, true); + ctx->man = NULL; + } + } + Z_Free(ctx->mantext); + Z_Free(ctx->url); + Z_Free(ctx->manifestname); + FS_Manifest_Free(ctx->man); + Z_Free(ctx); +} +static void FS_ModInstallGot(struct dl_download *dl) +{ + modinstall_t *ctx = dl->user_ctx; + if (dl->file && dl->status == DL_FINISHED) + { + ctx->mansize = VFS_GETLEN(dl->file); + ctx->mantext = Z_Malloc(ctx->mansize+1); + VFS_READ(dl->file, ctx->mantext, ctx->mansize); + ctx->mantext[ctx->mansize] = 0; + + ctx->man = FS_Manifest_ReadMem(ctx->manifestname, com_gamepath, ctx->mantext); + if (ctx->man && !strcmp(ctx->man->basedir, com_gamepath)) + { + //should probably show just the hostname for brevity. + Menu_Prompt(FS_ModInstallConfirmed, ctx, va("Install %s from\n%s ?", ctx->man->formalname, ctx->url), "Install", NULL, "Cancel"); + return; + } + } + + FS_ModInstallConfirmed(ctx, PROMPT_CANCEL); +} +static void FS_ModInstall(const char *dest, const char *url) +{ + struct dl_download *dl; + char fmfpath[MAX_OSPATH]; + + ftemanifest_t *man = NULL; + + //find out a writable path for the fmf. + if (!FS_NativePath(va("%s.fmf", dest), FS_ROOT, fmfpath, sizeof(fmfpath))) + return; + + //read it in if it exists. + man = FS_Manifest_ReadMod(dest); + if (man) + { + FS_ChangeGame(man, cfg_reload_on_gamedir.ival, false); + return; + } + + dl = HTTP_CL_Get(url, NULL, FS_ModInstallGot); + if (dl) + { + modinstall_t *m = Z_Malloc(sizeof(*m)); + m->manifestname = Z_StrDup(fmfpath); + m->url = Z_StrDup(url); + dl->user_ctx = m; +#ifdef MULTITHREAD + DL_CreateThread(dl, NULL, NULL); +#endif + } +} +#else +static void FS_ModInstall(const char *dest, const char *url) +{ +} +#endif + + +//switches manifests +//no args: lists known games +//1 arg: +// ~/quake/ trailing slash switches basedir (using said basedir's default manifest) +// quake3 loads hardcoded mod +// ~/foo.fmf loads the specified manifest +// http://foo/bar.fmf loads the specified manifest. archaic. doesn't save the fmf anywhere (will download its pk3s) +//2 args: +// foo http://foo/bar.fmf downloads to $basedir/foo.fmf if it doesn't exist (prompts), and then always loads it (like 'gamedir', no prompt when it already exists). static void FS_ChangeGame_f(void) { - int i; - char *arg = Cmd_Argv(1); + unsigned int i; + const char *arg = Cmd_Argv(1); + char *end; + struct modlist_s *mod; + ftemanifest_t *man; - //don't execute this if we're executing rcon commands, as this can change game directories. + //don't execute this if we're executing rcon commands, as this can change game directories and ruin logging. if (cmd_blockwait) return; - if (Cmd_IsInsecure()) - return; - if (!*arg) - { - Con_Printf("Valid games are:\n"); - for (i = 0; gamemode_info[i].argname; i++) + if ((i = strtol(arg, &end, 10)) && !*end) + { //for use by qc. loading mods by number... + mod = Mods_GetMod(--i); + if (mod) { - char nbase[MAX_OSPATH]; - char *note = "not installed"; - if (FS_DirHasGame(com_gamepath, i)) - note = com_gamepath; - else if (Sys_FindGameData(gamemode_info[i].poshname, gamemode_info[i].argname+1, nbase, sizeof(nbase), false) && FS_FixPath(nbase, sizeof(nbase)) && FS_DirHasGame(nbase, i)) - note = nbase; - Con_Printf(" %s (%s)\n", gamemode_info[i].argname+1, note); +#ifdef HAVE_CLIENT + CL_Disconnect(NULL); +#endif +#ifdef HAVE_SERVER + if (sv.state) + SV_UnspawnServer(); +#endif + if (mod->manifest) + { + man = FS_Manifest_Clone(mod->manifest); //FS_ChangeGame takes ownership... don't crash if its cached. + FS_ChangeGame(man, true, true); + } + else + COM_Gamedir(mod->gamedir, NULL); +#ifdef HAVE_CLIENT + Cbuf_AddText("menu_restart\n", RESTRICT_LOCAL); +#endif + } + return; + } + else if (Cmd_Argc()==3) + { //allowed to bypass insecurity. + //acts like gamedir, but prompts if you try anything else. + FS_ModInstall(arg, Cmd_Argv(2)); + return; + } + else if (Cmd_IsInsecure()) + { + Con_Printf("Blocking insecure command: %s %s\n", Cmd_Argv(0), Cmd_Args()); + return; + } + else if (!*arg) + { + Con_Printf("Valid games/mods are:\n"); + for (i = 0; (mod = Mods_GetMod(i)); i++) + { + man = mod->manifest; + if (man) + Con_Printf("\t^[%s\\tip\\%s\\type\\fs_changegame \"%u\" //%s^] (%s)\n", man->installation, man->filename?man->filename:"", i+1, man->filename, man->basedir?man->basedir:"not installed"); + else + Con_Printf("\t^[%s\\type\\gamedir \"%s\"^]\n", mod->description?mod->description:mod->gamedir, mod->gamedir); } - //FIXME: scan for fmf files. } else { if (strrchr(arg, '/') && !strrchr(arg, '/')[1]) - { + { //ends in slash. a new basedir. Q_strncpyz(com_gamepath, arg, sizeof(com_gamepath)); host_parms.basedir = com_gamepath; FS_ChangeGame(FS_ReadDefaultManifest(NULL, 0, true), true, true); @@ -6397,14 +6368,11 @@ static void FS_ChangeGame_f(void) else { for (i = 0; gamemode_info[i].argname; i++) - { + { if (!Q_strcasecmp(gamemode_info[i].argname+1, arg)) { Con_Printf("Switching to %s\n", gamemode_info[i].argname+1); -#ifdef PACKAGEMANAGER - PM_Shutdown(); -#endif - FS_ChangeGame(FS_GenerateLegacyManifest(NULL, 0, true, i), true, true); + FS_ChangeGame(FS_GenerateLegacyManifest(i, NULL), true, true); return; } } @@ -6417,6 +6385,7 @@ static void FS_ChangeGame_f(void) } } +//this function exists for use by the QI plugin and uses hacked up variations of the default manifest. static void FS_ChangeMod_f(void) { char cachename[512]; @@ -6501,6 +6470,9 @@ static void FS_ChangeMod_f(void) static void FS_ShowManifest_f(void) { + if (Cmd_IsInsecure()) + return; + if (fs_manifest) FS_Manifest_Print(fs_manifest); else @@ -6844,8 +6816,10 @@ void COM_InitFilesystem (void) Cvar_Register(&dpcompat_ignoremodificationtimes, "Filesystem"); Cvar_Register(&com_fs_cache, "Filesystem"); Cvar_Register(&fs_gamename, "Filesystem"); +#ifdef PACKAGEMANAGER Cvar_Register(&pkg_downloads_url, "Filesystem"); Cvar_Register(&pkg_autoupdate, "Filesystem"); +#endif Cvar_Register(&com_protocolname, "Server Info"); Cvar_Register(&com_protocolversion, "Server Info"); Cvar_Register(&fs_game, "Filesystem"); diff --git a/engine/common/fs.h b/engine/common/fs.h index 83d93364..da6fae7d 100644 --- a/engine/common/fs.h +++ b/engine/common/fs.h @@ -78,10 +78,19 @@ unsigned int PM_MarkUpdates (void); //mark new/updated packages as needing insta void PM_ApplyChanges(void); //for -install/-doinstall args void PM_ManifestPackage(const char *name, int security); qboolean PM_FindUpdatedEngine(char *syspath, size_t syspathsize); //names the engine we should be running +void PM_AddManifestPackages(ftemanifest_t *man); void Menu_Download_Update(void); int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), void *usr); +struct modlist_s +{ + ftemanifest_t *manifest; + char *gamedir; + char *description; +}; +struct modlist_s *Mods_GetMod(size_t diridx); + #define SPF_REFERENCED 1 //something has been loaded from this path. should filter out client references... #define SPF_COPYPROTECTED 2 //downloads are not allowed fom here. #define SPF_TEMPORARY 4 //a map-specific path, purged at map change. diff --git a/engine/common/fs_stdio.c b/engine/common/fs_stdio.c index 76d2dcb3..f5fc3fc7 100644 --- a/engine/common/fs_stdio.c +++ b/engine/common/fs_stdio.c @@ -378,6 +378,31 @@ static qboolean QDECL FSSTDIO_FileStat (searchpathfuncs_t *handle, flocation_t * return false; } +static qboolean QDECL FSSTDIO_RenameFile(searchpathfuncs_t *handle, const char *oldname, const char *newname) +{ + stdiopath_t *sp = (void*)handle; + char oldsyspath[MAX_OSPATH]; + char newsyspath[MAX_OSPATH]; + if (fs_readonly) + return false; + if ((unsigned int)snprintf (oldsyspath, sizeof(oldsyspath), "%s/%s", sp->rootpath, oldname) > sizeof(oldsyspath)-1) + return false; //too long + if ((unsigned int)snprintf (newsyspath, sizeof(newsyspath), "%s/%s", sp->rootpath, newname) > sizeof(newsyspath)-1) + return false; //too long + return Sys_Rename(oldsyspath, newsyspath); +} +static qboolean QDECL FSSTDIO_RemoveFile(searchpathfuncs_t *handle, const char *filename) +{ + stdiopath_t *sp = (void*)handle; + char syspath[MAX_OSPATH]; + if (fs_readonly) + return false; + if ((unsigned int)snprintf (syspath, sizeof(syspath), "%s/%s", sp->rootpath, filename) > sizeof(syspath)-1) + return false; //too long + if (*filename && filename[strlen(filename)-1] == '/') + return Sys_rmdir(syspath); + return Sys_remove(syspath); +} searchpathfuncs_t *QDECL FSSTDIO_OpenPath(vfsfile_t *mustbenull, searchpathfuncs_t *parent, const char *filename, const char *desc, const char *prefix) { @@ -404,6 +429,8 @@ searchpathfuncs_t *QDECL FSSTDIO_OpenPath(vfsfile_t *mustbenull, searchpathfuncs np->pub.PollChanges = FSSTDIO_PollChanges; np->pub.FileStat = FSSTDIO_FileStat; np->pub.CreateFile = FSSTDIO_CreateLoc; + np->pub.RenameFile = FSSTDIO_RenameFile; + np->pub.RemoveFile = FSSTDIO_RemoveFile; return &np->pub; } diff --git a/engine/common/fs_win32.c b/engine/common/fs_win32.c index 1b4a8e30..a1f1648f 100644 --- a/engine/common/fs_win32.c +++ b/engine/common/fs_win32.c @@ -657,6 +657,8 @@ static qboolean QDECL VFSW32_RemoveFile(searchpathfuncs_t *handle, const char *f if (fs_readonly) return false; snprintf (syspath, sizeof(syspath)-1, "%s/%s", wp->rootpath, filename); + if (*filename && filename[strlen(filename)-1] == '/') + return Sys_rmdir(syspath); return Sys_remove(syspath); } diff --git a/engine/common/log.c b/engine/common/log.c index 36c5e344..f2b2e996 100644 --- a/engine/common/log.c +++ b/engine/common/log.c @@ -755,10 +755,10 @@ struct certprompt_s qbyte cert[1]; }; static struct certprompt_s *certlog_curprompt; -static void CertLog_Add_Prompted(void *vctx, int button) +static void CertLog_Add_Prompted(void *vctx, promptbutton_t button) { struct certprompt_s *ctx = vctx; - if (button == 0) //button_yes / button_left + if (button == PROMPT_YES) //button_yes / button_left { CertLog_Update(ctx->hostname, ctx->cert, ctx->certsize); CertLog_Write(); diff --git a/engine/common/plugin.c b/engine/common/plugin.c index 9cc13f81..939b5865 100644 --- a/engine/common/plugin.c +++ b/engine/common/plugin.c @@ -1962,6 +1962,7 @@ static void *QDECL PlugBI_GetEngineInterface(const char *interfacename, size_t s static rbeplugfuncs_t funcs = { RBEPLUGFUNCS_VERSION, + sizeof(wedict_t), World_RegisterPhysicsEngine, World_UnregisterPhysicsEngine, diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index da6448ef..24bb0685 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -1606,29 +1606,26 @@ void QCBUILTIN PF_memcpy (pubprogfuncs_t *prinst, struct globalvars_s *pr_global int dst = G_INT(OFS_PARM0); int src = G_INT(OFS_PARM1); int size = G_INT(OFS_PARM2); - if (dst < 0 || dst+size >= prinst->stringtablesize) - { + if (size < 0 || size > prinst->stringtablesize) + PR_BIError(prinst, "PF_memcpy: invalid size\n"); + else if (dst < 0 || dst+size > prinst->stringtablesize) PR_BIError(prinst, "PF_memcpy: invalid dest\n"); - return; - } - if (src < 0 || src+size >= prinst->stringtablesize) - { + else if (src < 0 || src+size > prinst->stringtablesize) PR_BIError(prinst, "PF_memcpy: invalid source\n"); - return; - } - memmove(prinst->stringtable + dst, prinst->stringtable + src, size); + else + memmove(prinst->stringtable + dst, prinst->stringtable + src, size); } void QCBUILTIN PF_memfill8 (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { int dst = G_INT(OFS_PARM0); int val = G_INT(OFS_PARM1); int size = G_INT(OFS_PARM2); - if (dst < 0 || dst+size >= prinst->stringtablesize) - { - PR_BIError(prinst, "PF_memcpy: invalid dest\n"); - return; - } - memset(prinst->stringtable + dst, val, size); + if (size < 0 || size > prinst->stringtablesize) + PR_BIError(prinst, "PF_memcpy: invalid size\n"); + else if (dst < 0 || dst+size > prinst->stringtablesize) + PR_BIError(prinst, "PF_memfill8: invalid dest\n"); + else + memset(prinst->stringtable + dst, val, size); } void QCBUILTIN PF_memptradd (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) @@ -1670,6 +1667,8 @@ typedef struct char *stringdata; }; } pf_hashentry_t; +#define HASH_REPLACE 256 +#define HASH_ADD 512 #define FIRSTTABLE 1 static pf_hashtab_t *pf_hashtab; static size_t pf_hash_maxtables; @@ -1787,6 +1786,7 @@ void QCBUILTIN PF_hash_getcb (pubprogfuncs_t *prinst, struct globalvars_s *pr_gl memcpy(G_VECTOR(OFS_RETURN), G_VECTOR(OFS_PARM2), sizeof(vec3_t)); */ } + void QCBUILTIN PF_hash_add (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { pf_hashtab_t *tab = PF_hash_findtab(prinst, G_FLOAT(OFS_PARM0)); @@ -1799,7 +1799,7 @@ void QCBUILTIN PF_hash_add (pubprogfuncs_t *prinst, struct globalvars_s *pr_glob { if (!type) type = tab->defaulttype; - if (!(flags & 512) || (flags & 256)) + if (!(flags & HASH_ADD) || (flags & HASH_REPLACE)) { ent = Hash_Get(&tab->tab, name); if (ent) @@ -1907,11 +1907,138 @@ void QCBUILTIN PF_hash_createtab (pubprogfuncs_t *prinst, struct globalvars_s *p G_FLOAT(OFS_RETURN) = i + FIRSTTABLE; } -void pf_hash_savegame(void) //write the persistant table to a saved game. +static void PF_hash_savetab(void *ctx, void *data) { + char tmp[8192]; + pf_hashentry_t *ent = data; + if (ent->type == ev_string) + VFS_PRINTF (ctx, "\t%i \"%s\" %s\n", ent->type, ent->name, COM_QuotedString(ent->stringdata, tmp, sizeof(tmp), false)); + else if (ent->type == ev_vector) + VFS_PRINTF (ctx, "\t%i \"%s\" %f %f %f\n", ent->type, ent->name, ent->data[0], ent->data[1], ent->data[2]); + else if (ent->type == ev_float) + VFS_PRINTF (ctx, "\t%i \"%s\" %f\n", ent->type, ent->name, *(float*)ent->data); + else + VFS_PRINTF (ctx, "\t%i \"%s\" %#x\n", ent->type, ent->name, *(int*)ent->data); } -void pf_hash_loadgame(void) //(re)load the persistant table. +static void PR_hash_savegame(vfsfile_t *f, pubprogfuncs_t *prinst, qboolean binary) //write the persistant table to a saved game. { + unsigned int tab; + char *tmp = NULL; + + for (tab = 0; tab < pf_hash_maxtables; tab++) + { + if (pf_hashtab[tab].prinst == prinst)// && (pf_hashtab[tab].flags & BUFFLAG_SAVED)) + { + VFS_PRINTF (f, "hashtable %u %i %u\n", tab+FIRSTTABLE, pf_hashtab[tab].defaulttype, (unsigned int)pf_hashtab[tab].tab.numbuckets); + VFS_PRINTF (f, "{\n"); + Hash_Enumerate(&pf_hashtab[tab].tab, PF_hash_savetab, f); + VFS_PRINTF (f, "}\n"); + } + } + free(tmp); +} +static const char *PR_hash_loadgame(pubprogfuncs_t *prinst, const char *l) +{ + char name[8192]; + char token[65536]; + int tabno; + int nlen, vlen; + etype_t hashtype; + size_t buffersize; + com_tokentype_t tt; + pf_hashtab_t *tab; + pf_hashentry_t *ent; + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return NULL; + tabno = atoi(token)-FIRSTTABLE; + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return NULL; + hashtype = atoi(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return NULL; + buffersize = atoi(token); + + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_LINEENDING)return NULL; + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_PUNCTUATION)return NULL; + if (strcmp(token, "{")) return NULL; + + if (tabno < 0 || tabno >= 1<<16) + return NULL; + if (tabno >= pf_hash_maxtables) + Z_ReallocElements((void**)&pf_hashtab, &pf_hash_maxtables, tabno+1, sizeof(*pf_hashtab)); + + tab = &pf_hashtab[tabno]; + if (tab->prinst) + { + tab->prinst = NULL; + Hash_Enumerate(&tab->tab, PF_hash_destroytab_enum, NULL); + Z_Free(tab->bucketmem); + tab->bucketmem = NULL; + } + tab->prinst = prinst; + tab->defaulttype = hashtype; + tab->bucketmem = Z_Malloc(Hash_BytesForBuckets(buffersize)); + Hash_InitTable(&tab->tab, buffersize, tab->bucketmem); + + for(;;) + { + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt); + if (tt == TTP_LINEENDING) + continue; + if (tt == TTP_PUNCTUATION && !strcmp(token, "}")) + break; + if (tt != TTP_RAWTOKEN) + break; + hashtype = atoi(token); + l = COM_ParseTokenOut(l, NULL, name, sizeof(name), &tt);if (tt != TTP_STRING)return NULL; + + nlen = strlen(name); + if (hashtype == ev_string) + { + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_STRING)return NULL; + vlen = strlen(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_LINEENDING)return NULL; + + ent = BZ_Malloc(sizeof(*ent) + nlen+1 + vlen+1); + ent->name = (char*)(ent+1); + ent->type = hashtype; + ent->stringdata = ent->name+(nlen+1); + memcpy(ent->name, name, nlen); + ent->name[nlen] = 0; + memcpy(ent->stringdata, token, vlen+1); + Hash_Add(&tab->tab, ent->name, ent, &ent->buck); + } + else + { + vec3_t data = {0,0,0}; + if (hashtype == ev_vector) + { + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return NULL; + data[0] = atof(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return NULL; + data[1] = atof(token); + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return NULL; + data[2] = atof(token); + } + else if (hashtype == ev_float) + { + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return NULL; + *data = atof(token); + } + else //treat it as an ev_int + { + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_RAWTOKEN)return NULL; + *(int*)data = atoi(token); + } + l = COM_ParseTokenOut(l, NULL, token, sizeof(token), &tt);if (tt != TTP_LINEENDING)return NULL; + + ent = BZ_Malloc(sizeof(*ent) + nlen + 1); + ent->name = (char*)(ent+1); + ent->type = hashtype; + memcpy(ent->name, name, nlen); + ent->name[nlen] = 0; + memcpy(ent->data, data, sizeof(vec3_t)); + Hash_Add(&tab->tab, ent->name, ent, &ent->buck); + } + } + return l; } void pf_hash_preserve(void) //map changed, make sure it can be reset properly. { @@ -4042,7 +4169,7 @@ struct strbuf { }; #define BUFFLAG_SAVED 1 -#define BUFSTRBASE 1 +#define BUFSTRBASE 1 //officially these are 0-based (ie: use negatives for not-a-buffer), but fte biases it to catch qc bugs. struct strbuf *strbuflist; size_t strbufmax; @@ -4532,9 +4659,9 @@ void QCBUILTIN PF_buf_cvarlist (pubprogfuncs_t *prinst, struct globalvars_s *pr for (grp=cvar_groups ; grp ; grp=grp->next) for (var=grp->cvars ; var ; var=var->next) { - if (plen && (pwc?wildcmp(pattern, var->name):strncmp(var->name, pattern, plen))) + if (plen && (pwc?!wildcmp(pattern, var->name):strncmp(var->name, pattern, plen))) continue; - if (alen && (awc?!wildcmp(antipattern, var->name):!strncmp(var->name, antipattern, alen))) + if (alen && (awc?wildcmp(antipattern, var->name):!strncmp(var->name, antipattern, alen))) continue; PF_bufstr_add_internal(bufno, var->name, true); @@ -4819,7 +4946,8 @@ static void PR_uri_get_callback2(int iarg, void *data) G_INT(OFS_PARM2) = prinst->AllocTempString(prinst, &buffer, len+1); len = VFS_READ(ctx->file, buffer, len); if (len < 0) - buffer[len] = 0; + len = 0; + buffer[len] = 0; G_INT(OFS_PARM3) = len; } @@ -6571,6 +6699,12 @@ qboolean PR_Common_LoadGame(pubprogfuncs_t *prinst, char *command, const char ** if (!l) return false; } + else if (!strcmp(command, "hashtable")) + { + l = PR_hash_loadgame(prinst, l); + if (!l) + return false; + } else return false; *file = l; @@ -6579,6 +6713,7 @@ qboolean PR_Common_LoadGame(pubprogfuncs_t *prinst, char *command, const char ** void PR_Common_SaveGame(vfsfile_t *f, pubprogfuncs_t *prinst, qboolean binary) { PR_buf_savegame(f, prinst, binary); + PR_hash_savegame(f, prinst, binary); } @@ -6804,6 +6939,7 @@ lh_extension_t QSG_Extensions[] = { #ifndef SERVERONLY {"DP_CON_SETA", 0, NULL, {NULL}, "The 'seta' console command exists, like the 'set' command, but also marks the cvar for archiving, allowing it to be written into the user's config. Use this command in your default.cfg file."}, #endif + {"DP_CSQC_ROTATEMOVES"}, {"DP_EF_ADDITIVE"}, //--{"DP_ENT_ALPHA"}, //listed above {"DP_EF_BLUE"}, //hah!! This is QuakeWorld!!! @@ -6841,6 +6977,7 @@ lh_extension_t QSG_Extensions[] = { {"DP_QC_CVAR_DEFSTRING", 1, NULL, {"cvar_defstring"}}, {"DP_QC_CVAR_STRING", 1, NULL, {"cvar_string"}}, //448 builtin. {"DP_QC_CVAR_TYPE", 1, NULL, {"cvar_type"}}, + {"DP_QC_DIGEST_SHA256"}, {"DP_QC_EDICT_NUM", 1, NULL, {"edict_num"}}, {"DP_QC_ENTITYDATA", 5, NULL, {"numentityfields", "entityfieldname", "entityfieldtype", "getentityfieldstring", "putentityfieldstring"}}, {"DP_QC_ETOS", 1, NULL, {"etos"}}, @@ -6987,6 +7124,10 @@ lh_extension_t QSG_Extensions[] = { {"FTE_QC_CHECKPVS", 1, NULL, {"checkpvs"}}, {"FTE_QC_CROSSPRODUCT", 1, NULL, {"crossproduct"}}, {"FTE_QC_CUSTOMSKINS", 1, NULL, {"setcustomskin", "loadcustomskin", "applycustomskin", "releasecustomskin"}, "The engine supports the use of q3 skins, as well as the use of such skin 'files' to specify rich top+bottom colours, qw skins, geomsets, or texture composition even on non-players.."}, + {"FTE_QC_DIGEST_SHA1"}, + {"FTE_QC_DIGEST_SHA224"}, + {"FTE_QC_DIGEST_SHA384"}, + {"FTE_QC_DIGEST_SHA512"}, {"FTE_QC_FS_SEARCH_SIZEMTIME", 2, NULL, {"search_getfilesize", "search_getfilemtime"}}, {"FTE_QC_HARDWARECURSORS", 0, NULL, {NULL}, "setcursormode exists in both csqc+menuqc, and accepts additional arguments to specify a cursor image to use when this module has focus. If the image exceeds hardware limits (or hardware cursors are unsupported), it will be emulated using regular draws - this at least still avoids conflicting cursors as only one will ever be used, even if console+menu+csqc are all overlayed."}, {"FTE_QC_HASHTABLES", 6, NULL, {"hash_createtab", "hash_destroytab", "hash_add", "hash_get", "hash_delete", "hash_getkey"}, "Provides efficient string-based lookups."}, diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index 50369d86..ff4ffda7 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -455,6 +455,7 @@ void QCBUILTIN PF_cl_setwindowcaption (pubprogfuncs_t *prinst, struct globalvars void QCBUILTIN PF_cl_playingdemo (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_cl_runningserver (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_cl_getgamedirinfo (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_cl_getpackagemanagerinfo (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_cs_media_create (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_cs_media_destroy (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_cs_media_command (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); @@ -606,8 +607,9 @@ unsigned int FTEToDPContents(unsigned int contents); #define SOLID_BSP 4 // bsp clip, touch on edge, block #define SOLID_PHASEH2 5 // hexen2 flag - these ents can be freely walked through or something #define SOLID_CORPSE 5 // non-solid to solid_slidebox entities and itself. -#define SOLID_LADDER 20 //dmw. touch on edge, not blocking. Touching players have different physics. Otherwise a SOLID_TRIGGER +#define SOLID_LADDER 20 //spike: legacy. forces FTECONTENTS_LADDER. #define SOLID_PORTAL 21 //1: traces always use point-size. 2: various movetypes automatically transform entities. 3: traces that impact portal bbox use a union. 4. traces ignore part of the world within the portal's box +#define SOLID_BSPTRIGGER 22 //spike: like solid trigger, except uses bsp checks instead of just aabb. #define SOLID_PHYSICS_BOX 32 // deprecated. physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) #define SOLID_PHYSICS_SPHERE 33 // deprecated. physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) #define SOLID_PHYSICS_CAPSULE 34 // deprecated. physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) @@ -639,6 +641,7 @@ unsigned int FTEToDPContents(unsigned int contents); typedef struct { int version; + int wedictsize; //sizeof(wedict_t) qboolean (QDECL *RegisterPhysicsEngine)(const char *enginename, void(QDECL*start_physics)(world_t*world)); //returns false if there's already one active. void (QDECL *UnregisterPhysicsEngine)(const char *enginename); //returns false if there's already one active. @@ -745,6 +748,22 @@ typedef enum VF_SKYROOM_CAMERA = 222, VF_PIXELPSCALE = 223, //[dpi_x, dpi_y, dpi_y/dpi_x] VF_PROJECTIONOFFSET = 224, //allows for off-axis projections. + + + VF_DP_CLEARSCREEN = 201, // weird behaviour that disables a whole load of things. +//fuck DP and their complete lack of respect for existing implemenetations + VF_DP_FOG_DENSITY = 202, //misassigned + VF_DP_FOG_COLOR = 203, //misassigned + VF_DP_FOG_COLOR_R = 204, //misassigned + VF_DP_FOG_COLOR_G = 205, //misassigned + VF_DP_FOG_COLOR_B = 206, //misassigned + VF_DP_FOG_ALPHA = 207, //misassigned + VF_DP_FOG_START = 208, //misassigned + VF_DP_FOG_END = 209, //misassigned + VF_DP_FOG_HEIGHT = 210, //misassigned + VF_DP_FOG_FADEDEPTH = 211, //misassigned + VF_DP_MAINVIEW = 400, // defective. should be a viewid instead, allowing for per-view motionblur instead of disabling it outright + VF_DP_MINFPS_QUALITY = 401, //multiplier for lod and culling to try to reduce costs. } viewflags; /*FIXME: this should be changed*/ @@ -808,6 +827,32 @@ enum csqc_input_event CSIE_GYROSCOPE = 7, /*x, y, z rotational acceleration*/ }; +enum getgamedirinfo_e +{ + GGDI_GAMEDIR=0, //the publically visible gamedir reported by servers. + GGDI_DESCRIPTION=1, //some text from the .fmf or a gamedirin + GGDI_OVERRIDES=2, //some text you can parse for custom info. + GGDI_LOADCOMMAND=3, //returns a string which can be localcmded to load the mod, with whatever quirks are needed to activate it properly. + GGDI_ICON=4, //returns a string which can be drawpiced. + GGDI_ALLGAMEDIRS=5, //; delimited list basegames;gamedirs ordering +}; +enum packagemanagerinfo_e +{ + GPMI_NAME, //name of the package, for use with the pkg command. + GPMI_CATEGORY, //category text + GPMI_TITLE, //name of the package, for showing the user. + GPMI_VERSION, //version info (may have multiple with the same name but different versions) + GPMI_DESCRIPTION, //some blurb + GPMI_LICENSE, //what license its distributed under + GPMI_AUTHOR, //name of the person(s) who created it + GPMI_WEBSITE, //where to contribute/find out more info/etc + GPMI_INSTALLED, //current state + GPMI_ACTION, //desired state + GPMI_AVAILABLE, //whether it may be downloaded or not. + GPMI_FILESIZE, //whether it may be downloaded or not. + GPMI_GAMEDIR, //so you know which mod(s) its relevant for +}; + #ifdef TERRAIN enum terrainedit_e { diff --git a/engine/common/protocol.h b/engine/common/protocol.h index ebc4d884..d202fbaa 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -363,14 +363,19 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. enum clustercmdops_e { ccmd_bad = 0, //abort! - ccmd_stuffcmd = 1, //regular ol stuffcmd + ccmd_stuffcmd, //regular ol stuffcmd //string concommand - ccmd_print = 2, + ccmd_setcvar, //master->server to order a cvar change. + ccmd_print, //string message ccmd_acceptserver, //serverid ccmd_lostplayer, //player dropped/timed out //long plid + ccmd_foundplayer, //server->master, saying that a player tried to connect directly (and we need their info) + //string name + //string clientaddress + //string guid ccmd_takeplayer, //master->server, saying to allocate a slot for a player. //long plid //long fromsvid (0=no reply needed) diff --git a/engine/common/q3common.c b/engine/common/q3common.c index 09d78743..2e1c8dd3 100644 --- a/engine/common/q3common.c +++ b/engine/common/q3common.c @@ -193,6 +193,8 @@ void VM_fcloseall (int owner) +//filesystem searches result in a tightly-packed blob of null-terminated filenames (along with a count for how many entries) +//$modlist searches give both gamedir AND description strings (in that order) instead of just one string per entry (loaded via fs_game cvar along with a vid_restart). typedef struct { char *initialbuffer; char *buffer; diff --git a/engine/common/sys_linux_threads.c b/engine/common/sys_linux_threads.c index b6a8749a..588648ef 100644 --- a/engine/common/sys_linux_threads.c +++ b/engine/common/sys_linux_threads.c @@ -425,6 +425,7 @@ pubsubserver_t *Sys_ForkServer(void) linsubserver_t *ctx; char *argv[64]; int argc = 0; + int l; argv[argc++] = exename; argv[argc++] = "-clusterslave"; @@ -437,9 +438,10 @@ pubsubserver_t *Sys_ForkServer(void) #elif 0 strcpy(exename, "/tmp/ftedbg/fteqw.sv"); #else - memset(exename, 0, sizeof(exename)); //having problems with valgrind being stupid. - if (readlink("/proc/self/exe", exename, sizeof(exename)-1) <= 0) + l = readlink("/proc/self/exe", exename, sizeof(exename)-1); + if (l <= 0) return NULL; + exename[l] = 0; #endif Con_DPrintf("Execing %s\n", exename); diff --git a/engine/gl/gl_alias.c b/engine/gl/gl_alias.c index 1acd190e..99a88aa5 100644 --- a/engine/gl/gl_alias.c +++ b/engine/gl/gl_alias.c @@ -2898,7 +2898,7 @@ void BE_GenModelBatches(batch_t **batches, const dlight_t *dl, unsigned int bemo switch(emodel->type) { case mod_brush: - if (r_drawentities.ival == 2) + if (r_drawentities.ival == 2 && cls.allow_cheats) //2 is considered a cheat, because it can be used as a wallhack (whereas mdls are not normally considered as occluding). continue; Surf_GenBrushBatches(batches, ent); break; diff --git a/engine/gl/gl_backend.c b/engine/gl/gl_backend.c index 7880883f..34d680b1 100644 --- a/engine/gl/gl_backend.c +++ b/engine/gl/gl_backend.c @@ -1021,48 +1021,6 @@ void R_FetchPlayerColour(unsigned int cv, vec3_t rgb) }*/ } -static void RevertToKnownState(void) -{ - if (shaderstate.currentvao) - qglBindVertexArray(0); - shaderstate.currentvao = 0; - shaderstate.curvertexvbo = ~0; - GL_SelectVBO(0); -// GL_SelectEBO(0); - - while(shaderstate.lastpasstmus>0) - { - GL_LazyBind(--shaderstate.lastpasstmus, 0, r_nulltex); - } - GL_SelectTexture(0); - -#ifndef GLSLONLY - if (!gl_config_nofixedfunc) - { - BE_SetPassBlendMode(0, PBM_REPLACE); - qglColor4f(1,1,1,1); - - GL_DeSelectProgram(); - } -#endif - - shaderstate.shaderbits &= ~(SBITS_DEPTHFUNC_BITS|SBITS_MASK_BITS|SBITS_AFFINE); - shaderstate.shaderbits |= SBITS_MISC_DEPTHWRITE; - - shaderstate.shaderbits &= ~(SBITS_BLEND_BITS); - qglDisable(GL_BLEND); - - qglDepthFunc(GL_LEQUAL); - qglDepthMask(GL_TRUE); - - qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); -} - -void PPL_RevertToKnownState(void) -{ - RevertToKnownState(); -} - #ifdef RTLIGHTS void GLBE_SetupForShadowMap(dlight_t *dl, int texwidth, int texheight, float shadowscale) { @@ -5552,7 +5510,7 @@ void GLBE_SubmitMeshes (batch_t **worldbatches, int start, int stop) } } -static void BE_UpdateLightmaps(void) +void GLBE_UpdateLightmaps(void) { lightmapinfo_t *lm; int lmidx; @@ -5580,7 +5538,20 @@ static void BE_UpdateLightmaps(void) GL_MTBind(0, GL_TEXTURE_2D, lm->lightmap_texture); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - qglTexImage2D(GL_TEXTURE_2D, 0, gl_config.formatinfo[lm->fmt].internalformat, lm->width, lm->height, 0, gl_config.formatinfo[lm->fmt].format, gl_config.formatinfo[lm->fmt].type, lm->lightmaps); + if (qglTexStorage2D && gl_config.formatinfo[lm->fmt].sizedformat) + { + qglTexStorage2D(GL_TEXTURE_2D, 1, gl_config.formatinfo[lm->fmt].sizedformat, lm->width, lm->height); + if (lm->pbo_handle) + { + qglBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, lm->pbo_handle); + qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, t, lm->width, b-t, gl_config.formatinfo[lm->fmt].format, gl_config.formatinfo[lm->fmt].type, (char*)NULL + t*lm->width*lm->pixbytes); + qglBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + } + else + qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, t, lm->width, b-t, gl_config.formatinfo[lm->fmt].format, gl_config.formatinfo[lm->fmt].type, lm->lightmaps+t*lm->width*lm->pixbytes); + } + else + qglTexImage2D(GL_TEXTURE_2D, 0, gl_config.formatinfo[lm->fmt].internalformat, lm->width, lm->height, 0, gl_config.formatinfo[lm->fmt].format, gl_config.formatinfo[lm->fmt].type, lm->lightmaps); if (gl_config.glversion >= (gl_config.gles?3.0:3.3)) { @@ -5593,7 +5564,14 @@ static void BE_UpdateLightmaps(void) else { GL_MTBind(0, GL_TEXTURE_2D, lm->lightmap_texture); - qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, t, lm->width, b-t, gl_config.formatinfo[lm->fmt].format, gl_config.formatinfo[lm->fmt].type, lm->lightmaps+t*lm->width*lm->pixbytes); + if (lm->pbo_handle) + { + qglBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, lm->pbo_handle); + qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, t, lm->width, b-t, gl_config.formatinfo[lm->fmt].format, gl_config.formatinfo[lm->fmt].type, (char*)NULL + t*lm->width*lm->pixbytes); + qglBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + } + else + qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, t, lm->width, b-t, gl_config.formatinfo[lm->fmt].format, gl_config.formatinfo[lm->fmt].type, lm->lightmaps+t*lm->width*lm->pixbytes); } lm->modified = false; lm->rectchange.l = lm->width; @@ -5651,10 +5629,7 @@ void GLBE_RenderToTextureUpdate2d(qboolean destchanged) shaderstate.tex_sourcecol = R2D_RT_GetTexture(r_refdef.rt_sourcecolour.texname, &width, &height); shaderstate.tex_sourcedepth = R2D_RT_GetTexture(r_refdef.rt_depth.texname, &width, &height); - if (*r_refdef.nearenvmap.texname) - shaderstate.tex_reflectcube = Image_GetTexture(r_refdef.nearenvmap.texname, NULL, IF_TEXTYPE_CUBE, NULL, NULL, 0, 0, TF_INVALID); - else - shaderstate.tex_reflectcube = r_nulltex; + shaderstate.tex_reflectcube = R_GetDefaultEnvmap(); } } void GLBE_FBO_Sources(texid_t sourcecolour, texid_t sourcedepth) @@ -6301,7 +6276,7 @@ void GLBE_DrawWorld (batch_t **worldbatches) // else // shaderstate.updatetime = cl.servertime; - BE_UpdateLightmaps(); + GLBE_UpdateLightmaps(); if (worldbatches) { if (worldbatches[SHADER_SORT_SKY] && r_refdef.skyroom_enabled) diff --git a/engine/gl/gl_font.c b/engine/gl/gl_font.c index 03e226b4..14e5d97c 100644 --- a/engine/gl/gl_font.c +++ b/engine/gl/gl_font.c @@ -2665,7 +2665,10 @@ int Font_LineBreaks(conchar_t *start, conchar_t *end, int maxpixelwidth, int max if (codepoint > ' ') l = n; else + { + l = n; break; + } } if (l == start && bt>start) l = Font_DecodeReverse(bt, start, &codeflags, &codepoint); @@ -2677,6 +2680,14 @@ int Font_LineBreaks(conchar_t *start, conchar_t *end, int maxpixelwidth, int max if (foundlines == maxlines) break; + for (;;) + { + n = Font_Decode(l, &codeflags, &codepoint); + if (!(codeflags & CON_HIDDEN) && (codepoint != ' ')) + break; + l = n; + } + start=l; if (start == end) break; diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c index ba297682..6604d63c 100644 --- a/engine/gl/gl_model.c +++ b/engine/gl/gl_model.c @@ -3413,7 +3413,9 @@ static void Mod_LoadMiptex(model_t *loadmodel, texture_t *tx, miptex_t *mt, size else if (!strncmp(extfmt, "RGBA", 4)) newfmt = PTI_RGBA8; //32bpp, we don't normally need this alpha precision (padding can be handy though, for the lazy). else if (!strncmp(extfmt, "RGB", 4)) newfmt = PTI_RGB8; //24bpp else if (!strncmp(extfmt, "565", 4)) newfmt = PTI_RGB565; //16bpp + else if (!strncmp(extfmt, "4444", 4)) newfmt = PTI_RGBA4444; //16bpp else if (!strncmp(extfmt, "5551", 4)) newfmt = PTI_RGBA5551; //16bpp + else if (!strncmp(extfmt, "LUM8", 4)) newfmt = PTI_L8; //8bpp else if (!strncmp(extfmt, "EXP5", 4)) newfmt = PTI_E5BGR9; //32bpp, we don't normally need this alpha precision... else if (!strncmp(extfmt, "BC1", 4)) newfmt = PTI_BC1_RGBA; //4bpp else if (!strncmp(extfmt, "BC2", 4)) newfmt = PTI_BC2_RGBA; //8bpp, we don't normally need this alpha precision... @@ -5482,6 +5484,10 @@ static qboolean QDECL Mod_LoadBrushModel (model_t *mod, void *buffer, size_t fsi // // set up the submodels (FIXME: this is confusing) // + + for (j=0 ; j<2 ; j++) + Q1BSP_CheckHullNodes(&mod->hulls[j]); + for (i=0, submod = mod; inumsubmodels ; i++) { bm = &mod->submodels[i]; @@ -5489,7 +5495,7 @@ static qboolean QDECL Mod_LoadBrushModel (model_t *mod, void *buffer, size_t fsi submod->rootnode = submod->nodes + bm->headnode[0]; submod->hulls[0].firstclipnode = bm->headnode[0]; submod->hulls[0].available = true; - Q1BSP_CheckHullNodes(&submod->hulls[0]); +// Q1BSP_CheckHullNodes(&submod->hulls[0]); TRACE(("LoadBrushModel %i\n", __LINE__)); for (j=1 ; jhulls[j].firstclipnode > submod->hulls[j].lastclipnode) submod->hulls[j].available = false; - if (submod->hulls[j].available) - Q1BSP_CheckHullNodes(&submod->hulls[j]); +// if (submod->hulls[j].available) +// Q1BSP_CheckHullNodes(&submod->hulls[j]); } if (mod->fromgame == fg_halflife && i) diff --git a/engine/gl/gl_model.h b/engine/gl/gl_model.h index d7099dcb..ef4ed9ab 100644 --- a/engine/gl/gl_model.h +++ b/engine/gl/gl_model.h @@ -157,8 +157,9 @@ typedef struct batch_s { struct { - unsigned int shadowbatch; //a unique index to accelerate shadowmesh generation (dlights, yay!) - unsigned int ebobatch; // + unsigned int shadowbatch; //a unique index to accelerate shadowmesh generation (dlights, yay!) + unsigned int ebobatch; // + unsigned int webobatch; //su }; struct { @@ -166,6 +167,11 @@ typedef struct batch_s unsigned int surf_count; }; vec4_t plane; /*used only at load (for portal surfaces, so multiple planes are not part of the same batch)*/ + struct + { + mesh_t meshbuf; + mesh_t *meshptr; + }; }; } batch_t; /* diff --git a/engine/gl/gl_rmain.c b/engine/gl/gl_rmain.c index 98d6337f..c187dfe5 100644 --- a/engine/gl/gl_rmain.c +++ b/engine/gl/gl_rmain.c @@ -1365,7 +1365,7 @@ void R_Clear (qboolean fbo) { /*tbh, this entire function should be in the backend*/ { - qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); //FIXME: breaks backend! if (!depthcleared || fbo) { GL_ForceDepthWritable(); diff --git a/engine/gl/gl_rsurf.c b/engine/gl/gl_rsurf.c index eed494af..e43f1c88 100644 --- a/engine/gl/gl_rsurf.c +++ b/engine/gl/gl_rsurf.c @@ -532,58 +532,4 @@ void GLBE_GenBrushModelVBO(model_t *mod) #endif } -void GLBE_UploadAllLightmaps(void) -{ - lightmapinfo_t *lm; - int i; - // - // upload all lightmaps that were filled - // - for (i=0 ; irectchange.l = lm->width; - lm->rectchange.t = lm->height; - lm->rectchange.r = 0; - lm->rectchange.b = 0; - if (!lm->modified) - continue; - lm->modified = false; - if (!TEXVALID(lm->lightmap_texture)) - TEXASSIGN(lm->lightmap_texture, Image_CreateTexture(va("***lightmap %i***", i), NULL, (r_lightmap_nearest.ival?IF_NEAREST:IF_LINEAR)|IF_NOMIPMAP)); - if (!lm->lightmap_texture->num) - qglGenTextures(1, &lm->lightmap_texture->num); - GL_MTBind(0, GL_TEXTURE_2D, lm->lightmap_texture); - if (r_lightmap_nearest.ival) - { - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - else - { - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - - qglTexImage2D(GL_TEXTURE_2D, 0, gl_config.formatinfo[lm->fmt].internalformat, lm->width, lm->height, 0, gl_config.formatinfo[lm->fmt].format, gl_config.formatinfo[lm->fmt].type, lightmap[i]->lightmaps); - - if (gl_config.glversion >= (gl_config.gles?3.0:3.3)) - { - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, gl_config.formatinfo[lm->fmt].swizzle_r); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, gl_config.formatinfo[lm->fmt].swizzle_g); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, gl_config.formatinfo[lm->fmt].swizzle_b); - qglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, gl_config.formatinfo[lm->fmt].swizzle_a); - } - - //for completeness. - lm->lightmap_texture->format = lm->fmt; - lm->lightmap_texture->width = lm->width; - lm->lightmap_texture->height = lm->height; - lm->lightmap_texture->depth = 1; - lm->lightmap_texture->status = TEX_LOADED; - } -} - #endif diff --git a/engine/gl/gl_screen.c b/engine/gl/gl_screen.c index 7c4a562c..498ccc3a 100644 --- a/engine/gl/gl_screen.c +++ b/engine/gl/gl_screen.c @@ -161,7 +161,13 @@ qboolean GLSCR_UpdateScreen (void) if (r_clear.ival) { GL_ForceDepthWritable(); - qglClearColor((r_clear.ival&1)?1:0, (r_clear.ival&2)?1:0, (r_clear.ival&4)?1:0, 1); + if (r_clearcolour.ival) + { + r_clearcolour.vec4[0] = host_basepal[(r_clearcolour.ival & 0xFF)*3+0]/255.0; + r_clearcolour.vec4[1] = host_basepal[(r_clearcolour.ival & 0xFF)*3+1]/255.0; + r_clearcolour.vec4[2] = host_basepal[(r_clearcolour.ival & 0xFF)*3+2]/255.0; + } + qglClearColor(r_clearcolour.vec4[0], r_clearcolour.vec4[1], r_clearcolour.vec4[2], 1); qglClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); depthcleared = true; } diff --git a/engine/gl/gl_vidcommon.c b/engine/gl/gl_vidcommon.c index a7405e29..43e6bcbb 100644 --- a/engine/gl/gl_vidcommon.c +++ b/engine/gl/gl_vidcommon.c @@ -3854,7 +3854,7 @@ rendererinfo_t openglrendererinfo = { GLBE_Init, GLBE_GenBrushModelVBO, GLBE_ClearVBO, - GLBE_UploadAllLightmaps, + GLBE_UpdateLightmaps, GLBE_SelectEntity, GLBE_SelectDLight, GLBE_Scissor, diff --git a/engine/gl/gl_vidlinuxglx.c b/engine/gl/gl_vidlinuxglx.c index 1d2874bc..e236d29c 100644 --- a/engine/gl/gl_vidlinuxglx.c +++ b/engine/gl/gl_vidlinuxglx.c @@ -4447,7 +4447,7 @@ rendererinfo_t eglrendererinfo = GLBE_Init, GLBE_GenBrushModelVBO, GLBE_ClearVBO, - GLBE_UploadAllLightmaps, + GLBE_UpdateLightmaps, GLBE_SelectEntity, GLBE_SelectDLight, GLBE_Scissor, diff --git a/engine/gl/gl_vidwayland.c b/engine/gl/gl_vidwayland.c index 236f1eb3..dcd130c7 100644 --- a/engine/gl/gl_vidwayland.c +++ b/engine/gl/gl_vidwayland.c @@ -1697,7 +1697,7 @@ rendererinfo_t rendererinfo_wayland_gl = GLBE_Init, GLBE_GenBrushModelVBO, GLBE_ClearVBO, - GLBE_UploadAllLightmaps, + GLBE_UpdateLightmaps, GLBE_SelectEntity, GLBE_SelectDLight, GLBE_Scissor, diff --git a/engine/gl/gl_warp.c b/engine/gl/gl_warp.c index 4d77b749..94c0595d 100644 --- a/engine/gl/gl_warp.c +++ b/engine/gl/gl_warp.c @@ -52,6 +52,18 @@ void R_SkyShutdown(void) forcedsky = NULL; } +//lets the backend know which fallback envmap it can use. +texid_t R_GetDefaultEnvmap(void) +{ + if (*r_refdef.nearenvmap.texname) + return Image_GetTexture(r_refdef.nearenvmap.texname, NULL, IF_TEXTYPE_CUBE, NULL, NULL, 0, 0, TF_INVALID); + + if (forcedsky && TEXLOADED(forcedsky->defaulttextures->reflectcube)) + return forcedsky->defaulttextures->reflectcube; + + return r_nulltex; +} + void R_SetSky(const char *sky) { int i; diff --git a/engine/gl/glquake.h b/engine/gl/glquake.h index 3e03cbe4..0024e829 100644 --- a/engine/gl/glquake.h +++ b/engine/gl/glquake.h @@ -319,7 +319,7 @@ extern vec3_t r_origin; // extern refdef_t r_refdef; extern unsigned int r_viewcontents; -int r_viewarea; +extern int r_viewarea; extern int r_viewcluster, r_viewcluster2, r_oldviewcluster, r_oldviewcluster2; //q2 extern texture_t *r_notexture_mip; @@ -330,8 +330,6 @@ extern const char *gl_vendor; extern const char *gl_renderer; extern const char *gl_version; -FTE_DEPRECATED void PPL_RevertToKnownState(void); - qboolean R_CullBox (vec3_t mins, vec3_t maxs); qboolean R_CullEntityBox(entity_t *e, vec3_t modmins, vec3_t modmaxs); qboolean R_CullSphere (vec3_t origin, float radius); @@ -735,7 +733,16 @@ extern GLboolean (APIENTRY *qglUnmapBufferARB)(GLenum target); #define GLintptr qintptr_t #define GLsizeiptr quintptr_t #ifndef GL_MAP_READ_BIT -#define GL_MAP_READ_BIT 1 +#define GL_MAP_READ_BIT 0x0001 +#endif +#ifndef GL_MAP_WRITE_BIT +#define GL_MAP_WRITE_BIT 0x0002 +#endif +#ifndef GL_MAP_PERSISTENT_BIT +#define GL_MAP_PERSISTENT_BIT 0x0040 +#endif +#ifndef GL_MAP_COHERENT_BIT +#define GL_MAP_COHERENT_BIT 0x0080 #endif extern void *(APIENTRY *qglMapBufferRange)(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); diff --git a/engine/gl/shader.h b/engine/gl/shader.h index f94b6fcc..4d4100c0 100644 --- a/engine/gl/shader.h +++ b/engine/gl/shader.h @@ -857,7 +857,7 @@ void GLBE_SubmitBatch(batch_t *batch); batch_t *GLBE_GetTempBatch(void); void GLBE_GenBrushModelVBO(model_t *mod); void GLBE_ClearVBO(vbo_t *vbo, qboolean dataonly); -void GLBE_UploadAllLightmaps(void); +void GLBE_UpdateLightmaps(void); void GLBE_DrawWorld (batch_t **worldbatches); qboolean GLBE_LightCullModel(vec3_t org, model_t *model); void GLBE_SelectEntity(entity_t *ent); diff --git a/engine/qclib/initlib.c b/engine/qclib/initlib.c index afc3658b..e3ffdddc 100644 --- a/engine/qclib/initlib.c +++ b/engine/qclib/initlib.c @@ -196,7 +196,7 @@ static void PF_fmem_unlink(progfuncs_t *progfuncs, qcmemfreeblock_t *p) } } -static void PR_memvalidate (progfuncs_t *progfuncs) +void PR_memvalidate (progfuncs_t *progfuncs) { qcmemfreeblock_t *p; unsigned int b,l; diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 0f7db70e..52e626d2 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -363,7 +363,7 @@ pbool PDECL ED_CanFree (edict_t *ed) ed->v->solid = 0; ed->xv->pvsflags = 0; -#ifdef QUAKETC +#ifndef HAVE_LEGACY //ideal world... ed->v->nextthink = 0; ed->v->think = 0; @@ -1492,7 +1492,9 @@ static void PR_SSProfile_f(void) static void PR_SSPoke_f(void) { - if (!SV_MayCheat()) + if (MSV_ForwardToAutoServer()) + ; + else if (!SV_MayCheat()) Con_TPrintf ("Please set sv_cheats 1 and restart the map first.\n"); else if (svprogfuncs && svprogfuncs->EvaluateDebugString) Con_TPrintf("Result: %s\n", svprogfuncs->EvaluateDebugString(svprogfuncs, Cmd_Args())); @@ -4211,7 +4213,7 @@ static void QCBUILTIN PF_sv_getlight (pubprogfuncs_t *prinst, struct globalvars_ } } -#ifndef QUAKETC +#ifdef HAVE_LEGACY /* ========= PF_conprint @@ -4670,6 +4672,15 @@ static void QCBUILTIN PF_walkmove (pubprogfuncs_t *prinst, struct globalvars_s * // pr_xfunction = oldf; pr_global_struct->self = oldself; } +static void QCBUILTIN PF_walkmovedist (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ //for wrath, doesn't actually provide anything useful other than to stop crashes. + wedict_t *ent = PROG_TO_WEDICT(prinst, pr_global_struct->self); + vec3_t start; + VectorCopy(ent->v->origin, start); + PF_walkmove(prinst, pr_globals); + VectorSubtract(ent->v->origin, start, start); + G_FLOAT(OFS_RETURN) = VectorLength(start); +} void QCBUILTIN PF_applylightstyle(int style, const char *val, vec3_t rgb) { @@ -5707,7 +5718,7 @@ static void QCBUILTIN PF_WriteString2 (pubprogfuncs_t *prinst, struct globalvars G_FLOAT(OFS_PARM1) = old; } -#if !defined(QUAKETC) && defined(NETPREPARSE) +#if defined(HAVE_LEGACY) && defined(NETPREPARSE) //qtest-only builtins. static void QCBUILTIN PF_qtSingle_WriteByte (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { @@ -6468,7 +6479,7 @@ static void QCBUILTIN PF_Ignore(pubprogfuncs_t *prinst, struct globalvars_s *pr_ G_INT(OFS_RETURN) = 0; } -#ifndef QUAKETC +#ifdef HAVE_LEGACY static void QCBUILTIN PF_mvdsv_newstring(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) //mvdsv { char *s; @@ -7115,7 +7126,7 @@ void QCBUILTIN PF_ExecuteCommand (pubprogfuncs_t *prinst, struct globalvars_s * pr_global_struct->other = old_other; } -#ifndef QUAKETC +#ifdef HAVE_LEGACY /* ================= PF_teamfield @@ -10502,7 +10513,7 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"altstr_ins", PF_Fixme, 0, 0, 0, 86, D("string(string str, float num, string set)", NULL), true}, {"findflags", PF_Fixme, 0, 0, 0, 87, "entity(entity start, .float field, float match)"}, {"findchainflags", PF_Fixme, 0, 0, 0, 88, "entity(.float field, float match)"}, - {"mcvar_defstring", PF_Fixme, 0, 0, 0, 89, "string(string name)" STUB}, + {"cvar_defstring", PF_Fixme, 0, 0, 0, 89, "string(string name)"}, {"setmodel", PF_Fixme, 0, 0, 0, 90, D("void(entity ent, string mname)", "Menuqc-specific version.")}, {"precache_model", PF_Fixme, 0, 0, 0, 91, D("void(string mname)", "Menuqc-specific version.")}, @@ -10585,7 +10596,7 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"WriteString", PF_WriteString, 58, 58, 58, 0, D("void(float to, string val)", "Writes a variable-length null terminated string. There are length limits. The codepage is not translated, so be sure that client+server agree on whether utf-8 is being used or not (or just stick to ascii+markup).")}, //58 {"WriteEntity", PF_WriteEntity, 59, 59, 59, 0, D("void(float to, entity val)", "Writes the index of the specified entity (the network data size is not specified). This can be read clientside using the readentitynum builtin, with caveats.")}, //59 -#if !defined(QUAKETC) && defined(NETPREPARSE) +#if defined(HAVE_LEGACY) && defined(NETPREPARSE) {"swritebyte", PF_qtSingle_WriteByte, 0, 0, 0, 0, D("void(float val)", "A legacy of qtest - like WriteByte, except writes explicitly to the MSG_ONE target."), true}, //52 {"swritechar", PF_qtSingle_WriteChar, 0, 0, 0, 0, D("void(float val)", NULL), true}, //53 {"swriteshort", PF_qtSingle_WriteShort, 0, 0, 0, 0, D("void(float val)", NULL), true}, //54 @@ -10656,7 +10667,7 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs "void(vector where, float set)", "Once the MSG_MULTICAST network message buffer has been filled with data, this builtin is used to dispatch it to the given target, filtering by pvs for reduced network bandwidth.")}, //82 -#ifndef QUAKETC +#ifdef HAVE_LEGACY //mvdsv (don't require ebfs usage in qw) {"executecommand", PF_ExecuteCommand, 0, 0, 0, 83, D("void()","Attempt to flush the localcmd buffer NOW. This is unsafe, as many events might cause the map to be purged while still executing qc code."), true}, {"mvdtokenize", PF_tokenize_console,0, 0, 0, 84, D("void(string str)",NULL), true}, @@ -10982,10 +10993,10 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"frametoname", PF_frametoname, 0, 0, 0, 284, "string(float modidx, float framenum)"}, {"skintoname", PF_skintoname, 0, 0, 0, 285, "string(float modidx, float skin)"}, {"resourcestatus", PF_resourcestatus, 0, 0, 0, 286, D("float(float resourcetype, float tryload, string resourcename)", "resourcetype must be one of the RESTYPE_ constants. Returns one of the RESSTATE_ constants. Tryload 0 is a query only. Tryload 1 will attempt to reload the content if it was flushed.")}, - {"hash_createtab", PF_hash_createtab, 0, 0, 0, 287, D("hashtable(float tabsize, optional float defaulttype)", "Creates a hash table object with at least 'tabsize' slots. hash table with index 0 is a game-persistant table and will NEVER be returned by this builtin (except as an error return).")}, + {"hash_createtab", PF_hash_createtab, 0, 0, 0, 287, D("hashtable(float tabsize, optional float defaulttype)", "Creates a hash table object.\nThe tabsize argument is a performance hint and should generally be set to something similar to the number of entries expected, typically a power of two assumption. Too high simply wastes memory, too low results in extra string compares but no actual bugs.\ndefaulttype must be one of the EV_* values, if specified.\nThe hash table with index 0 is a game-persistant table and will NEVER be returned by this builtin (except as an error return).")}, {"hash_destroytab", PF_hash_destroytab, 0, 0, 0, 288, D("void(hashtable table)", "Destroys a hash table object.")}, - {"hash_add", PF_hash_add, 0, 0, 0, 289, D("void(hashtable table, string name, __variant value, optional float typeandflags)", "Adds the given key with the given value to the table.\nIf flags&HASH_REPLACE, the old value will be removed, if not set then multiple values may be added for a single key, they won't overwrite.\nThe type argument describes how the value should be stored and saved to files. While you can claim that all variables are just vectors, being more precise can result in less issues with tempstrings or saved games.")}, - {"hash_get", PF_hash_get, 0, 0, 0, 290, D("__variant(hashtable table, string name, optional __variant deflt, optional float requiretype, optional float index)", "looks up the specified key name in the hash table. returns deflt if key was not found. If stringsonly=1, the return value will be in the form of a tempstring, otherwise it'll be the original value argument exactly as it was. If requiretype is specified, then values not of the specified type will be ignored. Hurrah for multiple types with the same name.")}, + {"hash_add", PF_hash_add, 0, 0, 0, 289, D("void(hashtable table, string name, __variant value, optional float typeandflags)", "Adds the given key with the given value to the table.\nIf flags&HASH_REPLACE, the old value will be removed, otherwise if flags&HASH_ADD then a duplicate entry will be added with a second value (can be obtained via hash_get's index argument).\nThe type argument describes how the value should be stored in saved games, as well as providing constraints with the hash_get function. While you can claim that all variables are just vectors, being more precise can result in less issues with tempstrings or saved games - be sure to be explicit with EV_STRING where appropriate because tempstrings may be reclaimed before the get (especially with saved games or table 0).")}, + {"hash_get", PF_hash_get, 0, 0, 0, 290, D("__variant(hashtable table, string name, optional __variant deflt, optional float requiretype, optional float index)","Looks up the specified key name in the hash table. Returns deflt if the key was not found.\nIf requiretype is specified then the function will only consider entries of the matching type (allowing you to store both flags+strings under a single name without getting confused).\nIf index is specified then the function will ignore the first N entries with the same key (applicable only with entries added using HASH_ADD, not HASH_REPLACE), allowing you to store multiple entries. Keep querying higher indexes starting from 0 until it returns the deflt value.\nYou will usually need to cast the result of this function to a real datatype.")}, {"hash_delete", PF_hash_delete, 0, 0, 0, 291, D("__variant(hashtable table, string name)", "removes the named key. returns the value of the object that was destroyed, or 0 on error.")}, {"hash_getkey", PF_hash_getkey, 0, 0, 0, 292, D("string(hashtable table, float idx)", "gets some random key name. add+delete can change return values of this, so don't blindly increment the key index if you're removing all.")}, {"hash_getcb", PF_hash_getcb, 0, 0, 0, 293, D("void(hashtable table, void(string keyname, __variant val) callback, optional string name)", "For each item in the table that matches the name, call the callback. if name is omitted, will enumerate ALL keys."), true}, @@ -11150,6 +11161,8 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"cvars_haveunsaved",PF_Fixme, 0, 0, 0, 0, D("float()", "Returns true if any archived cvar has an unsaved value.")}, {"entityprotection",PF_entityprotection,0, 0, 0, 0, D("float(entity e, float nowreadonly)", "Changes the protection on the specified entity to protect it from further edits from QC. The return value is the previous setting. Note that this can be used to unprotect the world, but doing so long term is not advised as you will no longer be able to detect invalid entity references. Also, world is not networked, so results might not be seen by clients (or in other words, world.avelocity_y=64 is a bad idea).")}, + + {"getlocationname", PF_Fixme, 0, 0, 0, 0, D("string(vector pos)", "Looks up the specified position in the current map's .loc file and reports the nearest marked name.")}, //end fte extras //DP extras @@ -11409,7 +11422,8 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"addwantedhostcachekey",PF_Fixme, 0, 0, 0, 623, "void(string key)"}, {"getextresponse", PF_Fixme, 0, 0, 0, 624, "string()"}, {"netaddress_resolve",PF_netaddress_resolve,0, 0, 0, 625, "string(string dnsname, optional float defport)"}, - {"getgamedirinfo", PF_Fixme, 0, 0, 0, 626, "string(float n, float prop)" STUB}, + {"getgamedirinfo", PF_Fixme, 0, 0, 0, 626, "string(float n, float prop)"}, + {"getpackagemanagerinfo",PF_Fixme, 0, 0, 0, 0, D("string(int n, int prop)", "Queries information about a package from the engine's package manager subsystem. Actions can be taken via the pkg console command.")}, {"sprintf", PF_sprintf, 0, 0, 0, 627, D("string(string fmt, ...)", "'prints' to a formatted temp-string. Mostly acts as in C, however %d assumes floats (fteqcc has arg checking. Use it.).\ntype conversions: l=arg is an int, h=arg is a float, and will work as a prefix for any float or int representation.\nfloat representations: d=decimal, e,E=exponent-notation, f,F=floating-point notation, g,G=terse float, c=char code, x,X=hex\nother representations: i=int, s=string, S=quoted and marked-up string, v=vector, p=pointer\nso %ld will accept an int arg, while %hi will expect a float arg.\nentities, fields, and functions will generally need to be printed as ints with %i.")}, {"getsurfacenumtriangles",PF_getsurfacenumtriangles,0,0,0, 628, "float(entity e, float s)"}, {"getsurfacetriangle",PF_getsurfacetriangle,0, 0, 0, 629, "vector(entity e, float s, float n)"}, @@ -11421,12 +11435,13 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"crypto_getencryptlevel",PF_Fixme, 0, 0, 0, 635, "string(string addr)" STUB}, {"crypto_getmykeyfp",PF_Fixme, 0, 0, 0, 636, "string(string addr)" STUB}, {"crypto_getmyidfp",PF_Fixme, 0, 0, 0, 637, "string(float addr)" STUB}, -// {"VM_CL_RotateMoves",PF_Fixme, 0, 0, 0, 638, ""}, +// {"CL_RotateMoves", PF_Fixme, 0, 0, 0, 638, D("void(vector anglechange)", "Rewrites the input log history to rotate all unacknowledged frames according to the angle delta specified.")}, {"digest_hex", PF_digest_hex, 0, 0, 0, 639, "string(string digest, string data, ...)"}, {"digest_ptr", PF_digest_ptr, 0, 0, 0, 0, D("string(string digest, void *data, int length)", "Calculates the digest of a single contiguous block of memory (including nulls) using the specified hash function.")}, -// {"V_CalcRefdef", PF_Fixme, 0, 0, 0, 640, "void(entity e)"}, + {"V_CalcRefdef", PF_Fixme, 0, 0, 0, 640, "void(entity e, float flags)" STUB}, {"crypto_getmyidstatus",PF_Fixme, 0, 0, 0, 641, "float(float i)" STUB}, - + {"coverage", PF_Fixme, 0, 0, 0, 642, "void()" STUB}, + {"crypto_getidstatus",PF_Fixme, 0, 0, 0, 643, "float(string addr)" STUB}, //end dp extras //wrath extras... @@ -11435,6 +11450,7 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"fremove", PF_fremove, 0, 0, 0, 652, D("float(string fname)", "Deletes the named file - path is relative to data/ subdir, like fopen's FILE_WRITE. Returns 0 on success.")}, {"fexists", PF_fexists, 0, 0, 0, 653, D("float(string fname)", "Use whichpack instead. Returns true if it exists inside the default writable path.")}, {"rmtree", PF_rmtree, 0, 0, 0, 654, D("float(string path)", "Dangerous, but sandboxed to data/")}, + {"walkmovedist", PF_walkmovedist, 0, 0, 0, 655, D("float(float yaw, float dist, optional float settraceglobals)", "Attempt to walk the entity at a given angle for a given distance.\nif settraceglobals is set, the trace_* globals will be set, showing the results of the movement.\nThis function will trigger touch events."), true}, //end wrath extras {"getrmqeffectsversion",PF_Ignore, 0, 0, 0, 666, "float()" STUB}, @@ -11590,7 +11606,7 @@ void PR_ResetBuiltins(progstype_t type) //fix all nulls to PF_FIXME and add any builtincount[i]=100; } -#if !defined(QUAKETC) && defined(NETPREPARSE) +#if defined(HAVE_LEGACY) && defined(NETPREPARSE) if (type == PROG_PREREL) { pr_builtin[52] = PF_qtSingle_WriteByte; @@ -12120,6 +12136,10 @@ void PR_DumpPlatform_f(void) {"trace_triangle_id", "int", QW|NQ|CS, D("1-based. 0 if not known.")}, {"trace_networkentity", "int", CS, D("Repots which ssqc entnum was hit when a csqc traceline impacts an ssqc-based brush entity.")}, + {"pmove_org", "vector", CS, D("Reports the origin of the engineside player (after prediction). Does not work when the player is a csqc-owned entity.")}, + {"pmove_vel", "vector", CS, D("Reports the velocity of the engineside player (after prediction). Does not work when the player is a csqc-owned entity.")}, + {"pmove_onground", "float", CS, D("Reports the onground state of the engineside player (after prediction). Does not work when the player is a csqc-owned entity.")}, + {"global_gravitydir", "vector", QW|NQ|CS, D("The direction gravity should act in if not otherwise specified per entity."), 0,"'0 0 -1'"}, {"serverid", "int", QW|NQ|CS, D("The unique id of this server within the server cluster.")}, @@ -12259,6 +12279,7 @@ void PR_DumpPlatform_f(void) {"SOLID_CORPSE", "const float", QW|NQ|CS, D("Non-solid to SOLID_SLIDEBOX or other SOLID_CORPSE entities. For hitscan weapons to hit corpses, change the player's .solid value to SOLID_BBOX or so, perform the traceline, then revert the player's .solid value."), SOLID_CORPSE}, {"SOLID_LADDER", "const float", QW|NQ|CS, D("Obsolete and may be removed at some point. Use skin=CONTENT_LADDER and solid_bsp or solid_trigger instead."), SOLID_LADDER}, {"SOLID_PORTAL", "const float", QW|NQ|CS, D("CSG subtraction volume combined with entity transformations on impact."), SOLID_PORTAL}, + {"SOLID_BSPTRIGGER", "const float", QW|NQ|CS, D("For complex-shaped trigger volumes, instead of being a pure aabb."), SOLID_BSPTRIGGER}, {"SOLID_PHYSICS_BOX", "const float", QW|NQ|CS, NULL, SOLID_PHYSICS_BOX}, {"SOLID_PHYSICS_SPHERE", "const float", QW|NQ|CS, NULL, SOLID_PHYSICS_SPHERE}, {"SOLID_PHYSICS_CAPSULE", "const float", QW|NQ|CS, NULL, SOLID_PHYSICS_CAPSULE}, @@ -12385,7 +12406,7 @@ void PR_DumpPlatform_f(void) //not putting other svcs here, qc shouldn't otherwise need to generate svcs directly. {"SVC_CGAMEPACKET", "const float", QW|NQ, D("Direct ssqc->csqc message. Must only be multicast. The data triggers a CSQC_Parse_Event call in the csqc for the csqc to read the contents. The server *may* insert length information for clients connected via proxies which are not able to cope with custom csqc payloads. This should only ever be used in conjunction with the MSG_MULTICAST destination."), svcfte_cgamepacket}, -#ifndef QUAKETC +#ifdef HAVE_LEGACY {"MSG_BROADCAST", "const float", QW|NQ, D("The byte(s) will be unreliably sent to all players. MSG_ constants are valid arguments to the Write* builtin family."), MSG_BROADCAST}, {"MSG_ONE", "const float", QW|NQ, D("The byte(s) will be reliably sent to the player specified in the msg_entity global. WARNING: in quakeworld servers without network preparsing enabled, this can result in illegible server messages (due to individual reliable messages being split between multiple backbuffers/packets). NQ has larger reliable buffers which avoids this issue, but still kicks the client."), MSG_ONE}, {"MSG_ALL", "const float", QW|NQ, D("The byte(s) will be reliably sent to all players."), MSG_ALL}, @@ -12676,6 +12697,13 @@ void PR_DumpPlatform_f(void) {"IE_JOYAXIS", "const float", CS|MENU, D("Specifies that what value a joystick/controller axis currently specifies. x=axis, y=value. Will be called multiple times, once for each axis of each active controller."), CSIE_JOYAXIS}, {"IE_GYROSCOPE", "const float", CS|MENU, NULL, CSIE_GYROSCOPE}, + {"GGDI_GAMEDIR", "const float", CS|MENU, D("Used with getgamedirinfo to query the mod's public gamedir. There is often other info that cannot be expressed with just a gamedir name, resulting in dupes or other weirdness."), GGDI_GAMEDIR}, + {"GGDI_DESCRIPTION", "const float", CS|MENU, D("The human-readable title of the mod. Empty when no data is known (ie: the gamedir just contains some maps)."), GGDI_DESCRIPTION}, + {"GGDI_OVERRIDES", "const float", CS|MENU, D("A list of settings overrides."), GGDI_OVERRIDES}, + {"GGDI_LOADCOMMAND", "const float", CS|MENU, D("The console command needed to actually load the mod."), GGDI_LOADCOMMAND}, + {"GGDI_ICON", "const float", CS|MENU, D("The mod's Icon path, ready for drawpic."), GGDI_ICON}, + {"GGDI_GAMEDIRLIST", "const float", CS|MENU, D("A semi-colon delimited list of gamedirs that the mod's content can be loaded through."), GGDI_ICON}, + {"CLIENTTYPE_DISCONNECTED","const float", QW|NQ, D("Return value from clienttype() builtin. This entity is a player slot that is currently empty."), CLIENTTYPE_DISCONNECTED}, {"CLIENTTYPE_REAL", "const float", QW|NQ, D("This is a real player, and not a bot."), CLIENTTYPE_REAL}, {"CLIENTTYPE_BOT", "const float", QW|NQ, D("This player slot does not correlate to a real player, any messages sent to this client will be ignored."), CLIENTTYPE_BOT}, diff --git a/engine/server/savegame.c b/engine/server/savegame.c index 19ac330d..02068db7 100644 --- a/engine/server/savegame.c +++ b/engine/server/savegame.c @@ -184,298 +184,6 @@ pbool PDECL SV_ExtendedSaveData(pubprogfuncs_t *progfuncs, void *loadctx, const } #ifndef QUAKETC - -//expects the version to have already been parsed -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]; - float time; - char str[32768]; - int i; - edict_t *ent; - int pt; - int lstyles; - - int slots; - - client_t *cl; - int clnum; - char plname[32]; - - int filelen, filepos; - char *file; - - char *modelnames[MAX_PRECACHE_MODELS]; - char *soundnames[MAX_PRECACHE_SOUNDS]; - - 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 false; - } - VFS_GETS(f, str, sizeof(str)); //discard comment. - Con_Printf("loading legacy game from %s...\n", filename); - - - - for (clnum = 0; clnum < svs.allocated_client_slots; clnum++) //clear the server for the level change. - { - cl = &svs.clients[clnum]; - if (cl->state <= cs_loadzombie) - continue; - -#ifndef SERVERONLY - if (cl->netchan.remote_address.type == NA_LOOPBACK) - CL_Disconnect(NULL); - else -#endif - { - MSG_WriteByte (&cl->netchan.message, svc_stufftext); - MSG_WriteString (&cl->netchan.message, "disconnect;wait;reconnect\n"); //kindly ask the client to come again. - } - cl->drop = true; - } - SV_SendMessagesToAll(); - - if (version == SAVEGAME_VERSION_NQ || version == SAVEGAME_VERSION_QW) - { - slots = 1; - SV_UpdateMaxPlayers(1); - cl = &svs.clients[0]; -#ifdef SERVERONLY - Q_strncpyz(cl->namebuf, "", sizeof(cl->namebuf)); -#else - Q_strncpyz(cl->namebuf, name.string, sizeof(cl->namebuf)); -#endif - Q_strncpyz(cl->namebuf, com_token, sizeof(cl->namebuf)); - cl->name = cl->namebuf; - cl->state = cs_loadzombie; - cl->connection_started = realtime+20; - cl->spawned = cl->istobeloaded = true; - - for (i=0 ; i<16 ; i++) - { - VFS_GETS(f, str, sizeof(str)); - cl->spawn_parms[i] = atof(str); - } - for (; i < NUM_SPAWN_PARMS; i++) - cl->spawn_parms[i] = 0; - } - else //fte saves ALL the clients on the server. - { - VFS_GETS(f, str, strlen(str)); - slots = atoi(str); - if (!slots) //err - { - VFS_CLOSE(f); - Con_Printf ("Corrupted save game"); - 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. - { - cl = &svs.clients[clnum]; - VFS_GETS(f, plname, sizeof(plname)); - - cl->spawned = false; - cl->istobeloaded = false; - - cl->state = cs_free; - - COM_Parse(plname); - - if (!*com_token) - continue; - - Q_strncpyz(cl->namebuf, com_token, sizeof(cl->namebuf)); - cl->name = cl->namebuf; - cl->state = cs_loadzombie; - cl->connection_started = realtime+20; - cl->istobeloaded = true; - cl->userid = 0; - - //probably should be 32, rather than NUM_SPAWN_PARMS(64) - for (i=0 ; ispawn_parms[i] = atof(str); - } - } - } - if (version == SAVEGAME_VERSION_NQ || version == SAVEGAME_VERSION_QW) - { - VFS_GETS(f, str, sizeof(str)); - Cvar_SetValue (Cvar_FindVar("skill"), atof(str)); - Cvar_SetValue (Cvar_FindVar("deathmatch"), 0); - Cvar_SetValue (Cvar_FindVar("coop"), 0); - Cvar_SetValue (Cvar_FindVar("teamplay"), 0); - - if (version == SAVEGAME_VERSION_NQ) - { - progstype = PROG_NQ; - Cvar_Set (&pr_ssqc_progs, "progs.dat"); //NQ's progs. - } - else - { - progstype = PROG_QW; - Cvar_Set (&pr_ssqc_progs, "spprogs"); //zquake's single player qw progs. - } - pt = 0; - } - else - { - VFS_GETS(f, str, sizeof(str)); - pt = atoi(str); - - VFS_GETS(f, str, sizeof(str)); - Cvar_SetValue (Cvar_FindVar("skill"), atof(str)); - - VFS_GETS(f, str, sizeof(str)); - Cvar_SetValue (Cvar_FindVar("deathmatch"), atof(str)); - VFS_GETS(f, str, sizeof(str)); - Cvar_SetValue (Cvar_FindVar("coop"), atof(str)); - VFS_GETS(f, str, sizeof(str)); - Cvar_SetValue (Cvar_FindVar("teamplay"), atof(str)); - } - VFS_GETS(f, mapname, sizeof(mapname)); - VFS_GETS(f, str, sizeof(str)); - time = atof(str); - - SV_SpawnServer (mapname, NULL, false, false); //always inits MAX_CLIENTS slots. That's okay, because we can cut the max easily. - if (sv.state != ss_active) - { - VFS_CLOSE (f); - Con_TPrintf ("Couldn't load map\n"); - return false; - } - - sv.allocated_client_slots = slots; - -// load the light styles - - lstyles = 64; - if (lstyles > sv.maxlightstyles) - Z_ReallocElements((void**)&sv.lightstyles, &sv.maxlightstyles, lstyles, sizeof(*sv.lightstyles)); - for (i=0 ; iload_ents(svprogfuncs, file, NULL, NULL, SV_ExtendedSaveData); - BZ_Free(file); - - PR_LoadGlabalStruct(false); - - pr_global_struct->time = sv.world.physicstime = sv.time = time; - sv.starttime = Sys_DoubleTime() - sv.time; - - VFS_CLOSE(f); - - //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); - - sv.spawned_client_slots = 0; - sv.spawned_observer_slots = 0; - for (i=0 ; istate) - sv.spawned_client_slots += 1; - ent = EDICT_NUM_PB(svprogfuncs, i+1); - } - else - ent = NULL; - cl->edict = ent; - cl->spawned = false; - - cl->name = PR_AddString(svprogfuncs, cl->namebuf, sizeof(cl->namebuf), false); - cl->team = PR_AddString(svprogfuncs, cl->teambuf, sizeof(cl->teambuf), false); - -#ifdef HEXEN2 - if (ent) - cl->playerclass = ent->xv->playerclass; - else - cl->playerclass = 0; -#endif - } - return true; -} - static qboolean SV_LegacySavegame (const char *savename, qboolean verbose) { size_t len; @@ -549,7 +257,7 @@ static qboolean SV_LegacySavegame (const char *savename, qboolean verbose) VFS_PRINTF(f, "%i\n", sv.allocated_client_slots); for (cl = svs.clients, clnum=0; clnum < sv.allocated_client_slots; cl++,clnum++) { - if (cl->state < cs_spawned && !cl->istobeloaded) //don't save if they are still connecting + if (cl->state < cs_loadzombie || !cl->spawned) //don't save if they are still connecting { VFS_PRINTF(f, "\"\"\n"); continue; @@ -736,7 +444,7 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * { char *s; flocation_t loc; - SV_SpawnServer (level, startspot, false, false); + SV_SpawnServer (level, startspot, false, false, 0); World_ClearWorld(&sv.world, false); if (!ge) @@ -907,7 +615,7 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * //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_SpawnServer (mapname, startspot, false, false, svs.allocated_client_slots); sv.time = time; if (svs.gametype != gametype) { @@ -1009,7 +717,7 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * svs.clients[i].name = PR_AddString(svprogfuncs, svs.clients[i].namebuf, sizeof(svs.clients[i].namebuf), false); svs.clients[i].team = PR_AddString(svprogfuncs, svs.clients[i].teambuf, sizeof(svs.clients[i].teambuf), false); - svs.clients[i].spawned = (svs.clients[i].state == cs_loadzombie); + //svs.clients[i].spawned = (svs.clients[i].state == cs_loadzombie); #ifdef HEXEN2 if (ent) svs.clients[i].playerclass = ent->xv->playerclass; @@ -1223,25 +931,9 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) else if (progstype == PROG_H2) cl->edict->ereftype = ER_FREE; //hexen2 has some annoying prints. it never formally dropped clients on map changes (we'll reset this later, so they'll just not appear in the saved game). else if (!cl->spawned) //don't drop if they are still connecting - { cl->edict->v->solid = 0; - } - else if (!cl->spectator) - { - // call the prog function for removing a client - // this will set the body to a dead frame, among other things - pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict); - PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect); - sv.spawned_client_slots--; - } - else if (SpectatorDisconnect) - { - // call the prog function for removing a client - // this will set the body to a dead frame, among other things - pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict); - PR_ExecuteProgram (svprogfuncs, SpectatorDisconnect); - sv.spawned_observer_slots--; - } + else + SV_DespawnClient(cl); } } @@ -1466,29 +1158,17 @@ void SV_Savegame (const char *savename, qboolean mapchange) if (*cl->name) { - 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) { - 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... - } + 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. } } @@ -1634,7 +1314,9 @@ void SV_Savegame_c(int argn, const char *partial, struct xcommandargcompletioncb void SV_Savegame_f (void) { - if (Cmd_Argc() <= 2) + if (sv.state == ss_clustermode && MSV_ForwardToAutoServer()) + ; + else if (Cmd_Argc() <= 2) { const char *savename = Cmd_Argv(1); if (strstr(savename, "..")) @@ -1714,6 +1396,475 @@ void SV_AutoSave(void) #endif } +typedef struct +{ + char name[32]; + union + { + int i; + float f; + } parm[NUM_SPAWN_PARMS]; + char *parmstr; + + client_t *source; +} loadplayer_t; +static void SV_SwapPlayers(client_t *a, client_t *b) +{ + size_t i; + client_t tmp; + if (a==b) + return; //o.O + tmp = *a; + *a = *b; + *b = tmp; + + //swap over pointers for splitscreen. + for (i = 0; i < svs.allocated_client_slots; i++) + { + if (svs.clients[i].controller == a) + svs.clients[i].controller = b; + else if (svs.clients[i].controller == b) + svs.clients[i].controller = a; + if (svs.clients[i].controlled == a) + svs.clients[i].controlled = b; + else if (svs.clients[i].controlled == b) + svs.clients[i].controlled = a; + } + + //undo some damage... + b->edict = a->edict; + a->edict = tmp.edict; + + if (a->name == b->namebuf) a->name = a->namebuf; + if (b->name == a->namebuf) b->name = b->namebuf; + if (a->team == b->teambuf) a->team = a->teambuf; + if (b->team == a->teambuf) b->team = b->teambuf; + + if (a->netchan.message.data) + a->netchan.message.data += (qbyte*)b-(qbyte*)a; + if (a->datagram.data) + a->datagram.data += (qbyte*)b-(qbyte*)a; + if (a->backbuf.data) + a->backbuf.data += (qbyte*)b-(qbyte*)a; + if (b->netchan.message.data) + b->netchan.message.data += (qbyte*)a-(qbyte*)b; + if (b->datagram.data) + b->datagram.data += (qbyte*)a-(qbyte*)b; + if (b->backbuf.data) + b->backbuf.data += (qbyte*)a-(qbyte*)b; +} +void SV_LoadPlayers(loadplayer_t *lp, size_t slots) +{ //loading games is messy as fuck + //we need to reorder players to the order in the saved game. + //swapping players around is really rather messy... + + client_t *cl; + size_t clnum, p, p2; + int to[255]; + + //despawn any entity data, and try to find the loaded player to move them to + for (clnum = 0; clnum < svs.allocated_client_slots; clnum++) //clear the server for the level change. + { + to[clnum] = -1; + cl = &svs.clients[clnum]; + if (cl->state <= cs_loadzombie) + continue; + SV_DespawnClient(cl); + if (cl->state == cs_spawned) + cl->state = cs_connected; + + //FIXME: try to match by guids (but we don't have saved guid info) + + //try to match the player to a slot by name. + for (p = 0; p < slots; p++) + if (*lp[p].name) + { + if (!strcmp(cl->name, lp[p].name) || slots == 1) + { //this player matched matched... + to[clnum] = p; + lp[p].source = cl; + break; + } + } + } + //for loaded players that don't have a client go and find a player to spawn there, to try to deal with players that renamed themselves. + for (p = 0; p < slots; p++) + { + if (!*lp[p].name || lp[p].source) + continue; + for (clnum = 0; clnum < svs.allocated_client_slots; clnum++) + { + cl = &svs.clients[clnum]; + if (cl->state <= cs_loadzombie) + continue; + if (to[clnum] >= 0) + continue; //was already mapped + if (cl->spectator) + continue; //spectators shouldn't be pulled into a player against their will. it may still happen though. + to[clnum] = p; + lp[p].source = cl; + break; + } + } + + //we walk the list in order, pulling from the appropriate slot. + //we're swapping each time, so uninteresting players will bubble to the end instead of breaking our finalised list.. + //if we're swapping from an earlier slot then that slot wasn't relevant anyway. + for (p = 0; p < slots; p++) + { + if (lp[p].source && lp[p].source!=&svs.clients[p]) + { + SV_SwapPlayers(&svs.clients[p], lp[p].source); + for (p2 = 0; p2 < slots; p2++) + { + if (p == p2) + continue; + if (lp[p2].source == &svs.clients[p]) + lp[p2].source = lp[p].source; + else if (lp[p2].source == lp[p].source) + lp[p2].source = &svs.clients[p]; + } + } + } + + if (slots > svs.allocated_client_slots) //will be trimmed later + SV_UpdateMaxPlayers(slots); + for (cl = svs.clients, clnum=0; clnum < slots; cl++,clnum++) + { + if (*lp[clnum].name) + { //okay so we have a player ready for this slot. + for (p = 0; p < NUM_SPAWN_PARMS; p++) + cl->spawn_parms[p] = lp[clnum].parm[p].f; + cl->spawn_parmstring = lp[clnum].parmstr; + continue; + } + else if (cl->state > cs_zombie) + SV_DropClient(cl); +/* + Q_strncpyz(cl->namebuf, lp[clnum].name, sizeof(cl->namebuf)); + cl->name = cl->namebuf; + if (*cl->namebuf) + { + cl->state = cs_loadzombie; + cl->connection_started = realtime+20; + cl->istobeloaded = true; //the parms are known + cl->userid = 0; + + memset(&cl->netchan, 0, sizeof(cl->netchan)); + + for (p = 0; p < NUM_SPAWN_PARMS; p++) + cl->spawn_parms[p] = lp[clnum].parm[p].f; + cl->spawn_parmstring = lp[clnum].parmstr; + }*/ + } +} + +static void SV_GameLoaded(loadplayer_t *lp, size_t slots, const char *savename) +{ + size_t clnum; + client_t *cl; + + //make sure autosave doesn't save too early. + sv.autosave_time = sv.time + sv_autosave.value*60; + + //let the restart command know the name of the saved game to reload. + Q_strncpyz(sv.loadgame_on_restart, savename, sizeof(sv.loadgame_on_restart)); + + slots = min(slots, svs.allocated_client_slots); + + //make sure the player state is set up properly. + for (clnum = 0; clnum < slots; clnum++) + { + cl = &svs.clients[clnum]; + cl->spawned = !!*lp[clnum].name; + if (cl->spawned) + sv.spawned_client_slots++; + + cl->name = PR_AddString(svprogfuncs, cl->namebuf, sizeof(cl->namebuf), false); + cl->team = PR_AddString(svprogfuncs, cl->teambuf, sizeof(cl->teambuf), false); + + cl->edict = EDICT_NUM_PB(svprogfuncs, clnum+1); + +#ifdef HEXEN2 + { + if (cl->edict) + cl->playerclass = cl->edict->xv->playerclass; + else + cl->playerclass = 0; + } +#endif + + if (cl->state == cs_spawned) //shouldn't have gotten past SV_SpawnServer, but just in case... + cl->state = cs_connected; //client needs new serverinfo. + if (cl->spawned && cl->state < cs_connected) //make sure the player slot is active when the gamecode thinks it was (with a loadzombie if needed) + { + cl->state = cs_loadzombie; + cl->connection_started = realtime+20; + cl->istobeloaded = true; //the parms are known + cl->userid = 0; + memset(&cl->netchan, 0, sizeof(cl->netchan)); + } + + if (cl->controller) + continue; + if (cl->state>=cs_connected) + { + if (cl->protocol == SCP_QUAKE3) + continue; + if (cl->protocol == SCP_BAD) + continue; + + host_client = cl; +#ifdef NQPROT + if (ISNQCLIENT(host_client)) + SVNQ_New_f(); + else +#endif + SV_New_f(); + } + } + host_client = NULL; +} + +#ifndef QUAKETC + +//expects the version to have already been parsed +static qboolean SV_Loadgame_Legacy(const char *savename, const char *filename, vfsfile_t *f, int version) +{ + //FIXME: Multiplayer save probably won't work with spectators. + char mapname[MAX_QPATH]; + float time; + char str[32768]; + int i; + int pt; + int lstyles; + + int slots; + + int clnum; + char plname[32]; + + int filelen, filepos; + char *file; + + char *modelnames[MAX_PRECACHE_MODELS]; + char *soundnames[MAX_PRECACHE_SOUNDS]; + loadplayer_t lp[255]; + + 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 false; + } + VFS_GETS(f, str, sizeof(str)); //discard comment. + Con_Printf("loading legacy game from %s...\n", filename); + + if (version == SAVEGAME_VERSION_NQ || version == SAVEGAME_VERSION_QW) + { + slots = 1; + +#ifdef SERVERONLY + Q_strncpyz(lp[0].name, "", sizeof(lp[0].name)); +#else + Q_strncpyz(lp[0].name, name.string, sizeof(lp[0].name)); +#endif + lp[0].parmstr = NULL; + lp[0].source = NULL; + + for (i=0 ; i<16 ; i++) + { + VFS_GETS(f, str, sizeof(str)); + lp[0].parm[i].f = atof(str); + } + for (; i < countof(lp[0].parm); i++) + lp[0].parm[i].i = 0; + } + else //fte saves ALL the clients on the server. + { + VFS_GETS(f, str, strlen(str)); + slots = atoi(str); + if (!slots || slots >= countof(lp)) //err + { + VFS_CLOSE(f); + Con_Printf ("Corrupted save game"); + return false; + } + for (clnum = 0; clnum < slots; clnum++) + { + VFS_GETS(f, plname, sizeof(plname)); + COM_Parse(plname); + Q_strncpyz(lp[clnum].name, com_token, sizeof(lp[clnum].name)); + lp[clnum].parmstr = NULL; + lp[clnum].source = NULL; + + if (!*com_token) + continue; + + //probably should be 32, rather than NUM_SPAWN_PARMS(64) + for (i=0 ; i sv.maxlightstyles) + Z_ReallocElements((void**)&sv.lightstyles, &sv.maxlightstyles, lstyles, sizeof(*sv.lightstyles)); + for (i=0 ; iload_ents(svprogfuncs, file, NULL, NULL, SV_ExtendedSaveData); + BZ_Free(file); + + PR_LoadGlabalStruct(false); + + pr_global_struct->time = sv.world.physicstime = sv.time = time; + sv.starttime = Sys_DoubleTime() - sv.time; + + VFS_CLOSE(f); + + //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); + + SV_GameLoaded(lp, slots, savename); + return true; +} +#endif + //Attempts to load a named saved game. qboolean SV_Loadgame (const char *unsafe_savename) { @@ -1725,11 +1876,11 @@ qboolean SV_Loadgame (const char *unsafe_savename) int version; int clnum; int slots; - int loadzombies = 0; + int p; client_t *cl; gametype_e gametype; + loadplayer_t lp[255]; - int len; struct { char *pattern; @@ -1755,17 +1906,17 @@ qboolean SV_Loadgame (const char *unsafe_savename) for (n = 0; n < countof(autoload); n++) { - for (len = 0; len < countof(savefiles)-1; len++) + for (p = 0; p < countof(savefiles)-1; p++) { - int d = FS_FLocateFile(va(savefiles[len].pattern, autoload[n]), FSLF_DONTREFERENCE, &savefiles[len].loc); + int d = FS_FLocateFile(va(savefiles[p].pattern, autoload[n]), FSLF_DONTREFERENCE, &savefiles[p].loc); if (!d) continue; - FS_GetLocMTime(&savefiles[len].loc, &t); + FS_GetLocMTime(&savefiles[p].loc, &t); if (d < bestd || (bestd==d&&t>bestt)) { bestd = d; bestt = t; - best = len; + best = p; strcpy(savename, autoload[n]); } @@ -1773,17 +1924,17 @@ qboolean SV_Loadgame (const char *unsafe_savename) } } - for (len = 0; len < countof(savefiles); len++) + for (p = 0; p < countof(savefiles); p++) { - int d = FS_FLocateFile(va(savefiles[len].pattern, savename), FSLF_DONTREFERENCE, &savefiles[len].loc); + int d = FS_FLocateFile(va(savefiles[p].pattern, savename), FSLF_DONTREFERENCE, &savefiles[p].loc); if (!d) continue; - FS_GetLocMTime(&savefiles[len].loc, &t); + FS_GetLocMTime(&savefiles[p].loc, &t); if (d < bestd || (bestd==d&&t>bestt)) { bestd = d; bestt = t; - best = len; + best = p; } } @@ -1808,12 +1959,7 @@ qboolean SV_Loadgame (const char *unsafe_savename) Con_TPrintf ("Unable to load savegame of version %i\n", version); return false; #else - if (SV_Loadgame_Legacy(filename, f, version)) - { - Q_strncpyz(sv.loadgame_on_restart, savename, sizeof(sv.loadgame_on_restart)); - return true; - } - return false; + return SV_Loadgame_Legacy(savename, filename, f, version); #endif } @@ -1825,112 +1971,72 @@ qboolean SV_Loadgame (const char *unsafe_savename) Con_TPrintf ("Loading game from %s...\n", filename); - for (clnum = 0; clnum < svs.allocated_client_slots; clnum++) //clear the server for the level change. - { - cl = &svs.clients[clnum]; - if (cl->state <= cs_loadzombie) - continue; - -#ifndef SERVERONLY - if (cl->netchan.remote_address.type == NA_LOOPBACK) - { -// CL_Disconnect(); - cl->state = cs_zombie; - } - else -#endif - { - if (cl->protocol == SCP_QUAKE2) - MSG_WriteByte (&cl->netchan.message, svcq2_stufftext); - else - MSG_WriteByte (&cl->netchan.message, svc_stufftext); - MSG_WriteString (&cl->netchan.message, "echo Loading Game;disconnect;wait;wait;reconnect\n"); //kindly ask the client to come again. - } - cl->istobeloaded = false; - } - -#ifndef SERVERONLY - if (cls.state) - { - unsigned int rec = cls.demorecording; - cls.demorecording = DPB_NONE; - CL_Disconnect_f(); - cls.demorecording = rec; - } -#endif - - SV_SendMessagesToAll(); VFS_GETS(f, str, sizeof(str)-1); slots = atoi(str); - if (slots > svs.allocated_client_slots) - SV_UpdateMaxPlayers(slots); + + if (slots < 1 || slots > countof(lp)) + { + VFS_CLOSE (f); + Con_Printf ("invalid player count in saved game\n"); + return false; + } + for (cl = svs.clients, clnum=0; clnum < slots; cl++,clnum++) { - if (cl->state > cs_zombie) - SV_DropClient(cl); - VFS_GETS(f, str, sizeof(str)-1); str[sizeof(cl->namebuf)-1] = '\0'; for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) *trim='\0'; for (trim = str; *trim <= ' ' && *trim; trim++) ; - strcpy(cl->namebuf, str); - cl->name = cl->namebuf; + strcpy(lp[clnum].name, str); + lp[clnum].parmstr = NULL; + lp[clnum].source = NULL; + if (*str) { - cl->state = cs_loadzombie; - cl->connection_started = realtime+20; - cl->spawned = cl->istobeloaded = true; - cl->userid = 0; - loadzombies++; - memset(&cl->netchan, 0, sizeof(cl->netchan)); - - for (len = 0; len < NUM_SPAWN_PARMS; len++) + VFS_GETS(f, str, sizeof(str)-1); + if (*str == '{') { - VFS_GETS(f, str, sizeof(str)-1); - if (*str == '{') + while(VFS_GETS(f, str, sizeof(str)-1)) { - while(VFS_GETS(f, str, sizeof(str)-1)) + if (*str == '}') + break; + trim = COM_Parse(str); + if (!strcmp(com_token, "parm_string")) { - 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); + COM_Parse(trim); + Z_Free(lp[clnum].parmstr); + lp[clnum].parmstr = Z_StrDup(com_token); } - break; - } - for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) - *trim='\0'; - for (trim = str; *trim <= ' ' && *trim; trim++) - ; - if (*trim == '(') - cl->spawn_parms[len] = atof(trim+1); - else - { - version = atoi(str); - cl->spawn_parms[len] = *(float *)&version; + else if (!strncmp(com_token, "parm", 4)) + { + unsigned int parm = atoi(com_token+4); + COM_Parse(trim); + if (parm < NUM_SPAWN_PARMS) + { + if (!strncmp(com_token, "0x", 2)) + lp[clnum].parm[parm].i = strtoul(com_token, NULL, 16); + else + lp[clnum].parm[parm].f = strtod(com_token, NULL); + } + } + else + Con_Printf("Unknown player data: %s\n", com_token); } } + else + { //we used to have N integers, where N was some random outdated constant. + VFS_CLOSE (f); + Con_Printf ("Incompatible saved game\n"); + return false; + } } } + SV_LoadPlayers(lp, slots); + VFS_GETS(f, str, sizeof(str)-1); for (trim = str+strlen(str)-1; trim>=str && *trim <= ' '; trim--) @@ -2038,12 +2144,8 @@ qboolean SV_Loadgame (const char *unsafe_savename) svs.gametype = gametype; SV_LoadLevelCache(savename, str, "", true); - sv.allocated_client_slots = slots; - 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)); + SV_GameLoaded(lp, slots, savename); return true; } @@ -2057,6 +2159,48 @@ void SV_Loadgame_f (void) } #endif - SV_Loadgame(Cmd_Argv(1)); + if (sv.state == ss_clustermode && MSV_ForwardToAutoServer()) + ; + else + SV_Loadgame(Cmd_Argv(1)); +} + +#include "fs.h" +void SV_DeleteSavegame_f (void) +{ + const char *savename = Cmd_Argv(1); + + //either saves/$FOO/info.fsv (rmtree) or $FOO.sav (single file) + //extensions are strictly implicit to limit damage. + + const char *fname; + flocation_t loc; + + if (!*savename || *savename == '.' || strchr(savename, '/') || strchr(savename, '\\')) + { + Con_Printf("\"%s\" is not a valid saved game name to delete\n", savename); + return; + } + + fname = va("saves/%s/info.fsv", savename); + if (FS_FLocateFile(fname, FSLF_IGNORELINKS|FSLF_DONTREFERENCE, &loc)) + { + fname = va("saves/%s/", savename); + if (FS_RemoveTree(loc.search->handle, fname)) + Con_Printf("Removed %s\n", fname); + else + Con_Printf("Unable to remove %s\n", fname); + } + +#ifndef QUAKETC + fname = va("%s.sav", savename); + if (FS_FLocateFile(fname, FSLF_IGNORELINKS|FSLF_DONTREFERENCE, &loc)) + { + if (loc.search->handle->RemoveFile && loc.search->handle->RemoveFile(loc.search->handle, fname)) + Con_Printf("Removed %s\n", fname); + else + Con_Printf("Unable to remove %s\n", fname); + } +#endif } #endif diff --git a/engine/server/server.h b/engine/server/server.h index d49e2df3..76bf683e 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -618,8 +618,8 @@ typedef struct client_s //true/false/persist unsigned int penalties; - qbyte istobeloaded; //loadgame creates place holders for clients to connect to. Effectivly loading a game reconnects all clients, but has precreated ents. - qboolean spawned; //the player's entity was spawned. + qbyte istobeloaded; //spawnparms are known. + qboolean spawned; //gamecode knows about it. double floodprotmessage; double lastspoke; @@ -1227,6 +1227,10 @@ typedef struct pubsubserver_s netadr_t addrv4; netadr_t addrv6; char printtext[4096]; //to split it into lines. + qboolean started; +#ifdef HAVE_CLIENT + console_t *console; +#endif } pubsubserver_t; extern qboolean isClusterSlave; void SSV_UpdateAddresses(void); @@ -1244,12 +1248,12 @@ void Sys_InstructMaster(sizebuf_t *cmd); //first two bytes will always be the le #define SSV_IsSubServer() isClusterSlave -void MSV_SubServerCommand_f(void); void MSV_SubServerCommand_f(void); void MSV_MapCluster_f(void); void SSV_Send(const char *dest, const char *src, const char *cmd, const char *msg); qboolean MSV_ClusterLogin(svconnectinfo_t *info); void MSV_PollSlaves(void); +qboolean MSV_ForwardToAutoServer(void); //forwards console command to a default subserver. ie: whichever one our client is on. void MSV_Status(void); void MSV_OpenUserDatabase(void); #else @@ -1257,12 +1261,14 @@ void MSV_OpenUserDatabase(void); #define MSV_ClusterLogin(info) false #define SSV_IsSubServer() false #define MSV_OpenUserDatabase() +#define MSV_PollSlaves() false +#define MSV_ForwardToAutoServer() false #endif // // sv_init.c // -void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, qboolean usecinematic); +void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, qboolean usecinematic, int playerslots); void SV_UnspawnServer (void); void SV_FlushSignon (qboolean force); void SV_UpdateMaxPlayers(int newmax); @@ -1356,7 +1362,8 @@ void SV_VoiceSendPacket(client_t *client, sizebuf_t *buf); #endif void SV_ClientThink (void); -void SV_Begin_Core(client_t *split); +void SV_Begin_Core(client_t *split); //sets up the player's gamecode state +void SV_DespawnClient(client_t *cl); //shuts down the gamecode state. void VoteFlushAll(void); void SV_SetUpClientEdict (client_t *cl, edict_t *ent); @@ -1651,6 +1658,7 @@ int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *heade // savegame.c void SV_Savegame_f (void); +void SV_DeleteSavegame_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); diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 80df50bd..13e54d48 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -36,6 +36,9 @@ qboolean SV_MayCheat(void) return sv_allow_cheats!=0; } +#ifdef SUBSERVERS +cvar_t sv_autooffload = CVARD("sv_autooffload", "0", "Automatically start the server in a separate process, so that sporadic or persistent gamecode slowdowns do not affect visual framerates. Note: Offloaded servers have separate cvar states which may complicate usage."); +#endif extern cvar_t cl_warncmd; cvar_t sv_cheats = CVARF("sv_cheats", "0", CVAR_LATCH); extern redirect_t sv_redirected; @@ -557,6 +560,12 @@ void SV_Map_f (void) } #endif +#ifdef SUBSERVERS + //disconnect first if you want to stop your current server getting the command instead. + if (sv.state == ss_clustermode && MSV_ForwardToAutoServer()) + return; +#endif + if (!Q_strcasecmp(Cmd_Argv(0), "map_restart")) { const char *arg = Cmd_Argv(1); @@ -754,13 +763,21 @@ void SV_Map_f (void) SCR_SetLoadingStage(LS_NONE); #endif - if (SSV_IsSubServer()) + if (SSV_IsSubServer() && !sv.state) //subservers don't leave defunct servers with no maps lying around. Cbuf_AddText("\nquit\n", RESTRICT_LOCAL); return; } } } +#ifdef SUBSERVERS + if (!isDedicated && sv_autooffload.ival && !sv.state && !SSV_IsSubServer() && !strcmp(Cmd_Argv(0), "map") && Cmd_Argc()==2) + { + Cmd_ExecuteString(va("mapcluster \"%s\"", Cmd_Argv(1)), Cmd_ExecLevel); + return; + } +#endif + #ifdef MVD_RECORDING if (sv.mvdrecording) SV_MVDStop_f(); @@ -914,7 +931,7 @@ void SV_Map_f (void) { if (waschangelevel && !startspot) startspot = ""; - SV_SpawnServer (level, startspot, false, cinematic); + SV_SpawnServer (level, startspot, false, cinematic, 0); } SCR_SetLoadingFile("server spawned"); @@ -3181,6 +3198,9 @@ void SV_InitOperatorCommands (void) Cmd_AddCommand ("download", SV_Download_f); } +#ifdef SUBSERVERS + Cvar_Register(&sv_autooffload, "server control variables"); +#endif Cvar_Register(&sv_cheats, "Server Permissions"); if (COM_CheckParm ("-cheats")) { diff --git a/engine/server/sv_cluster.c b/engine/server/sv_cluster.c index 8e964c88..42e58ecc 100644 --- a/engine/server/sv_cluster.c +++ b/engine/server/sv_cluster.c @@ -94,6 +94,15 @@ static void MSV_ServerCrashed(pubsubserver_t *server) link_t *l, *next; clusterplayer_t *pl; +#ifdef HAVE_CLIENT + if (server->console) + { + Con_PrintCon(server->console, "\n", server->console->flags); + Q_snprintfz(server->console->title, sizeof(server->console->title), "SERVER DEAD"); + server->console->userdata = NULL; //forget about us! for we are no more! + } +#endif + //forget any players that are meant to be on this server. for (l = clusterplayers.next ; l != &clusterplayers ; l = next) { @@ -101,7 +110,7 @@ static void MSV_ServerCrashed(pubsubserver_t *server) pl = STRUCT_FROM_LINK(l, clusterplayer_t, allplayers); if (pl->server == server) { - Con_Printf("%s(%s) crashed out\n", pl->name, server->name); + Con_Printf("%s's node crashed out (%s)\n", pl->name, server->name); RemoveLink(&pl->allplayers); Z_Free(pl); } @@ -145,6 +154,35 @@ static int MSV_Loop_Read(pubsubserver_t *ps) return 1; } +static void MSV_SendCvars(pubsubserver_t *s) +{ + extern cvar_t skill, sv_nqplayerphysics, sv_pure, sv_minpitch, sv_maxpitch; + cvar_t *cvars[] = { + &developer, + &deathmatch, &coop, &skill, &teamplay, + &nomonsters, &gamecfg, &noexit, &temp1, + &scratch1, &scratch2, &scratch3, &scratch4, + &saved1, &saved2, &saved3, &saved4, &savedgamecfg, + &sv_nqplayerphysics, &sv_pure, &sv_mintic, &sv_maxtic, + &sv_minpitch, &sv_maxpitch}; + + sizebuf_t send; + char send_buf[8192]; + size_t v; + + memset(&send, 0, sizeof(send)); + send.data = send_buf; + send.maxsize = sizeof(send_buf); + for (v = 0; v < countof(cvars); v++) + { + send.cursize = 2; + MSG_WriteByte(&send, ccmd_setcvar); + MSG_WriteString(&send, cvars[v]->name); + MSG_WriteString(&send, cvars[v]->string); + s->funcs.InstructSlave(s, &send); + } +} + static void MSV_Link_Server(pubsubserver_t *s, int id, const char *mapname) { sizebuf_t send; @@ -159,6 +197,8 @@ static void MSV_Link_Server(pubsubserver_t *s, int id, const char *mapname) if (mapname) { + MSV_SendCvars(s); + Q_strncpyz(s->name, mapname, sizeof(s->name)); memset(&send, 0, sizeof(send)); @@ -258,20 +298,25 @@ qboolean MSV_AddressForServer(netadr_t *ret, int natype, pubsubserver_t *s) return false; } -void MSV_InstructSlave(unsigned int id, sizebuf_t *cmd) +qboolean MSV_InstructSlave(unsigned int id, sizebuf_t *cmd) { pubsubserver_t *s; if (!id) { for (s = subservers; s; s = s->next) s->funcs.InstructSlave(s, cmd); + return subservers?true:false; } else { s = MSV_FindSubServer(id); if (s) + { s->funcs.InstructSlave(s, cmd); + return true; + } } + return false; } void SV_SetupNetworkBuffers(qboolean bigcoords); @@ -351,7 +396,7 @@ void MSV_Status(void) clusterplayer_t *pl; for (s = subservers; s; s = s->next) { - Con_Printf("%i: %s", s->id, s->name); + Con_Printf("^[%i: %s\\ssv\\%u^]", s->id, s->name, s->id); if (s->addrv4.type != NA_INVALID) Con_Printf(" %s", NET_AdrToString(bufmem, sizeof(bufmem), &s->addrv4)); if (s->addrv6.type != NA_INVALID) @@ -362,13 +407,111 @@ void MSV_Status(void) FOR_EACH_LINK(l, clusterplayers) { pl = STRUCT_FROM_LINK(l, clusterplayer_t, allplayers); - Con_Printf("%i(%s): (%s) %s (%s)\n", pl->playerid, pl->server->name, pl->guid, pl->name, pl->address); + Con_Printf("^[%i(%s)\\ssv\\%u^]: (%s) %s (%s)\n", pl->playerid, pl->server->name, pl->server->id, pl->guid, pl->name, pl->address); } } + +#ifdef HAVE_CLIENT +static int MSV_SubConsole_LineBuffered(console_t *con, const char *utf8line) +{ + pubsubserver_t *s = con->userdata; + if (s) + { + sizebuf_t buf; + char bufmem[65536]; + + Con_PrintCon(con, va("]%s\n", utf8line), PFS_FORCEUTF8|PFS_NONOTIFY); + + if (!strcmp(utf8line, "clear")) + { + Con_ClearCon(con); + return true; + } + + buf.data = bufmem; + buf.maxsize = sizeof(bufmem); + buf.cursize = 2; + buf.packing = SZ_RAWBYTES; + MSG_WriteByte(&buf, ccmd_stuffcmd); + MSG_WriteString(&buf, utf8line); //FIXME: is utf-8 a problem? + buf.data[0] = buf.cursize & 0xff; + buf.data[1] = (buf.cursize>>8) & 0xff; + s->funcs.InstructSlave(s, &buf); + } + else + Con_Footerf(con, false, "< Unable to send >"); + return true; +} +static qboolean MSV_SubConsole_Close (console_t *con, qboolean force) +{ //force=true is the final close, the rest are merely queries to see if its save. + pubsubserver_t *s = con->userdata; + if (force && s) + { //stop prints from this server from going here. + s->console = NULL; + } + return true; +} +static void MSV_SubConsole_Update(pubsubserver_t *s) +{ + if (s->console) + { + if (s->console->flags & CONF_ISWINDOW) + Q_snprintfz(s->console->title, sizeof(s->console->title), "%u:%s", s->id, s->name); + else + Q_snprintfz(s->console->title, sizeof(s->console->title), "Server %u: %s", s->id, s->name); + } +} +static void MSV_SubConsole_Show(pubsubserver_t *s, qboolean show) +{ + console_t *con = s->console; + if (!con) + { + for (con = con_head; con; con = con->next) + { + if (con->close == MSV_SubConsole_Close && !con->userdata) + break; + } + if (!con) + { + con = Con_Create(NULL, CONF_NOTIFY); + if (0)//con) + { + /*make it a console window thing*/ + con->flags |= CONF_ISWINDOW; + con->wnd_x = 0; + con->wnd_y = 0; + con->wnd_w = vid.width/2; + con->wnd_h = vid.height/2; + } + } + if (con) + { + s->console = con; + MSV_SubConsole_Update(s); + + con->parseflags = PFS_FORCEUTF8; + con->userdata = s; + con->linebuffered = MSV_SubConsole_LineBuffered; +// con->redirect = Con_Editor_Key; + con->close = MSV_SubConsole_Close; + con->maxlines = 0x7fffffff; //line limit is effectively unbounded, for a 31-bit process. + + //use the server's status command as a header. + if (show) + MSV_SubConsole_LineBuffered(con, "status"); + } + } + if (con && show) + Con_SetActive(con); +} +#else + #define MSV_SubConsole_Update(s) +#endif + void MSV_SubServerCommand_f(void) { sizebuf_t buf; - char bufmem[1024]; + char bufmem[65536]; pubsubserver_t *s; int id; char *c; @@ -377,7 +520,7 @@ void MSV_SubServerCommand_f(void) Con_Printf("Active servers on this cluster:\n"); for (s = subservers; s; s = s->next) { - Con_Printf("%i: %s %i+%i", s->id, s->name, s->activeplayers, s->transferingplayers); + Con_Printf("^[%i: %s %i+%i\\ssv\\%u^]", s->id, s->name, s->activeplayers, s->transferingplayers, s->id); if (s->addrv4.type != NA_INVALID) Con_Printf(" %s", NET_AdrToString(bufmem, sizeof(bufmem), &s->addrv4)); if (s->addrv6.type != NA_INVALID) @@ -388,6 +531,15 @@ void MSV_SubServerCommand_f(void) } if (!strcmp(Cmd_Argv(0), "ssv_all")) id = 0; +#ifdef HAVE_CLIENT + else if (Cmd_Argc() == 2) + { //subservers, meet subconsoles + s = MSV_FindSubServer(atoi(Cmd_Argv(1))); + if (s) + MSV_SubConsole_Show(s, true); + return; + } +#endif else { id = atoi(Cmd_Argv(1)); @@ -403,22 +555,90 @@ void MSV_SubServerCommand_f(void) MSG_WriteString(&buf, c); buf.data[0] = buf.cursize & 0xff; buf.data[1] = (buf.cursize>>8) & 0xff; - MSV_InstructSlave(id, &buf); + if (!MSV_InstructSlave(id, &buf)) + Con_Printf("No node for index.\n"); +} + +qboolean MSV_ForwardToAutoServer(void) +{ + pubsubserver_t *s; + if (sv.state > ss_clustermode) + return false; //don't forward if we have our own server. + + if (subservers && !subservers->next && sv.state == ss_clustermode) + s = subservers; //there is only one. + else + { + s = NULL; +#ifdef HAVE_CLIENT + if (!s && cls.state >= ca_connected) + { //find the one the local player is currently on. + for (; s; s = s->next) + { + if (s->addrv6.type!=NA_INVALID && NET_CompareAdr(&s->addrv4, &cls.netchan.remote_address)) + break; + if (s->addrv6.type!=NA_INVALID && NET_CompareAdr(&s->addrv6, &cls.netchan.remote_address)) + break; + } + } +#endif + if (!s) + return false; + } + + { + sizebuf_t buf; + char bufmem[65536]; + const char *cmd = Cmd_Argv(0); + const char *args = Cmd_Args();; + buf.data = bufmem; + buf.maxsize = sizeof(bufmem); + buf.cursize = 2; + buf.packing = SZ_RAWBYTES; + MSG_WriteByte(&buf, ccmd_stuffcmd); + SZ_Write(&buf, cmd, strlen(cmd)); + if(*args) + MSG_WriteChar(&buf, ' '); + MSG_WriteString(&buf, args); + buf.data[0] = buf.cursize & 0xff; + buf.data[1] = (buf.cursize>>8) & 0xff; + s->funcs.InstructSlave(s, &buf); + return true; + } } static void MSV_PrintFromSubServer(pubsubserver_t *s, const char *newtext) { char *nl; +#ifdef HAVE_CLIENT + if (!s->console) + { +// extern cvar_t con_window; +// if (con_window.ival) //might as well pop one up. + MSV_SubConsole_Show(s, false); + } + if (s->console) + { + if (*s->printtext) + { //flush it if there was something buffered there... + Con_PrintCon(s->console, s->printtext, s->console->flags); + *s->printtext = 0; + } + Con_PrintCon(s->console, newtext, s->console->flags); + return; + } +#endif + Q_strncatz(s->printtext, newtext, sizeof(s->printtext)); while((nl = strchr(s->printtext, '\n'))) { //FIXME: handle overflows. *nl++ = 0; - Con_Printf("^6%i(%s)^7: %s\n", s->id, s->name, s->printtext); + Con_Printf("^[^6%i(%s)\\ssv\\%u^]: %s\n", s->id, s->name, s->id, s->printtext); memmove(s->printtext, nl, strlen(nl)+1); } if (strlen(s->printtext) > sizeof(s->printtext)/2) { - Con_Printf("^6%i(%s)^7: %s\n", s->id, s->name, s->printtext); + Con_Printf("^[^6%i(%s)\\ssv\\%u^]: %s\n", s->id, s->name, s->id, s->printtext); *s->printtext = 0; } } @@ -510,6 +730,51 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) } } break; + case ccmd_foundplayer: + { + char guid[64]; + char plnamebuf[64]; + char *plname = MSG_ReadStringBuffer(plnamebuf, sizeof(plnamebuf)); + char *claddr = MSG_ReadString(); + char *clguid = MSG_ReadStringBuffer(guid, sizeof(guid)); + extern int nextuserid; + const unsigned int statsblobsize = 0; + const void *statsblob = NULL; + + sizebuf_t send; + qbyte send_buf[MAX_QWMSGLEN]; + clusterplayer_t *pl; + + if (sv.logindatabase) + break; //if we're using a login database then this could be used as an exploit. + + memset(&send, 0, sizeof(send)); + send.data = send_buf; + send.maxsize = sizeof(send_buf); + send.cursize = 2; + + pl = Z_Malloc(sizeof(*pl)); + Q_strncpyz(pl->name, plname, sizeof(pl->name)); + Q_strncpyz(pl->guid, clguid, sizeof(pl->guid)); + Q_strncpyz(pl->address, claddr, sizeof(pl->address)); + pl->playerid = ++nextuserid; + InsertLinkBefore(&pl->allplayers, &clusterplayers); + pl->server = s; + s->activeplayers++; + + MSG_WriteByte(&send, ccmd_takeplayer); + MSG_WriteLong(&send, pl->playerid); + MSG_WriteString(&send, pl->name); + MSG_WriteLong(&send, 0); //from server + MSG_WriteString(&send, pl->address); + MSG_WriteString(&send, pl->guid); + + MSG_WriteByte(&send, statsblobsize/4); + SZ_Write(&send, statsblob, statsblobsize&~3); + s->funcs.InstructSlave(s, &send); + } + break; + case ccmd_transferplayer: { //server is offering a player to another server char guid[64]; @@ -639,8 +904,13 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) } } } + MSV_SubConsole_Update(s); + if (s->started) + Con_DPrintf("^[^6[%i:%s: map changed]\\ssv\\%u\\tip\\Click for server's console^]\n", s->id, s->name, s->id); + else + Con_Printf("^[^6[%i:%s: new node initialised]\\ssv\\%u\\tip\\Click for server's console^]\n", s->id, s->name, s->id); + s->started = true; } - Con_Printf("%i:%s: restarted\n", s->id, s->name); break; case ccmd_stringcmd: { @@ -770,6 +1040,15 @@ void SSV_ReadFromControlServer(void) SV_EndRedirect(); break; + case ccmd_setcvar: + { + cvar_t *var = Cvar_FindVar(MSG_ReadString()); + const char *val = MSG_ReadString(); + Con_Printf("Setting cvar \"%s\" to \"%s\"\n", var?var->name:"UNKNOWN", val); + Cvar_Set(var, val); + } + break; + //cluster has 'accepted' us as an allowed server. this is where it tells us who we're meant to be, which needs to be set up ready for the players that are (probably) about to join us case ccmd_acceptserver: svs.clusterserverid = MSG_ReadLong(); diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index 2812bf4a..c3577dfe 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -3443,14 +3443,13 @@ void SV_Snapshot_BuildStateQ1(entity_state_t *state, edict_t *ent, client_t *cli state->effects |= EF_GREEN; } } - else + + if (state->number <= sv.allocated_client_slots) // clear only client ents { if (state->effects & NQEF_NODRAW) state->modelindex = 0; - } - - if (state->number <= sv.allocated_client_slots) // clear only client ents state->effects &= ~ (QWEF_FLAG1|QWEF_FLAG2); + } if ((state->effects & EF_DIMLIGHT) && !(state->effects & (EF_RED|EF_BLUE))) { @@ -3771,9 +3770,10 @@ void SV_Snapshot_BuildQ1(client_t *client, packet_entities_t *pack, pvscamera_t } //QSG_DIMENSION_PLANES - if (client->edict) - if (!((int)client->edict->xv->dimension_see & ((int)ent->xv->dimension_seen | (int)ent->xv->dimension_ghost))) - continue; //not in this dimension - sorry... + if (clent) //don't crash + if (!((int)clent->xv->dimension_see & ((int)ent->xv->dimension_seen | (int)ent->xv->dimension_ghost))) //not able to see it. + if (c >= maxc) //always network the player entity though + continue; if (cameras && tracecullent && !((unsigned int)ent->v->effects & (EF_DIMLIGHT|EF_BLUE|EF_RED|EF_BRIGHTLIGHT|EF_BRIGHTFIELD|EF_NODEPTHTEST))) diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index a198ba29..bd9fed32 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -694,6 +694,10 @@ void SV_UpdateMaxPlayers(int newmax) } for (i = 0; i < min(newmax, svs.allocated_client_slots); i++) { + if (svs.clients[i].name == old[i].namebuf) + svs.clients[i].name = svs.clients[i].namebuf; + if (svs.clients[i].team == old[i].teambuf) + svs.clients[i].team = svs.clients[i].teambuf; if (svs.clients[i].netchan.message.data) svs.clients[i].netchan.message.data = (qbyte*)&svs.clients[i] + (svs.clients[i].netchan.message.data - (qbyte*)&old[i]); if (svs.clients[i].datagram.data) @@ -823,7 +827,7 @@ clients along with it. This is only called from the SV_Map_f() function. ================ */ -void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, qboolean usecinematic) +void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, qboolean usecinematic, int playerslots) { extern cvar_t allow_download_refpackages; func_t f; @@ -1331,6 +1335,8 @@ MSV_OpenUserDatabase(); i = QWMAX_CLIENTS; } } + if (playerslots) + i = playerslots; //saved game? force it. if (i > MAX_CLIENTS) i = MAX_CLIENTS; SV_UpdateMaxPlayers(i); @@ -1376,7 +1382,7 @@ MSV_OpenUserDatabase(); #endif #ifdef Q3SERVER case GT_QUAKE3: - SV_UpdateMaxPlayers(max(8,maxclients.ival)); + SV_UpdateMaxPlayers(playerslots?playerslots:max(8,maxclients.ival)); break; #endif #ifdef HLSERVER diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 1e9ab193..fde50526 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -86,7 +86,8 @@ extern cvar_t password; #endif cvar_t spectator_password = CVARF("spectator_password", "", CVAR_NOUNSAFEEXPAND); // password for entering as a sepctator -cvar_t allow_download = CVARAD("allow_download", "1", /*q3*/"sv_allowDownload", "If 1, permits downloading. Set to 0 to unconditionally block *ALL* downloads."); +static cvar_t sv_dlURL = CVARFD(/*ioq3*/"sv_dlURL", "", CVAR_SERVERINFO|CVAR_ARCHIVE, "Provides clients with an external url from which they can obtain pk3s/packages from an external http server instead of having to download over udp."); +cvar_t allow_download = CVARAD("allow_download", "1", /*q3*/"sv_allowDownload", "If 1, permits downloading. Set to 0 to unconditionally block *ALL* downloads from this server. You may wish to set sv_dlURL if you wish clients to still be able to download content."); cvar_t allow_download_skins = CVARD("allow_download_skins", "1", "0 blocks downloading of any file in the skins/ directory"); cvar_t allow_download_models = CVARD("allow_download_models", "1", "0 blocks downloading of any file in the progs/ or models/ directory"); cvar_t allow_download_sounds = CVARD("allow_download_sounds", "1", "0 blocks downloading of any file in the sound/ directory"); @@ -295,7 +296,7 @@ void SV_Shutdown (void) #endif Mod_Shutdown(true); #ifdef PACKAGEMANAGER - PM_Shutdown(); + PM_Shutdown(false); #endif COM_DestroyWorkerThread(); FS_Shutdown(); @@ -570,43 +571,7 @@ void SV_DropClient (client_t *drop) case GT_PROGS: if (svprogfuncs) { - if (drop->spawned && host_initialized) - { -#ifdef VM_Q1 - if (svs.gametype == GT_Q1QVM) - { - Q1QVM_DropClient(drop); - } - else -#endif - { - if (!drop->spectator) - { - // call the prog function for removing a client - // this will set the body to a dead frame, among other things - pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, drop->edict); - if (pr_global_ptrs->ClientDisconnect) - PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect); - sv.spawned_client_slots--; - } - else - { - // call the prog function for removing a client - // this will set the body to a dead frame, among other things - pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, drop->edict); - if (SpectatorDisconnect) - PR_ExecuteProgram (svprogfuncs, SpectatorDisconnect); - sv.spawned_observer_slots--; - } - } - - if (progstype == PROG_NQ) - ED_Clear(svprogfuncs, drop->edict); - } - drop->spawned = false; - - if (svprogfuncs && drop->edict && drop->edict->v) - drop->edict->v->frags = 0; + SV_DespawnClient(drop); drop->edict = NULL; if (drop->spawninfo) @@ -2657,9 +2622,29 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) { if (SSV_IsSubServer()) { - SV_RejectMessage (info->protocol, "Direct connections are not permitted.\n"); - Con_TPrintf ("* rejected direct connection\n"); - return; + if (1) + { + sizebuf_t s; + qbyte send[8192]; + + memset(&s, 0, sizeof(s)); + s.data = send; + s.maxsize = sizeof(send); + s.cursize = 2; + + MSG_WriteByte(&s, ccmd_foundplayer); + MSG_WriteString(&s, name); + MSG_WriteString(&s, NET_AdrToString (adrbuf, sizeof(adrbuf), &info->adr)); + MSG_WriteString(&s, info->guid); + SSV_InstructMaster(&s); + return; + } + else + { + SV_RejectMessage (info->protocol, "Direct connections are not permitted.\n"); + Con_TPrintf ("* rejected direct connection\n"); + return; + } } /*single player logic*/ @@ -5436,6 +5421,7 @@ void SV_InitLocal (void) Cvar_Register (&filterban, cvargroup_servercontrol); + Cvar_Register (&sv_dlURL, cvargroup_serverpermissions); Cvar_Register (&allow_download, cvargroup_serverpermissions); Cvar_Register (&allow_download_skins, cvargroup_serverpermissions); Cvar_Register (&allow_download_models, cvargroup_serverpermissions); @@ -5497,6 +5483,7 @@ void SV_InitLocal (void) Cmd_AddCommandAD ("loadgame", SV_Loadgame_f, SV_Savegame_c, "Loads an existing saved game."); Cmd_AddCommandAD ("save", SV_Savegame_f, SV_Savegame_c, "Saves the game to the named location."); Cmd_AddCommandAD ("load", SV_Loadgame_f, SV_Savegame_c, "Loads an existing saved game."); + Cmd_AddCommandAD ("unsavegame", SV_DeleteSavegame_f, SV_Savegame_c, "Wipes an existing saved game from disk."); #endif #ifdef MVD_RECORDING @@ -6028,12 +6015,7 @@ void SV_Init (quakeparms_t *parms) manarg = COM_CheckParm("-manifest"); if (manarg && manarg < com_argc-1 && com_argv[manarg+1]) - { - char *man = FS_MallocFile(com_argv[manarg+1], FS_SYSTEM, NULL); - - FS_ChangeGame(FS_Manifest_Parse(NULL, man), true, true); - BZ_Free(man); - } + FS_ChangeGame(FS_Manifest_ReadSystem(com_argv[manarg+1], NULL), true, true); else FS_ChangeGame(NULL, true, true); diff --git a/engine/server/sv_master.c b/engine/server/sv_master.c index 3ad383f2..fba466df 100644 --- a/engine/server/sv_master.c +++ b/engine/server/sv_master.c @@ -1288,12 +1288,7 @@ void SV_Init (struct quakeparms_s *parms) manarg = COM_CheckParm("-manifest"); if (manarg && manarg < com_argc-1 && com_argv[manarg+1]) - { - char *man = FS_MallocFile(com_argv[manarg+1], FS_SYSTEM, NULL); - - FS_ChangeGame(FS_Manifest_Parse(NULL, man), true, true); - BZ_Free(man); - } + FS_ChangeGame(FS_Manifest_ReadSystem(com_argv[manarg+1], NULL), true, true); else FS_ChangeGame(NULL, true, true); diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 19863ffc..ee3b1eaf 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -59,6 +59,7 @@ cvar_t sv_gameplayfix_multiplethinks = CVARD( "sv_gameplayfix_multiplethinks", cvar_t sv_gameplayfix_stepdown = CVARD( "sv_gameplayfix_stepdown", "0", "Attempt to step down steps, instead of only up them. Affects non-predicted movetype_walk."); cvar_t sv_gameplayfix_bouncedownslopes = CVARD( "sv_gameplayfix_grenadebouncedownslopes", "0", "MOVETYPE_BOUNCE speeds are calculated relative to the impacted surface, instead of the vertical, reducing the chance of grenades just sitting there on slopes."); cvar_t sv_gameplayfix_trappedwithin = CVARD( "sv_gameplayfix_trappedwithin", "0", "Blocks further entity movement when an entity is already inside another entity. This ensures that bsp precision issues cannot allow the entity to completely pass through eg the world."); +//cvar_t sv_gameplayfix_radialmaxvelocity = CVARD( "sv_gameplayfix_radialmaxvelocity", "0", "Applies maxvelocity radially instead of axially."); #if !defined(CLIENTONLY) && defined(NQPROT) && defined(HAVE_LEGACY) cvar_t sv_gameplayfix_spawnbeforethinks = CVARD( "sv_gameplayfix_spawnbeforethinks", "0", "Fixes an issue where player thinks (including Pre+Post) can be called before PutClientInServer. Unfortunately at least one mod depends upon PreThink being called first in order to correctly determine spawn positions."); #endif @@ -150,28 +151,50 @@ SV_CheckVelocity void WPhys_CheckVelocity (world_t *w, wedict_t *ent) { int i; + extern cvar_t sv_nqplayerphysics; -// -// bound velocity -// - for (i=0 ; i<3 ; i++) - { - if (IS_NAN(ent->v->velocity[i])) + if (sv_nqplayerphysics.ival) + { //bound axially (like vanilla) + for (i=0 ; i<3 ; i++) { - Con_DPrintf ("Got a NaN velocity on entity %i (%s)\n", ent->entnum, PR_GetString(w->progs, ent->v->classname)); - ent->v->velocity[i] = 0; - } - if (IS_NAN(ent->v->origin[i])) - { - Con_Printf ("Got a NaN origin on entity %i (%s)\n", ent->entnum, PR_GetString(w->progs, ent->v->classname)); - ent->v->origin[i] = 0; + if (IS_NAN(ent->v->velocity[i])) + { + Con_DPrintf ("Got a NaN velocity on entity %i (%s)\n", ent->entnum, PR_GetString(w->progs, ent->v->classname)); + ent->v->velocity[i] = 0; + } + if (IS_NAN(ent->v->origin[i])) + { + Con_Printf ("Got a NaN origin on entity %i (%s)\n", ent->entnum, PR_GetString(w->progs, ent->v->classname)); + ent->v->origin[i] = 0; + } + + if (ent->v->velocity[i] > sv_maxvelocity.value) + ent->v->velocity[i] = sv_maxvelocity.value; + else if (ent->v->velocity[i] < -sv_maxvelocity.value) + ent->v->velocity[i] = -sv_maxvelocity.value; } } + else + { //bound radially (for sanity) + for (i=0 ; i<3 ; i++) + { + if (IS_NAN(ent->v->velocity[i])) + { + Con_DPrintf ("Got a NaN velocity on entity %i (%s)\n", ent->entnum, PR_GetString(w->progs, ent->v->classname)); + ent->v->velocity[i] = 0; + } + if (IS_NAN(ent->v->origin[i])) + { + Con_Printf ("Got a NaN origin on entity %i (%s)\n", ent->entnum, PR_GetString(w->progs, ent->v->classname)); + ent->v->origin[i] = 0; + } + } - if (Length(ent->v->velocity) > sv_maxvelocity.value) - { -// Con_DPrintf("Slowing %s\n", PR_GetString(w->progs, ent->v->classname)); - VectorScale (ent->v->velocity, sv_maxvelocity.value/Length(ent->v->velocity), ent->v->velocity); + if (Length(ent->v->velocity) > sv_maxvelocity.value) + { +// Con_DPrintf("Slowing %s\n", PR_GetString(w->progs, ent->v->classname)); + VectorScale (ent->v->velocity, sv_maxvelocity.value/Length(ent->v->velocity), ent->v->velocity); + } } } diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index 28659454..08c13e62 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -1457,13 +1457,14 @@ void SV_StartSound (int ent, vec3_t origin, float *velocity, int seenmask, int c void QDECL SVQ1_StartSound (float *origin, wedict_t *wentity, int channel, const char *sample, int volume, float attenuation, float pitchadj, float timeofs, unsigned int chflags) { edict_t *entity = (edict_t*)wentity; - int i; + int i, solid; vec3_t originbuf; float *velocity = NULL; if (!origin) { origin = originbuf; - if (entity->v->solid == SOLID_BSP) + solid = entity->v->solid; + if (solid == SOLID_BSP || solid == SOLID_BSPTRIGGER) { for (i=0 ; i<3 ; i++) origin[i] = entity->v->origin[i]+0.5*(entity->v->mins[i]+entity->v->maxs[i]); diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 23adbeaa..fa84dd78 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -1890,7 +1890,7 @@ void SVQW_Spawn_f (void) { ent = split->edict; - if (split->istobeloaded) //minimal setup + if (split->spawned) //minimal setup { split->entgravity = ent->xv->gravity; split->maxspeed = ent->xv->maxspeed; @@ -1984,6 +1984,62 @@ void SV_SpawnSpectator (void) } } +void SV_DespawnClient(client_t *cl) +{ //this disconnects the client from its entity state + if (!cl->spawned) + return; //nothing to do. + cl->spawned = false; + +#ifdef Q2SERVER + if (ge) + { + ge->ClientDisconnect(cl->q2edict); + return; + } +#endif + + + if (svprogfuncs) + { + if (host_initialized) + { +#ifdef VM_Q1 + if (svs.gametype == GT_Q1QVM) + { + Q1QVM_DropClient(cl); + } + else +#endif + { + if (!cl->spectator) + { + // call the prog function for removing a client + // this will set the body to a dead frame, among other things + pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict); + if (pr_global_ptrs->ClientDisconnect) + PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect); + sv.spawned_client_slots--; + } + else + { + // call the prog function for removing a client + // this will set the body to a dead frame, among other things + pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict); + if (SpectatorDisconnect) + PR_ExecuteProgram (svprogfuncs, SpectatorDisconnect); + sv.spawned_observer_slots--; + } + } + + if (progstype == PROG_NQ) + ED_Clear(svprogfuncs, cl->edict); + } + + if (svprogfuncs && cl->edict && cl->edict->v) + cl->edict->v->frags = 0; + } +} + void SV_Begin_Core(client_t *split) { //this is the client-protocol-independant core, for q1/q2 gamecode client_t *oh; @@ -1993,7 +2049,28 @@ void SV_Begin_Core(client_t *split) #endif if (split->spawned) + { + //NEH_RESTOREGAME + //officially RestoreGame tells the mod when the game has been loaded. + //this allows mods to send any stuffcmds the client will have forgotten. + //the original intention would not have been client-specific (and indeed nehahra only saves in singleplayer) + //doing it elsewhere unfortunately results in race conditions. + func_t f = PR_FindFunction(svprogfuncs, "RestoreGame", PR_ANY); + if (f) + { + pr_global_struct->time = sv.world.physicstime; + pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict); + PR_ExecuteProgram (svprogfuncs, f); + } + + ClientReliableWrite_Begin(split, svc_setangle, 8); + if (host_client->ezprotocolextensions1 & EZPEXT1_SETANGLEREASON) + ClientReliableWrite_Byte (host_client, 0); + ClientReliableWrite_Angle (host_client, split->edict->v->v_angle[0]); + ClientReliableWrite_Angle (host_client, split->edict->v->v_angle[1]); + ClientReliableWrite_Angle (host_client, 0); //roll angle is messy with cl_roll. we don't want to be stuck rolling. return; + } split->spawned = true; #ifdef Q2SERVER @@ -2010,20 +2087,6 @@ void SV_Begin_Core(client_t *split) } else #endif - if (split->istobeloaded) - { - func_t f; - split->istobeloaded = false; - - f = PR_FindFunction(svprogfuncs, "RestoreGame", PR_ANY); - if (f) - { - pr_global_struct->time = sv.world.physicstime; - pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict); - PR_ExecuteProgram (svprogfuncs, f); - } - } - else { #ifdef HAVE_LEGACY split->edict->xv->clientcolors = split->playercolor; @@ -5349,17 +5412,7 @@ void Cmd_Join_f (void) if (!host_client->spectator) continue; - // call the prog function for removing a client - // this will set the body to a dead frame, among other things - pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player); -#ifdef VM_Q1 - if (svs.gametype == GT_Q1QVM) - Q1QVM_DropClient(host_client); - else -#endif - if (SpectatorDisconnect) - PR_ExecuteProgram (svprogfuncs, SpectatorDisconnect); - sv.spawned_observer_slots--; + SV_DespawnClient(host_client); SV_SetUpClientEdict (host_client, host_client->edict); @@ -5483,18 +5536,8 @@ void Cmd_Observe_f (void) if (host_client->spectator) continue; - // call the prog function for removing a client - // this will set the body to a dead frame, among other things -#ifdef VM_Q1 - if (svs.gametype == GT_Q1QVM) - Q1QVM_DropClient(host_client); - else -#endif - { - pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player); - PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect); - } - sv.spawned_client_slots--; + SV_DespawnClient(host_client); + SV_SetUpClientEdict (host_client, host_client->edict); @@ -5630,9 +5673,10 @@ void SV_EnableClientsCSQC(void) host_client->csqcactive = true; //if the csqc has just restarted, its probably going to want us to resend all csqc ents from scratch because of all the setup it might do. - for (e = 1; e < host_client->max_net_ents; e++) - if (host_client->pendingcsqcbits[e] & SENDFLAGS_PRESENT) - host_client->pendingcsqcbits[e] |= SENDFLAGS_USABLE; + if (host_client->pendingcsqcbits) + for (e = 1; e < host_client->max_net_ents; e++) + if (host_client->pendingcsqcbits[e] & SENDFLAGS_PRESENT) + host_client->pendingcsqcbits[e] |= SENDFLAGS_USABLE; } void SV_DisableClientsCSQC(void) { @@ -6603,7 +6647,7 @@ static qboolean AddEntityToPmove(world_t *w, wedict_t *player, wedict_t *check) pe->forcecontentsmask = 0; break; } - if (solid == SOLID_PORTAL || solid == SOLID_BSP) + if (solid == SOLID_PORTAL || solid == SOLID_BSP || solid == SOLID_BSPTRIGGER) { if(progstype != PROG_H2) pe->angles[0]*=r_meshpitch.value; //quake is wierd. I guess someone fixed it hexen2... or my code is buggy or something... @@ -8099,8 +8143,16 @@ void SV_ExecuteClientMessage (client_t *cl) #endif case clcdp_ackframe: cl->delta_sequence = MSG_ReadLong(); - if (cl->delta_sequence == -1 && cl->pendingdeltabits) - cl->pendingdeltabits[0] = UF_REMOVE; + if (cl->delta_sequence == -1) + { + unsigned int e; + if (cl->pendingdeltabits) + cl->pendingdeltabits[0] = UF_REMOVE; + if (host_client->pendingcsqcbits) + for (e = 1; e < host_client->max_net_ents; e++) + if (host_client->pendingcsqcbits[e] & SENDFLAGS_PRESENT) + host_client->pendingcsqcbits[e] |= SENDFLAGS_USABLE; + } SV_AckEntityFrame(cl, cl->delta_sequence); break; } @@ -8617,8 +8669,16 @@ void SVNQ_ExecuteClientMessage (client_t *cl) case clcdp_ackframe: cl->delta_sequence = MSG_ReadLong(); - if (cl->delta_sequence == -1 && cl->pendingdeltabits) - cl->pendingdeltabits[0] = UF_REMOVE; + if (cl->delta_sequence == -1) + { + unsigned int e; + if (cl->pendingdeltabits) + cl->pendingdeltabits[0] = UF_REMOVE; + if (host_client->pendingcsqcbits) + for (e = 1; e < host_client->max_net_ents; e++) + if (host_client->pendingcsqcbits[e] & SENDFLAGS_PRESENT) + host_client->pendingcsqcbits[e] |= SENDFLAGS_USABLE; + } SV_AckEntityFrame(cl, cl->delta_sequence); // if (cl->frameunion.frames[cl->delta_sequence&UPDATE_MASK].sequence == cl->delta_sequence) // if (cl->frameunion.frames[cl->delta_sequence&UPDATE_MASK].ping_time < 0) diff --git a/engine/server/world.c b/engine/server/world.c index c4ec7d19..14d28b56 100644 --- a/engine/server/world.c +++ b/engine/server/world.c @@ -31,6 +31,8 @@ line of sight checks trace->crosscontent, but bullets don't */ +#define SOLID_ISTRIGGER(solid) ((solid)==SOLID_TRIGGER||(solid)==SOLID_BSPTRIGGER||(solid)==SOLID_LADDER) + size_t areagridsequence; //used to avoid poking the same ent twice. extern cvar_t sv_compatiblehulls; @@ -445,7 +447,7 @@ void World_TouchLinks (world_t *w, wedict_t *ent, areanode_t *node) if (touch == ent) continue; - if (!touch->v->touch || touch->v->solid != SOLID_TRIGGER) + if (!touch->v->touch || !SOLID_ISTRIGGER(touch->v->solid)) continue; if (ent->v->absmin[0] > touch->v->absmax[0] @@ -469,7 +471,7 @@ void World_TouchLinks (world_t *w, wedict_t *ent, areanode_t *node) //make sure nothing moved it away if (ED_ISFREE(touch)) continue; - if (!touch->v->touch || touch->v->solid != SOLID_TRIGGER) + if (!touch->v->touch || !SOLID_ISTRIGGER(touch->v->solid)) continue; if (ent->v->absmin[0] > touch->v->absmax[0] @@ -511,6 +513,7 @@ void QDECL World_LinkEdict (world_t *w, wedict_t *ent, qboolean touch_triggers) { vec_t *mins; vec_t *maxs; + int solid; #ifdef USEAREAGRID World_UnlinkEdict (ent); // unlink from old position @@ -549,7 +552,8 @@ void QDECL World_LinkEdict (world_t *w, wedict_t *ent, qboolean touch_triggers) } // set the abs box - if (ent->v->solid == SOLID_BSP && + solid = ent->v->solid; + if ((solid == SOLID_BSP||solid == SOLID_BSPTRIGGER) && (ent->v->angles[0] || ent->v->angles[1] || ent->v->angles[2]) ) { // expand for rotation #if 1 @@ -1186,7 +1190,7 @@ wedict_t *World_TestEntityPosition (world_t *w, wedict_t *ent) { trace_t trace; - trace = World_Move (w, ent->v->origin, ent->v->mins, ent->v->maxs, ent->v->origin, ((ent->v->solid == SOLID_NOT || ent->v->solid == SOLID_TRIGGER)?MOVE_NOMONSTERS:0), ent); + trace = World_Move (w, ent->v->origin, ent->v->mins, ent->v->maxs, ent->v->origin, ((ent->v->solid == SOLID_NOT || ent->v->solid == SOLID_TRIGGER || ent->v->solid == SOLID_BSPTRIGGER)?MOVE_NOMONSTERS:0), ent); if (trace.startsolid || trace.allsolid) return trace.ent?trace.ent:w->edicts; @@ -1268,7 +1272,7 @@ static trace_t World_ClipMoveToEntity (world_t *w, wedict_t *ent, vec3_t eorg, v framestate_t framestate; // get the clipping hull - if ((ent->v->solid == SOLID_BSP || ent->v->solid == SOLID_PORTAL) && mdlidx) + if ((ent->v->solid == SOLID_BSP || ent->v->solid == SOLID_BSPTRIGGER || ent->v->solid == SOLID_PORTAL) && mdlidx) { model = w->Get_CModel(w, mdlidx); if (!model || (model->type != mod_brush && model->type != mod_heightmap)) @@ -1309,7 +1313,7 @@ static trace_t World_ClipMoveToEntity (world_t *w, wedict_t *ent, vec3_t eorg, v trace.startsolid = false; hitmodel = false; } - else if (ent->v->solid != SOLID_BSP) + else if (ent->v->solid != SOLID_BSP && ent->v->solid != SOLID_BSPTRIGGER) { eang[0]*=r_meshpitch.value; //carmack made bsp models rotate wrongly. World_TransformedTrace(model, hullnum, &framestate, start, end, mins, maxs, capsule, &trace, eorg, eang, hitcontentsmask); @@ -1408,7 +1412,7 @@ int World_AreaEdicts (world_t *w, vec3_t mins, vec3_t maxs, wedict_t **list, int if (check->v->solid == SOLID_NOT) continue; // deactivated - if ((check->v->solid == SOLID_TRIGGER) != (areatype == AREA_TRIGGER)) + if ((check->v->solid == SOLID_TRIGGER||check->v->solid == SOLID_BSPTRIGGER) != (areatype == AREA_TRIGGER)) continue; } @@ -1449,7 +1453,7 @@ int World_AreaEdicts (world_t *w, vec3_t mins, vec3_t maxs, wedict_t **list, int if (check->v->solid == SOLID_NOT) continue; // deactivated - if ((check->v->solid == SOLID_TRIGGER) != (areatype == AREA_TRIGGER)) + if ((check->v->solid == SOLID_TRIGGER||check->v->solid == SOLID_BSPTRIGGER) != (areatype == AREA_TRIGGER)) continue; } @@ -1500,7 +1504,7 @@ static void World_AreaEdicts_r (areanode_t *node) continue; // deactivated /*q2 still has solid/trigger lists, emulate that here*/ - if ((check->v->solid == SOLID_TRIGGER) != (area_type == AREA_TRIGGER)) + if ((check->v->solid == SOLID_TRIGGER||check->v->solid == SOLID_BSPTRIGGER) != (area_type == AREA_TRIGGER)) continue; if (check->v->absmin[0] > area_maxs[0] @@ -1841,7 +1845,7 @@ static void World_ClipToEverything (world_t *w, moveclip_t *clip) continue; if (touch->v->solid == SOLID_NOT && !((int)touch->v->flags & FL_FINDABLE_NONSOLID)) continue; - if (touch->v->solid == SOLID_TRIGGER && !((int)touch->v->flags & FL_FINDABLE_NONSOLID)) + if ((touch->v->solid == SOLID_TRIGGER||touch->v->solid == SOLID_BSPTRIGGER) && !((int)touch->v->flags & FL_FINDABLE_NONSOLID)) continue; if (touch == clip->passedict) @@ -1919,7 +1923,7 @@ static void World_ClipToEverything (world_t *w, moveclip_t *clip) void World_TouchAllLinks (world_t *w, wedict_t *ent) { wedict_t *touchedicts[2048], *touch; - int num; + int num, solid; num = World_AreaEdicts(w, ent->v->absmin, ent->v->absmax, touchedicts, countof(touchedicts), AREA_TRIGGER); while (num-- > 0) { @@ -1928,7 +1932,8 @@ void World_TouchAllLinks (world_t *w, wedict_t *ent) //make sure nothing moved it away if (ED_ISFREE(touch)) continue; - if (!touch->v->touch || touch->v->solid != SOLID_TRIGGER) + solid =touch->v->solid; + if (!touch->v->touch || (solid!= SOLID_TRIGGER && solid!= SOLID_BSPTRIGGER)) continue; if (touch == ent) continue; @@ -1944,6 +1949,12 @@ void World_TouchAllLinks (world_t *w, wedict_t *ent) if (!((int)ent->xv->dimension_solid & (int)touch->xv->dimension_hit)) //didn't change did it?... continue; + if (solid == SOLID_BSPTRIGGER) + { + if (!World_ClipMoveToEntity(w, touch, touch->v->origin, touch->v->angles, ent->v->origin, ent->v->mins, ent->v->maxs, ent->v->origin, 0, false, (ent->xv->geomtype == GEOMTYPE_CAPSULE), MASK_WORLDSOLID).startsolid) + continue; + } + w->Event_Touch(w, touch, ent, NULL); if (ED_ISFREE(ent)) @@ -1985,7 +1996,7 @@ static void World_ClipToLinks (world_t *w, areagridlink_t *node, moveclip_t *cli continue; /*if its a trigger, we only clip against it if the flags are aligned*/ - if (touch->v->solid == SOLID_TRIGGER || touch->v->solid == SOLID_LADDER) + if (SOLID_ISTRIGGER(touch->v->solid)) { if (!(clip->type & MOVE_TRIGGERS)) continue; @@ -2218,7 +2229,7 @@ static void World_ClipToLinks (world_t *w, areanode_t *node, moveclip_t *clip) continue; /*if its a trigger, we only clip against it if the flags are aligned*/ - if (touch->v->solid == SOLID_TRIGGER || touch->v->solid == SOLID_LADDER) + if (SOLID_ISTRIGGER(touch->v->solid)) { if (!(clip->type & MOVE_TRIGGERS)) continue; @@ -2351,7 +2362,7 @@ static unsigned int World_ContentsOfLinks (world_t *w, areanode_t *node, vec3_t continue; /*if its a trigger, we only clip against it if the flags are aligned*/ - if (touch->v->solid == SOLID_TRIGGER) + if (touch->v->solid == SOLID_TRIGGER||touch->v->solid == SOLID_BSPTRIGGER) continue; if (pos[0] > touch->v->absmax[0] @@ -2723,7 +2734,7 @@ trace_t World_Move (world_t *w, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t e } else if (passedict->v->solid == SOLID_CORPSE) clip.hitcontentsmask = FTECONTENTS_SOLID|Q2CONTENTS_WINDOW | FTECONTENTS_BODY; //corpses ignore corpses - else if (passedict->v->solid == SOLID_TRIGGER) + else if (passedict->v->solid == SOLID_TRIGGER||passedict->v->solid == SOLID_BSPTRIGGER) clip.hitcontentsmask = FTECONTENTS_SOLID|Q2CONTENTS_WINDOW | FTECONTENTS_BODY; //triggers ignore corpses too, apparently else clip.hitcontentsmask = FTECONTENTS_SOLID|Q2CONTENTS_WINDOW | FTECONTENTS_BODY | FTECONTENTS_CORPSE; //regular projectiles. @@ -2834,7 +2845,7 @@ trace_t World_Move (world_t *w, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t e continue; if (touch == clip.passedict) continue; - if (touch->v->solid == SOLID_TRIGGER || touch->v->solid == SOLID_LADDER) + if (SOLID_ISTRIGGER(touch->v->solid)) { if (!(clip.type & MOVE_TRIGGERS)) continue;