diff --git a/src/common/console/c_cvars.h b/src/common/console/c_cvars.h index c560bb1658..e9fc87420c 100644 --- a/src/common/console/c_cvars.h +++ b/src/common/console/c_cvars.h @@ -218,6 +218,9 @@ public: void* GetExtraDataPointer(); + int pnum = -1; + FName userinfoName; + protected: virtual void DoSet (UCVarValue value, ECVarType type) = 0; virtual void InstantiateZSCVar() diff --git a/src/common/engine/serializer.cpp b/src/common/engine/serializer.cpp index 0255fe0571..d7b94739b0 100644 --- a/src/common/engine/serializer.cpp +++ b/src/common/engine/serializer.cpp @@ -1757,6 +1757,19 @@ void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointer } } +bool FSerializer::ReadOptionalInt(const char * key, int &into) +{ + if(!isReading()) return false; + + auto val = r->FindKey(key); + if(val && val->IsInt()) + { + into = val->GetInt(); + return true; + } + return false; +} + #include "renderstyle.h" FSerializer& Serialize(FSerializer& arc, const char* key, FRenderStyle& style, FRenderStyle* def) { diff --git a/src/common/engine/serializer.h b/src/common/engine/serializer.h index f3afd3724f..29677237b5 100644 --- a/src/common/engine/serializer.h +++ b/src/common/engine/serializer.h @@ -108,6 +108,10 @@ public: FSerializer &AddString(const char *key, const char *charptr); const char *GetString(const char *key); FSerializer &ScriptNum(const char *key, int &num); + + + bool ReadOptionalInt(const char * key, int &into); + bool isReading() const { return r != nullptr; diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index 51c6e0dc02..a20f1e9fb6 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -393,7 +393,7 @@ void D_SetupUserInfo () // Reset everybody's userinfo to a default state. for (i = 0; i < MAXPLAYERS; i++) { - players[i].userinfo.Reset(); + players[i].userinfo.Reset(i); } // Initialize the console player's user info coninfo = &players[consoleplayer].userinfo; @@ -426,7 +426,7 @@ void D_SetupUserInfo () R_BuildPlayerTranslation(consoleplayer); } -void userinfo_t::Reset() +void userinfo_t::Reset(int pnum) { // Clear this player's userinfo. TMapIterator it(*this); @@ -469,6 +469,9 @@ void userinfo_t::Reset() newcvar->SetExtraDataPointer(cvar); // store backing cvar } + newcvar->pnum = pnum; + newcvar->userinfoName = cvarname; + Insert(cvarname, newcvar); } } @@ -981,7 +984,6 @@ void ReadUserInfo(FSerializer &arc, userinfo_t &info, FString &skin) const char *key; const char *str; - info.Reset(); skin = ""; if (arc.BeginObject("userinfo")) { diff --git a/src/g_game.cpp b/src/g_game.cpp index e8af5cf80e..5fdaf1ef5c 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1981,8 +1981,12 @@ void C_SerializeCVars(FSerializer& arc, const char* label, uint32_t filter) } +void SetupLoadingCVars(); +void FinishLoadingCVars(); + void G_DoLoadGame () { + SetupLoadingCVars(); bool hidecon; if (gameaction != ga_autoloadgame) @@ -2125,6 +2129,7 @@ void G_DoLoadGame () // load a base level bool demoplaybacksave = demoplayback; G_InitNew(map.GetChars(), false); + FinishLoadingCVars(); demoplayback = demoplaybacksave; savegamerestore = false; diff --git a/src/playsim/bots/b_bot.cpp b/src/playsim/bots/b_bot.cpp index 415cc271d7..744bcc428f 100644 --- a/src/playsim/bots/b_bot.cpp +++ b/src/playsim/bots/b_bot.cpp @@ -202,7 +202,7 @@ void FCajunMaster::ClearPlayer (int i, bool keepTeam) } players[i].~player_t(); ::new(&players[i]) player_t; - players[i].userinfo.Reset(); + players[i].userinfo.Reset(i); playeringame[i] = false; } diff --git a/src/playsim/d_player.h b/src/playsim/d_player.h index 4ee4cfeb71..b422147b78 100644 --- a/src/playsim/d_player.h +++ b/src/playsim/d_player.h @@ -269,7 +269,7 @@ struct userinfo_t : TMap return *static_cast(*CheckKey(NAME_Wi_NoAutostartMap)); } - void Reset(); + void Reset(int pnum); int TeamChanged(int team); int SkinChanged(const char *skinname, int playerclass); int SkinNumChanged(int skinnum); diff --git a/src/playsim/p_user.cpp b/src/playsim/p_user.cpp index eec52a77d5..f7372d0d17 100644 --- a/src/playsim/p_user.cpp +++ b/src/playsim/p_user.cpp @@ -1620,6 +1620,7 @@ void player_t::Serialize(FSerializer &arc) if (arc.isReading()) { + userinfo.Reset(mo->Level->PlayerNum(this)); ReadUserInfo(arc, userinfo, skinname); } else diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 52121569dc..64a68d6bc3 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -646,6 +646,15 @@ static int propcmp(const void * a, const void * b) //========================================================================== void InitImports(); +struct UserInfoCVarNamePlayer +{ + FBaseCVar** addr; + FString name; + int pnum; +}; + +TArray LoadGameUserInfoCVars; + void InitThingdef() { // Some native types need size and serialization information added before the scripts get compiled. @@ -803,6 +812,79 @@ void InitThingdef() auto fspp = NewStruct("FSpawnParticleParams", nullptr); fspp->Size = sizeof(FSpawnParticleParams); fspp->Align = alignof(FSpawnParticleParams); + + auto cvst = NewStruct("CVar", nullptr, true); + NewPointer(cvst, false)->InstallHandlers( + [](FSerializer &arc, const char *key, const void *addr) + { + const FBaseCVar * self = *(const FBaseCVar**)addr; + + if(self) + { + arc.BeginObject(key); + + + if(self->pnum != -1) + { + int32_t pnum = self->pnum; + FName name = self->userinfoName; + arc("name", name); + arc("player", pnum); + } + else + { + FString name = self->GetName(); + arc("name", name); + } + + arc.EndObject(); + } + }, + [](FSerializer &arc, const char *key, void *addr) + { + FBaseCVar ** self = (FBaseCVar**)addr; + + FString name; + arc.BeginObject(key); + + arc("name", name); + + FBaseCVar * backing = FindCVar(name.GetChars(), nullptr); + if(!backing) + { + I_Error("Attempt to load pointer to inexisted CVar '%s'", name.GetChars()); + } + else if((backing->GetFlags() & (CVAR_USERINFO|CVAR_IGNORE)) == CVAR_USERINFO) + { + if(int pnum; arc.ReadOptionalInt("player", pnum)) + { + *self = nullptr; + LoadGameUserInfoCVars.Push({self, name, pnum}); // this needs to be done later, since userinfo isn't loaded yet + arc.EndObject(); + return true; + } + } + + *self = backing; + + arc.EndObject(); + + return true; + } + ); +} + +void SetupLoadingCVars() +{ + LoadGameUserInfoCVars.Clear(); +} + +void FinishLoadingCVars() +{ + for(UserInfoCVarNamePlayer &cvar : LoadGameUserInfoCVars) + { + (*cvar.addr) = GetCVar(cvar.pnum, cvar.name.GetChars()); + } } void SynthesizeFlagFields()