diff --git a/FeLib/CMakeLists.txt b/FeLib/CMakeLists.txt index f73c3c298..427c942c7 100644 --- a/FeLib/CMakeLists.txt +++ b/FeLib/CMakeLists.txt @@ -16,6 +16,15 @@ else() message(STATUS "Using SDL1") endif() +# libcurl, for sending/fetching high-scores to/from a global high-score server. +find_package(CURL) +if(CURL_FOUND) + message(STATUS "Using libcurl ${CURL_VERSION_STRING}") + target_compile_definitions(FeLib PUBLIC USE_HIGHSCORE_SERVER) +else() + message(WARNING "libcurl not found, global high-score submissions will be disabled") +endif() + if(MSVC) # Not very pretty solution. This finds SDL2.dll from SDL2.lib path, so that it can be installed where ivan.exe will end up. message("SDL2_LIBRARY ${SDL2_LIBRARY}") @@ -51,5 +60,6 @@ if(NOT PNG_FOUND) endif() target_include_directories(FeLib PUBLIC Include) -target_include_directories(FeLib SYSTEM PUBLIC ${SDL2_INCLUDE_DIR} PRIVATE ${PNG_INCLUDE_DIRS}) -target_link_libraries(FeLib ${SDL2_LIBRARY} ${PNG_LIBRARIES}) +target_include_directories(FeLib SYSTEM PUBLIC ${SDL2_INCLUDE_DIR} + PRIVATE ${PNG_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS}) +target_link_libraries(FeLib ${SDL2_LIBRARY} ${PNG_LIBRARIES} ${CURL_LIBRARIES}) diff --git a/FeLib/Include/config.h b/FeLib/Include/config.h index 54baf6a4f..b84c9479e 100644 --- a/FeLib/Include/config.h +++ b/FeLib/Include/config.h @@ -33,6 +33,7 @@ class configsystem static void Show(void (*)() = 0, void (*)(felist&) = 0, truth = false); static void AddOption(configoption*); static void NormalStringDisplayer(const stringoption*, festring&); + static void SecretStringDisplayer(const stringoption*, festring&); static void NormalNumberDisplayer(const numberoption*, festring&); static void NormalTruthDisplayer(const truthoption*, festring&); static void NormalCycleDisplayer(const cycleoption*, festring&); @@ -41,6 +42,7 @@ class configsystem static truth NormalTruthChangeInterface(truthoption*); static truth NormalCycleChangeInterface(cycleoption*); static void NormalStringChanger(stringoption*, cfestring&); + static void SecretStringChanger(stringoption*, cfestring&); static void NormalNumberChanger(numberoption*, long); static void NormalTruthChanger(truthoption*, truth); static void NormalCycleChanger(cycleoption*, long); diff --git a/FeLib/Include/feio.h b/FeLib/Include/feio.h index 901fd03b7..2a3a857f2 100644 --- a/FeLib/Include/feio.h +++ b/FeLib/Include/feio.h @@ -27,7 +27,7 @@ class iosystem static festring ContinueMenu(col16, col16, cfestring&); static int StringQuestion(festring&, cfestring&, v2, col16, festring::sizetype, festring::sizetype, - truth, truth, stringkeyhandler = 0); + truth, truth, stringkeyhandler = 0, truth = false); static long NumberQuestion(cfestring&, v2, col16, truth, truth = false); static long ScrollBarQuestion(cfestring&, v2, long, long, long, diff --git a/FeLib/Include/felibdef.h b/FeLib/Include/felibdef.h index 8c21d4920..fa492d1e1 100644 --- a/FeLib/Include/felibdef.h +++ b/FeLib/Include/felibdef.h @@ -214,6 +214,7 @@ inline int GetMinColor24(col24 Color) #define LIST_WAS_EMPTY 0xFFFF #define ESCAPED 0xFFFE #define NOTHING_SELECTED 0xFFFD +#define UNSELECTABLE_SELECT 0xFFFC #define NO_LIMIT 0xFFFF diff --git a/FeLib/Include/hscore.h b/FeLib/Include/hscore.h index 4caf3ff3d..38c35e086 100644 --- a/FeLib/Include/hscore.h +++ b/FeLib/Include/hscore.h @@ -28,12 +28,18 @@ class festring; +enum highscoreview +{ + LOCAL, + GLOBAL +}; + class highscore { public: highscore(cfestring& = HIGH_SCORE_FILENAME); - truth Add(long, cfestring&); - void Draw() const; + truth Add(long, cfestring&, cfestring&, cfestring&, cfestring&); + void Draw(cfestring&); void Save(cfestring& = HIGH_SCORE_FILENAME) const; void Load(cfestring& = HIGH_SCORE_FILENAME); truth LastAddFailed() const; @@ -47,13 +53,22 @@ class highscore void Clear(); truth CheckVersion() const; private: - truth Add(long, cfestring&, time_t, long); + truth Add(long, cfestring&, time_t, long, cfestring&, cfestring&, cfestring&); + void ToggleBetweenLocalAndGlobalView() { View = static_cast(!View); } std::vector Entry; std::vector Score; std::vector Time; std::vector RandomID; + std::vector GlobalEntry; + std::vector GlobalScore; + std::vector GlobalTime; int LastAdd; ushort Version; + static highscoreview View; }; +festring FetchAuthToken(cfestring& HighScoreServerURL, + cfestring& HighScoreServerUsername, + cfestring& HighScoreServerPassword); + #endif diff --git a/FeLib/Source/config.cpp b/FeLib/Source/config.cpp index c0feec36c..b54190b61 100644 --- a/FeLib/Source/config.cpp +++ b/FeLib/Source/config.cpp @@ -181,6 +181,13 @@ void configsystem::NormalStringDisplayer(const stringoption* O, Entry << '-'; } +void configsystem::SecretStringDisplayer(const stringoption* O, + festring& Entry) +{ + if(O->Value.IsEmpty()) + Entry << "not set"; +} + void configsystem::NormalNumberDisplayer(const numberoption* O, festring& Entry) { diff --git a/FeLib/Source/feio.cpp b/FeLib/Source/feio.cpp index 1500025ac..de3e4b784 100644 --- a/FeLib/Source/feio.cpp +++ b/FeLib/Source/feio.cpp @@ -286,7 +286,8 @@ int iosystem::StringQuestion(festring& Input, festring::sizetype MinLetters, festring::sizetype MaxLetters, truth Fade, truth AllowExit, - stringkeyhandler StringKeyHandler) + stringkeyhandler StringKeyHandler, + truth SecretInput) { v2 V(RES.X, 10); ///??????????? bitmap BackUp(V, 0); @@ -303,7 +304,8 @@ int iosystem::StringQuestion(festring& Input, bitmap Buffer(RES, 0); Buffer.ActivateFastFlag(); FONT->Printf(&Buffer, Pos, Color, "%s", Topic.CStr()); - FONT->Printf(&Buffer, v2(Pos.X, Pos.Y + 10), Color, "%s", Input.CStr()); + FONT->Printf(&Buffer, v2(Pos.X, Pos.Y + 10), Color, "%s", + SecretInput ? festring(Input.GetSize(), '*').CStr() : Input.CStr()); Buffer.FadeToScreen(); } else @@ -317,8 +319,8 @@ int iosystem::StringQuestion(festring& Input, { B.Bitmap = DOUBLE_BUFFER; BackUp.NormalBlit(B); - FONT->Printf(DOUBLE_BUFFER, v2(Pos.X, Pos.Y + 10), - Color, "%s", Input.CStr()); + FONT->Printf(DOUBLE_BUFFER, v2(Pos.X, Pos.Y + 10), Color, "%s", + SecretInput ? festring(Input.GetSize(), '*').CStr() : Input.CStr()); FONT->Printf(DOUBLE_BUFFER, v2(Pos.X, Pos.Y + 11), Color, "%*c", CursorPos + 1, '_'); diff --git a/FeLib/Source/felist.cpp b/FeLib/Source/felist.cpp index 4a60b9567..7382cdff4 100644 --- a/FeLib/Source/felist.cpp +++ b/FeLib/Source/felist.cpp @@ -252,10 +252,17 @@ uint felist::Draw() continue; } - if(Flags & SELECTABLE && Pressed == KEY_ENTER) + if(Pressed == KEY_ENTER) { - Return = Selected; + if(Flags & SELECTABLE) + Return = Selected; +#ifdef USE_HIGHSCORE_SERVER + else + // Used by the hall of fame to toggle between local and global scores. + Return = UNSELECTABLE_SELECT; + break; +#endif } if(Pressed == KEY_ESC) diff --git a/FeLib/Source/hscore.cpp b/FeLib/Source/hscore.cpp index c4627fd55..ffc1b4e88 100644 --- a/FeLib/Source/hscore.cpp +++ b/FeLib/Source/hscore.cpp @@ -10,24 +10,48 @@ * */ +#ifdef USE_HIGHSCORE_SERVER +#include +#endif + #include "hscore.h" #include "save.h" #include "felist.h" #include "feio.h" #include "femath.h" +#ifdef USE_HIGHSCORE_SERVER +static truth RetrieveHighScoresFromServer(cfestring&, + std::vector&, + std::vector&, + std::vector&); +static void SubmitHighScoreToServer(cfestring&, cfestring&, cfestring&, + long, cfestring&, time_t, long); +#endif + /* Increment this if changes make highscores incompatible */ #define HIGH_SCORE_VERSION 128 +highscoreview highscore::View = LOCAL; + cfestring& highscore::GetEntry(int I) const { return Entry[I]; } long highscore::GetScore(int I) const { return Score[I]; } long highscore::GetSize() const { return Entry.size(); } highscore::highscore(cfestring& File) : LastAdd(0xFF), Version(HIGH_SCORE_VERSION) { Load(File); } -truth highscore::Add(long NewScore, cfestring& NewEntry, - time_t NewTime, long NewRandomID) +truth highscore::Add(long NewScore, cfestring& NewEntry, time_t NewTime, + long NewRandomID, cfestring& HighScoreServerURL, + cfestring& HighScoreServerUsername, + cfestring& HighScoreServerAuthToken) { +#ifdef USE_HIGHSCORE_SERVER + if (!HighScoreServerURL.IsEmpty() && NewScore) + SubmitHighScoreToServer(HighScoreServerURL, HighScoreServerUsername, + HighScoreServerAuthToken, NewScore, NewEntry, + NewTime, NewRandomID); +#endif + for(uint c = 0; c < Score.size(); ++c) if(Score[c] < NewScore) { @@ -64,7 +88,7 @@ truth highscore::Add(long NewScore, cfestring& NewEntry, } } -void highscore::Draw() const +void highscore::Draw(cfestring& HighScoreServerURL) { if(Score.empty()) { @@ -79,23 +103,74 @@ void highscore::Draw() const return; } - felist List(CONST_S("Adventurers' Hall of Fame")); - festring Desc; - - for(uint c = 0; c < Score.size(); ++c) + for(;;) { - Desc.Empty(); - Desc << c + 1; - Desc.Resize(5, ' '); - Desc << Score[c]; - Desc.Resize(13, ' '); - Desc << Entry[c]; - List.AddEntry(Desc, c == uint(LastAdd) ? WHITE : LIGHT_GRAY, 13); - } + festring Title; + +#ifdef USE_HIGHSCORE_SERVER + if(View == LOCAL) + Title = CONST_S("Adventurers' Hall of Fame " + "Press ENTER to view global highscores"); + else if(View == GLOBAL) + Title = CONST_S("Global Adventurers' Hall of Fame " + "Press ENTER to view local highscores"); +#else + Title = CONST_S("Adventurers' Hall of Fame"); +#endif + + felist List(Title); + festring Desc; + + if(View == LOCAL) + for(uint c = 0; c < Score.size(); ++c) + { + Desc.Empty(); + Desc << c + 1; + Desc.Resize(5, ' '); + Desc << Score[c]; + Desc.Resize(13, ' '); + Desc << Entry[c]; + List.AddEntry(Desc, c == uint(LastAdd) ? WHITE : LIGHT_GRAY, 13); + } +#ifdef USE_HIGHSCORE_SERVER + else if(View == GLOBAL) + { + RetrieveHighScoresFromServer(HighScoreServerURL, GlobalEntry, + GlobalScore, GlobalTime); + + for(uint c = 0; c < GlobalScore.size(); ++c) + { + Desc.Empty(); + Desc << c + 1; + Desc.Resize(5, ' '); + Desc << GlobalScore[c]; + Desc.Resize(13, ' '); + Desc << GlobalEntry[c]; + List.AddEntry(Desc, LIGHT_GRAY, 13); + } + } +#endif + + List.SetFlags(FADE); + List.SetPageLength(40); - List.SetFlags(FADE); - List.SetPageLength(40); - List.Draw(); +#ifdef USE_HIGHSCORE_SERVER + cuint DrawResult = List.Draw(); + + if(DrawResult == UNSELECTABLE_SELECT) // Enter was pressed. + ToggleBetweenLocalAndGlobalView(); + else if(DrawResult == LIST_WAS_EMPTY) + { + iosystem::TextScreen(CONST_S("Couldn't fetch global highscores from the server.")); + View = LOCAL; + } + else + break; +#else + List.Draw(); + break; +#endif + } } void highscore::Save(cfestring& File) const @@ -138,16 +213,20 @@ truth highscore::MergeToFile(highscore* To) const for(uint c = 0; c < Score.size(); ++c) if(!To->Find(Score[c], Entry[c], Time[c], RandomID[c])) { - To->Add(Score[c], Entry[c], Time[c], RandomID[c]); + To->Add(Score[c], Entry[c], Time[c], RandomID[c], "", "", ""); MergedSomething = true; } return MergedSomething; } -truth highscore::Add(long NewScore, cfestring& NewEntry) +truth highscore::Add(long NewScore, cfestring& NewEntry, + cfestring& HighScoreServerURL, + cfestring& HighScoreServerUsername, + cfestring& HighScoreServerAuthToken) { - return Add(NewScore, NewEntry, time(0), RAND()); + return Add(NewScore, NewEntry, time(0), RAND(), HighScoreServerURL, + HighScoreServerUsername, HighScoreServerAuthToken); } /* Because of major stupidity this return the number of NEXT @@ -183,3 +262,162 @@ truth highscore::CheckVersion() const { return Version == HIGH_SCORE_VERSION; } + +#ifdef USE_HIGHSCORE_SERVER +static void ParseHighScoresFromCSV(cfestring& CSV, + std::vector& GlobalEntry, + std::vector& GlobalScore, + std::vector& GlobalTime) +{ + for(festring::sizetype LastIndex = 0;;) + { + festring::csizetype ScoreIndex = CSV.Find("\n", LastIndex) + 1; + if(!ScoreIndex || ScoreIndex == festring::NPos) return; + GlobalScore.push_back(atol(CSV.CStr() + ScoreIndex)); + festring::csizetype EntryIndex = CSV.Find("\"", ScoreIndex) + 1; + festring::csizetype EntryLength = CSV.Find("\"", EntryIndex) - EntryIndex; + GlobalEntry.push_back(festring(CSV.CStr() + EntryIndex, EntryLength) + '\0'); + GlobalTime.push_back(0); // TODO + LastIndex = EntryIndex + EntryLength; + } +} + +static size_t WriteMemoryCallback(char* Contents, size_t Size, size_t Count, void* UserData) +{ + const size_t RealSize = Size * Count; + *static_cast(UserData) << cfestring(Contents, RealSize); + return RealSize; +} + +static truth RetrieveHighScoresFromServer(cfestring& HighScoreServerURL, + std::vector& GlobalEntry, + std::vector& GlobalScore, + std::vector& GlobalTime) +{ + iosystem::TextScreen(CONST_S("Fetching highscores from the server...\n\n" + "This may take some time, please wait."), + ZERO_V2, WHITE, false, true); + + if(curl_global_init(CURL_GLOBAL_ALL) != 0) + return false; + + truth Success = false; + + if(CURL* Curl = curl_easy_init()) + { + festring RetrievedData; + festring HighScoreRetrieveURL = HighScoreServerURL + "/highscores"; + + curl_easy_setopt(Curl, CURLOPT_URL, HighScoreRetrieveURL.CStr()); + curl_easy_setopt(Curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, &WriteMemoryCallback); + curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &RetrievedData); + CURLcode Res = curl_easy_perform(Curl); + long ResponseCode; + curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &ResponseCode); + + if(Res == CURLE_OK && ResponseCode == 200) + { + GlobalEntry.clear(); + GlobalScore.clear(); + GlobalTime.clear(); + ParseHighScoresFromCSV(RetrievedData, GlobalEntry, GlobalScore, GlobalTime); + Success = true; + } + + curl_easy_cleanup(Curl); + } + + curl_global_cleanup(); + return Success; +} +#endif + +/* Sends a HTTP request to the specified high-score server to validate the + given username and password combination. Returns the user's auth token + if authentication was successful, otherwise returns an empty string. */ + +festring FetchAuthToken(cfestring& HighScoreServerURL, + cfestring& HighScoreServerUsername, + cfestring& HighScoreServerPassword) +{ +#ifdef USE_HIGHSCORE_SERVER + if(curl_global_init(CURL_GLOBAL_ALL) != 0) + return ""; + + festring AuthToken; + + if(CURL* Curl = curl_easy_init()) + { + festring AuthTokenFetchURL = HighScoreServerURL + "/get_auth_token"; + + curl_easy_setopt(Curl, CURLOPT_URL, AuthTokenFetchURL.CStr()); + curl_easy_setopt(Curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(Curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_easy_setopt(Curl, CURLOPT_USERNAME, HighScoreServerUsername.CStr()); + curl_easy_setopt(Curl, CURLOPT_PASSWORD, HighScoreServerPassword.CStr()); + curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, &WriteMemoryCallback); + curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &AuthToken); + CURLcode Res = curl_easy_perform(Curl); + long ResponseCode; + curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &ResponseCode); + + if(Res != CURLE_OK || ResponseCode != 200) + AuthToken.Empty(); + + curl_easy_cleanup(Curl); + } + + curl_global_cleanup(); + return AuthToken; +#else + return ""; +#endif +} + +#ifdef USE_HIGHSCORE_SERVER + +static void SubmitHighScoreToServer(cfestring& HighScoreServerURL, + cfestring& HighScoreServerUsername, + cfestring& HighScoreServerAuthToken, + long NewScore, cfestring& NewEntry, + time_t NewTime, long NewRandomID) +{ + iosystem::TextScreen(CONST_S("Submitting highscore to the server...\n\n" + "This may take some time, please wait."), + ZERO_V2, WHITE, false, true); + + if(curl_global_init(CURL_GLOBAL_ALL) != 0) + return; + + if(CURL* Curl = curl_easy_init()) + { + curl_slist* const Headers = curl_slist_append(nullptr, "Content-Type: application/json"); + + festring Json; + Json << + "{" + "\"username\": \"" << HighScoreServerUsername << "\"," + "\"auth_token\": \"" << HighScoreServerAuthToken << "\"," + "\"score\": " << NewScore << "," + "\"entry\": \"" << NewEntry << "\"," + "\"version\": \"" << IVAN_VERSION << "\"" + "}"; + + festring HighScoreSubmitURL = HighScoreServerURL + "/submit_score"; + + curl_easy_setopt(Curl, CURLOPT_URL, HighScoreSubmitURL.CStr()); + curl_easy_setopt(Curl, CURLOPT_POST, 1); + curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers); + curl_easy_setopt(Curl, CURLOPT_POSTFIELDS, Json.CStr()); + + curl_easy_perform(Curl); + + curl_slist_free_all(Headers); + curl_easy_cleanup(Curl); + } + + curl_global_cleanup(); +} + +#endif // USE_HIGHSCORE_SERVER diff --git a/FeLib/Source/rawbit.cpp b/FeLib/Source/rawbit.cpp index eb07c6118..f6f880942 100644 --- a/FeLib/Source/rawbit.cpp +++ b/FeLib/Source/rawbit.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include "png.h" @@ -453,6 +454,7 @@ void rawbitmap::Printf(bitmap* Bitmap, v2 Pos, packcol16 Color, cchar* Format, . for(int c = 0; Buffer[c]; ++c, B.Dest.X += 8) { + assert(Buffer[c] != '\n'); B.Src.X = ((Buffer[c] - 0x20) & 0xF) << 4; B.Src.Y = (Buffer[c] - 0x20) & 0xF0; //printf("'%c' -> X=%5d -- Y=%5d\n", Buffer[c], B.Src.X, B.Src.Y); diff --git a/Main/Include/iconf.h b/Main/Include/iconf.h index e2a8172b1..bacd512ab 100644 --- a/Main/Include/iconf.h +++ b/Main/Include/iconf.h @@ -31,6 +31,9 @@ class ivanconfig static truth GetBeNice() { return BeNice.Value; } static long GetVolume() { return Volume.Value; } static long GetMIDIOutputDevice() { return MIDIOutputDevice.Value; } + static cfestring& GetHighScoreServerURL() { return HighScoreServerURL.Value; } + static cfestring& GetHighScoreServerUsername() { return HighScoreServerUsername.Value; } + static cfestring& GetHighScoreServerAuthToken() { return HighScoreServerAuthToken.Value; } #ifndef __DJGPP__ static int GetGraphicsScale() { return GraphicsScale.Value; } static truth GetFullScreenMode() { return FullScreenMode.Value; } @@ -60,6 +63,7 @@ class ivanconfig static void VolumeDisplayer(const numberoption*, festring&); static truth VolumeChangeInterface(numberoption*); static void VolumeChanger(numberoption*, long); + static truth PasswordChangeInterface(stringoption*); #ifndef __DJGPP__ static void GraphicsScaleDisplayer(const cycleoption*, festring&); static truth GraphicsScaleChangeInterface(cycleoption*); @@ -81,6 +85,9 @@ class ivanconfig static truthoption BeNice; static scrollbaroption Volume; static cycleoption MIDIOutputDevice; + static stringoption HighScoreServerURL; + static stringoption HighScoreServerUsername; + static stringoption HighScoreServerAuthToken; #ifndef __DJGPP__ static cycleoption GraphicsScale; static truthoption FullScreenMode; diff --git a/Main/Source/char.cpp b/Main/Source/char.cpp index d6dcd372f..23d0d5a1c 100644 --- a/Main/Source/char.cpp +++ b/Main/Source/char.cpp @@ -2148,7 +2148,10 @@ void character::AddScoreEntry(cfestring& Description, double Multiplier, truth A Desc << " in " << game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()); } - HScore.Add(long(game::GetScore() * Multiplier), Desc); + HScore.Add(long(game::GetScore() * Multiplier), Desc, + ivanconfig::GetHighScoreServerURL(), + ivanconfig::GetHighScoreServerUsername(), + ivanconfig::GetHighScoreServerAuthToken()); HScore.Save(); } } diff --git a/Main/Source/game.cpp b/Main/Source/game.cpp index e1dcb117e..4ca245a99 100644 --- a/Main/Source/game.cpp +++ b/Main/Source/game.cpp @@ -1776,7 +1776,7 @@ void game::End(festring DeathMessage, truth Permanently, truth AndGoToMenu) + GetPlayerName() + ", " + DeathMessage + "\nRIP"); } else - HScore.Draw(); + HScore.Draw(ivanconfig::GetHighScoreServerURL()); } if(AndGoToMenu) diff --git a/Main/Source/iconf.cpp b/Main/Source/iconf.cpp index 6f11c353e..dba141836 100644 --- a/Main/Source/iconf.cpp +++ b/Main/Source/iconf.cpp @@ -18,6 +18,7 @@ #include "bitmap.h" #include "igraph.h" #include "audio.h" +#include "hscore.h" stringoption ivanconfig::DefaultName( "DefaultName", "player's default name", @@ -72,6 +73,17 @@ cycleoption ivanconfig::MIDIOutputDevice( "MIDIOutputDevice", "select MIDI output device", 0, 0, // {default value, number of options to cycle through} &MIDIOutputDeviceDisplayer); +stringoption ivanconfig::HighScoreServerURL("HighScoreServerURL", + "server to use for global high-scores", + "https://ivan-hall-of-fame.herokuapp.com"); +stringoption ivanconfig::HighScoreServerUsername("HighScoreServerUsername", + "username for submitting global high-scores", + ""); +stringoption ivanconfig::HighScoreServerAuthToken("HighScoreServerAuthToken", + "password for submitting global high-scores", + "", + &configsystem::SecretStringDisplayer, + &PasswordChangeInterface); #ifndef __DJGPP__ cycleoption ivanconfig::GraphicsScale( "GraphicsScale", "select graphics scale factor", @@ -240,6 +252,43 @@ void ivanconfig::VolumeChanger(numberoption* O, long What) audio::SetVolumeLevel(What); } +truth ivanconfig::PasswordChangeInterface(stringoption* O) +{ + festring Password; + festring Topic = CONST_S("Enter password for submitting global high-scores:"); + truth FirstTry = true; + + while(iosystem::StringQuestion(Password, Topic, v2(30, 30), WHITE, 0, 80, + true, true, nullptr, true) == NORMAL_EXIT) + { + if(Password.IsEmpty()) + { + O->ChangeValue(""); + break; + } + + festring AuthToken = FetchAuthToken(ivanconfig::GetHighScoreServerURL(), + ivanconfig::GetHighScoreServerUsername(), + Password); + + if(!AuthToken.IsEmpty()) + { + O->ChangeValue(AuthToken); + break; + } + + if(FirstTry) + { + Topic.Insert(0, "Incorrect username or password! "); + FirstTry = false; + } + + Password.Empty(); + } + + return false; +} + #ifndef __DJGPP__ void ivanconfig::GraphicsScaleDisplayer(const cycleoption* O, festring& Entry) @@ -333,6 +382,11 @@ void ivanconfig::Initialize() MIDIOutputDevice.CycleCount = NumDevices+1; configsystem::AddOption(&MIDIOutputDevice); +#ifdef USE_HIGHSCORE_SERVER + configsystem::AddOption(&HighScoreServerURL); + configsystem::AddOption(&HighScoreServerUsername); + configsystem::AddOption(&HighScoreServerAuthToken); +#endif #ifndef __DJGPP__ configsystem::AddOption(&GraphicsScale); configsystem::AddOption(&FullScreenMode); diff --git a/Main/Source/main.cpp b/Main/Source/main.cpp index 734e4e9f5..e2a844bbe 100644 --- a/Main/Source/main.cpp +++ b/Main/Source/main.cpp @@ -121,7 +121,7 @@ int main(int argc, char** argv) case 3: { highscore HScore; - HScore.Draw(); + HScore.Draw(ivanconfig::GetHighScoreServerURL()); break; } case 4: