From 3998821865f8c4e7176735de11dde08916d74f7c Mon Sep 17 00:00:00 2001 From: Spoike Date: Wed, 29 Oct 2014 05:03:03 +0000 Subject: [PATCH] Polished and documented the manifest support a little. Feels a bit more robust now. Try to avoid certain prediction issues with positions truncated into solid areas, that appears to happen on q3bsp and thus might happen elsewhere too. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4780 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/client.h | 1 - engine/client/renderer.c | 30 +--- engine/client/skin.c | 10 +- engine/client/snd_dma.c | 4 +- engine/common/common.h | 3 +- engine/common/fs.c | 288 +++++++++++++++++++++++++-------------- engine/common/fs_zip.c | 3 + engine/common/pmove.c | 2 +- specs/fte_manifests.txt | 120 ++++++++++++++++ 9 files changed, 325 insertions(+), 136 deletions(-) create mode 100644 specs/fte_manifests.txt diff --git a/engine/client/client.h b/engine/client/client.h index 8d1c615d5..6b01888bb 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -947,7 +947,6 @@ void CL_BeginServerConnect(int port); char *CL_TryingToConnect(void); void CL_ExecInitialConfigs(char *defaultexec); -ftemanifest_t *CL_Manifest_Parse(vfsfile_t *file, const char *defaultsourceurl); extern int cl_numvisedicts; extern int cl_maxvisedicts; diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 87208460b..2a862c468 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -1309,19 +1309,6 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n")); cl.model_precache[i] = Mod_ForName (cl.model_name[i], MLV_SILENT); else cl.model_precache[i] = Mod_FindName (Mod_FixName(cl.model_name[i], cl.model_name[1])); - - if ((!cl.model_precache[i] || cl.model_precache[i]->type == mod_dummy) && i == 1) - { - Con_Printf ("\nThe required model file '%s' could not be found.\n\n" - , cl.model_name[i]); - Con_Printf ("You may need to download or purchase a client " - "pack in order to play on this server.\n\n"); - CL_Disconnect (); -#ifdef VM_UI - UI_Reset(); -#endif - return false; - } } for (i=0; i < MAX_VWEP_MODELS; i++) @@ -1341,22 +1328,10 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n")); cl.model_csqcprecache[i] = NULL; TRACE(("dbg: R_ApplyRenderer: reloading csqc model %s\n", cl.model_csqcname[i])); cl.model_csqcprecache[i] = Mod_ForName (Mod_FixName(cl.model_csqcname[i], cl.model_name[1]), MLV_SILENT); - - if (!cl.model_csqcprecache[i]) - { - Con_Printf ("\nThe required model file '%s' could not be found.\n\n" - , cl.model_csqcname[i]); - Con_Printf ("You may need to download or purchase a client " - "pack in order to play on this server.\n\n"); - CL_Disconnect (); -#ifdef VM_UI - UI_Reset(); -#endif - return false; - } } #endif + //fixme: worldmodel could be ssqc or csqc. cl.worldmodel = cl.model_precache[1]; if (cl.worldmodel && cl.worldmodel->loadstate == MLS_LOADING) @@ -1365,6 +1340,9 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n")); TRACE(("dbg: R_ApplyRenderer: done the models\n")); if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED) { +// Con_Printf ("\nThe required model file '%s' could not be found.\n\n", cl.model_name[i]); +// Con_Printf ("You may need to download or purchase a client pack in order to play on this server.\n\n"); + CL_Disconnect (); #ifdef VM_UI UI_Reset(); diff --git a/engine/client/skin.c b/engine/client/skin.c index 70c5cac5b..b5549b214 100644 --- a/engine/client/skin.c +++ b/engine/client/skin.c @@ -339,8 +339,6 @@ void Skin_WorkerLoad(void *skinptr, void *data, size_t a, size_t b) } Skin_WorkerDone(skin, out, srcw, srch); - skin->loadstate = SKIN_LOADED; - } /* @@ -529,8 +527,12 @@ void Skin_FlushAll(void) { //wipe the skin info int i; for (i=0 ; ipackage) / sizeof(man->package[0]); i++) { Z_Free(man->package[i].path); + Z_Free(man->package[i].extractname); for (j = 0; j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++) Z_Free(man->package[i].mirrors[j]); } @@ -240,6 +242,8 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm) { if (oldm->package[i].path) newm->package[i].path = Z_StrDup(oldm->package[i].path); + if (oldm->package[i].extractname) + newm->package[i].extractname = Z_StrDup(oldm->package[i].extractname); for (j = 0; j < sizeof(newm->package[i].mirrors) / sizeof(newm->package[i].mirrors[0]); j++) if (oldm->package[i].mirrors[j]) newm->package[i].mirrors[j] = Z_StrDup(oldm->package[i].mirrors[j]); @@ -278,13 +282,17 @@ void FS_Manifest_Print(ftemanifest_t *man) { if (man->package[i].path) { + if (man->package[i].extractname) + Con_Printf("achived"); if (man->package[i].crcknown) Con_Printf("package %s 0x%x", COM_QuotedString(man->package[i].path, buffer, sizeof(buffer)), man->package[i].crc); else Con_Printf("package %s -", COM_QuotedString(man->package[i].path, buffer, sizeof(buffer))); + if (man->package[i].extractname) + Con_Printf(" %s", COM_QuotedString(man->package[i].extractname, buffer, sizeof(buffer))); for (j = 0; j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++) if (man->package[i].mirrors[j]) - Con_Printf(" \"%s\"", COM_QuotedString(man->package[i].mirrors[j], buffer, sizeof(buffer))); + Con_Printf(" %s", COM_QuotedString(man->package[i].mirrors[j], buffer, sizeof(buffer))); Con_Printf("\n"); } } @@ -300,6 +308,8 @@ static void FS_Manifest_PurgeGamedirs(ftemanifest_t *man) { Z_Free(man->gamepath[i].path); man->gamepath[i].path = NULL; + + //FIXME: remove packages from the removed paths. } } } @@ -394,16 +404,17 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) } } } - else if (!Q_strcasecmp(fname, "package")) + else if (!Q_strcasecmp(fname, "package") || !Q_strcasecmp(fname, "archivedpackage")) { qboolean crcknown; int crc; int i, j; + int arg = 1; - fname = Cmd_Argv(1); + fname = Cmd_Argv(arg++); - crcknown = (strcmp(Cmd_Argv(2), "-") && *Cmd_Argv(2)); - crc = strtoul(Cmd_Argv(2), NULL, 0); + crcknown = (strcmp(Cmd_Argv(arg), "-") && *Cmd_Argv(arg)); + crc = strtoul(Cmd_Argv(arg++), NULL, 0); for (i = 0; i < sizeof(man->package) / sizeof(man->package[0]); i++) { @@ -412,9 +423,13 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) man->package[i].path = Z_StrDup(fname); man->package[i].crcknown = crcknown; man->package[i].crc = crc; - for (j = 0; j < Cmd_Argc()-3 && j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++) + if (!Q_strcasecmp(fname, "archivedpackage")) + man->package[i].extractname = Z_StrDup(Cmd_Argv(arg++)); + else + man->package[i].extractname = NULL; + for (j = 0; arg+j < Cmd_Argc() && j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++) { - man->package[i].mirrors[j] = Z_StrDup(Cmd_Argv(3+j)); + man->package[i].mirrors[j] = Z_StrDup(Cmd_Argv(arg+j)); } break; } @@ -2215,24 +2230,54 @@ void COM_Gamedir (const char *dir) return; } - man = FS_Manifest_Clone(fs_manifest); - FS_Manifest_PurgeGamedirs(man); - if (*dir) + man = NULL; + if (!man) { - char token[MAX_QPATH]; - char *dup = Z_StrDup(dir); //FIXME: is this really needed? - dir = dup; - while ((dir = COM_ParseStringSet(dir, token, sizeof(token)))) + vfsfile_t *f = VFSOS_Open(va("%s%s.fmf", com_gamepath, dir), "rb"); + if (f) { - if (!strcmp(dir, ";")) - continue; - if (!*token) - continue; - - Cmd_TokenizeString(va("gamedir \"%s\"", token), false, false); - FS_Manifest_ParseTokens(man); + 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); + } + } + + if (!man) + { + //generate a new manifest based upon the current one. + man = FS_ReadDefaultManifest(com_gamepath, sizeof(com_gamepath), true); + if (man && strcmp(man->installation, fs_manifest->installation)) + { + FS_Manifest_Free(man); + man = NULL; + } + if (!man) + man = FS_Manifest_Clone(fs_manifest); + FS_Manifest_PurgeGamedirs(man); + if (*dir) + { + char token[MAX_QPATH]; + char *dup = Z_StrDup(dir); //FIXME: is this really needed? + dir = dup; + while ((dir = COM_ParseStringSet(dir, token, sizeof(token)))) + { + if (!strcmp(dir, ";")) + continue; + if (!*token) + continue; + + Cmd_TokenizeString(va("gamedir \"%s\"", token), false, false); + FS_Manifest_ParseTokens(man); + } + Z_Free(dup); } - Z_Free(dup); } FS_ChangeGame(man, cfg_reload_on_gamedir.ival); } @@ -2250,7 +2295,7 @@ void COM_Gamedir (const char *dir) #define Q2CFG "com_nogamedirnativecode 0\n" /*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/ #define Q3CFG "gl_overbright 2\nseta model sarge\nseta headmodel sarge\nseta handicap 100\ncom_nogamedirnativecode 0\n" -#define RMQCFG "sv_bigcoords 1\n" +//#define RMQCFG "sv_bigcoords 1\n" typedef struct { const char *argname; //used if this was used as a parameter. @@ -2282,13 +2327,13 @@ const gamemode_info_t gamemode_info[] = { //various quake-based mods. {"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"}, "Nexuiz"}, {"-xonotic", "xonotic", "Xonotic", {"xonotic.exe"}, NEXCFG, {"data", "*ftedata"}, "Xonotic"}, - {"-spark", "spark", "Spark", {"base/src/progs.src", - "base/qwprogs.dat", - "base/pak0.pak"}, DMFCFG, {"base", }, "Spark"}, - {"-scouts", "scouts", "FTE-SJ", {"basesj/src/progs.src", - "basesj/progs.dat", - "basesj/pak0.pak"}, NULL, {"basesj", }, "Scouts Journey"}, - {"-rmq", "rmq", "RMQ", {NULL}, RMQCFG, {"id1", "qw", "rmq", "*fte"}, "Remake Quake"}, +// {"-spark", "spark", "Spark", {"base/src/progs.src", +// "base/qwprogs.dat", +// "base/pak0.pak"}, DMFCFG, {"base", }, "Spark"}, +// {"-scouts", "scouts", "FTE-SJ", {"basesj/src/progs.src", +// "basesj/progs.dat", +// "basesj/pak0.pak"}, NULL, {"basesj", }, "Scouts Journey"}, +// {"-rmq", "rmq", "RMQ", {NULL}, RMQCFG, {"id1", "qw", "rmq", "*fte"}, "Remake Quake"}, #ifdef HEXEN2 //hexen2's mission pack generally takes precedence if both are installed. @@ -2305,21 +2350,22 @@ const gamemode_info_t gamemode_info[] = { #if defined(Q3CLIENT) || defined(Q3SERVER) {"-quake3", "q3", "FTE-Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena"}, //the rest are not supported in any real way. maps-only mostly, if that - {"-quake4", "q4", "FTE-Quake4", {"q4base/pak00.pk4"}, NULL, {"q4base", "*fteq4"}, "Quake 4"}, - {"-et", NULL, "FTE-EnemyTerritory", {"etmain/pak0.pk3"}, NULL, {"etmain", "*fteet"}, "Wolfenstein - Enemy Territory"}, +// {"-quake4", "q4", "FTE-Quake4", {"q4base/pak00.pk4"}, NULL, {"q4base", "*fteq4"}, "Quake 4"}, +// {"-et", NULL, "FTE-EnemyTerritory", {"etmain/pak0.pk3"}, NULL, {"etmain", "*fteet"}, "Wolfenstein - Enemy Territory"}, - {"-jk2", "jk2", "FTE-JK2", {"base/assets0.pk3"}, NULL, {"base", "*ftejk2"}, "Jedi Knight II: Jedi Outcast"}, - {"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"}, +// {"-jk2", "jk2", "FTE-JK2", {"base/assets0.pk3"}, NULL, {"base", "*ftejk2"}, "Jedi Knight II: Jedi Outcast"}, +// {"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"}, #endif #if !defined(QUAKETC) && !defined(MINIMAL) - {"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"}, "Doom"}, - {"-doom2", "doom2", "FTE-Doom2", {"doom2.wad"}, NULL, {"*", "*ftedoom"}, "Doom2"}, - {"-doom3", "doom3", "FTE-Doom3", {"doom3.wad"}, NULL, {"based3", "*ftedoom3"},"Doom3"}, +// {"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"}, "Doom"}, +// {"-doom2", "doom2", "FTE-Doom2", {"doom2.wad"}, NULL, {"*", "*ftedoom"}, "Doom2"}, +// {"-doom3", "doom3", "FTE-Doom3", {"doom3.wad"}, NULL, {"based3", "*ftedoom3"},"Doom3"}, //for the luls - {"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"}, - - //can run in windows, needs hl gamecode enabled. +// {"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"}, +#endif +#if defined(HLSERVER) || defined(HLCLIENT) + //can run in windows, needs hl gamecode enabled. maps can always be viewed, but meh. {"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, NULL, {"valve", "*ftehl"}, "Half-Life"}, #endif @@ -3316,10 +3362,8 @@ static int FS_IdentifyDefaultGame(char *newbase, int sizeof_newbase, qboolean fi return gamenum; } -static - //allowed to modify newbasedir if fixedbasedir isn't set -ftemanifest_t *FS_GenerateLegacyManifest(char *newbasedir, int sizeof_newbasedir, qboolean fixedbasedir, int game) +static ftemanifest_t *FS_GenerateLegacyManifest(char *newbasedir, int sizeof_newbasedir, qboolean fixedbasedir, int game) { int i; ftemanifest_t *man; @@ -3383,13 +3427,13 @@ ftemanifest_t *FS_GenerateLegacyManifest(char *newbasedir, int sizeof_newbasedir static char *FS_RelativeURL(char *base, char *file, char *buffer, int bufferlen) { //fixme: cope with windows paths - qboolean baseisurl = !!strchr(base, ':'); + qboolean baseisurl = base?!!strchr(base, ':'):false; qboolean fileisurl = !!strchr(file, ':'); //qboolean baseisabsolute = (*base == '/' || *base == '\\'); qboolean fileisabsolute = (*file == '/' || *file == '\\'); char *ebase; - if (fileisurl) + if (fileisurl || !base) return file; if (fileisabsolute) { @@ -3415,6 +3459,7 @@ static char *FS_RelativeURL(char *base, char *file, char *buffer, int bufferlen) #ifdef WEBCLIENT static struct dl_download *curpackagedownload; +static char fspdl_internalname[MAX_QPATH]; static char fspdl_temppath[MAX_OSPATH]; static char fspdl_finalpath[MAX_OSPATH]; static void FS_BeginNextPackageDownload(void); @@ -3431,9 +3476,60 @@ static void FS_PackageDownloaded(struct dl_download *dl) { //rename the file as needed. COM_CreatePath(fspdl_finalpath); - if (!Sys_Rename(fspdl_temppath, fspdl_finalpath)) + + if (*fspdl_internalname) //if zip... + { //archive + searchpathfuncs_t *archive = FSZIP_LoadArchive(VFSOS_Open(fspdl_temppath, "rb"), dl->url); + if (archive) + { + flocation_t loc; + int status = FF_NOTFOUND; +//FIXME: loop through all 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) + { + vfsfile_t *in = archive->OpenVFS(archive, &loc, "rb"); + if (in) + { + vfsfile_t *out = VFSOS_Open(fspdl_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); + } + VFS_CLOSE(in); + } + } + else + { + Con_Printf("Unable to find %s in %s\n", fspdl_internalname, fspdl_temppath); + } + archive->ClosePath(archive); + } + } + else { - Con_Printf("Unable to rename \"%s\" to \"%s\"\n", fspdl_temppath, fspdl_finalpath); + //direct file + if (!Sys_Rename(fspdl_temppath, fspdl_finalpath)) + { + Con_Printf("Unable to rename \"%s\" to \"%s\"\n", fspdl_temppath, fspdl_finalpath); + } } } Sys_remove (fspdl_temppath); @@ -3481,6 +3577,11 @@ static void FS_BeginNextPackageDownload(void) continue; } + if (man->package[j].extractname) + Q_strncpyz(fspdl_internalname, man->package[j].extractname, sizeof(fspdl_internalname)); + else + Q_strncpyz(fspdl_internalname, "", sizeof(fspdl_internalname)); + //figure out a temp name and figure out where we're going to get it from. FS_NativePath(buffer, FS_ROOT, fspdl_finalpath, sizeof(fspdl_finalpath)); if (!FS_GenCachedPakName(va("%s.tmp", man->package[j].path), crcstr, buffer, sizeof(buffer))) @@ -3615,6 +3716,42 @@ qboolean FS_FoundManifest(void *usr, ftemanifest_t *man) return false; } +//reads the default manifest based upon the basedir, the commandline arguments, the name of the exe, etc. +//may still fail if no game was identified. +//if fixedbasedir is true, stuff like -quake won't override/change the active basedir (ie: -basedir or gamedir switching without breaking gamedir) +ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedirsize, qboolean fixedbasedir) +{ + ftemanifest_t *man = NULL; + + vfsfile_t *f; +#ifdef BRANDING_NAME + f = VFSOS_Open(va("%s"STRINGIFY(BRANDING_NAME)".fmf", newbasedir), "rb"); + if (!f) +#endif + 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); + BZ_Free(fdata); + } + VFS_CLOSE(f); + } + + if (!man) + { + int game = FS_IdentifyDefaultGame(newbasedir, newbasedirsize, fixedbasedir); + if (game != -1) + man = FS_GenerateLegacyManifest(newbasedir, newbasedirsize, fixedbasedir, game); + } + return man; +} + //this is potentially unsafe. needs lots of testing. qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs) { @@ -3649,35 +3786,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs) if (fs_manifest) return false; - if (!man) - { - vfsfile_t *f; -#ifdef BRANDING_NAME - f = VFSOS_Open(va("%s"STRINGIFY(BRANDING_NAME)".fmf", newbasedir), "rb"); - if (!f) -#endif - 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); - BZ_Free(fdata); - } - VFS_CLOSE(f); - } - } - - if (!man) - { - int game = FS_IdentifyDefaultGame(newbasedir, sizeof(newbasedir), fixedbasedir); - if (game != -1) - man = FS_GenerateLegacyManifest(newbasedir, sizeof(newbasedir), fixedbasedir, game); - } + man = FS_ReadDefaultManifest(newbasedir, sizeof(newbasedir), fixedbasedir); if (!man && isDedicated) { //dedicated servers have no menu code, so just pick the first fmf we could find. @@ -4093,33 +4202,6 @@ void FS_ShowManifest_f(void) else Con_Printf("no manifest loaded...\n"); } - -int FS_ThreadTest(void*arg) -{ - vfsfile_t *f = NULL; - char startdata[256]; - char lastdata[256]; - while(!f) - { - if (Sys_LockMutex(fs_thread_mutex)) - { - f = FS_OpenVFS("gfx/pop.lmp", "rb", FS_GAME); - Sys_UnlockMutex(fs_thread_mutex); - } - } - VFS_READ(f, startdata, sizeof(startdata)); - VFS_CLOSE(f); - for(;;) - { - Sys_LockMutex(fs_thread_mutex); - f = FS_OpenVFS("gfx/pop.lmp", "rb", FS_GAME); - Sys_UnlockMutex(fs_thread_mutex); - VFS_READ(f, lastdata, sizeof(lastdata)); - VFS_CLOSE(f); - if (memcmp(startdata, lastdata, sizeof(lastdata))) - Con_Printf("FS_ThreadTest failed\n"); - } -} /* ================ COM_InitFilesystem diff --git a/engine/common/fs_zip.c b/engine/common/fs_zip.c index f719d287a..4308e25f1 100644 --- a/engine/common/fs_zip.c +++ b/engine/common/fs_zip.c @@ -1274,6 +1274,9 @@ searchpathfuncs_t *QDECL FSZIP_LoadArchive (vfsfile_t *packhandle, const char *d zipfile_t *zip; struct zipinfo info; + if (!packhandle) + return NULL; + zip = Z_Malloc(sizeof(zipfile_t)); Q_strncpyz(zip->filename, desc, sizeof(zip->filename)); zip->raw = packhandle; diff --git a/engine/common/pmove.c b/engine/common/pmove.c index 1fb2e1f24..903b45c33 100644 --- a/engine/common/pmove.c +++ b/engine/common/pmove.c @@ -863,7 +863,7 @@ void PM_CategorizePosition (void) else { trace = PM_PlayerTracePortals (pmove.origin, point, MASK_PLAYERSOLID, NULL); - if (trace.fraction == 1 || -DotProduct(pmove.gravitydir, trace.plane.normal) < MIN_STEP_NORMAL) + if (!trace.startsolid && (trace.fraction == 1 || -DotProduct(pmove.gravitydir, trace.plane.normal) < MIN_STEP_NORMAL)) pmove.onground = false; else { diff --git a/specs/fte_manifests.txt b/specs/fte_manifests.txt new file mode 100644 index 000000000..7aaed536f --- /dev/null +++ b/specs/fte_manifests.txt @@ -0,0 +1,120 @@ +Manifest files are small files that describe various attributes of the game, primarily branding and filesystem configuration for mods or standalone games. +Manifest files are useful for standalone games that need to distance themselves from other games using the same engine. They are also useful for auto-updating mods or for mod compilations where mods should not be downloaded up-front. + +FTE Manifest Files (.fmf) can either be invoked by opening them with FTE (set up a file association), or automatically via a default.fmf file in the path specified by the -basedir argument, or in the working directory (settable via shortcuts). A default.fmf file will override the engine's game determination routines completely. +These files are also supported by the Android port, where opening one will give an option to open with FTEDroid automatically (much like on a desktop OS), or in the webgl port with the url specified after a # in the url. +The gamedir command (eg: 'gamedir foo', or '-game foo' on the commandline) will attempt to load an fmf file with the specified name (ie: foo.fmf) from the current basedir. If the new manifest has a different 'game' value, the engine may ignore. + +Each line of a manifest file forms a single command. New lines denote new commands. Comments are C++-style // double forward slashes. + + +Example: + +//example manifest file for Sock's 'In The Shadows' stealth mod. Because why not? +FTEMANIFEST 1 +GAME quake +NAME "In The Shadows" +PROTOCOLNAME ITShadows +DEFAULTEXEC "" +//UPDATEURL "http://example.com/mods/its.fmf" +BASEGAME id1 +BASEGAME qw +BASEGAME *fte +GAMEDIR shadows +//it would be nice if we could specify pak0.pak and pak1.pak here, alas doing so would violate id's various copyrights. instead, hope the user has a copy already installed. +//this is a major pain for systems where there is no official installer (read: fte's webgl port, or android... or win64). +//its pak0 can be downloaded from sock's public server +ARCHIVEDPACKAGE shadows/pak0.pak "0xb1768147" "shadows/pak0.pak" "http://simonoc.com/files/maps/sp/its_demo_v1_1.zip" + + + + +Commands: + +FTEMANIFEST 1 +Specifies the actual version of the manifest file, and what the file actually is. Only valid as the first line. The version number is included for potential future use. Version 1 is the current version. + +GAME quake +This specifies the code-name of the game upon which your mod is based. +Standalone games should pick their own string to use here. +Mods for one of the games that FTE intrinsically understands may be automatically located via the system registry for an existing installation. This is useful when the fmf file has been downloaded and opened via a web browser. +Known game name (namely: quake, quake2, quake3, hexen2) may imply other settings if they were not otherwise specified. +It is NOT recommended to omit this, but if you do, 'quake' will be assumed. + +NAME "Example Quake Mod" +This specifies what the game should be referred to by various console prints etc. This should be the formal name of your mod. + +PROTOCOLNAME "foo" +This is how the game/mod should be identified to master servers. A unique value here helps prevent servers from other games being listed, as well as preventing other games from seeing this game/mod. +FIXME: engine support for this should be improved. + +DEFAULTEXEC "" +If specified, this is inserted before the game's normal default.cfg file, and can be used to override the engine's default cvar settings for mods where the default.cfg was originally written for a different engine (and thus omits certain settings). This should only be used for games where the default.cfg itself is not replacable. Standalone mods should not need to specify this, and should instead just put everything in the mod's default.cfg file. +For recognised games, the engine will automatically assume usable defaults, but its possible that a mod depends upon a setting that I overlooked. + +BASEGAME "id1" +Multiple basegame lines can be specified. These are the core subdirectories that should always be loaded, even if the gamedir command was used to load a different mod. +Standadlone games will typically specify only a single value. +Each additional basegame will be favoured in preference to the prior ones. +(note: 'basedir' refers to the path from which all game subdirs are found in, thus manifests use basegame to avoid ambiguity at the cost of consistancy) +A basedir with a leading * is a private directory that will not be reported to clients nor appear in server browsers. This is typically used on fte-specific game directories, so that other-engine clients will not get confused. + +GAMEDIR "mymod" +These act almost exactly like basegame, except they flushed and replaced if the gamedir command is used. Additionally these are never implied by the 'game' command. +These will always have a higher preference than any BASEGAME subdir. +There are arguments both for and against a mod specifying its subdir as a basegame or as just a gamedir. + +DISABLEHOMEDIR 1 +If non-zero, the game/mod will not attempt to use any directory within the user's homedir by default. This may cause issues if the base game was installed in some read-only system location like C:\Program Files\, and so should only be used for manually installed games (ones that contain just the exe+fmf file). + +UPDATEURL "http://example.com/foo.fmf" +This command gives the url from which to update the manifest file from. The updated manifest *MUST* contain the same url for the update to be accepted. Because of this, you should ensure that it is has a url that will be valid for as long as your mod will be relevent. +This replaces just the manifest, but because the manifest file specifies a list of packages that should be used, a new set of packages can be downloaded automatically, and this is the true power of the update url. + +PACKAGE "id1/pak0.pak" 0x4f069cac "http://mirror1.example.com/qsw106_pak0.pak" "http://mirror2.example.com/qsw106_pak0.pak" +WARNING: be sure to respect third-party copyrights. +This command, of which multiple can be specified, lists the packages which should automatically be downloaded. +The first name (in this case 'id1/pak0.pak') is the logical name of the package, including the gamedir in which it is to be found. The second argument is the CRC of the package's table of contents, and any additional arguments are alternative mirrors from which to download updated versions. The engine will randomize the order on load (for load balancing) and will try other mirrors if the any fails to provide a file. +Multiple versions of the same package may be installed. This is handled via the crc. If a mirror provides a file who's crc does not match, the download will be ignored and another mirror will be tried. +A CRC of '-' can be used if you really do not want to provide crcs. This suppresses all crc checks, and so is not recommended (if you instead use a different filename for each version, this is not always an issue). The CRC can be determined by using the wrong crc and fixing it up to the correct value that the engine claims that it should be. +If the user already has an actual id1/pak0.pak installed (ie: a prior install of quake), then an updated version will not be downloaded even if their crc does not match. Otherwise the downloaded file will be placed inside a 'dlcache' directory, with a crc postfix, allowing multiple versions. +If you are directly serving pk3 files, it is recommended to just compress the pk3. +If you are directly serving pak files, you will likely wish to ensure that gzip transfer compression is enabled on your mirrors for the files in question. +Its recommended to place pk3 files directly on your mirrors as they are already compressed, which keeps things consistant and avoids stalls. However, if you use a single installation zip that you wish to reuse with manifest files, you can use ARCHIVEDPACKAGE instead. + +ARCHIVEDPACKAGE "id1/pak0.pak" 0x4f069cac "id1/pak0.pak" "http://mirror1.example.com/qsw106.zip" "http://mirror2.example.com/qsw106.zip" +Mostly identical to the PACKAGE command, but with an extra argument before the mirror list. +The difference is that if the file is downloaded, it is assumed to be a zip archive from which to extract the package. The extra/third argument is the filename that is read from the zip. +This command is provided for compatibility with existing installation media from third parties who do not themselves support manifest files. + +MINVER FTE 4778 +The manifest should not be used if the engine parsing it is a lower version than this. There is no default limit. +The first arg (ie: 'fte' in this case) specifies that the limit is specific to a specific fork or reimplementation of the file format. +The second arg should be an svn revision number (visible from the 'version' console command). +When updating a manifest, the engine will ignore the new manifest if its minver is too high. This can be used to avoid breaking installations if the user tries running the mod with too old an engine. + +MAXVER FTE 4778 +The manifest should not be used if the engine parsing it is a higher version than this. See minver for argument info. +I'm not sure how useful this will actually be, but it is included for completeness. + + +Console Commands: +/gamedir foo +This console command will read foo.fmf from the current basedir and load that instead. The basedir will not be changed to match the installation path of the specified game. +If there was no fmf file with that name, it will load the default.fmf file from that basedir instead, or try to guess which game is installed in the current basedir instead. From this default.fmf file, the gamedir attributes will be stripped and 'GAMEDIR foo' will be inserted internally. +/fs_showmanifest +This console command displays the effective/active manifest file that is currently in effect. +/fs_changegame +This command recognises various game names and will switch the basedir in order to try loading it (the game may be found via a prompt for the game's basedir, via a cached result of that prompt, or via known registry keys from that game's installer). +If the specified game is not recognised, the argument will be assumed to be a manifest named via a url or a system path. + +Possible Future Direction: +ARCHIVEDPACKAGE should support multiple files without redownloading. +ARCHIVEDPACKAGE needs some way to not care about crcs/filenames, and extract+use all included files. May be better to just decompress the zip and directly use it with a path prefix. +Uninstallation/auto-pruning. +Extra polish. +Multiple mods specified within a single fmf. +Individual files (ie: maps) that can be downloaded on demand? +Other stuff. +Scan for a new manifest instead of merely switching gamedirs. +Readme/license/credits info is needed. \ No newline at end of file