diff --git a/src/actor.h b/src/actor.h index ed5c812f02..599d5f3fdf 100644 --- a/src/actor.h +++ b/src/actor.h @@ -1487,6 +1487,7 @@ public: void DeleteAttachedLights(); static void DeleteAllAttachedLights(); static void RecreateAllAttachedLights(); + bool isFrozen(); bool hasmodel; }; diff --git a/src/actorinlines.h b/src/actorinlines.h index 7eb41d0d9e..59593ad3c3 100644 --- a/src/actorinlines.h +++ b/src/actorinlines.h @@ -96,6 +96,28 @@ inline double AActor::AttackOffset(double offset) } +inline bool AActor::isFrozen() +{ + if (!(flags5 & MF5_NOTIMEFREEZE)) + { + auto state = currentSession->isFrozen(); + if (state) + { + if (player == nullptr || player->Bot != nullptr) return true; + + // This is the only place in the entire game where the two freeze flags need different treatment. + // The time freezer flag also freezes other players, the global setting does not. + + if ((state & 1) && player->timefreezer == 0) + { + return true; + } + } + } + return false; +} + + class FActorIterator { public: diff --git a/src/b_bot.cpp b/src/b_bot.cpp index 60272fc4cc..062e118794 100644 --- a/src/b_bot.cpp +++ b/src/b_bot.cpp @@ -139,7 +139,7 @@ void DBot::Tick () { Super::Tick (); - if (player->mo == nullptr || Level->freeze) + if (player->mo == nullptr || currentSession->isFrozen()) { return; } diff --git a/src/b_game.cpp b/src/b_game.cpp index c6964bc798..aa4f016e04 100644 --- a/src/b_game.cpp +++ b/src/b_game.cpp @@ -138,7 +138,7 @@ void FCajunMaster::Main(FLevelLocals *Level) return; //Add new bots? - if (wanted_botnum > botnum && !Level->freeze) + if (wanted_botnum > botnum && !currentSession->isFrozen()) { if (t_join == ((wanted_botnum - botnum) * SPAWN_DELAY)) { diff --git a/src/decallib.cpp b/src/decallib.cpp index ad9f61f421..5b297df6b3 100644 --- a/src/decallib.cpp +++ b/src/decallib.cpp @@ -1178,7 +1178,7 @@ void DDecalFader::Tick () } else { - if (Level->maptime < TimeToStartDecay || Level->freeze) + if (Level->maptime < TimeToStartDecay || currentSession->isFrozen()) { return; } @@ -1265,7 +1265,7 @@ void DDecalStretcher::Tick () Destroy (); return; } - if (Level->maptime < TimeToStart || Level->freeze) + if (Level->maptime < TimeToStart || currentSession->isFrozen()) { return; } @@ -1333,7 +1333,7 @@ void DDecalSlider::Tick () Destroy (); return; } - if (Level->maptime < TimeToStart || Level->freeze) + if (Level->maptime < TimeToStart || currentSession->isFrozen()) { return; } @@ -1401,7 +1401,7 @@ void DDecalColorer::Tick () } else { - if (Level->maptime < TimeToStartDecay || Level->freeze) + if (Level->maptime < TimeToStartDecay || currentSession->isFrozen()) { return; } diff --git a/src/doomstat.cpp b/src/doomstat.cpp index ec272476a8..506bda1665 100644 --- a/src/doomstat.cpp +++ b/src/doomstat.cpp @@ -67,10 +67,6 @@ CUSTOM_CVAR (String, language, "auto", CVAR_ARCHIVE) // [RH] Network arbitrator int Net_Arbitrator = 0; -int NextSkill = -1; - -int SinglePlayerClass[MAXPLAYERS]; - bool ToggleFullscreen; FString LumpFilterIWAD; diff --git a/src/doomstat.h b/src/doomstat.h index 23356b25ff..bffbbe4306 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -68,7 +68,6 @@ extern FString StoredWarp; // [RH] +warp at the command line // Selected by user. EXTERN_CVAR (Int, gameskill); -extern int NextSkill; // [RH] Skill to use at next level load // Netgame? Only true if >1 player. extern bool netgame; @@ -91,10 +90,6 @@ EXTERN_CVAR (Bool, teamplay) // [RH] Friendly fire amount EXTERN_CVAR (Float, teamdamage) -// [RH] The class the player will spawn as in single player, -// in case using a random class with Hexen. -extern int SinglePlayerClass[/*MAXPLAYERS*/]; - // ------------------------- // Internal parameters for sound rendering. diff --git a/src/g_game.cpp b/src/g_game.cpp index 47df3aad92..9f49f3c364 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -73,7 +73,6 @@ #include "dobjgc.h" #include "gi.h" -#include "g_hub.h" #include "g_levellocals.h" #include "events.h" @@ -95,7 +94,6 @@ void G_DoWorldDone (void); void G_DoSaveGame (bool okForQuicksave, FString filename, const char *description); void G_DoAutoSave (); -void STAT_Serialize(FSerializer &file); bool WriteZip(const char *filename, TArray &filenames, TArray &content); FIntCVar gameskill ("skill", 2, CVAR_SERVERINFO|CVAR_LATCH); @@ -1886,12 +1884,9 @@ void G_DoLoadGame () return; } - - // Read intermission data for hubs - G_SerializeHub(arc); - bglobal.RemoveAllBots(true); + // read the global state FString cvar; arc("importantcvars", cvar); if (!cvar.IsEmpty()) @@ -1899,17 +1894,13 @@ void G_DoLoadGame () uint8_t *vars_p = (uint8_t *)cvar.GetChars(); C_ReadCVars(&vars_p); } + FRandom::StaticReadRNGState(arc); - uint32_t time[2] = { 1,0 }; - - arc("ticrate", time[0]) - ("leveltime", time[1]); - // dearchive all the modifications - currentSession->time = Scale(time[1], TICRATE, time[0]); + // Read the session data + currentSession->SerializeSession(arc); G_ReadSnapshots(resfile.get()); resfile.reset(nullptr); // we no longer need the resource file below this point - G_ReadVisited(arc); // load a base level savegamerestore = true; // Use the player actors in the savegame @@ -1918,13 +1909,6 @@ void G_DoLoadGame () demoplayback = demoplaybacksave; savegamerestore = false; - STAT_Serialize(arc); - FRandom::StaticReadRNGState(arc); - P_ReadACSDefereds(arc); - P_ReadACSVars(arc); - - NextSkill = -1; - arc("nextskill", NextSkill); // Delete all snapshots that were created for the currently active levels. ForAllLevels([](FLevelLocals *Level) @@ -2113,7 +2097,7 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio char buf[100]; - bool checkok = gamestate != GS_LEVEL; + bool checkok = gamestate == GS_LEVEL; ForAllLevels([&](FLevelLocals *Level) { @@ -2124,6 +2108,12 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio } }); + if (!checkok) + { + Printf("Cannot save outside a level\n"); + return; + } + if (demoplayback) { filename = G_BuildSaveName ("demosave." SAVEGAME_EXT, -1); @@ -2194,33 +2184,15 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio PutSaveWads (savegameinfo); PutSaveComment (savegameinfo); - // Intermission stats for hubs - G_SerializeHub(savegameglobals); - { FString vars = C_GetMassCVarString(CVAR_SERVERINFO); savegameglobals.AddString("importantcvars", vars.GetChars()); } - if (currentSession->time != 0) - { - int tic = TICRATE; - savegameglobals("ticrate", tic); - savegameglobals("leveltime", currentSession->time); - } - - STAT_Serialize(savegameglobals); FRandom::StaticWriteRNGState(savegameglobals); - P_WriteACSDefereds(savegameglobals); - P_WriteACSVars(savegameglobals); - G_WriteVisited(savegameglobals); + currentSession->SerializeSession(savegameglobals); - if (NextSkill != -1) - { - savegameglobals("nextskill", NextSkill); - } - auto picdata = savepic.GetBuffer(); FCompressedBuffer bufpng = { picdata->Size(), picdata->Size(), METHOD_STORED, 0, static_cast(crc32(0, &(*picdata)[0], picdata->Size())), (char*)&(*picdata)[0] }; @@ -2238,6 +2210,13 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio savegameManager.NotifyNewSave (filename, description, okForQuicksave); + + // delete the JSON buffers we created just above. Everything else will + // either still be needed or taken care of automatically. + savegame_content[1].Clean(); + savegame_content[2].Clean(); + + // Check whether the file is ok by trying to open it. FResourceFile *test = FResourceFile::OpenResourceFile(filename, true); if (test != nullptr) diff --git a/src/g_hub.cpp b/src/g_hub.cpp index cca660dbb2..d116f2a7a1 100644 --- a/src/g_hub.cpp +++ b/src/g_hub.cpp @@ -34,7 +34,6 @@ */ #include "doomstat.h" -#include "g_hub.h" #include "g_level.h" #include "g_game.h" #include "m_png.h" @@ -44,39 +43,7 @@ #include "g_levellocals.h" -//========================================================================== -// -// Player is leaving the current level -// -//========================================================================== - -struct FHubInfo -{ - int levelnum; - - int maxkills; - int maxitems; - int maxsecret; - int maxfrags; - - wbplayerstruct_t plyr[MAXPLAYERS]; - - FHubInfo &operator=(const wbstartstruct_t &wbs) - { - levelnum = wbs.finished_ep; - maxkills = wbs.maxkills; - maxsecret= wbs.maxsecret; - maxitems = wbs.maxitems; - maxfrags = wbs.maxfrags; - memcpy(plyr, wbs.plyr, sizeof(plyr)); - return *this; - } -}; - - -static TArray hubdata; - -void G_LeavingHub(int mode, cluster_info_t * cluster, wbstartstruct_t * wbs, FLevelLocals *Level) +void FGameSession::LeavingHub(int mode, cluster_info_t * cluster, wbstartstruct_t * wbs, FLevelLocals *Level) { unsigned int i, j; @@ -176,13 +143,3 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FHubInfo &h, FHubInfo } return arc; } - -void G_SerializeHub(FSerializer &arc) -{ - arc("hubinfo", hubdata); -} - -void G_ClearHubInfo() -{ - hubdata.Clear(); -} \ No newline at end of file diff --git a/src/g_hub.h b/src/g_hub.h deleted file mode 100644 index 05c298e05b..0000000000 --- a/src/g_hub.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef __G_HUB_H -#define __G_HUB_H - -struct cluster_info_t; -struct wbstartstruct_t; -class FSerializer; -struct FLevelLocals; - -void G_SerializeHub (FSerializer &file); -void G_LeavingHub(int mode, cluster_info_t * cluster, struct wbstartstruct_t * wbs, FLevelLocals *Level); - -#endif - diff --git a/src/g_level.cpp b/src/g_level.cpp index 30494e9ce9..b5fd5cf4f0 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -84,14 +84,17 @@ #include "gi.h" -#include "g_hub.h" #include "g_levellocals.h" #include "actorinlines.h" #include "i_time.h" #include "p_maputl.h" -void STAT_StartNewGame(const char *lev); -void STAT_ChangeLevel(const char *newl, FLevelLocals *Level); +bool globalfreeze; +DEFINE_GLOBAL(globalfreeze); + +void STAT_StartNewGame(TArray &LevelData, const char *lev); +void STAT_ChangeLevel(TArray &LevelData, const char *newl, FLevelLocals *Level); +void STAT_Serialize(TArray &LevelData, FSerializer &file); EXTERN_CVAR(Bool, save_formatted) EXTERN_CVAR (Float, sv_gravity) @@ -411,7 +414,7 @@ void G_NewInit () } BackupSaveName = ""; consoleplayer = 0; - NextSkill = -1; + currentSession->NextSkill = -1; } //========================================================================== @@ -440,21 +443,17 @@ void G_DoNewGame (void) // //========================================================================== - -static void InitPlayerClasses () +void FGameSession::InitPlayerClasses () { - if (!savegamerestore) + for (int i = 0; i < MAXPLAYERS; ++i) { - for (int i = 0; i < MAXPLAYERS; ++i) + SinglePlayerClass[i] = players[i].userinfo.GetPlayerClassNum(); + if (SinglePlayerClass[i] < 0 || !playeringame[i]) { - SinglePlayerClass[i] = players[i].userinfo.GetPlayerClassNum(); - if (SinglePlayerClass[i] < 0 || !playeringame[i]) - { - SinglePlayerClass[i] = (pr_classchoice()) % PlayerClasses.Size (); - } - players[i].cls = NULL; - players[i].CurrentPlayerClass = SinglePlayerClass[i]; + SinglePlayerClass[i] = (pr_classchoice()) % PlayerClasses.Size (); } + players[i].cls = nullptr; + players[i].CurrentPlayerClass = SinglePlayerClass[i]; } } @@ -474,12 +473,7 @@ void G_InitNew (const char *mapname, bool bTitleLevel) if (!savegamerestore) { - G_ClearHubInfo(); - G_ClearSnapshots (); - P_RemoveDefereds (); - - // [RH] Mark all levels as not visited - currentSession->Visited.Clear(); + currentSession->Reset(); } UnlatchCVars (); @@ -530,14 +524,14 @@ void G_InitNew (const char *mapname, bool bTitleLevel) if (!multiplayer || !deathmatch) { - InitPlayerClasses (); + currentSession->InitPlayerClasses (); } // force players to be initialized upon first level load for (i = 0; i < MAXPLAYERS; i++) players[i].playerstate = PST_ENTER; // [BC] - STAT_StartNewGame(mapname); + STAT_StartNewGame(currentSession->Statistics, mapname); } usergame = !bTitleLevel; // will be set false if a demo @@ -639,7 +633,7 @@ void G_ChangeLevel(FLevelLocals *OldLevel, const char *levelname, int position, } if (nextSkill != -1) - NextSkill = nextSkill; + currentSession->NextSkill = nextSkill; if (flags & CHANGELEVEL_NOINTERMISSION) { @@ -681,7 +675,7 @@ void G_ChangeLevel(FLevelLocals *OldLevel, const char *levelname, int position, E_WorldUnloadedUnsafe(); unloading = false; - STAT_ChangeLevel(nextlevel, OldLevel); + STAT_ChangeLevel(currentSession->Statistics, nextlevel, OldLevel); if (thiscluster && (thiscluster->flags & CLUSTER_HUB)) { @@ -877,7 +871,7 @@ void G_DoCompleted () } // Intermission stats for entire hubs - G_LeavingHub(mode, thiscluster, &wminfo, Level); + currentSession->LeavingHub(mode, thiscluster, &wminfo, Level); for (i = 0; i < MAXPLAYERS; i++) { @@ -1012,12 +1006,12 @@ void G_DoLoadLevel (const FString &nextlevel, int position, bool autosave, bool gamestate_t oldgs = gamestate; int i; - if (NextSkill >= 0) + if (currentSession->NextSkill >= 0) { UCVarValue val; - val.Int = NextSkill; + val.Int = currentSession->NextSkill; gameskill.ForceSet (val, CVAR_Int); - NextSkill = -1; + currentSession->NextSkill = -1; } if (position == -1) @@ -1071,6 +1065,8 @@ void G_DoLoadLevel (const FString &nextlevel, int position, bool autosave, bool players[i].fragcount = 0; } + globalfreeze = false; + // Set up all needed levels. for(auto &linfo : MapSet) { @@ -1115,6 +1111,8 @@ void G_DoLoadLevel (const FString &nextlevel, int position, bool autosave, bool // Restore the state of the levels G_UnSnapshotLevel (currentSession->Levelinfo, !savegamerestore); + + globalfreeze = !!(currentSession->isFrozen() & 2); int pnumerr = G_FinishTravel (currentSession->Levelinfo[0]); @@ -1785,15 +1783,146 @@ void G_ClearSnapshots (void) //========================================================================== // -// Remove any existing defereds // //========================================================================== -void P_RemoveDefereds (void) +void FGameSession::SerializeACSDefereds(FSerializer &arc) { - currentSession->ClearDefered(); + if (arc.isWriting()) + { + if (DeferredScripts.CountUsed() == 0) return; + decltype(DeferredScripts)::Iterator it(DeferredScripts); + decltype(DeferredScripts)::Pair *pair; + + if (arc.BeginObject("deferred")) + { + while (it.NextPair(pair)) + { + arc(pair->Key, pair->Value); + } + } + arc.EndObject(); + } + else + { + FString MapName; + + DeferredScripts.Clear(); + + if (arc.BeginObject("deferred")) + { + const char *key; + + while ((key = arc.GetKey())) + { + TArray deferred; + arc(nullptr, deferred); + DeferredScripts.Insert(key, std::move(deferred)); + } + arc.EndObject(); + } + } } +//========================================================================== +// +// +//========================================================================== + +void FGameSession::SerializeVisited(FSerializer &arc) +{ + if (arc.isWriting()) + { + decltype(Visited)::Iterator it(Visited); + decltype(Visited)::Pair *pair; + + if (arc.BeginArray("visited")) + { + while (it.NextPair(pair)) + { + // Write out which levels have been visited + arc.AddString(nullptr, pair->Key); + } + arc.EndArray(); + } + + // Store player classes to be used when spawning a random class + if (multiplayer) + { + arc.Array("randomclasses", SinglePlayerClass, MAXPLAYERS); + } + + if (arc.BeginObject("playerclasses")) + { + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i]) + { + FString key; + key.Format("%d", i); + arc(key, players[i].cls); + } + } + arc.EndObject(); + } + } + else + { + if (arc.BeginArray("visited")) + { + for (int s = arc.ArraySize(); s > 0; s--) + { + FString str; + arc(nullptr, str); + Visited.Insert(str, true); + } + arc.EndArray(); + } + + arc.Array("randomclasses", SinglePlayerClass, MAXPLAYERS); + + if (arc.BeginObject("playerclasses")) + { + for (int i = 0; i < MAXPLAYERS; ++i) + { + FString key; + key.Format("%d", i); + arc(key, players[i].cls); + } + arc.EndObject(); + } + } +} + + +//========================================================================== +// +// +// +//========================================================================== + +void FGameSession::SerializeSession(FSerializer &arc) +{ + if (arc.BeginObject("session")) + { + arc("f1pic", F1Pic) + ("musicvolume", MusicVolume) + ("totaltime", totaltime) + ("time", time) + ("frozenstate", frozenstate) + ("hubinfo", hubdata) + ("nextskill", NextSkill); + + SerializeACSDefereds(arc); + SerializeVisited(arc); + STAT_Serialize(Statistics, arc); + if (arc.isReading()) P_ReadACSVars(arc); + else P_WriteACSVars(arc); + arc.EndObject(); + } +} + + //========================================================================== // // Archives the current level @@ -1910,47 +2039,6 @@ void G_WriteSnapshots(TArray &filenames, TArray &buf // //========================================================================== -void G_WriteVisited(FSerializer &arc) -{ - decltype(currentSession->Visited)::Iterator it(currentSession->Visited); - decltype(currentSession->Visited)::Pair *pair; - - if (arc.BeginArray("visited")) - { - while (it.NextPair(pair)) - { - // Write out which levels have been visited - arc.AddString(nullptr, pair->Key); - } - arc.EndArray(); - } - - // Store player classes to be used when spawning a random class - if (multiplayer) - { - arc.Array("randomclasses", SinglePlayerClass, MAXPLAYERS); - } - - if (arc.BeginObject("playerclasses")) - { - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i]) - { - FString key; - key.Format("%d", i); - arc(key, players[i].cls); - } - } - arc.EndObject(); - } -} - -//========================================================================== -// -// -//========================================================================== - void G_ReadSnapshots(FResourceFile *resf) { G_ClearSnapshots(); @@ -1976,38 +2064,6 @@ void G_ReadSnapshots(FResourceFile *resf) // //========================================================================== -void G_ReadVisited(FSerializer &arc) -{ - if (arc.BeginArray("visited")) - { - for (int s = arc.ArraySize(); s > 0; s--) - { - FString str; - arc(nullptr, str); - currentSession->Visited.Insert(str, true); - } - arc.EndArray(); - } - - arc.Array("randomclasses", SinglePlayerClass, MAXPLAYERS); - - if (arc.BeginObject("playerclasses")) - { - for (int i = 0; i < MAXPLAYERS; ++i) - { - FString key; - key.Format("%d", i); - arc(key, players[i].cls); - } - arc.EndObject(); - } -} - -//========================================================================== -// -// -//========================================================================== - CCMD(listsnapshots) { decltype(currentSession->Snapshots)::Iterator it(currentSession->Snapshots); @@ -2023,52 +2079,6 @@ CCMD(listsnapshots) } } -//========================================================================== -// -// -//========================================================================== - -void P_WriteACSDefereds (FSerializer &arc) -{ - if (currentSession->DeferredScripts.CountUsed() == 0) return; - decltype(currentSession->DeferredScripts)::Iterator it(currentSession->DeferredScripts); - decltype(currentSession->DeferredScripts)::Pair *pair; - - if (arc.BeginObject("deferred")) - { - while (it.NextPair(pair)) - { - arc(pair->Key, pair->Value); - } - } - arc.EndObject(); -} - -//========================================================================== -// -// -//========================================================================== - -void P_ReadACSDefereds (FSerializer &arc) -{ - FString MapName; - - P_RemoveDefereds (); - - if (arc.BeginObject("deferred")) - { - const char *key; - - while ((key = arc.GetKey())) - { - TArray deferred; - arc(nullptr, deferred); - currentSession->DeferredScripts.Insert(key, std::move(deferred)); - } - arc.EndObject(); - } -} - //========================================================================== // // This object is responsible for marking sectors during the propagate diff --git a/src/g_level.h b/src/g_level.h index e657296bb7..11eedc2e6b 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -481,14 +481,12 @@ FString CalcMapName (int episode, int level); void G_ParseMapInfo (FString basemapinfo); void G_ClearSnapshots (void); -void P_RemoveDefereds (); void G_SnapshotLevel (void); void G_UnSnapshotLevel(const TArray &levels, bool hubLoad); void G_ReadSnapshots (FResourceFile *); void G_WriteSnapshots (TArray &, TArray &); void G_WriteVisited(FSerializer &arc); void G_ReadVisited(FSerializer &arc); -void G_ClearHubInfo(); enum ESkillProperty { diff --git a/src/g_levellocals.h b/src/g_levellocals.h index ce627af78b..e86b01b2fe 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -46,6 +46,7 @@ #include "p_acs.h" #include "p_tags.h" #include "p_effect.h" +#include "wi_stuff.h" #include "p_destructible.h" #include "p_conversation.h" #include "r_data/r_interpolate.h" @@ -340,6 +341,46 @@ struct FLevelLocals : public FLevelData } }; +//========================================================================== +// +// Player is leaving the current level +// +//========================================================================== + +struct FHubInfo +{ + int levelnum; + + int maxkills; + int maxitems; + int maxsecret; + int maxfrags; + + wbplayerstruct_t plyr[MAXPLAYERS]; + + FHubInfo &operator=(const wbstartstruct_t &wbs) + { + levelnum = wbs.finished_ep; + maxkills = wbs.maxkills; + maxsecret = wbs.maxsecret; + maxitems = wbs.maxitems; + maxfrags = wbs.maxfrags; + memcpy(plyr, wbs.plyr, sizeof(plyr)); + return *this; + } +}; + + +// This struct is used to track statistics data in game +struct OneLevel +{ + int totalkills, killcount; + int totalitems, itemcount; + int totalsecrets, secretcount; + int leveltime; + FString Levelname; +}; + class FGameSession { @@ -349,25 +390,50 @@ public: TMap Snapshots; TMap> DeferredScripts; TMap Visited; - + TArray hubdata; + TArray Statistics;// Current game's statistics + int SinglePlayerClass[MAXPLAYERS]; + FString F1Pic; - float MusicVolume; - int time; // time in the hub - int totaltime; // time in the game - + float MusicVolume = 1.0f; + int time = 0; // time in the hub + int totaltime = 0; // time in the game + int frozenstate = 0; + int changefreeze = 0; + int NextSkill = -1; + FString nextlevel; // Level to go to on exit - int nextstartpos; // [RH] Support for multiple starts per level + int nextstartpos = 0; // [RH] Support for multiple starts per level void SetMusicVolume(float vol); - - void Reset(); + void LeavingHub(int mode, cluster_info_t * cluster, wbstartstruct_t * wbs, FLevelLocals *Level); + void InitPlayerClasses(); + + void Reset() + { + Levelinfo.DeleteAndClear(); + ClearSnapshots(); + DeferredScripts.Clear(); + Visited.Clear(); + hubdata.Clear(); + Statistics.Clear(); + + MusicVolume = 1.0f; + time = 0; + totaltime = 0; + frozenstate = 0; + changefreeze = 0; + + nextlevel = ""; + nextstartpos = 0; + } + int isFrozen() const + { + return frozenstate; + } bool isValid(); FString LookupLevelName (); - void ClearDefered() - { - DeferredScripts.Clear(); - } void ClearSnapshots() { decltype(Snapshots)::Iterator it(Snapshots); @@ -388,6 +454,9 @@ public: } Snapshots.Remove(mapname); } + void SerializeSession(FSerializer &arc); + void SerializeACSDefereds(FSerializer &arc); + void SerializeVisited(FSerializer &arc); }; @@ -499,3 +568,6 @@ inline void ForAllLevels(T func) for (auto Level : currentSession->Levelinfo) func(Level); } } + +FSerializer &Serialize(FSerializer &arc, const char *key, wbplayerstruct_t &h, wbplayerstruct_t *def); +FSerializer &Serialize(FSerializer &arc, const char *key, FHubInfo &h, FHubInfo *def); \ No newline at end of file diff --git a/src/hwrenderer/scene/hw_sprites.cpp b/src/hwrenderer/scene/hw_sprites.cpp index 03ab9c2ae3..09fe49ee38 100644 --- a/src/hwrenderer/scene/hw_sprites.cpp +++ b/src/hwrenderer/scene/hw_sprites.cpp @@ -1198,7 +1198,7 @@ void GLSprite::ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t * const auto &vp = di->Viewpoint; double timefrac = vp.TicFrac; - if (paused || di->Level->freeze || (di->Level->flags2 & LEVEL2_FROZEN)) + if (paused || currentSession->isFrozen()) timefrac = 0.; float xvf = (particle->Vel.X) * timefrac; float yvf = (particle->Vel.Y) * timefrac; diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index c2c116c8d3..a41c2c75cc 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -515,9 +515,8 @@ void cht_DoCheat (player_t *player, int cheat) case CHT_FREEZE: { - auto Level = player->mo->Level; - Level->changefreeze ^= 1; - if (Level->freeze ^ Level->changefreeze) + currentSession->changefreeze ^= 2; + if (currentSession->isFrozen() ^ currentSession->changefreeze) { msg = GStrings("TXT_FREEZEON"); } diff --git a/src/p_effect.cpp b/src/p_effect.cpp index dbbf51e6d3..b272f2a0eb 100644 --- a/src/p_effect.cpp +++ b/src/p_effect.cpp @@ -236,7 +236,7 @@ void P_ThinkParticles (FLevelLocals *Level) { particle = &Level->Particles[i]; i = particle->tnext; - if (!particle->notimefreeze && ((Level->freeze) || (Level->flags2 & LEVEL2_FROZEN))) + if (!particle->notimefreeze && currentSession->isFrozen()) { prev = particle; continue; diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index 99828da161..3064a5cb58 100644 --- a/src/p_lnspec.cpp +++ b/src/p_lnspec.cpp @@ -3140,11 +3140,11 @@ FUNC(LS_ChangeSkill) { if ((unsigned)arg0 >= AllSkills.Size()) { - NextSkill = -1; + currentSession->NextSkill = -1; } else { - NextSkill = arg0; + currentSession->NextSkill = arg0; } return true; } diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index f2a6cc9cb4..deb039d751 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -3522,7 +3522,7 @@ void AActor::Tick () if (!(flags5 & MF5_NOTIMEFREEZE)) { //Added by MC: Freeze mode. - if (Level->freeze || Level->flags2 & LEVEL2_FROZEN) + if (isFrozen()) { // Boss cubes shouldn't be accelerated by timefreeze if (flags6 & MF6_BOSSCUBE) @@ -3569,27 +3569,16 @@ void AActor::Tick () return; } - if (!(flags5 & MF5_NOTIMEFREEZE)) + if (isFrozen()) { // Boss cubes shouldn't be accelerated by timefreeze if (flags6 & MF6_BOSSCUBE) { special2++; } - //Added by MC: Freeze mode. - if (Level->freeze && !(player && player->Bot == NULL)) - { - return; - } - - // Apply freeze mode. - if ((Level->flags2 & LEVEL2_FROZEN) && (player == NULL || player->timefreezer == 0)) - { - return; - } + return; } - if (effects & FX_ROCKET) { if (++smokecounter == 4) @@ -4934,7 +4923,7 @@ AActor *P_SpawnPlayer (FLevelLocals *Level, FPlayerStart *mthing, int playernum, if (!deathmatch || !multiplayer) { - type = SinglePlayerClass[playernum]; + type = currentSession->SinglePlayerClass[playernum]; } else { diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 6e1b9eceba..28883dee94 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -950,14 +950,11 @@ void G_SerializeLevel(FSerializer &arc, FLevelLocals *Level, bool hubload) ("fragglethinker", Level->FraggleScriptThinker) ("acsthinker", Level->ACSThinker) ("impactdecalcount", Level->ImpactDecalCount) - ("freeze", Level->freeze) - ("changefreeze", Level->changefreeze) ("sndseqlisthead", Level->SequenceListHead) ("am_markpointnum", Level->am_markpointnum) .Array("am_markpoints", &Level->am_markpoints[0].x, Level->AM_NUMMARKPOINTS * 2) // write as a double array. ("am_scale_mtof", Level->am_scale_mtof) - ("am_scale_ftom", Level->am_scale_ftom) - .EndObject(); + ("am_scale_ftom", Level->am_scale_ftom); @@ -1026,5 +1023,4 @@ void G_SerializeLevel(FSerializer &arc, FLevelLocals *Level, bool hubload) } AActor::RecreateAllAttachedLights(); InitPortalGroups(Level); - } diff --git a/src/p_saveg.h b/src/p_saveg.h index b3311c29a4..57b311a77d 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -41,9 +41,6 @@ class FSerializer; // Also see farchive.(h|cpp) void P_DestroyThinkers(bool hubLoad); -void P_ReadACSDefereds (FSerializer &); -void P_WriteACSDefereds (FSerializer &); - void G_SerializeLevel(FSerializer &arc, FLevelLocals *Level, bool hubLoad); #endif // __P_SAVEG_H__ diff --git a/src/p_tick.cpp b/src/p_tick.cpp index e62ced78db..1fef190d47 100644 --- a/src/p_tick.cpp +++ b/src/p_tick.cpp @@ -39,6 +39,7 @@ #include "actorinlines.h" extern gamestate_t wipegamestate; +extern bool globalfreeze; //========================================================================== // @@ -110,10 +111,11 @@ void P_Ticker (void) // [RH] Frozen mode is only changed every 4 tics, to make it work with A_Tracer(). if ((Level->maptime & 3) == 0) { - if (Level->changefreeze) + if (currentSession->changefreeze) { - Level->freeze ^= 1; - Level->changefreeze = 0; + currentSession->frozenstate ^= currentSession->changefreeze; + currentSession->changefreeze = 0; + globalfreeze = !!(currentSession->isFrozen() & 2); // for ZScript backwards compatibiity. } } @@ -153,7 +155,7 @@ void P_Ticker (void) DThinker::RunThinkers(Level); //if added by MC: Freeze mode. - if (!Level->freeze && !(Level->flags2 & LEVEL2_FROZEN)) + if (!currentSession->isFrozen()) { P_UpdateSpecials(Level); P_RunEffects(Level); // [RH] Run particle effects diff --git a/src/p_user.cpp b/src/p_user.cpp index c43a7f8508..31d55c4f0a 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -1226,7 +1226,7 @@ void P_PlayerThink (player_t *player) } // Bots do not think in freeze mode. - if (player->mo->Level->freeze && player->Bot != nullptr) + if (currentSession->isFrozen() && player->Bot != nullptr) { return; } @@ -1695,7 +1695,7 @@ bool P_IsPlayerTotallyFrozen(const player_t *player) return gamestate == GS_TITLELEVEL || player->cheats & CF_TOTALLYFROZEN || - ((player->mo->Level->flags2 & LEVEL2_FROZEN) && player->timefreezer == 0); + player->mo->isFrozen(); } diff --git a/src/polyrenderer/scene/poly_particle.cpp b/src/polyrenderer/scene/poly_particle.cpp index 3ddacec796..fe8de94f42 100644 --- a/src/polyrenderer/scene/poly_particle.cpp +++ b/src/polyrenderer/scene/poly_particle.cpp @@ -35,7 +35,7 @@ EXTERN_CVAR(Int, gl_particles_style) void RenderPolyParticle::Render(PolyRenderThread *thread, particle_t *particle, subsector_t *sub, uint32_t stencilValue) { double timefrac = r_viewpoint.TicFrac; - if (paused || PolyRenderer::Instance()->Level->freeze || (PolyRenderer::Instance()->Level->flags2 & LEVEL2_FROZEN)) + if (paused || currentSession->isFrozen()) timefrac = 0.; DVector3 pos = particle->Pos + (particle->Vel * timefrac); double psize = particle->size / 8.0; diff --git a/src/r_data/models/models.cpp b/src/r_data/models/models.cpp index 76423c5311..b95404b3b9 100644 --- a/src/r_data/models/models.cpp +++ b/src/r_data/models/models.cpp @@ -230,7 +230,7 @@ void FModelRenderer::RenderFrameModels(FLevelLocals *Level, const FSpriteModelFr // [BB] To interpolate at more than 35 fps we take tic fractions into account. float ticFraction = 0.; // [BB] In case the tic counter is frozen we have to leave ticFraction at zero. - if (ConsoleState == c_up && menuactive != MENU_On && !(Level->flags2 & LEVEL2_FROZEN)) + if (ConsoleState == c_up && menuactive != MENU_On && !currentSession->isFrozen()) { ticFraction = I_GetTimeFrac(); } diff --git a/src/scripting/vmthunks.cpp b/src/scripting/vmthunks.cpp index bc5321e5fc..4666580a06 100644 --- a/src/scripting/vmthunks.cpp +++ b/src/scripting/vmthunks.cpp @@ -2667,7 +2667,32 @@ DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, Vec3Offset, Vec3Offset) ACTION_RETURN_VEC3(result); } +static int isFrozen(FGameSession *self) +{ + return self->isFrozen(); +} +DEFINE_ACTION_FUNCTION_NATIVE(FGameSession, isFrozen, isFrozen) +{ + PARAM_SELF_STRUCT_PROLOGUE(FGameSession); + return isFrozen(self); +} + +void setFrozen(FGameSession *self, int on) +{ + self->frozenstate = (self->frozenstate & ~1) | on; + // For compatibility. The engine itself never checks this. + if (on) self->Levelinfo[0]->flags2 |= LEVEL2_FROZEN; + else self->Levelinfo[0]->flags2 &= ~LEVEL2_FROZEN; +} + +DEFINE_ACTION_FUNCTION_NATIVE(FGameSession, setFrozen, setFrozen) +{ + PARAM_SELF_STRUCT_PROLOGUE(FGameSession); + PARAM_BOOL(on); + setFrozen(self, on); + return 0; +} //===================================================================================== // @@ -2744,7 +2769,6 @@ DEFINE_FIELD(FLevelLocals, outsidefogdensity) DEFINE_FIELD(FLevelLocals, skyfog) DEFINE_FIELD(FLevelLocals, pixelstretch) DEFINE_FIELD(FLevelLocals, deathsequence) -DEFINE_FIELD(FLevelLocals, freeze) DEFINE_FIELD_BIT(FLevelLocals, flags, noinventorybar, LEVEL_NOINVENTORYBAR) DEFINE_FIELD_BIT(FLevelLocals, flags, monsterstelefrag, LEVEL_MONSTERSTELEFRAG) @@ -2872,4 +2896,4 @@ DEFINE_FIELD(DHUDFont, mFont); DEFINE_GLOBAL(StatusBar); -DEFINE_FIELD(DThinker, Level); \ No newline at end of file +DEFINE_FIELD(DThinker, Level); diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index 9119b86f8d..1f9d9cdc98 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -1680,6 +1680,18 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, compat_mushroom, compat_mushroom_) ACTION_RETURN_INT(compat_mushroom_(self)); } +static int isFrozen(AActor *self) +{ + return self->isFrozen(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, isFrozen, isFrozen) +{ + PARAM_SELF_STRUCT_PROLOGUE(AActor); + return isFrozen(self); +} + + //=========================================================================== // // PlayerPawn functions diff --git a/src/statistics.cpp b/src/statistics.cpp index 86110f22e8..cbb53dbc34 100644 --- a/src/statistics.cpp +++ b/src/statistics.cpp @@ -68,18 +68,6 @@ CVAR(String, statfile, "zdoomstat.txt", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // //========================================================================== -// This struct is used to track statistics data in game -struct OneLevel -{ - int totalkills, killcount; - int totalitems, itemcount; - int totalsecrets, secretcount; - int leveltime; - FString Levelname; -}; - -// Current game's statistics -static TArray LevelData; static FEpisode *StartEpisode; // The statistics for one level @@ -363,7 +351,7 @@ static void LevelStatEntry(FSessionStatistics *es, const char *level, const char // //========================================================================== -void STAT_StartNewGame(const char *mapname) +void STAT_StartNewGame(TArray &LevelData, const char *mapname) { LevelData.Clear(); if (!deathmatch && !multiplayer) @@ -386,7 +374,7 @@ void STAT_StartNewGame(const char *mapname) // //========================================================================== -static void StoreLevelStats(FLevelLocals *Level) +static void StoreLevelStats(TArray &LevelData, FLevelLocals *Level) { unsigned int i; @@ -432,10 +420,10 @@ static void StoreLevelStats(FLevelLocals *Level) // //========================================================================== -void STAT_ChangeLevel(const char *newl, FLevelLocals *Level) +void STAT_ChangeLevel(TArray &LevelData, const char *newl, FLevelLocals *Level) { // record the current level's stats. - StoreLevelStats(Level); + StoreLevelStats(LevelData, Level); const level_info_t *thisinfo = Level->info; level_info_t *nextinfo = NULL; @@ -524,7 +512,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, OneLevel &l, OneLevel return arc; } -void STAT_Serialize(FSerializer &arc) +void STAT_Serialize(TArray &LevelData, FSerializer &arc) { FString startlevel; int i = LevelData.Size(); @@ -564,6 +552,7 @@ void STAT_Serialize(FSerializer &arc) FString GetStatString() { + auto &LevelData = currentSession->Statistics; FString compose; for(unsigned i = 0; i < LevelData.Size(); i++) { @@ -577,7 +566,7 @@ FString GetStatString() CCMD(printstats) { - if (currentSession) StoreLevelStats(currentSession->Levelinfo[0]); // Refresh the current level's results. + if (currentSession) StoreLevelStats(currentSession->Statistics, currentSession->Levelinfo[0]); // Refresh the current level's results. Printf("%s", GetStatString().GetChars()); } @@ -596,6 +585,6 @@ CCMD(finishgame) ADD_STAT(statistics) { - if (currentSession) StoreLevelStats(currentSession->Levelinfo[0]); // Refresh the current level's results. + if (currentSession) StoreLevelStats(currentSession->Statistics, currentSession->Levelinfo[0]); // Refresh the current level's results. return GetStatString(); } diff --git a/src/swrenderer/things/r_particle.cpp b/src/swrenderer/things/r_particle.cpp index 2a51b35630..e6c9465392 100644 --- a/src/swrenderer/things/r_particle.cpp +++ b/src/swrenderer/things/r_particle.cpp @@ -79,7 +79,7 @@ namespace swrenderer sector_t* heightsec = NULL; double timefrac = r_viewpoint.TicFrac; - if (paused || sector->Level->freeze || (sector->Level->flags2 & LEVEL2_FROZEN)) + if (paused || currentSession->isFrozen()) timefrac = 0.; double ippx = particle->Pos.X + particle->Vel.X * timefrac; diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index b199b6694c..23b3296767 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -448,6 +448,7 @@ class Actor : Thinker native return sin(fb * (180./32)) * 8; } + native bool isFrozen(); virtual native void BeginPlay(); virtual native void Activate(Actor activator); virtual native void Deactivate(Actor activator); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 0591ad0267..4fa4602642 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -7,7 +7,6 @@ struct _ native // These are the global variables, the struct is only here to av native readonly Array<@Team> Teams; native int validcount; native readonly bool multiplayer; - //native play @LevelLocals level; native @KeyBindings Bindings; native @KeyBindings AutomapBindings; native play @DehInfo deh; @@ -46,6 +45,9 @@ struct _ native // These are the global variables, the struct is only here to av native int LocalViewPitch; native GameSession currentSession; + //native play @LevelLocals level; + deprecated("3.8") native readonly bool globalfreeze; + } struct TexMan @@ -633,6 +635,10 @@ struct GameSession native native readonly String F1Pic; native readonly int time; native readonly int totaltime; + + native bool isFrozen(); + native void setFrozen(bool on); + } struct LevelLocals native @@ -709,7 +715,7 @@ struct LevelLocals native native readonly bool polygrind; native readonly bool nomonsters; native readonly bool allowrespawn; - native bool frozen; + deprecated("3.8") native bool frozen; native readonly bool infinite_flight; native readonly bool no_dlg_freeze; native readonly bool keepfullinventory; @@ -719,7 +725,6 @@ struct LevelLocals native native readonly int skyfog; native readonly float pixelstretch; native name deathsequence; - native readonly uint8 freeze; // level_info_t *info cannot be done yet. native String GetUDMFString(int type, int index, Name key); @@ -769,6 +774,7 @@ struct LevelLocals native int sec = Thinker.Tics2Seconds(totals? currentSession.totaltime : currentSession.time); return String.Format("%02d:%02d:%02d", sec / 3600, (sec % 3600) / 60, sec % 60); } + } struct StringTable native diff --git a/wadsrc/static/zscript/inventory/powerups.txt b/wadsrc/static/zscript/inventory/powerups.txt index 23c8108217..38c2a80f06 100644 --- a/wadsrc/static/zscript/inventory/powerups.txt +++ b/wadsrc/static/zscript/inventory/powerups.txt @@ -1531,7 +1531,7 @@ class PowerTimeFreezer : Powerup // Make sure the effect starts and ends on an even tic. if ((Level.maptime & 1) == 0) { - Level.frozen = true;; + currentSession.SetFrozen(true); } else { @@ -1560,7 +1560,7 @@ class PowerTimeFreezer : Powerup // [RH] The "blinking" can't check against EffectTics exactly or it will // never happen, because InitEffect ensures that EffectTics will always // be odd when Level.maptime is even. - Level.frozen = ( EffectTics > 4*32 + currentSession.SetFrozen ( EffectTics > 4*32 || (( EffectTics > 3*32 && EffectTics <= 4*32 ) && ((EffectTics + 1) & 15) != 0 ) || (( EffectTics > 2*32 && EffectTics <= 3*32 ) && ((EffectTics + 1) & 7) != 0 ) || (( EffectTics > 32 && EffectTics <= 2*32 ) && ((EffectTics + 1) & 3) != 0 ) @@ -1598,7 +1598,7 @@ class PowerTimeFreezer : Powerup } // No, so allow other actors to move about freely once again. - Level.frozen = false; + currentSession.SetFrozen(false); // Also, turn the music back on. S_ResumeSound(false); diff --git a/wadsrc/static/zscript/shared/fastprojectile.txt b/wadsrc/static/zscript/shared/fastprojectile.txt index 15fcf14669..da0c003e1e 100644 --- a/wadsrc/static/zscript/shared/fastprojectile.txt +++ b/wadsrc/static/zscript/shared/fastprojectile.txt @@ -50,14 +50,8 @@ class FastProjectile : Actor ClearInterpolation(); double oldz = pos.Z; - if (!bNoTimeFreeze) - { - //Added by MC: Freeze mode. - if (Level.freeze || Level.Frozen) - { - return; - } - } + if (isFrozen()) + return; // [RH] Ripping is a little different than it was in Hexen FCheckPosition tm; diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index 453d0f976b..4bd7b78c29 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -2764,7 +2764,7 @@ struct PlayerInfo native play // self is what internally is known as player_t return gamestate == GS_TITLELEVEL || (cheats & CF_TOTALLYFROZEN) || - (mo.Level.frozen && timefreezer == 0); + mo.isFrozen(); } void Uncrouch() diff --git a/wadsrc/static/zscript/shared/player_inventory.txt b/wadsrc/static/zscript/shared/player_inventory.txt index 88a4dcf26c..ca622707c9 100644 --- a/wadsrc/static/zscript/shared/player_inventory.txt +++ b/wadsrc/static/zscript/shared/player_inventory.txt @@ -210,7 +210,7 @@ extend class PlayerPawn { // You can't use items if you're totally frozen return false; } - if ((Level.FROZEN) && (player == NULL || player.timefreezer == 0)) + if (isFrozen()) { // Time frozen return false;