- 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.
This commit is contained in:
Christoph Oelckers 2019-01-12 13:50:15 +01:00
parent 9f1aedd135
commit 9099dc600f
34 changed files with 379 additions and 353 deletions

View file

@ -1487,6 +1487,7 @@ public:
void DeleteAttachedLights();
static void DeleteAllAttachedLights();
static void RecreateAllAttachedLights();
bool isFrozen();
bool hasmodel;
};

View file

@ -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:

View file

@ -139,7 +139,7 @@ void DBot::Tick ()
{
Super::Tick ();
if (player->mo == nullptr || Level->freeze)
if (player->mo == nullptr || currentSession->isFrozen())
{
return;
}

View file

@ -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))
{

View file

@ -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;
}

View file

@ -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;

View file

@ -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.

View file

@ -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<FString> &filenames, TArray<FCompressedBuffer> &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<unsigned int>(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)

View file

@ -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<FHubInfo> 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();
}

View file

@ -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

View file

@ -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<OneLevel> &LevelData, const char *lev);
void STAT_ChangeLevel(TArray<OneLevel> &LevelData, const char *newl, FLevelLocals *Level);
void STAT_Serialize(TArray<OneLevel> &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<acsdefered_t> 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<FString> &filenames, TArray<FCompressedBuffer> &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<acsdefered_t> deferred;
arc(nullptr, deferred);
currentSession->DeferredScripts.Insert(key, std::move(deferred));
}
arc.EndObject();
}
}
//==========================================================================
//
// This object is responsible for marking sectors during the propagate

View file

@ -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<FLevelLocals *> &levels, bool hubLoad);
void G_ReadSnapshots (FResourceFile *);
void G_WriteSnapshots (TArray<FString> &, TArray<FCompressedBuffer> &);
void G_WriteVisited(FSerializer &arc);
void G_ReadVisited(FSerializer &arc);
void G_ClearHubInfo();
enum ESkillProperty
{

View file

@ -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<FName, FCompressedBuffer> Snapshots;
TMap<FName, TArray<acsdefered_t>> DeferredScripts;
TMap<FName, bool> Visited;
TArray<FHubInfo> hubdata;
TArray<OneLevel> 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);

View file

@ -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;

View file

@ -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");
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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
{

View file

@ -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);
}

View file

@ -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__

View file

@ -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

View file

@ -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();
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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);
DEFINE_FIELD(DThinker, Level);

View file

@ -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

View file

@ -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<OneLevel> 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<OneLevel> &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<OneLevel> &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<OneLevel> &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<OneLevel> &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();
}

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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()

View file

@ -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;