From 4d351b60f39650fc429a8006fe0b5bb6e1e45694 Mon Sep 17 00:00:00 2001 From: Spoike Date: Thu, 27 May 2021 11:34:15 +0000 Subject: [PATCH] Add SV_PerformSave/SV_PerformLoad entrypoints for QC-controlled saved games, at Eukara's request. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5872 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/common/bothdefs.h | 6 +- engine/common/pr_bgcmd.c | 83 ++++++++++++++- engine/common/pr_common.h | 4 + engine/qclib/initlib.c | 1 + engine/qclib/pr_edict.c | 106 +++++++++---------- engine/qclib/progsint.h | 1 + engine/qclib/progslib.h | 3 +- engine/server/pr_cmds.c | 4 + engine/server/savegame.c | 210 ++++++++++++++++++++++++++++---------- engine/server/sv_main.c | 41 ++++---- 10 files changed, 326 insertions(+), 133 deletions(-) diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index 224e65739..e983bd061 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -1163,9 +1163,9 @@ STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION = 255, // DP #define SAVEGAME_VERSION_QW 6 //actually zQuake, but the functional difference is that its qw instead of nq. #define SAVEGAME_VERSION_FTE_LEG 667 //found in .sav files. this is for legacy-like saved games with multiple players. #define SAVEGAME_VERSION_FTE_HUB 25000 //found in .fsv files. includes svs.gametype, so bumps should be large. -#define CACHEGAME_VERSION_OLD 513 -#define CACHEGAME_VERSION_VERBOSE 514 -#define CACHEGAME_VERSION_BINARY 515 +#define CACHEGAME_VERSION_OLD 513 //lame ordering. +#define CACHEGAME_VERSION_VERBOSE 514 //saved fields got names, making it more extensible. +#define CACHEGAME_VERSION_MODSAVED 515 //qc is responsible for saving all they need to, and restoring it after. #define PM_DEFAULTSTEPHEIGHT 18 diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index 76ec3793c..de739c9ff 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -2435,7 +2435,56 @@ void QCBUILTIN PF_fopen (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals G_FLOAT(OFS_RETURN) = -1; break; } - } +} + +//non-builtin function used by saved game code. +int PR_QCFile_From_VFS (pubprogfuncs_t *prinst, const char *name, vfsfile_t *f, qboolean write) +{ + int i; + + for (i = 0; i < MAX_QC_FILES; i++) + if (!pf_fopen_files[i].prinst) + break; + if (i == MAX_QC_FILES) //too many already open + return -1; + + pf_fopen_files[i].accessmode = write?FRIK_FILE_STREAM:FRIK_FILE_READ_DELAY; + + Q_strncpyz(pf_fopen_files[i].name, name, sizeof(pf_fopen_files[i].name)); + pf_fopen_files[i].file = f; + + pf_fopen_files[i].ofs = VFS_TELL(pf_fopen_files[i].file); + if (pf_fopen_files[i].file) + { + pf_fopen_files[i].len = VFS_GETLEN(pf_fopen_files[i].file); + + pf_fopen_files[i].prinst = prinst; + return i + FIRST_QC_FILE_INDEX; + } + else + return -1; +} +int PR_QCFile_From_Buffer (pubprogfuncs_t *prinst, const char *name, void *buffer, size_t ofs, size_t len) +{ + int i; + + for (i = 0; i < MAX_QC_FILES; i++) + if (!pf_fopen_files[i].prinst) + break; + if (i == MAX_QC_FILES) //too many already open + return -1; + + pf_fopen_files[i].accessmode = FRIK_FILE_READ; + + Q_strncpyz(pf_fopen_files[i].name, name, sizeof(pf_fopen_files[i].name)); + pf_fopen_files[i].file = NULL; + pf_fopen_files[i].data = (void*)buffer; + pf_fopen_files[i].ofs = ofs; + pf_fopen_files[i].len = len; + + pf_fopen_files[i].prinst = prinst; + return i + FIRST_QC_FILE_INDEX; +} //internal function used by search_begin static int PF_fopen_search (pubprogfuncs_t *prinst, const char *name, flocation_t *loc) @@ -3014,6 +3063,15 @@ void QCBUILTIN PF_rmtree (pubprogfuncs_t *prinst, struct globalvars_s *pr_global { const char *fname = PR_GetStringOfs(prinst, OFS_PARM0); Con_Printf("rmtree(\"%s\"): rmtree is not implemented at this\n", fname); + + /*flocation_t loc; + G_FLOAT(OFS_RETURN) = -1; //error + if (FS_FLocateFile(fname, FSLF_IGNORELINKS|FSLF_DONTREFERENCE, &loc)) //find the right gamedir for it... + { + fname = va("%s/", fname); //its meant to be a directory, make sure that's explicit + if (FS_RemoveTree(loc.search->handle, fname)) + G_FLOAT(OFS_RETURN) = 0; + }*/ } //DP_QC_WHICHPACK @@ -3737,10 +3795,27 @@ void QCBUILTIN PF_Spawn (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals void QCBUILTIN PF_spawn_object (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { - int obj = G_INT(OFS_PARM0); - int size = G_INT(OFS_PARM1); + int size = G_INT(OFS_PARM0); struct edict_s *ed; - ed = ED_Alloc(prinst, obj, size); + if (prinst->callargc > 1) + { + int idx = G_INT(OFS_PARM1); + ed = EDICT_NUM_UB(prinst, idx); + G_FLOAT(OFS_PARM2) = (ed && ed->ereftype == ER_ENTITY); + ed = prinst->EntAllocIndex(prinst, idx, true, size); + } + else + ed = ED_Alloc(prinst, true, size); + pr_globals = PR_globals(prinst, PR_CURRENT); + RETURN_EDICT(prinst, ed); +} + +void QCBUILTIN PF_respawnedict (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + int idx = G_FLOAT(OFS_PARM0); + struct edict_s *ed = EDICT_NUM_UB(prinst, idx); + G_FLOAT(OFS_PARM1) = (ed && ed->ereftype == ER_ENTITY); + ed = prinst->EntAllocIndex(prinst, idx, false, 0); pr_globals = PR_globals(prinst, PR_CURRENT); RETURN_EDICT(prinst, ed); } diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index e2966e199..f80b48b3b 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -151,6 +151,8 @@ void QCBUILTIN PF_stov (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) void QCBUILTIN PF_strzone(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_strunzone(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_Spawn (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_spawn_object (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +void QCBUILTIN PF_respawnedict (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_entityprotection (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_copyentity (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_droptofloor (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); @@ -364,6 +366,8 @@ void QCBUILTIN PF_randomvec (pubprogfuncs_t *prinst, struct globalvars_s *pr_glo void QCBUILTIN PF_strreplace (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_strireplace (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_randomvector (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); +int PR_QCFile_From_VFS (pubprogfuncs_t *prinst, const char *name, vfsfile_t *f, qboolean write); +int PR_QCFile_From_Buffer (pubprogfuncs_t *prinst, const char *name, void *buffer, size_t offset, size_t len); void QCBUILTIN PF_fopen (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); void QCBUILTIN PF_fcopy (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals); diff --git a/engine/qclib/initlib.c b/engine/qclib/initlib.c index eec02b81a..bc31e7bac 100644 --- a/engine/qclib/initlib.c +++ b/engine/qclib/initlib.c @@ -1565,6 +1565,7 @@ static pubprogfuncs_t deffuncs = { PR_RunError, ED_Print, ED_Alloc, + ED_AllocIndex, ED_Free, QC_EDICT_NUM, diff --git a/engine/qclib/pr_edict.c b/engine/qclib/pr_edict.c index 6b067f6e9..efeec12b5 100644 --- a/engine/qclib/pr_edict.c +++ b/engine/qclib/pr_edict.c @@ -32,10 +32,34 @@ void PDECL QC_ClearEdict (pubprogfuncs_t *ppf, struct edict_s *ed) e->entnum = num; } -edictrun_t *ED_AllocIntoTable (progfuncs_t *progfuncs, int num, pbool object, unsigned int fields_size) +struct edict_s *PDECL ED_AllocIndex (pubprogfuncs_t *ppf, unsigned int num, pbool object, size_t extrasize) { + progfuncs_t *progfuncs = (progfuncs_t*)ppf; edictrun_t *e; + unsigned int fields_size; + + if (num >= prinst.maxedicts) + { + externs->Sys_Error ("ED_AllocIndex: index %u exceeds limit of %u", num, prinst.maxedicts); + return NULL; + } + if (!object) + { + while(sv_num_edicts < num) + { //fill in any holes + e = (edictrun_t*)EDICT_NUM(progfuncs, sv_num_edicts); + if (!e) + { + e = (edictrun_t*)ED_AllocIndex(&progfuncs->funcs, sv_num_edicts, object, extrasize); + e->ereftype=ER_FREE; + } + sv_num_edicts++; + } + if (num >= sv_num_edicts) + sv_num_edicts=num+1; + } + e = prinst.edicttable[num]; if (!e) { @@ -43,6 +67,9 @@ edictrun_t *ED_AllocIntoTable (progfuncs_t *progfuncs, int num, pbool object, un prinst.edicttable[num] = e; memset(e, 0, externs->edictsize); } + + fields_size = object?0:prinst.fields_size; + fields_size += extrasize; if (e->fieldsize != fields_size) { if (e->fields) @@ -58,7 +85,10 @@ edictrun_t *ED_AllocIntoTable (progfuncs_t *progfuncs, int num, pbool object, un memset (e->fields, 0, e->fieldsize); e->ereftype = object?ER_OBJECT:ER_ENTITY; - return e; + + if (externs->entspawn) + externs->entspawn((struct edict_s *) e, false); + return (struct edict_s*)e; } /* @@ -77,27 +107,18 @@ struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *ppf, pbool object, size_t extras progfuncs_t *progfuncs = (progfuncs_t*)ppf; unsigned int i; edictrun_t *e; - unsigned int fields_size; - - fields_size = object?0:prinst.fields_size; - fields_size += extrasize; if (object) { - //objects are allocated at the end. + //objects are allocated at the end (won't be networked, so this reduces issues with users on old protocols). + //also they're potentially higher than num_edicts, which is handy. for ( i=prinst.maxedicts-1 ; i>0 ; i--) { e = (edictrun_t*)EDICT_NUM(progfuncs, i); // the first couple seconds of server time can involve a lot of // freeing and allocating, so relax the replacement policy if (!e || (e->ereftype==ER_FREE && ( e->freetime < 2 || *externs->gametime - e->freetime > 0.5 ) )) - { - e = ED_AllocIntoTable(progfuncs, i, object, fields_size); - - if (externs->entspawn) - externs->entspawn((struct edict_s *) e, false); - return (struct edict_s *)e; - } + return ED_AllocIndex(&progfuncs->funcs, i, object, extrasize); } externs->Sys_Error ("ED_Alloc: no free edicts (max is %i)", prinst.maxedicts); } @@ -111,13 +132,7 @@ struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *ppf, pbool object, size_t extras // the first couple seconds of server time can involve a lot of // freeing and allocating, so relax the replacement policy if (!e || (e->ereftype==ER_FREE && ( e->freetime < 2 || *externs->gametime - e->freetime > 0.5 ) )) - { - e = ED_AllocIntoTable(progfuncs, i, object, fields_size); - - if (externs->entspawn) - externs->entspawn((struct edict_s *) e, false); - return (struct edict_s *)e; - } + return ED_AllocIndex(&progfuncs->funcs, i, object, extrasize); } if (i >= prinst.maxedicts-1) //try again, but use timed out ents. @@ -128,13 +143,7 @@ struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *ppf, pbool object, size_t extras // the first couple seconds of server time can involve a lot of // freeing and allocating, so relax the replacement policy if (!e || (e->ereftype==ER_FREE)) - { - e = ED_AllocIntoTable(progfuncs, i, object, fields_size); - - if (externs->entspawn) - externs->entspawn((struct edict_s *) e, false); - return (struct edict_s *)e; - } + return ED_AllocIndex(&progfuncs->funcs, i, object, extrasize); } if (i >= prinst.maxedicts-2) @@ -151,25 +160,7 @@ struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *ppf, pbool object, size_t extras } } - while(sv_num_edicts < i) - { - e = (edictrun_t*)EDICT_NUM(progfuncs, sv_num_edicts); - if (!e) - { - e = ED_AllocIntoTable(progfuncs, sv_num_edicts, object, fields_size); - if (externs->entspawn) - externs->entspawn((struct edict_s *) e, false); - e->ereftype=ER_FREE; - } - sv_num_edicts++; - } - sv_num_edicts++; - - e = ED_AllocIntoTable(progfuncs, i, object, fields_size); - if (externs->entspawn) - externs->entspawn((struct edict_s *) e, false); - - return (struct edict_s *)e; + return ED_AllocIndex(&progfuncs->funcs, i, object, extrasize); } /* @@ -2137,7 +2128,7 @@ int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PD if (!ed) { - ed = ED_AllocIntoTable(progfuncs, num, false, prinst.fields_size); + ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, num, false, 0); ed->ereftype = ER_FREE; if (externs->entspawn) externs->entspawn((struct edict_s *) ed, true); @@ -2160,7 +2151,7 @@ int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PD if (!ed) { externs->Sys_Error("Edict was not allocated\n"); - ed = ED_AllocIntoTable(progfuncs, num, false, prinst.fields_size); + ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, num, false, 0); } } ed->ereftype = ER_ENTITY; @@ -2226,7 +2217,7 @@ int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PD if (!ed) { - ed = ED_AllocIntoTable(progfuncs, num, false, prinst.fields_size); + ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, num, false, 0); ed->ereftype = ER_FREE; } @@ -2403,7 +2394,7 @@ int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PD } else { - ed = ED_AllocIntoTable(progfuncs, numents, false, prinst.fields_size); + ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, numents, false, 0); if (externs->entspawn) externs->entspawn((struct edict_s *) ed, true); @@ -2426,7 +2417,7 @@ int PDECL PR_LoadEnts(pubprogfuncs_t *ppf, const char *file, void *ctx, void (PD if (!ed) { - ed = ED_AllocIntoTable(progfuncs, num, false, prinst.fields_size); + ed = (edictrun_t *)ED_AllocIndex(&progfuncs->funcs, num, false, 0); ed->ereftype = ER_FREE; } } @@ -2563,7 +2554,10 @@ struct edict_s *PDECL PR_RestoreEnt (pubprogfuncs_t *ppf, const char *buf, size_ return NULL; if (strcmp(qcc_token, "{")) - externs->Sys_Error("Restore Ent with no opening brace"); + { + externs->Printf("PR_RestoreEnt: with no opening brace"); + return NULL; + } if (!ed) ent = (edictrun_t *)ED_Alloc(&progfuncs->funcs, false, 0); @@ -2571,7 +2565,13 @@ struct edict_s *PDECL PR_RestoreEnt (pubprogfuncs_t *ppf, const char *buf, size_ ent = (edictrun_t *)ed; if (ent->ereftype == ER_FREE && externs->entspawn) + { + memset (ent->fields, 0, ent->fieldsize); + ent->ereftype = ER_ENTITY; externs->entspawn((struct edict_s *) ent, false); + } + if (ent->ereftype != ER_ENTITY) + return NULL; //not allowed to spawn it into that ent. buf = ED_ParseEdict(progfuncs, buf, ent, &maphacks); diff --git a/engine/qclib/progsint.h b/engine/qclib/progsint.h index 2ad8decd0..765b4d6a7 100644 --- a/engine/qclib/progsint.h +++ b/engine/qclib/progsint.h @@ -423,6 +423,7 @@ void *PRHunkAlloc(progfuncs_t *progfuncs, int ammount, const char *name); void PR_Profile_f (void); struct edict_s *PDECL ED_Alloc (pubprogfuncs_t *progfuncs, pbool object, size_t extrasize); +struct edict_s *PDECL ED_AllocIndex (pubprogfuncs_t *progfuncs, unsigned int num, pbool object, size_t extrasize); void PDECL ED_Free (pubprogfuncs_t *progfuncs, struct edict_s *ed, pbool instant); #ifdef QCGC diff --git a/engine/qclib/progslib.h b/engine/qclib/progslib.h index 2cc1a7ad5..69ef0b01e 100644 --- a/engine/qclib/progslib.h +++ b/engine/qclib/progslib.h @@ -132,7 +132,8 @@ struct pubprogfuncs_s void (VARGS *RunError) (pubprogfuncs_t *prinst, const char *msg, ...) LIKEPRINTF(2); //builtins call this to say there was a problem void (PDECL *PrintEdict) (pubprogfuncs_t *prinst, struct edict_s *ed); //get a listing of all vars on an edict (sent back via 'print') - struct edict_s *(PDECL *EntAlloc) (pubprogfuncs_t *prinst, pbool object, size_t extrasize); + struct edict_s *(PDECL *EntAlloc) (pubprogfuncs_t *prinst, pbool object, size_t extrasize); //allocate a random index. + struct edict_s *(PDECL *EntAllocIndex) (pubprogfuncs_t *prinst, unsigned int idx, pbool object, size_t extrasize); //allocate a specific index. void (PDECL *EntFree) (pubprogfuncs_t *prinst, struct edict_s *ed, pbool instant); struct edict_s *(PDECL *EdictNum) (pubprogfuncs_t *prinst, unsigned int n); //get the nth edict diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 3e364d0d2..fd2d232aa 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -11484,6 +11484,8 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"clipboard_get", PF_Fixme, 0, 0, 0, 0, D("void(int cliptype)", "Attempts to query the system clipboard. Any pasted text will be returned via Menu_InputEvent")}, {"clipboard_set", PF_Fixme, 0, 0, 0, 0, D("void(int cliptype, string text)", "Changes the system clipboard to the specified text.")}, + {"respawnedict", PF_respawnedict, 0, 0, 0, 0, D("entity(float entnum, optional __out float wasspawned)", "Acts like edict_num returning a specific entity number, but also marks it as spawned. If it was previously spawned then all of its prior field data will be LOST (you may wish to use wasfreed(edict_num(idx)) to check.")}, +// {"spawn_object", PF_spawn_object, 0, 0, 0, 0, D("object(int objectsize, optional float entnum, optional __out float wasspawned)", "Spawns a new object with the specified size, normally called via qcc intrinsics.")}, //end fte extras //DP extras @@ -12799,6 +12801,8 @@ void PR_DumpPlatform_f(void) {"SV_RunClientCommand", "void()", QW|NQ, "Called each time a player movement packet was received from a client. Self is set to the player entity which should be updated, while the input_* globals specify the various properties stored within the input packet. The contents of this function should be somewaht identical to the equivelent function in CSQC, or prediction misses will occur. If you're feeling lazy, you can simply call 'runstandardplayerphysics' after modifying the inputs."}, {"SV_AddDebugPolygons", "void()", QW|NQ, "Called each video frame. This is the only place where ssqc is allowed to call the R_BeginPolygon/R_PolygonVertex/R_EndPolygon builtins. This is exclusively for debugging, and will break in anything but single player as it will not be called if the engine is not running both a client and a server."}, {"SV_PlayerPhysics", "DEP_CSQC void()", QW|NQ, "Compatibility method to tweak player input that does not reliably work with prediction (prediction WILL break). Mods that care about prediction should use SV_RunClientCommand instead. If pr_no_playerphysics is set to 1, this function will never be called, which will either fix prediction or completely break player movement depending on whether the feature was even useful."}, + {"SV_PerformSave", "void(float fd, float entcount, float playerslots)", QW|NQ, "Called by the engine as part of saved games. The QC is responsible for writing any data that will be required to restore the game. Save files are not limited to just text, you can use fwrite (and later fread) for binary data. Remember to close the passed file."}, + {"SV_PerformLoad", "void(float fd, float entcount, float playerslots)", QW|NQ, "Called by the engine to restore a saved game that was saved via SV_PerformSave, the server will have already started the game to its inital state (for precaches+makestatic calls), so entities+globals will have their post-spawn values (you will likely want to remove most of them, you can use the two extra args as helpers for that). You can use respawn_edict to un-remove specific entities, with parseentitydata or putentityfieldstring(findentityfield(NAME), ent, value) to assign to fields by name strings. Don't expect bprints/etc to work - players are likely in a pending state. Remember to close the passed file."}, {"EndFrame", "void()", QW|NQ, "Called after non-player entities have been run at the end of the physics frame. Player physics is performed out of order and can/will still occur between EndFrame and BeginFrame."}, {"SV_CheckRejectConnection","string(string addr, string uinfo, string features) ", QW|NQ, "Called to give the mod a chance to ignore connection requests based upon client protocol support or other properties. Use infoget to read the uinfo and features arguments."}, #ifdef HEXEN2 diff --git a/engine/server/savegame.c b/engine/server/savegame.c index a1502cc99..3e72f44f1 100644 --- a/engine/server/savegame.c +++ b/engine/server/savegame.c @@ -583,7 +583,7 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * VFS_GETS(f, str, sizeof(str)); version = atoi(str); - if (version != CACHEGAME_VERSION_OLD && version != CACHEGAME_VERSION_VERBOSE) + if (version != CACHEGAME_VERSION_OLD && version != CACHEGAME_VERSION_VERBOSE && version != CACHEGAME_VERSION_MODSAVED) { VFS_CLOSE (f); Con_TPrintf ("Savegame is version %i, not %i\n", version, CACHEGAME_VERSION_DEFAULT); @@ -664,6 +664,7 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * sv.time = time; if (svs.gametype != gametype) { + VFS_CLOSE (f); Con_Printf("Incorrect gamecode type. Cannot load game.\n"); return false; } @@ -674,6 +675,77 @@ qboolean SV_LoadLevelCache(const char *savename, const char *level, const char * return false; } + if (version == CACHEGAME_VERSION_MODSAVED) + { + const char *line; + void *pr_globals = PR_globals(svprogfuncs, PR_CURRENT); + func_t restorefunc = PR_FindFunction(svprogfuncs, "SV_PerformLoad", PR_ANY); + com_tokentype_t tt; + if (!restorefunc) + { + VFS_CLOSE (f); + Con_TPrintf ("SV_PerformLoad missing, unable to load game\n"); + return false; + } + + //shove it into a buffer to make the next bit easier. + filepos = VFS_TELL(f); + filelen = VFS_GETLEN(f); + filelen -= filepos; + file = BZ_Malloc(filelen+1); + memset(file, 0, filelen+1); + filelen = VFS_READ(f, file, filelen); + VFS_CLOSE (f); + if ((int)filelen < 0) + filelen = 0; + file[filelen]='\0'; + + //look for possible engine commands followed by a 'moddata' line (with no args) + line = file; + while(line && *line) + { + if (SV_ExtendedSaveData(svprogfuncs, NULL, &line)) + continue; + line = COM_ParseTokenOut(line, NULL, com_token, sizeof(com_token), &tt); + if (!strcmp(com_token, "moddata")) + { + //loop till end of line + while (line && tt != TTP_LINEENDING) + line = COM_ParseTokenOut(line, NULL, com_token, sizeof(com_token), &tt); + break; //terminates the postamble + } + + //loop till end of line + while (line && tt != TTP_LINEENDING) + line = COM_ParseTokenOut(line, NULL, com_token, sizeof(com_token), &tt); + } + if (!line) + { + BZ_Free(file); + Con_TPrintf ("unsupported saved game\n"); + return false; + } + +// for(i = sv.allocated_client_slots+1; i < sv.world.num_edicts; i++) +// svprogfuncs->EntFree (svprogfuncs, EDICT_NUM_PB(svprogfuncs, i), false); + + //and now we can stomp on everything. yay. + sv.world.edicts[0].readonly = false; + G_FLOAT(OFS_PARM0) = PR_QCFile_From_Buffer(svprogfuncs, name, file, line-file, filelen); + G_FLOAT(OFS_PARM1) = sv.world.num_edicts; + G_FLOAT(OFS_PARM2) = sv.allocated_client_slots; + PR_ExecuteProgram(svprogfuncs, restorefunc); + sv.world.edicts[0].readonly = true; + + //in case they forgot to setorigin everything after blindly loading its fields... + World_ClearWorld (&sv.world, true); + + //let time jump to match whatever time the mod wanted. + sv.time = sv.world.physicstime = pr_global_struct->time = time; + sv.starttime = Sys_DoubleTime() - sv.time; + return true; + } + // load the edicts out of the savegame file // the rest of the file is sent directly to the progs engine. @@ -853,6 +925,7 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) char comment[SAVEGAME_COMMENT_LENGTH+1]; levelcache_t *cache; int version = CACHEGAME_VERSION_DEFAULT; + func_t func; if (!sv.state) return; @@ -953,6 +1026,10 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) } #endif + func = PR_FindFunction(svprogfuncs, "SV_PerformSave", PR_ANY); + if (func) + version = CACHEGAME_VERSION_MODSAVED; + f = FS_OpenVFS (name, "wbp", FS_GAMEONLY); if (!f) { @@ -1039,60 +1116,75 @@ void SV_SaveLevelCache(const char *savedir, qboolean dontharmgame) VFS_PRINTF (f,"\n"); } - if (version >= CACHEGAME_VERSION_BINARY) + if (version == CACHEGAME_VERSION_MODSAVED) { - VFS_PUTS(f, va("%i\n", svprogfuncs->stringtablesize)); - VFS_WRITE(f, svprogfuncs->stringtable, svprogfuncs->stringtablesize); + struct globalvars_s *pr_globals = PR_globals(svprogfuncs, PR_CURRENT); + func_t func = PR_FindFunction(svprogfuncs, "SV_PerformSave", PR_ANY); + + //FIXME: save precaches here. + VFS_PRINTF (f, "moddata\n"); + G_FLOAT(OFS_PARM0) = PR_QCFile_From_VFS(svprogfuncs, name, f, true); + G_FLOAT(OFS_PARM1) = sv.world.num_edicts; + G_FLOAT(OFS_PARM2) = sv.allocated_client_slots; + PR_ExecuteProgram(svprogfuncs, func); } else { - s = PR_SaveEnts(svprogfuncs, NULL, &len, 0, 1); - VFS_PUTS(f, s); - VFS_PUTS(f, "\n"); - svprogfuncs->parms->memfree(s); - } + /*if (version >= CACHEGAME_VERSION_BINARY) + { + VFS_PUTS(f, va("%i\n", svprogfuncs->stringtablesize)); + VFS_WRITE(f, svprogfuncs->stringtable, svprogfuncs->stringtablesize); + } + else*/ + { + s = PR_SaveEnts(svprogfuncs, NULL, &len, 0, 1); + VFS_PUTS(f, s); + VFS_PUTS(f, "\n"); + svprogfuncs->parms->memfree(s); + } - if (version >= CACHEGAME_VERSION_VERBOSE) - { - char buf[8192]; - for (i=0 ; i= CACHEGAME_VERSION_VERBOSE) + { + char buf[8192]; + for (i=0 ; i= CACHEGAME_VERSION_BINARY); + PR_Common_SaveGame(f, svprogfuncs, false);//, version >= CACHEGAME_VERSION_BINARY); - //FIXME: string buffers - //FIXME: hash tables - //FIXME: skeletal objects? - //FIXME: static entities - //FIXME: midi track - //FIXME: custom temp-ents? - //FIXME: pending uri_gets? (if only just to report fails on load) - //FIXME: routing calls? - //FIXME: sql queries? - //FIXME: frik files? - //FIXME: qc threads? + //FIXME: string buffers + //FIXME: hash tables + //FIXME: skeletal objects? + //FIXME: static entities + //FIXME: midi track + //FIXME: custom temp-ents? + //FIXME: pending uri_gets? (if only just to report fails on load) + //FIXME: routing calls? + //FIXME: sql queries? + //FIXME: frik files? + //FIXME: qc threads? - // portalblobsize = CM_WritePortalState(sv.world.worldmodel, &portalblob); - // VFS_WRITE(f, portalblob, portalblobsize); + // portalblobsize = CM_WritePortalState(sv.world.worldmodel, &portalblob); + // VFS_WRITE(f, portalblob, portalblobsize); + } + + VFS_CLOSE (f); } - VFS_CLOSE (f); - if (!dontharmgame) { @@ -1131,7 +1223,7 @@ void SV_Savegame (const char *savename, qboolean mapchange) #ifndef QUAKETC { int savefmt = sv_savefmt.ival; - if (!*sv_savefmt.string && (svs.gametype != GT_PROGS || progstype == PROG_H2 || svs.levcache || (progstype == PROG_QW && strcmp(pr_ssqc_progs.string, "spprogs")))) + if (!*sv_savefmt.string && (svs.gametype != GT_PROGS || progstype == PROG_H2 || svs.levcache || (progstype == PROG_QW && strcmp(pr_ssqc_progs.string, "spprogs")) || (svs.gametype==GT_PROGS && PR_FindFunction(svprogfuncs, "SV_PerformSave", PR_ANY)))) savefmt = 1; //hexen2+q2/etc must not use the legacy format by default. can't use it when using any kind of hub system either (harder to detect upfront, which might give confused saved game naming but will at least work). else savefmt = sv_savefmt.ival; @@ -1262,7 +1354,7 @@ void SV_Savegame (const char *savename, qboolean mapchange) VFS_PRINTF (f, "%s\n", svs.name); - VFS_PRINTF (f, "%g\n", (float)svs.serverflags); + VFS_PRINTF (f, "%i\n", svs.serverflags); VFS_CLOSE(f); @@ -1474,17 +1566,18 @@ static void SV_SwapPlayers(client_t *a, client_t *b) if (b->team == a->teambuf) b->team = b->teambuf; if (a->netchan.message.data) - a->netchan.message.data += (qbyte*)b-(qbyte*)a; + a->netchan.message.data += (qbyte*)a-(qbyte*)b; if (a->datagram.data) - a->datagram.data += (qbyte*)b-(qbyte*)a; + a->datagram.data += (qbyte*)a-(qbyte*)b; if (a->backbuf.data) - a->backbuf.data += (qbyte*)b-(qbyte*)a; + a->backbuf.data += (qbyte*)a-(qbyte*)b; + if (b->netchan.message.data) - b->netchan.message.data += (qbyte*)a-(qbyte*)b; + b->netchan.message.data += (qbyte*)b-(qbyte*)a; if (b->datagram.data) - b->datagram.data += (qbyte*)a-(qbyte*)b; + b->datagram.data += (qbyte*)b-(qbyte*)a; if (b->backbuf.data) - b->backbuf.data += (qbyte*)a-(qbyte*)b; + b->backbuf.data += (qbyte*)b-(qbyte*)a; } void SV_LoadPlayers(loadplayer_t *lp, size_t slots) { //loading games is messy as fuck @@ -1519,9 +1612,9 @@ void SV_LoadPlayers(loadplayer_t *lp, size_t slots) { to[clnum] = -1; cl = &svs.clients[clnum]; + SV_DespawnClient(cl); if (cl->state <= cs_loadzombie) continue; - SV_DespawnClient(cl); if (cl->state == cs_spawned) cl->state = cs_connected; @@ -1555,6 +1648,8 @@ void SV_LoadPlayers(loadplayer_t *lp, size_t slots) continue; //spectators shouldn't be pulled into a player against their will. it may still happen though. to[clnum] = p; lp[p].source = cl; + + SV_BroadcastPrintf(PRINT_HIGH, "%s reprises %s\n", cl->name, lp[p].name); break; } } @@ -1647,6 +1742,9 @@ static void SV_GameLoaded(loadplayer_t *lp, size_t slots, const char *savename) cl->playerclass = 0; } #endif +#ifdef HAVE_LEGACY + cl->edict->xv->clientcolors = cl->playercolor; +#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. @@ -1678,6 +1776,10 @@ static void SV_GameLoaded(loadplayer_t *lp, size_t slots, const char *savename) } } host_client = NULL; + //make sure userinfos match any renamed players. + for (clnum = 0; clnum < slots; clnum++) + if (svs.clients[clnum].state >= cs_connected) + SV_ExtractFromUserinfo (&svs.clients[clnum], true); } #ifndef QUAKETC @@ -2209,12 +2311,14 @@ qboolean SV_Loadgame (const char *unsafe_savename) void SV_Loadgame_f (void) { -#ifndef SERVERONLY +#ifdef HAVE_CLIENT if (!Renderer_Started() && !isDedicated) { Cbuf_AddText(va("wait;%s %s\n", Cmd_Argv(0), Cmd_Args()), Cmd_ExecLevel); return; } + if (sv.state == ss_dead) + CL_Disconnect(NULL); #endif if (sv.state == ss_clustermode && MSV_ForwardToAutoServer()) diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 3783e41a8..c06ee2f2d 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -2663,29 +2663,32 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) clients++; if (cl->state == cs_loadzombie) - { - if (!newcl) + { //only try if they actually match this one... + if ((!strcmp(cl->name, name) || !*cl->name) && (!*cl->guid || !strcmp(info->guid, cl->guid))) { - if (((!strcmp(cl->name, name) || !*cl->name) && (!*cl->guid || !strcmp(info->guid, cl->guid))) || sv.allocated_client_slots <= 1) //named, or first come first serve. - { - if (cl->istobeloaded) - Con_DPrintf("%s:Using loadzombie\n", svs.name); - else - Con_DPrintf("%s:Using parmzombie\n", svs.name); - newcl = cl; - preserveparms = true; - temp.istobeloaded = cl->istobeloaded; - temp.spawned = cl->spawned; - memcpy(temp.spawn_parms, cl->spawn_parms, sizeof(temp.spawn_parms)); - if (cl->userid) - temp.userid = cl->userid; - break; - } + newcl = cl; + break; } + if (!newcl) //just use any. + newcl = cl; } } - if (!newcl) //client has no slot. It's possible to bipass this if server is loading a game. (or a duplicated qsocket) + if (newcl) + { //client is reprising a loaded slot. + SV_BroadcastTPrintf(PRINT_HIGH, "%s reprises %s\n", name, newcl->name); + if (cl->istobeloaded) + Con_DPrintf("%s:Using loadzombie\n", svs.name); + else + Con_DPrintf("%s:Using parmzombie\n", svs.name); + preserveparms = true; + temp.istobeloaded = newcl->istobeloaded; + temp.spawned = newcl->spawned; + memcpy(temp.spawn_parms, newcl->spawn_parms, sizeof(temp.spawn_parms)); + if (newcl->userid) + temp.userid = newcl->userid; + } + else //client has no existing slot. { #ifdef SUBSERVERS if (SSV_IsSubServer()) @@ -2893,7 +2896,7 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info) InfoSync_Clear(&newcl->infosync); *newcl = temp; newcl->userinfo.ChangeCB = svs.info.ChangeCB; - newcl->userinfo.ChangeCTX = &svs.clients[i].userinfo; + newcl->userinfo.ChangeCTX = &newcl->userinfo; InfoBuf_FromString(&newcl->userinfo, info->userinfo, false); newcl->challenge = info->challenge;