diff --git a/src/console/c_cmds.cpp b/src/console/c_cmds.cpp index 3f697465f6..026db5c3ed 100644 --- a/src/console/c_cmds.cpp +++ b/src/console/c_cmds.cpp @@ -570,6 +570,133 @@ CCMD (special) +//========================================================================== +// +// CCMD setdate +// +// Set the time to a specific value +// +//========================================================================== + +extern time_t epochoffset; +CCMD(setdate) +{ + if (argv.argc() != 3) + { + Printf("setdate HH:MM:SS DD-MM-YYYY: Set the current date\n"); + return; + } + + time_t today; + time(&today); + struct tm* timeinfo = localtime(&today); + if (timeinfo != nullptr) + { + auto clock = FString(argv[1]).Split(":"); + auto date = FString(argv[2]).Split("-"); + if(clock.Size() != 3 || date.Size() != 3) + { + Printf("setdate HH:MM:SS DD-MM-YYYY: Set the current date\n"); + return; + } + + if(!clock[0].IsInt()) + { + Printf("Invalid hour\n"); + return; + } + if (!clock[1].IsInt()) + { + Printf("Invalid minutes\n"); + return; + } + if (!clock[2].IsInt()) + { + Printf("Invalid seconds\n"); + return; + } + if (!date[0].IsInt()) + { + Printf("Invalid day\n"); + return; + } + if (!date[1].IsInt()) + { + Printf("Invalid month\n"); + return; + } + if (!date[2].IsInt()) + { + Printf("Invalid year\n"); + return; + } + + //Set Date + timeinfo->tm_hour = int( clock[0].ToLong() ); + timeinfo->tm_min = int( clock[1].ToLong() ); + timeinfo->tm_sec = int( clock[2].ToLong() ); + timeinfo->tm_mday = int( date[0].ToLong() ); + timeinfo->tm_mon = int( date[1].ToLong() - 1); // Month interally is 0 - 11 + timeinfo->tm_year = int( date[2].ToLong() - 1900 ); // Year interally is 00 - 138 + + time_t newTime = mktime(timeinfo); + tm* t_old = localtime(&today); + time_t oldTime = mktime(t_old); + + if (newTime == -1 || oldTime == -1) + { + Printf("Unable to set the date\n"); + return; + } + + epochoffset = newTime - oldTime; + + // This deals with some inconsistent display behaviour for DST + // In this case, we want to emulate GCC's behaviour + today += epochoffset; + struct tm* t_new = localtime(&today); + if (t_new != nullptr) + { + char timeString[1024]; + if (strftime(timeString, sizeof(timeString), "%H", t_new)) + { + auto hour = FString(timeString).ToLong(); + if (hour - clock[0].ToLong() == -1 || hour - clock[0].ToLong() == 23) + epochoffset += 3600; + else if (hour - clock[0].ToLong() == 1 || hour - clock[0].ToLong() == -23) + epochoffset -= 3600; + } + } + + return; + } + else + { + Printf("Unable to set the date\n"); + return; + } +} + +CCMD(getdate) +{ + time_t now; + time(&now); + now += epochoffset; + struct tm* timeinfo = localtime(&now); + if (timeinfo != nullptr) + { + char timeString[1024]; + if (strftime(timeString, sizeof(timeString), "%H:%M:%S %d-%m-%Y%n", timeinfo)) + Printf(timeString); + else + Printf("Error Retrieving Current Date\n"); + } + else + { + Printf("Error Retrieving Current Date\n"); + } +} + //========================================================================== // // CCMD warp diff --git a/src/g_game.cpp b/src/g_game.cpp index 1cca128225..cb87715f80 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -164,6 +164,8 @@ bool playeringame[MAXPLAYERS]; int consoleplayer; // player taking events int gametic; +time_t epochoffset = 0; // epoch start in seconds (0 = January 1st, 1970) + CVAR(Bool, demo_compress, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); FString newdemoname; FString newdemomap; diff --git a/src/scripting/vmthunks.cpp b/src/scripting/vmthunks.cpp index 782a7e24f7..0bbeffd59c 100644 --- a/src/scripting/vmthunks.cpp +++ b/src/scripting/vmthunks.cpp @@ -2669,20 +2669,84 @@ DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, setFrozen, setFrozen) // //===================================================================================== -static int GetRealTime() +extern time_t epochoffset; + +static int GetEpochTime() { time_t now; time(&now); - struct tm* timeinfo = localtime(&now); - return timeinfo ? timeinfo->tm_sec + timeinfo->tm_min * 60 + timeinfo->tm_hour * 3600 : 0; + return now != (time_t)(-1) ? now + epochoffset : (time_t)(-1); } -DEFINE_ACTION_FUNCTION_NATIVE(_AltHUD, GetRealTime, GetRealTime) +//Returns an empty string if the Strf tokens are valid, otherwise returns the problematic token +static FString CheckStrfString(FString timeForm) +{ + // Valid Characters after % + const char validSingles[] = { 'a','A','b','B','c','C','d','D','e','F','G','g','h','H','I','j','k','l','m','M','n','p','P','r','R','s','S','t','T','u','U','V','w','W','x','X','y','Y','z','Z' }; + + timeForm.Substitute("%%", "%a"); //Prevent %% from causing tokenizing problems + timeForm = "a" + timeForm; //Prevent %* at the beginning from causing a false error from tokenizing + + auto tokens = timeForm.Split("%"); + for (auto t : tokens) + { + bool found = false; + // % at end + if (t.Len() == 0) return FString("%"); + + // Single Character + for (int i = 0; i < sizeof(validSingles)/sizeof(validSingles[0]); i++) + { + if (t[0] == validSingles[i]) + { + found = true; + break; + } + } + if (found) continue; + return FString("%") + t[0]; + } + return ""; +} + +static void FormatTime(const FString& timeForm, int timeVal, FString* result) +{ + FString error = CheckStrfString(timeForm); + if (!error.IsEmpty()) + ThrowAbortException(X_FORMAT_ERROR, "'%s' is not a valid format specifier of SystemTime.Format()", error.GetChars()); + + time_t val = timeVal; + struct tm* timeinfo = localtime(&val); + if (timeinfo != nullptr) + { + char timeString[1024]; + if (strftime(timeString, sizeof(timeString), timeForm, timeinfo)) + *result = timeString; + } +} + +DEFINE_ACTION_FUNCTION_NATIVE(_SystemTime, Now, GetEpochTime) { PARAM_PROLOGUE; - ACTION_RETURN_INT(GetRealTime()); + ACTION_RETURN_INT(GetEpochTime()); } +DEFINE_ACTION_FUNCTION_NATIVE(_SystemTime, Format, FormatTime) +{ + PARAM_PROLOGUE; + PARAM_STRING(timeForm); + PARAM_INT(timeVal); + FString result; + FormatTime(timeForm, timeVal, &result); + ACTION_RETURN_STRING(result); +} + +//===================================================================================== +// +// +// +//===================================================================================== + DEFINE_ACTION_FUNCTION_NATIVE(_AltHUD, GetLatency, Net_GetLatency) { PARAM_PROLOGUE; diff --git a/wadsrc/static/zscript/base.zs b/wadsrc/static/zscript/base.zs index abed488f22..bd5298cc1f 100644 --- a/wadsrc/static/zscript/base.zs +++ b/wadsrc/static/zscript/base.zs @@ -422,6 +422,12 @@ struct GameInfoStruct native native double normsidemove[2]; } +struct SystemTime +{ + native static ui int Now(); // This returns the epoch time + native static clearscope String Format(String timeForm, int timeVal); // This converts an epoch time to a local time, then uses the strftime syntax to format it +} + class Object native { const TICRATE = 35; diff --git a/wadsrc/static/zscript/ui/statusbar/alt_hud.zs b/wadsrc/static/zscript/ui/statusbar/alt_hud.zs index 7ddbf902df..b3bd3b4a59 100644 --- a/wadsrc/static/zscript/ui/statusbar/alt_hud.zs +++ b/wadsrc/static/zscript/ui/statusbar/alt_hud.zs @@ -770,13 +770,12 @@ class AltHud ui // for meaning of all display modes // //--------------------------------------------------------------------------- - private native static int GetRealTime(); - virtual bool DrawTime(int y) { if (hud_showtime > 0 && hud_showtime <= 9) { int timeSeconds; + String timeString; if (hud_showtime < 8) { @@ -787,33 +786,35 @@ class AltHud ui ? Level.time : Level.totaltime); timeSeconds = Thinker.Tics2Seconds(timeTicks); - } - else - { - timeSeconds = GetRealTime(); - } - int hours = timeSeconds / 3600; - int minutes = (timeSeconds % 3600) / 60; - int seconds = timeSeconds % 60; + int hours = timeSeconds / 3600; + int minutes = (timeSeconds % 3600) / 60; + int seconds = timeSeconds % 60; - bool showMillis = 1 == hud_showtime; - bool showSeconds = showMillis || (0 == hud_showtime % 2); + bool showMillis = 1 == hud_showtime; + bool showSeconds = showMillis || (0 == hud_showtime % 2); - String timeString; - - if (showMillis) - { - int millis = (Level.time % Thinker.TICRATE) * (1000 / Thinker.TICRATE); - timeString = String.Format("%02i:%02i:%02i.%03i", hours, minutes, seconds, millis); + if (showMillis) + { + int millis = (Level.time % Thinker.TICRATE) * (1000 / Thinker.TICRATE); + timeString = String.Format("%02i:%02i:%02i.%03i", hours, minutes, seconds, millis); + } + else if (showSeconds) + { + timeString = String.Format("%02i:%02i:%02i", hours, minutes, seconds); + } + else + { + timeString = String.Format("%02i:%02i", hours, minutes); + } } - else if (showSeconds) + else if (hud_showtime == 8) { - timeString = String.Format("%02i:%02i:%02i", hours, minutes, seconds); + timeString = SystemTime.Format("%H:%M:%S",SystemTime.Now()); } - else + else //if (hud_showtime == 9) { - timeString = String.Format("%02i:%02i", hours, minutes); + timeString = SystemTime.Format("%H:%M",SystemTime.Now()); } int characterCount = timeString.length();