diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6d00ae0a..f63da0efb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,4 +100,4 @@ jobs: uses: actions/upload-artifact@master with: name: Game_mac - path: bin + path: bin \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 745598aca..fa5262ab0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ find_package(Vorbis REQUIRED) find_package(OGG REQUIRED) find_package(LibArchive REQUIRED) find_package(Iconv REQUIRED) +find_package(CryptoPP REQUIRED) # All projects use unicode define # this is mainly for windows functions either being defined to call A or W prefixed functions diff --git a/Main/CMakeLists.txt b/Main/CMakeLists.txt index 6d0f01218..03d77333b 100644 --- a/Main/CMakeLists.txt +++ b/Main/CMakeLists.txt @@ -80,7 +80,9 @@ target_link_libraries(usc-game nlohmann_json) target_link_libraries(usc-game ${SDL2_LIBRARY}) target_link_libraries(usc-game ${LibArchive_LIBRARIES}) +target_link_libraries(usc-game ${CryptoPP_LIBRARIES}) target_include_directories(usc-game SYSTEM PRIVATE ${LibArchive_INCLUDE_DIRS}) +target_include_directories(usc-game SYSTEM PRIVATE ${CryptoPP_INCLUDE_DIRS}) if(WIN32) target_link_libraries(usc-game diff --git a/Main/include/Application.hpp b/Main/include/Application.hpp index 7edc81c35..32a8c4234 100644 --- a/Main/include/Application.hpp +++ b/Main/include/Application.hpp @@ -16,6 +16,7 @@ extern class JobSheduler* g_jobSheduler; extern class Input g_input; extern class SkinConfig* g_skinConfig; extern class TransitionScreen* g_transition; +extern class NetworkingServices* g_networkingServices; class Application { diff --git a/Main/include/GameConfig.hpp b/Main/include/GameConfig.hpp index cce3397e8..336eca658 100644 --- a/Main/include/GameConfig.hpp +++ b/Main/include/GameConfig.hpp @@ -162,7 +162,12 @@ DefineEnum(GameConfigKeys, // Gameplay options GaugeType, MirrorChart, - RandomizeChart) + RandomizeChart, + + // Internet Ranking + IRBaseURL, + IRUsername, + IRPassword) DefineEnum(GaugeTypes, Normal, diff --git a/Main/include/NetworkingServices.hpp b/Main/include/NetworkingServices.hpp new file mode 100644 index 000000000..f27c1b564 --- /dev/null +++ b/Main/include/NetworkingServices.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include "GameConfig.hpp" +#include "Game.hpp" +#include "lua.hpp" + +class NetworkingServices +{ +public: + NetworkingServices(); + void Init(GameConfig* config); + bool TryLogin(); + void QueueHeartbeat(); + virtual bool SubmitScore(class Game* game, GameFlags m_flags); + bool ConnectionStatus(); + void PushLuaFunctions(lua_State* L); + int lGetScoresForTrack(lua_State* L); + int lIsConnected(lua_State* L); + +private: + bool Heartbeat(); + void WaitForHeartbeat(); + + // Configurable + String m_serviceUrl; + String m_username; + String m_password; + + // Connection Dependant + bool m_isConnected; + String m_refreshToken; + String m_bearerToken; + + // Static + std::thread::id m_heartbeat; + + // Lua + Map m_boundStates; +}; + +extern class NetworkingServices; \ No newline at end of file diff --git a/Main/src/Application.cpp b/Main/src/Application.cpp index 245e6bd35..d1dbf5b4a 100644 --- a/Main/src/Application.cpp +++ b/Main/src/Application.cpp @@ -23,6 +23,7 @@ #include "SkinConfig.hpp" #include "SkinHttp.hpp" #include "ShadedMesh.hpp" +#include "NetworkingServices.hpp" #ifdef EMBEDDED #define NANOVG_GLES2_IMPLEMENTATION @@ -48,6 +49,7 @@ Graphics::Window *g_gameWindow = nullptr; Application *g_application = nullptr; JobSheduler *g_jobSheduler = nullptr; TransitionScreen *g_transition = nullptr; +NetworkingServices* g_networkingServices = nullptr; Input g_input; // Tickable queue @@ -880,6 +882,15 @@ bool Application::m_Init() Path::CreateDir(Path::Absolute("replays")); Path::CreateDir(Path::Absolute("crash_dumps")); + // Initalize NetworkServices + g_networkingServices = new NetworkingServices(); + g_networkingServices->Init(&g_gameConfig); + auto net_loggedin = g_networkingServices->TryLogin(); + if (net_loggedin) + { + g_networkingServices->QueueHeartbeat(); + } + return true; } void Application::m_MainLoop() @@ -2353,6 +2364,9 @@ void Application::SetLuaBindings(lua_State *state) //http m_skinHttp.PushFunctions(state); + + //netserv + g_networkingServices->PushLuaFunctions(state); } bool JacketLoadingJob::Run() diff --git a/Main/src/GameConfig.cpp b/Main/src/GameConfig.cpp index 63afba7c9..b56294a23 100644 --- a/Main/src/GameConfig.cpp +++ b/Main/src/GameConfig.cpp @@ -201,6 +201,11 @@ void GameConfig::InitDefaults() Set(GameConfigKeys::MultiplayerUsername, ""); Set(GameConfigKeys::EnableFancyHighwayRoll, true); + + // Internet Ranking + Set(GameConfigKeys::IRBaseURL, "https://api.orchestra.fm"); + Set(GameConfigKeys::IRUsername, ""); + Set(GameConfigKeys::IRPassword, ""); //Gameplay Set(GameConfigKeys::RandomizeChart, false); diff --git a/Main/src/NetworkingServices.cpp b/Main/src/NetworkingServices.cpp new file mode 100644 index 000000000..c94f331e2 --- /dev/null +++ b/Main/src/NetworkingServices.cpp @@ -0,0 +1,346 @@ +#include "stdafx.h" +#include "NetworkingServices.hpp" +#include "GameConfig.hpp" +#include +#include +#include +#include +#include "cryptopp/cryptlib.h" +#include "cryptopp/hex.h" +#include "cryptopp/sha3.h" +#include +#include +#include "Game.hpp" +#include "Scoring.hpp" +#include "lua.hpp" +#include "Shared/LuaBindable.hpp" + +NetworkingServices::NetworkingServices() +{ + m_isConnected = false; + m_refreshToken = ""; + m_bearerToken = ""; + + Logf("[NetworkingServices] Module Enabled", Logger::Severity::Normal); +} + +void NetworkingServices::Init(GameConfig* config) +{ + m_serviceUrl = config->GetString(GameConfigKeys::IRBaseURL); + m_username = config->GetString(GameConfigKeys::IRUsername); + m_password = config->GetString(GameConfigKeys::IRPassword); + + Logf("[NetworkingServices] Module Initialized", Logger::Severity::Normal); +} + +bool NetworkingServices::TryLogin() +{ + Logf("[NetworkingServices] Attempting to login", Logger::Severity::Normal); + // assemble json request body data + nlohmann::json login_creds = { + {"username", m_username, }, + {"password", m_password, }, + }; + + // attempt to login + cpr::Response resp = cpr::Post(cpr::Url{ m_serviceUrl + "/api/v0/authorize/basic" }, + cpr::Body{ login_creds.dump() }, + cpr::Header{ {"Content-Type", "application/json"} }); + Logf("[NetworkingServices] Login request sent.", Logger::Severity::Normal); + + // on success + if (resp.status_code == 202) { + nlohmann::json tkns = nlohmann::json::parse(resp.text); + m_refreshToken = tkns["refresh"].get(); + m_bearerToken = tkns["bearer"].get(); + Logf("[NetworkingServices] Logged In", Logger::Severity::Normal); + return m_isConnected = true; + } + + // on failure + return m_isConnected = false; +} + +bool NetworkingServices::Heartbeat() +{ + // assemble json request body data + nlohmann::json login_creds = { + {"refresh_token", m_refreshToken, }, + }; + + // attempt to refresh credentials + cpr::Response resp = cpr::Post(cpr::Url{ m_serviceUrl + "/api/v0/authorize/refresh" }, + cpr::Body{ login_creds.dump() }, + cpr::Header{ {"Content-Type", "application/json"} }); + + // on success + if (resp.status_code == 202) { + nlohmann::json tkns = nlohmann::json::parse(resp.text); + m_refreshToken = tkns["refresh"].get(); + m_bearerToken = tkns["bearer"].get(); + return m_isConnected = true; + } + + // on failure + return false; +} + +void NetworkingServices::WaitForHeartbeat() +{ + m_heartbeat = std::this_thread::get_id(); + std::this_thread::sleep_for(std::chrono::minutes(2)); + NetworkingServices::Heartbeat(); + std::thread(&NetworkingServices::WaitForHeartbeat, this).detach(); +} + +void NetworkingServices::QueueHeartbeat() +{ + std::thread (&NetworkingServices::WaitForHeartbeat, this).detach(); +} + +bool NetworkingServices::ConnectionStatus() +{ + return m_isConnected; +} + +bool NetworkingServices::SubmitScore(class Game* game, GameFlags m_flags) +{ + // get scoring info + Scoring& m_scoring = game->GetScoring(); + + // check if we can login + String url = m_serviceUrl; + + // hash the file of the chart we played + CryptoPP::SHA3_512 hash; + String digest; + + CryptoPP::FileSource f( + String(game->GetChartIndex()->path).c_str(), + true, + new CryptoPP::HashFilter( + hash, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(digest), false)), true); + + // get the ids from the file hash + auto res = nlohmann::json::parse(cpr::Get(cpr::Url{ url + "/api/v0/board/sha3/" + digest }).text); + + uint64 track_id = res["track_id"]; + uint64 board_id = res["id"]; + + //auto replay = String(nlohmann::json::parse(m_simpleHitStats)); + + // post the score + nlohmann::json score_info = { + {"track", track_id, }, + {"board", board_id, }, + {"score", m_scoring.CalculateCurrentScore(), }, + {"combo", m_scoring.maxComboCounter, }, + {"rate", m_scoring.currentGauge, }, + {"criticals", m_scoring.categorizedHits[2], }, + {"nears", m_scoring.categorizedHits[1] , }, + {"errors", m_scoring.categorizedHits[0], }, + {"mods", m_flags, }, + //{"replaydata", replay, }, + }; + cpr::Response resp = cpr::Post(cpr::Url{ url + "/api/v0/score" }, + cpr::Body{ score_info.dump() }, + cpr::Header{ {"Content-Type", "application/json"}, + {"Authorization", "Bearer " + m_bearerToken }, + }); + if (resp.status_code == 200) + { + return true; + } + else + { + return false; + } +} + +void m_PushStringToTable(lua_State* m_lua, const char* name, const char* data) +{ + lua_pushstring(m_lua, name); + lua_pushstring(m_lua, data); + lua_settable(m_lua, -3); +} + +void m_PushFloatToTable(lua_State* m_lua, const char* name, float data) +{ + lua_pushstring(m_lua, name); + lua_pushnumber(m_lua, data); + lua_settable(m_lua, -3); +} + +void m_PushIntToTable(lua_State* m_lua, const char* name, int data) +{ + lua_pushstring(m_lua, name); + lua_pushinteger(m_lua, data); + lua_settable(m_lua, -3); +} + +int NetworkingServices::lGetScoresForTrack(lua_State* L) +{ + // TODO: cache the info so we aren't always wasting bandwidth + // get track we are talking about + String hash = luaL_checkstring(L, 2); + cpr::Response respTrackInfo = cpr::Get(cpr::Url{ m_serviceUrl + "/api/v0/board/sha3/" + hash }); + if (respTrackInfo.status_code != 200) + { + return 0; + } + + auto res = nlohmann::json::parse(respTrackInfo.text); + uint64 track_id = res["track_id"]; + uint64 board_id = res["id"]; + + uint8 limit = luaL_checkinteger(L, 3); + uint16 offset = luaL_checkinteger(L, 4); + + // get scores from board + cpr::Response respScoreList = cpr::Get(cpr::Url{ m_serviceUrl + + "/api/v0/score?track=" + std::to_string(track_id) + + "&board=" + std::to_string(board_id) + + "&limit=" + std::to_string(limit) + + "&offset=" + std::to_string(offset)}); + + if (respScoreList.status_code != 200) + { + return 0; + } + + lua_pushstring(L, "scores"); + lua_newtable(L); + int idx = 0; + for (auto scoreEntry : nlohmann::json::parse(respScoreList.text)) + { + lua_pushinteger(L, ++idx); + lua_newtable(L); + uint64 profileID = scoreEntry["profile"]; + cpr::Response respPlayerInfo = cpr::Get(cpr::Url{ m_serviceUrl + "/api/v0/profile/" + std::to_string(profileID) }); + String playerName; + if (respPlayerInfo.status_code != 200) + { + playerName = "UNINITALIZEZD"; + } + else + { + nlohmann::json resp = nlohmann::json::parse(respPlayerInfo.text); + String pplayerName = resp["name"]; // QUEST: Why do I need to do this and can't assign to playerName directly?? + playerName = pplayerName; + } + String dateCreated = scoreEntry["date_created"]; + float perfRating; + if (scoreEntry["performance"].is_null()) + { + perfRating = 0; + } + else + { + perfRating = scoreEntry["performance"]; + } + uint64 score; + if (scoreEntry["score"].is_null()) + { + score = 0; + } + else + { + score = scoreEntry["score"]; + } + uint32 combo; + if (scoreEntry["combo"].is_null()) + { + combo = 0; + } + else + { + combo = scoreEntry["combo"]; + } + uint8 status = scoreEntry["status"]; + float rate; + if (scoreEntry["rate"].is_null()) + { + rate = 0; + } + else + { + rate = scoreEntry["rate"]; + } + double accuracy; + if (scoreEntry["accuracy"].is_null()) + { + accuracy = 0; + } + else + { + accuracy = scoreEntry["accuracy"]; + } + uint32 crits; + if (scoreEntry["criticals"].is_null()) + { + crits = 0; + } + else + { + crits = scoreEntry["criticals"]; + } + uint32 nears; + if (scoreEntry["nears"].is_null()) + { + nears = 0; + } + else + { + nears = scoreEntry["nears"]; + } + uint32 errors; + if (scoreEntry["errors"].is_null()) + { + errors = 0; + } + else + { + errors = scoreEntry["errors"]; + } + Logf("[NetworkingServices] Date Created: %s", Logger::Severity::Normal, dateCreated); + Logf("[NetworkingServices] perfRating: %g", Logger::Severity::Normal, perfRating); + Logf("[NetworkingServices] Score: %d", Logger::Severity::Normal, score); + Logf("[NetworkingServices] Combo: %d", Logger::Severity::Normal, combo); + Logf("[NetworkingServices] Status: %d", Logger::Severity::Normal, status); + Logf("[NetworkingServices] Rate: %g", Logger::Severity::Normal, rate); + Logf("[NetworkingServices] Accuracy: %g", Logger::Severity::Normal, accuracy); + m_PushStringToTable(L, "player", playerName.c_str()); + m_PushStringToTable(L, "date_created", dateCreated.c_str()); + m_PushFloatToTable(L, "performance", perfRating); + m_PushIntToTable(L, "score", score); + m_PushIntToTable(L, "combo", combo); + m_PushIntToTable(L, "status", status); + m_PushFloatToTable(L, "rate", rate); + m_PushFloatToTable(L, "accuracy", accuracy); + m_PushIntToTable(L, "criticals", crits); + m_PushIntToTable(L, "nears", nears); + m_PushIntToTable(L, "errors", errors); + + lua_settable(L, -3); + } + + return 1; +} + +int NetworkingServices::lIsConnected(lua_State* L) { + lua_pushboolean(L, ConnectionStatus()); + + return 1; +} + +void NetworkingServices::PushLuaFunctions(lua_State* L) +{ + auto bindable = new LuaBindable(L, "NetServ"); + bindable->AddFunction("GetScoresForTrack", this, &NetworkingServices::lGetScoresForTrack); + bindable->AddFunction("IsConnected", this, &NetworkingServices::lIsConnected); + bindable->Push(); + lua_settop(L, 0); + //m_boundStates.Add(L, bindable); +} \ No newline at end of file diff --git a/Main/src/ScoreScreen.cpp b/Main/src/ScoreScreen.cpp index 44d54e579..73b275125 100644 --- a/Main/src/ScoreScreen.cpp +++ b/Main/src/ScoreScreen.cpp @@ -15,6 +15,13 @@ #include #include "MultiplayerScreen.hpp" #include "ChatOverlay.hpp" +#include +#include "cryptopp/cryptlib.h" +#include "cryptopp/hex.h" +#include "cryptopp/sha3.h" +#include +#include +#include "NetworkingServices.hpp" class ScoreScreen_Impl : public ScoreScreen { @@ -238,7 +245,7 @@ class ScoreScreen_Impl : public ScoreScreen m_graphTex->SetWrap(Graphics::TextureWrap::Clamp, Graphics::TextureWrap::Clamp); m_numPlayersSeen = m_stats->size(); - m_displayId = static_cast((*m_stats)[m_displayIndex].value("uid","")); + m_displayId = static_cast((*m_stats)[m_displayIndex].value("uid", "")); } @@ -285,6 +292,11 @@ class ScoreScreen_Impl : public ScoreScreen loadScoresFromGame(game); } + if (g_networkingServices->ConnectionStatus() == true) + { + g_networkingServices->SubmitScore(game, m_flags); + } + for (HitStat* stat : scoring.hitStats) { if (!stat->forReplay) @@ -419,6 +431,18 @@ class ScoreScreen_Impl : public ScoreScreen void updateLuaData() { + // hash the file of the chart we played + CryptoPP::SHA3_512 hash; + String digest; + + CryptoPP::FileSource f( + String(m_chartIndex->path).c_str(), + true, + new CryptoPP::HashFilter( + hash, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(digest), false)), true); + const bool isSelf = m_displayIndex == m_selfDisplayIndex; lua_newtable(m_lua); @@ -431,6 +455,7 @@ class ScoreScreen_Impl : public ScoreScreen m_PushIntToTable("maxCombo", m_maxCombo); m_PushIntToTable("level", m_beatmapSettings.level); m_PushIntToTable("difficulty", m_beatmapSettings.difficulty); + m_PushStringToTable("sha3", digest); if (m_multiplayer) { m_PushStringToTable("playerName", m_playerName); diff --git a/Main/src/SettingsScreen.cpp b/Main/src/SettingsScreen.cpp index 7c4bf1c52..c7e969291 100644 --- a/Main/src/SettingsScreen.cpp +++ b/Main/src/SettingsScreen.cpp @@ -194,6 +194,12 @@ class SettingsScreen_Impl : public SettingsScreen int m_multiplayerPasswordLen = 0; char m_multiplayerUsername[1024]; int m_multiplayerUsernameLen = 0; + char m_irBaseURL[1024]; + int m_irBaseURLLen = 0; + char m_irUsername[1024]; + int m_irUsernameLen = 0; + char m_irPassword[1024]; + int m_irPasswordLen = 0; const Vector* m_activeBTKeys = &m_keyboardKeys; const Vector* m_activeLaserKeys = &m_keyboardLaserKeys; bool m_useBTGamepad = false; @@ -282,6 +288,21 @@ class SettingsScreen_Impl : public SettingsScreen multiplayerUsername.TrimBack(' '); g_gameConfig.Set(GameConfigKeys::MultiplayerUsername, multiplayerUsername); + String irBaseURL = String(m_irBaseURL, m_irBaseURLLen); + irBaseURL.TrimBack('\n'); + irBaseURL.TrimBack(' '); + g_gameConfig.Set(GameConfigKeys::IRBaseURL, irBaseURL); + + String irUsername = String(m_irUsername, m_irUsernameLen); + irUsername.TrimBack('\n'); + irUsername.TrimBack(' '); + g_gameConfig.Set(GameConfigKeys::IRUsername, irUsername); + + String irPassword = String(m_irPassword, m_irPasswordLen); + irPassword.TrimBack('\n'); + irPassword.TrimBack(' '); + g_gameConfig.Set(GameConfigKeys::IRPassword, irPassword); + if (g_gameConfig.GetEnum(GameConfigKeys::ButtonInputDevice) == InputDevice::Mouse) { g_gameConfig.SetEnum(GameConfigKeys::ButtonInputDevice, InputDevice::Keyboard); @@ -520,6 +541,18 @@ class SettingsScreen_Impl : public SettingsScreen strcpy(m_multiplayerUsername, multiplayerUsername.c_str()); m_multiplayerUsernameLen = multiplayerUsername.length(); + String irBaseURL = g_gameConfig.GetString(GameConfigKeys::IRBaseURL); + strcpy(m_irBaseURL, irBaseURL.c_str()); + m_irBaseURLLen = irBaseURL.length(); + + String irUsername = g_gameConfig.GetString(GameConfigKeys::IRUsername); + strcpy(m_irUsername, irUsername.c_str()); + m_irUsernameLen = irUsername.length(); + + String irPassword = g_gameConfig.GetString(GameConfigKeys::IRPassword); + strcpy(m_irPassword, irPassword.c_str()); + m_irPasswordLen = irPassword.length(); + return true; } @@ -964,6 +997,15 @@ class SettingsScreen_Impl : public SettingsScreen nk_label(m_nctx, "Multiplayer Server Password:", nk_text_alignment::NK_TEXT_LEFT); nk_sdl_text(nk_edit_string(m_nctx, NK_EDIT_FIELD, m_multiplayerPassword, &m_multiplayerPasswordLen, 1024, nk_filter_default)); + + nk_label(m_nctx, "Internet Ranking URL:", nk_text_alignment::NK_TEXT_LEFT); + nk_sdl_text(nk_edit_string(m_nctx, NK_EDIT_FIELD, m_irBaseURL, &m_irBaseURLLen, 1024, nk_filter_default)); + + nk_label(m_nctx, "Account Username for Internet Ranking:", nk_text_alignment::NK_TEXT_LEFT); + nk_sdl_text(nk_edit_string(m_nctx, NK_EDIT_FIELD, m_irUsername, &m_irUsernameLen, 1024, nk_filter_default)); + + nk_label(m_nctx, "Account Password for Internet Ranking:", nk_text_alignment::NK_TEXT_LEFT); + nk_sdl_text(nk_edit_string(m_nctx, NK_EDIT_FIELD, m_irPassword, &m_irPasswordLen, 1024, nk_filter_default)); nk_tree_pop(m_nctx); } else diff --git a/Main/src/SongSelect.cpp b/Main/src/SongSelect.cpp index 7183b0e87..a25f0090e 100644 --- a/Main/src/SongSelect.cpp +++ b/Main/src/SongSelect.cpp @@ -21,6 +21,11 @@ #include "SongSort.hpp" #include "DBUpdateScreen.hpp" #include "PreviewPlayer.hpp" +#include "cryptopp/cryptlib.h" +#include "cryptopp/hex.h" +#include "cryptopp/sha3.h" +#include +#include class TextInput { @@ -839,6 +844,18 @@ class SelectionWheel m_PushStringToTable("effector", diff->effector.c_str()); m_PushStringToTable("illustrator", diff->illustrator.c_str()); m_PushIntToTable("topBadge", static_cast(Scoring::CalculateBestBadge(diff->scores))); + // TODO: Calculating a SHA3-512 hash everytime the wheel spins is going to be slow, we should cache this in the MapDatabase + CryptoPP::SHA3_512 hash; + String digest; + + CryptoPP::FileSource f( + String(diff->path).c_str(), + true, + new CryptoPP::HashFilter( + hash, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(digest), false)), true); + m_PushStringToTable("sha3_512", digest.c_str()); lua_pushstring(m_lua, "scores"); lua_newtable(m_lua); int scoreIndex = 0; diff --git a/Shared/CMakeLists.txt b/Shared/CMakeLists.txt index c714e9ece..f4a891120 100644 --- a/Shared/CMakeLists.txt +++ b/Shared/CMakeLists.txt @@ -45,6 +45,7 @@ target_include_directories(Shared PRIVATE ) target_link_libraries(Shared lua) +target_link_libraries(Shared cryptopp-static) target_link_libraries(Shared ${Iconv_LIBRARIES}) target_include_directories(Shared SYSTEM PRIVATE ${Iconv_INCLUDE_DIRS}) diff --git a/bin/skins/Default/scripts/result.lua b/bin/skins/Default/scripts/result.lua index 4a481846e..1b05c3539 100644 --- a/bin/skins/Default/scripts/result.lua +++ b/bin/skins/Default/scripts/result.lua @@ -146,7 +146,24 @@ result_set = function() hitGraphHoverScale = 10 end - if result.uid == nil then --local scores + if NetServ.IsConnected() == true then + for i,s in ipairs(NetServ.GetScoresForTrack(result.sha3, 50, 0)) do + entry = { } + + game.Log(s.score, 2) + game.Log(s.player, 2) + game.Log(s.rate, 2) + game.Log(s.status, 2) + entry.score = s.score + entry.subtext = s.player + entry.badge = s.status + entry.badgeDesc = getScoreBadgeDesc(result) + entry.color = {255, 127, 0} + entry.xoff = 0 + + table.insert(highScores, entry) + end + elseif result.uid == nil then --local scores for i,s in ipairs(result.highScores) do newScore = { } if currentAdded == false and result.score > s.score then diff --git a/bin/skins/Default/scripts/songselect/songwheel.lua b/bin/skins/Default/scripts/songselect/songwheel.lua index d529a6c33..80e528573 100644 --- a/bin/skins/Default/scripts/songselect/songwheel.lua +++ b/bin/skins/Default/scripts/songselect/songwheel.lua @@ -51,6 +51,8 @@ local badges = { gfx.CreateSkinImage("badges/perfect.png", 0) } +local recordCache = {} + gfx.LoadSkinFont("NotoSans-Regular.ttf"); game.LoadSkinSample("menu_click") @@ -138,43 +140,108 @@ check_or_create_cache = function(song, loadJacket) end end +function get_record(hash) + if recordCache[hash] then + return recordCache[hash] + end + + -- TODO: Set a field for when this cache expires (every 5 minutes) + recordCache[hash] = NetServ.GetScoresForTrack(hash, 1, 0) +end + draw_scores = function(difficulty, x, y, w, h) -- draw the top score for this difficulty local xOffset = 5 local height = h/3 - 10 local ySpacing = h/3 local yOffset = h/3 + gfx.FontSize(30); gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER); - gfx.FastText("HIGH SCORE", x +(w/2), y+(h/2)) + gfx.FastText("LOCAL SCORE", x +(w/4), y+(h/2)) + gfx.FastText("ONLINE RECORD", x + (3/4 * w), y + (h/2)) + + -- local high score rect gfx.BeginPath() - gfx.Rect(x+xOffset,y+h/2,w-(xOffset*2),h/2) + gfx.Rect(x+xOffset,y+h/2,w/2-(xOffset*2),h/2) gfx.FillColor(30,30,30,10) gfx.StrokeColor(0,128,255) gfx.StrokeWidth(1) gfx.Fill() gfx.Stroke() - if difficulty.scores[1] ~= nil then - local highScore = difficulty.scores[1] - scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0) - for i,v in ipairs(grades) do - if v.max > highScore.score then - gfx.BeginPath() - iw,ih = gfx.ImageSize(v.image) - iar = iw / ih; - gfx.ImageRect(x+xOffset,y+h/2 +5, iar * (h/2-10),h/2-10, v.image, 1, 0) - break - end + + -- online record rect + gfx.BeginPath() + gfx.Rect(x + xOffset + w/2,y+h/2,w/2-(xOffset*2),h/2) + gfx.FillColor(30,30,30,10) + gfx.StrokeColor(0,128,255) + gfx.StrokeWidth(1) + gfx.Fill() + gfx.Stroke() + + if difficulty.scores[1] ~= nil then + local highScore = difficulty.scores[1] + scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0) + for i,v in ipairs(grades) do + if v.max > highScore.score then + gfx.BeginPath() + iw,ih = gfx.ImageSize(v.image) + iarr = ih / iw + oldheight = h/2 - 10 + newheight = iarr * (h/2-10) + centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh + gfx.ImageRect(x+xOffset, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me + break + end end if difficulty.topBadge ~= 0 then gfx.BeginPath() - gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0) + gfx.ImageRect(x+xOffset+w/2-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0) end + gfx.FillColor(255,255,255) - gfx.FontSize(40); + gfx.FontSize(40); gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); - gfx.DrawLabel(scoreLabel, x+(w/2),y+(h/4)*3,w) + gfx.DrawLabel(scoreLabel, x+(w/4),y+(h/4)*3,w/2) + end + + onlinerecord = get_record(difficulty.sha3_512) + + if onlinerecord == nil then --record not set, but can be tracked + recordLabel = gfx.CreateLabel(string.format("%08d", 0), 40, 0) + gfx.FillColor(170, 170, 170) + gfx.FontSize(40) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); + gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2) + else + for i,s in ipairs(onlinerecord) do + recordScoreLabel = gfx.CreateLabel(string.format("%08d", s.score), 26, 0) + recordPlayerLabel = gfx.CreateLabel(s.player, 26, 0) + + gfx.BeginPath() + gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[s.status], 1, 0) + + for ii,v in ipairs(grades) do + if v.max > s.score then + gfx.BeginPath() + iw,ih = gfx.ImageSize(v.image) + iarr = ih / iw + oldheight = h/2 - 10 + newheight = iarr * (h/2-10) + centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh + gfx.ImageRect(x+xOffset+w/2, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me + break + end + end + + gfx.FillColor(255, 255, 255) + gfx.FontSize(40) + gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); + gfx.DrawLabel(recordPlayerLabel, x+(w * 3/4),y+(h/4)*2.55,w/2) + gfx.DrawLabel(recordScoreLabel, x+(w * 3/4),y+(h/4)*3.45,w/2) + end end + end draw_song = function(song, x, y, w, h, selected) diff --git a/bin/skins/Default/scripts/titlescreen.lua b/bin/skins/Default/scripts/titlescreen.lua index 84bff4084..16bd198e1 100644 --- a/bin/skins/Default/scripts/titlescreen.lua +++ b/bin/skins/Default/scripts/titlescreen.lua @@ -72,11 +72,17 @@ end function setButtons() if buttons == nil then buttons = {} - buttons[1] = {"Start", Menu.Start} - buttons[2] = {"Multiplayer", Menu.Multiplayer} - buttons[3] = {"Get Songs", Menu.DLScreen} - buttons[4] = {"Settings", Menu.Settings} - buttons[5] = {"Exit", Menu.Exit} + if NetServ.IsConnected() == true then + buttons[1] = {"Start", Menu.Start} + buttons[2] = {"Settings", Menu.Settings} + buttons[3] = {"Exit", Menu.Exit} + else + buttons[1] = {"Start", Menu.Start} + buttons[2] = {"Multiplayer", Menu.Multiplayer} + buttons[3] = {"Get Songs", Menu.DLScreen} + buttons[4] = {"Settings", Menu.Settings} + buttons[5] = {"Exit", Menu.Exit} + end end end diff --git a/build.windows b/build.windows index 20e134526..69c2d8688 100644 --- a/build.windows +++ b/build.windows @@ -5,4 +5,5 @@ sdl2:x64-windows libjpeg-turbo:x64-windows libvorbis:x64-windows libarchive[core,bzip2,libxml2,lz4,lzma,lzo]:x64-windows -libiconv:x64-windows \ No newline at end of file +libiconv:x64-windows +cryptopp:x64-windows \ No newline at end of file