diff --git a/src/c_cvars.cpp b/src/c_cvars.cpp index 9681f3ece..81ba01135 100644 --- a/src/c_cvars.cpp +++ b/src/c_cvars.cpp @@ -135,7 +135,7 @@ FBaseCVar::~FBaseCVar () void FBaseCVar::ForceSet (UCVarValue value, ECVarType type) { DoSet (value, type); - if (Flags & CVAR_USERINFO) + if ((Flags & CVAR_USERINFO) && !(Flags & CVAR_NOSEND)) D_UserInfoChanged (this); if (m_UseCallback) Callback (); @@ -849,9 +849,7 @@ UCVarValue FStringCVar::GetFavoriteRepDefault (ECVarType *type) const void FStringCVar::SetGenericRepDefault (UCVarValue value, ECVarType type) { - if (DefaultValue) - delete[] DefaultValue; - DefaultValue = ToString (value, type); + ReplaceString(&DefaultValue, ToString(value, type)); if (Flags & CVAR_ISDEFAULT) { SetGenericRep (value, type); @@ -1277,7 +1275,7 @@ void FilterCompactCVars (TArray &cvars, DWORD filter) // Accumulate all cvars that match the filter flags. for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->m_Next) { - if (cvar->Flags & filter) + if ((cvar->Flags & filter) && !(cvar->Flags & CVAR_NOSEND)) cvars.Push(cvar); } // Now sort them, so they're in a deterministic order and not whatever @@ -1316,7 +1314,7 @@ FString C_GetMassCVarString (DWORD filter, bool compact) { for (cvar = CVars; cvar != NULL; cvar = cvar->m_Next) { - if ((cvar->Flags & filter) && !(cvar->Flags & CVAR_NOSAVE)) + if ((cvar->Flags & filter) && !(cvar->Flags & (CVAR_NOSAVE|CVAR_NOSEND))) { UCVarValue val = cvar->GetGenericRep(CVAR_String); dump << '\\' << cvar->GetName() << '\\' << val.String; @@ -1489,6 +1487,7 @@ FBaseCVar *FindCVarSub (const char *var_name, int namelen) FBaseCVar *C_CreateCVar(const char *var_name, ECVarType var_type, DWORD flags) { assert(FindCVar(var_name, NULL) == NULL); + flags |= CVAR_AUTO; switch (var_type) { case CVAR_Bool: return new FBoolCVar(var_name, 0, flags); @@ -1540,7 +1539,7 @@ void C_ArchiveCVars (FConfigFile *f, uint32 filter) while (cvar) { if ((cvar->Flags & - (CVAR_GLOBALCONFIG|CVAR_ARCHIVE|CVAR_AUTO|CVAR_USERINFO|CVAR_SERVERINFO|CVAR_NOSAVE)) + (CVAR_GLOBALCONFIG|CVAR_ARCHIVE|CVAR_MODARCHIVE|CVAR_AUTO|CVAR_USERINFO|CVAR_SERVERINFO|CVAR_NOSAVE)) == filter) { UCVarValue val; @@ -1671,14 +1670,16 @@ void FBaseCVar::ListVars (const char *filter, bool plain) else { ++count; - Printf ("%c%c%c %s : :%s\n", - flags & CVAR_ARCHIVE ? 'A' : ' ', + Printf ("%c%c%c%c %s = %s\n", + flags & CVAR_ARCHIVE ? 'A' : + flags & CVAR_MODARCHIVE ? 'M' : ' ', flags & CVAR_USERINFO ? 'U' : - flags & CVAR_SERVERINFO ? 'S' : - flags & CVAR_AUTO ? 'C' : ' ', + flags & CVAR_SERVERINFO ? 'S' : + flags & CVAR_AUTO ? 'C' : ' ', flags & CVAR_NOSET ? '-' : - flags & CVAR_LATCH ? 'L' : - flags & CVAR_UNSETTABLE ? '*' : ' ', + flags & CVAR_LATCH ? 'L' : + flags & CVAR_UNSETTABLE ? '*' : ' ', + flags & CVAR_NOSEND ? 'X' : ' ', var->GetName(), var->GetGenericRep (CVAR_String).String); } diff --git a/src/c_cvars.h b/src/c_cvars.h index d7b767f33..744643d82 100644 --- a/src/c_cvars.h +++ b/src/c_cvars.h @@ -61,6 +61,8 @@ enum CVAR_GLOBALCONFIG = 1024, // cvar is saved to global config section CVAR_VIDEOCONFIG = 2048, // cvar is saved to video config section (not implemented) CVAR_NOSAVE = 4096, // when used with CVAR_SERVERINFO, do not save var to savegame + CVAR_MODARCHIVE = 8192, // cvar will be saved to a mod-specific section of the ini + CVAR_NOSEND = 16384,// do not send cvar across the network (dummy mod cvar) }; union UCVarValue diff --git a/src/d_main.cpp b/src/d_main.cpp index aa4abd366..0f66f68a6 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1358,6 +1358,136 @@ CCMD (endgame) } } +//========================================================================== +// +// ParseCVarInfo +// +//========================================================================== + +void ParseCVarInfo() +{ + int lump, lastlump = 0; + bool addedcvars = false; + + while ((lump = Wads.FindLump("CVARINFO", &lastlump)) != -1) + { + FScanner sc(lump); + sc.SetCMode(true); + + while (sc.GetToken()) + { + FString cvarname; + char *cvardefault = NULL; + ECVarType cvartype = CVAR_Dummy; + int cvarflags = CVAR_MODARCHIVE; + FBaseCVar *cvar; + + // Check for flag tokens. + while (sc.TokenType == TK_Identifier) + { + if (stricmp(sc.String, "server") == 0) + { + cvarflags |= CVAR_SERVERINFO; + } + else if (stricmp(sc.String, "user") == 0) + { + cvarflags |= CVAR_USERINFO; + } + else if (stricmp(sc.String, "noarchive") == 0) + { + cvarflags &= ~CVAR_MODARCHIVE; + } + else + { + sc.ScriptError("Unknown cvar attribute '%s'", sc.String); + } + sc.MustGetAnyToken(); + } + // Do some sanity checks. + if ((cvarflags & (CVAR_SERVERINFO|CVAR_USERINFO)) == 0 || + (cvarflags & (CVAR_SERVERINFO|CVAR_USERINFO)) == (CVAR_SERVERINFO|CVAR_USERINFO)) + { + sc.ScriptError("One of 'server' or 'user' must be specified"); + } + // The next token must be the cvar type. + if (sc.TokenType == TK_Bool) + { + cvartype = CVAR_Bool; + } + else if (sc.TokenType == TK_Int) + { + cvartype = CVAR_Int; + } + else if (sc.TokenType == TK_Float) + { + cvartype = CVAR_Float; + } + else if (sc.TokenType == TK_Color) + { + cvartype = CVAR_Color; + } + else if (sc.TokenType == TK_String) + { + cvartype = CVAR_String; + } + else + { + sc.ScriptError("Bad cvar type '%s'", sc.String); + } + // The next token must be the cvar name. + sc.MustGetToken(TK_Identifier); + if (FindCVar(sc.String, NULL) != NULL) + { + sc.ScriptError("cvar '%s' already exists", sc.String); + } + cvarname = sc.String; + // A default value is optional and signalled by a '=' token. + if (sc.CheckToken('=')) + { + switch (cvartype) + { + case CVAR_Bool: + if (!sc.CheckToken(TK_True) && !sc.CheckToken(TK_False)) + { + sc.ScriptError("Expected true or false"); + } + cvardefault = sc.String; + break; + case CVAR_Int: + sc.MustGetNumber(); + cvardefault = sc.String; + break; + case CVAR_Float: + sc.MustGetFloat(); + cvardefault = sc.String; + break; + default: + sc.MustGetString(); + cvardefault = sc.String; + break; + } + } + // Now create the cvar. + cvar = C_CreateCVar(cvarname, cvartype, cvarflags); + if (cvardefault != NULL) + { + UCVarValue val; + val.String = cvardefault; + cvar->SetGenericRepDefault(val, CVAR_String); + } + // To be like C and ACS, require a semicolon after everything. + sc.MustGetToken(';'); + addedcvars = true; + } + } + // Only load mod cvars from the config if we defined some, so we don't + // clutter up the cvar space when not playing mods with custom cvars. + if (addedcvars) + { + GameConfig->DoModSetup (gameinfo.ConfigName); + } +} + //========================================================================== // // D_AddFile @@ -2164,7 +2294,10 @@ void D_DoomMain (void) allwads.Clear(); allwads.ShrinkToFit(); SetMapxxFlag(); - + + // Now that wads are loaded, define mod-specific cvars. + ParseCVarInfo(); + // [RH] Initialize localizable strings. GStrings.LoadStrings (false); diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index 2f4d7c9b9..2f75d8372 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -92,22 +92,6 @@ enum const char *GenderNames[3] = { "male", "female", "other" }; -static const char *UserInfoStrings[] = -{ - "name", - "autoaim", - "color", - "skin", - "team", - "gender", - "neverswitchonpickup", - "movebob", - "stillbob", - "playerclass", - "colorset", - NULL -}; - // Replace \ with %/ and % with %% FString D_EscapeUserInfo (const char *str) { @@ -394,7 +378,7 @@ void D_SetupUserInfo () for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->GetNext()) { - if (cvar->GetFlags() & CVAR_USERINFO) + if ((cvar->GetFlags() & (CVAR_USERINFO|CVAR_NOSEND)) == CVAR_USERINFO) { FBaseCVar **newcvar; FName cvarname(cvar->GetName()); @@ -432,7 +416,7 @@ void userinfo_t::Reset() // Create userinfo vars for this player, initialized to their defaults. for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->GetNext()) { - if (cvar->GetFlags() & CVAR_USERINFO) + if ((cvar->GetFlags() & (CVAR_USERINFO|CVAR_NOSEND)) == CVAR_USERINFO) { ECVarType type; FName cvarname(cvar->GetName()); diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp index 56a8a2252..ddb331cfa 100644 --- a/src/gameconfigfile.cpp +++ b/src/gameconfigfile.cpp @@ -86,6 +86,7 @@ FGameConfigFile::FGameConfigFile () OkayToWrite = false; // Do not allow saving of the config before DoGameSetup() bMigrating = false; + bModSetup = false; pathname = GetConfigPath (true); ChangePathName (pathname); LoadConfigFile (MigrateStub, NULL); @@ -376,8 +377,8 @@ void FGameConfigFile::DoGameSetup (const char *gamename) SetRavenDefaults (gameinfo.gametype == GAME_Hexen); } - // The NetServerInfo section will be read when it's determined that - // a netgame is being played. + // The NetServerInfo section will be read and override anything loaded + // here when it's determined that a netgame is being played. strncpy (subsection, "LocalServerInfo", sublen); if (SetSection (section)) { @@ -445,6 +446,24 @@ void FGameConfigFile::DoGameSetup (const char *gamename) OkayToWrite = true; } +// Like DoGameSetup(), but for mod-specific cvars. +// Called after CVARINFO has been parsed. +void FGameConfigFile::DoModSetup(const char *gamename) +{ + mysnprintf(section, countof(section), "%s.Player.Mod", gamename); + if (SetSection(section)) + { + ReadCVars(CVAR_MODARCHIVE|CVAR_USERINFO|CVAR_NOSEND); + } + mysnprintf(section, countof(section), "%s.LocalServerInfo.Mod", gamename); + if (SetSection (section)) + { + ReadCVars (CVAR_MODARCHIVE|CVAR_SERVERINFO|CVAR_NOSEND); + } + // Signal that these sections should be rewritten when saving the config. + bModSetup = true; +} + void FGameConfigFile::ReadNetVars () { strncpy (subsection, "NetServerInfo", sublen); @@ -452,21 +471,35 @@ void FGameConfigFile::ReadNetVars () { ReadCVars (0); } + if (bModSetup) + { + mysnprintf(subsection, sublen, "NetServerInfo.Mod"); + if (SetSection(section)) + { + ReadCVars(CVAR_MODARCHIVE|CVAR_SERVERINFO|CVAR_NOSEND); + } + } } +// Read cvars from a cvar section of the ini. Flags are the flags to give +// to newly-created cvars that were not already defined. void FGameConfigFile::ReadCVars (DWORD flags) { const char *key, *value; FBaseCVar *cvar; UCVarValue val; + if (!(flags & CVAR_MODARCHIVE)) + { + flags |= CVAR_ARCHIVE|CVAR_UNSETTABLE; + } + flags |= CVAR_AUTO; while (NextInSection (key, value)) { cvar = FindCVar (key, NULL); if (cvar == NULL) { - cvar = new FStringCVar (key, NULL, - CVAR_AUTO|CVAR_UNSETTABLE|CVAR_ARCHIVE|flags); + cvar = new FStringCVar (key, NULL, flags); } val.String = const_cast(value); cvar->SetGenericRep (val, CVAR_String); @@ -485,18 +518,35 @@ void FGameConfigFile::ArchiveGameData (const char *gamename) ClearCurrentSection (); C_ArchiveCVars (this, CVAR_ARCHIVE|CVAR_USERINFO); + if (bModSetup) + { + strncpy (subsection + 6, ".Mod", sublen - 6); + SetSection (section, true); + ClearCurrentSection (); + C_ArchiveCVars (this, CVAR_MODARCHIVE|CVAR_AUTO|CVAR_USERINFO); + } + strncpy (subsection, "ConsoleVariables", sublen); SetSection (section, true); ClearCurrentSection (); C_ArchiveCVars (this, CVAR_ARCHIVE); - strncpy (subsection, netgame ? "NetServerInfo" : "LocalServerInfo", sublen); + // Do not overwrite the serverinfo section if playing a netgame, and + // this machine was not the initial host. if (!netgame || consoleplayer == 0) - { // Do not overwrite this section if playing a netgame, and - // this machine was not the initial host. + { + strncpy (subsection, netgame ? "NetServerInfo" : "LocalServerInfo", sublen); SetSection (section, true); ClearCurrentSection (); C_ArchiveCVars (this, CVAR_ARCHIVE|CVAR_SERVERINFO); + + if (bModSetup) + { + strncpy (subsection, netgame ? "NetServerInfo.Mod" : "LocalServerInfo.Mod", sublen); + SetSection (section, true); + ClearCurrentSection (); + C_ArchiveCVars (this, CVAR_MODARCHIVE|CVAR_AUTO|CVAR_SERVERINFO); + } } strncpy (subsection, "UnknownConsoleVariables", sublen); diff --git a/src/gameconfigfile.h b/src/gameconfigfile.h index 440d900da..74376b583 100644 --- a/src/gameconfigfile.h +++ b/src/gameconfigfile.h @@ -47,6 +47,7 @@ public: void DoGlobalSetup (); void DoGameSetup (const char *gamename); + void DoModSetup (const char *gamename); void ArchiveGlobalData (); void ArchiveGameData (const char *gamename); void AddAutoexec (DArgs *list, const char *gamename); @@ -65,6 +66,7 @@ private: void ReadCVars (DWORD flags); bool bMigrating; + bool bModSetup; char section[64]; char *subsection; diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index 4b4b0c5e8..dc1b8ccd8 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -957,5 +957,6 @@ CCMD(reset2saved) { GameConfig->DoGlobalSetup (); GameConfig->DoGameSetup (gameinfo.ConfigName); + GameConfig->DoModSetup (gameinfo.ConfigName); R_SetViewSize (screenblocks); } diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 2696863e1..a8469f46a 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -6885,7 +6885,8 @@ scriptwait: case PCD_GETCVAR: { FBaseCVar *cvar = FindCVar (FBehavior::StaticLookupString (STACK(1)), NULL); - if (cvar == NULL) + // Either the cvar doesn't exist, or it's for a mod that isn't loaded, so return 0. + if (cvar == NULL || (cvar->GetFlags() & CVAR_NOSEND)) { STACK(1) = 0; }