From 9099dc600fbdd0a244ff4016fbfb70d0473f3b70 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 12 Jan 2019 13:50:15 +0100 Subject: [PATCH] - fixed the 'frozen level' handling and did some cleanup on the session data in savegames. The handling for the two frozen flags was totally inconsistent. Furthermore, these need to be session data, not level data. The old exported variables for this still exist and shadow the real state, but are deprecated now. Frozen state should only be checked with "currentSession.isFrozen()" now. The session data in the savegame was grouped and separated from the global state, which onl consists of server CVARs and RNG state now. --- src/actor.h | 1 + src/actorinlines.h | 22 ++ src/b_bot.cpp | 2 +- src/b_game.cpp | 2 +- src/decallib.cpp | 8 +- src/doomstat.cpp | 4 - src/doomstat.h | 5 - src/g_game.cpp | 59 ++-- src/g_hub.cpp | 45 +-- src/g_hub.h | 13 - src/g_level.cpp | 312 +++++++++--------- src/g_level.h | 2 - src/g_levellocals.h | 94 +++++- src/hwrenderer/scene/hw_sprites.cpp | 2 +- src/m_cheat.cpp | 5 +- src/p_effect.cpp | 2 +- src/p_lnspec.cpp | 4 +- src/p_mobj.cpp | 19 +- src/p_saveg.cpp | 6 +- src/p_saveg.h | 3 - src/p_tick.cpp | 10 +- src/p_user.cpp | 4 +- src/polyrenderer/scene/poly_particle.cpp | 2 +- src/r_data/models/models.cpp | 2 +- src/scripting/vmthunks.cpp | 28 +- src/scripting/vmthunks_actors.cpp | 12 + src/statistics.cpp | 27 +- src/swrenderer/things/r_particle.cpp | 2 +- wadsrc/static/zscript/actor.txt | 1 + wadsrc/static/zscript/base.txt | 12 +- wadsrc/static/zscript/inventory/powerups.txt | 6 +- .../static/zscript/shared/fastprojectile.txt | 10 +- wadsrc/static/zscript/shared/player.txt | 2 +- .../zscript/shared/player_inventory.txt | 2 +- 34 files changed, 378 insertions(+), 352 deletions(-) delete mode 100644 src/g_hub.h diff --git a/src/actor.h b/src/actor.h index ed5c812f023..599d5f3fdf6 100644 --- a/src/actor.h +++ b/src/actor.h @@ -1487,6 +1487,7 @@ class AActor : public DThinker void DeleteAttachedLights(); static void DeleteAllAttachedLights(); static void RecreateAllAttachedLights(); + bool isFrozen(); bool hasmodel; }; diff --git a/src/actorinlines.h b/src/actorinlines.h index 7eb41d0d9e4..59593ad3c37 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 60272fc4cce..062e118794b 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 c6964bc798b..aa4f016e041 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 ad9f61f4217..5b297df6b3c 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 ec272476a8a..506bda16655 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 23356b25ff4..bffbbe43060 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 47df3aad925..9f49f3c364f 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,32 +2184,14 @@ 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 cca660dbb2c..d116f2a7a14 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 05c298e05bc..00000000000 --- 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 30494e9ce9d..b5fd5cf4f03 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 e657296bb7f..11eedc2e6be 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 ce627af78b0..e86b01b2fe9 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 @@ class FGameSession 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(); - bool isValid(); - FString LookupLevelName (); - void ClearDefered() + 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 ClearSnapshots() { decltype(Snapshots)::Iterator it(Snapshots); @@ -388,6 +454,9 @@ class FGameSession } 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 03ab9c2ae39..09fe49ee38a 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 c2c116c8d30..a41c2c75cc5 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 dbbf51e6d3e..b272f2a0eb1 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 99828da161b..3064a5cb581 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 f2a6cc9cb4e..deb039d7510 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 6e1b9eceba7..28883dee946 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 b3311c29a44..57b311a77d5 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 e62ced78dbf..1fef190d479 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 c43a7f85085..31d55c4f0a6 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 3ddacec796f..fe8de94f425 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 76423c5311b..b95404b3b93 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 bc5321e5fc7..4666580a06a 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 9119b86f8db..1f9d9cdc980 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 86110f22e8c..cbb53dbc343 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 2a51b356303..e6c94653922 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 b199b6694c5..23b32967678 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 0591ad02672..4fa4602642c 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 23c8108217e..38c2a80f068 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 15fcf14669f..da0c003e1e5 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 453d0f976b9..4bd7b78c292 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 88a4dcf26cc..ca622707c91 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;