added loading high scores from score server
authorHolger Schemel <info@artsoft.org>
Sat, 24 Apr 2021 23:56:20 +0000 (01:56 +0200)
committerHolger Schemel <info@artsoft.org>
Sat, 1 May 2021 13:38:31 +0000 (15:38 +0200)
src/files.c
src/files.h
src/game.c
src/game.h
src/libgame/system.h
src/main.c
src/main.h
src/screens.c

index eab833048a1e18811c6e040928080818497ca9b1..83cbc05947dbb181f793d4b7c2610d3154541b61 100644 (file)
@@ -8445,6 +8445,11 @@ static void setScoreInfoToDefaults(void)
   setScoreInfoToDefaultsExt(&scores);
 }
 
+static void setServerScoreInfoToDefaults(void)
+{
+  setScoreInfoToDefaultsExt(&server_scores);
+}
+
 static void LoadScore_OLD(int nr)
 {
   int i;
@@ -8899,6 +8904,158 @@ void SaveScore(int nr)
   SaveScoreToFilename(filename);
 }
 
+static void DownloadServerScoreToCacheExt(struct HttpRequest *request,
+                                         struct HttpResponse *response,
+                                         int nr)
+{
+  request->hostname = API_SERVER_HOSTNAME;
+  request->port     = API_SERVER_PORT;
+  request->method   = API_SERVER_METHOD;
+  request->uri      = API_SERVER_URI_GET;
+
+  snprintf(request->body, MAX_HTTP_BODY_SIZE,
+          "{\n"
+          "  \"levelset_identifier\":  \"%s\",\n"
+          "  \"level_nr\":             \"%d\",\n"
+          "  \"rate_time_over_score\": \"%d\"\n"
+          "}\n",
+          levelset.identifier, nr, level.rate_time_over_score);
+
+  if (!DoHttpRequest(request, response))
+  {
+    Error("HTTP request failed: %s", GetHttpError());
+
+    return;
+  }
+
+  if (!HTTP_SUCCESS(response->status_code))
+  {
+    Error("server failed to handle request: %d %s",
+         response->status_code,
+         response->status_text);
+
+    return;
+  }
+
+  if (response->body_size == 0)
+  {
+    // no scores available for this level
+
+    return;
+  }
+
+  ConvertHttpResponseBodyToClientEncoding(response);
+
+  char *filename = getScoreCacheFilename(nr);
+  FILE *file;
+  int i;
+
+  // used instead of "leveldir_current->subdir" (for network games)
+  InitScoreCacheDirectory(levelset.identifier);
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot save score cache file '%s'", filename);
+
+    return;
+  }
+
+  for (i = 0; i < response->body_size; i++)
+    fputc(response->body[i], file);
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+static void DownloadServerScoreToCache(int nr)
+{
+  struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
+  struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
+
+  DownloadServerScoreToCacheExt(request, response, nr);
+
+  checked_free(request);
+  checked_free(response);
+}
+
+static void LoadServerScoreFromCache(int nr)
+{
+  struct ScoreEntry score_entry;
+  struct
+  {
+    void *value;
+    boolean is_string;
+    int string_size;
+  }
+  score_mapping[] =
+  {
+    { &score_entry.score,              FALSE,  0                       },
+    { &score_entry.time,               FALSE,  0                       },
+    { score_entry.name,                        TRUE,   MAX_PLAYER_NAME_LEN     },
+    { score_entry.tape_basename,       TRUE,   MAX_FILENAME_LEN        },
+
+    { NULL,                            FALSE,  0                       }
+  };
+  char *filename = getScoreCacheFilename(nr);
+  SetupFileHash *score_hash = loadSetupFileHash(filename);
+  int i, j;
+
+  server_scores.num_entries = 0;
+
+  if (score_hash == NULL)
+    return;
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    score_entry = server_scores.entry[i];
+
+    for (j = 0; score_mapping[j].value != NULL; j++)
+    {
+      char token[10];
+
+      sprintf(token, "%02d.%d", i, j);
+
+      char *value = getHashEntry(score_hash, token);
+
+      if (value == NULL)
+       continue;
+
+      if (score_mapping[j].is_string)
+      {
+       char *score_value = (char *)score_mapping[j].value;
+       int value_size = score_mapping[j].string_size;
+
+       strncpy(score_value, value, value_size);
+       score_value[value_size] = '\0';
+      }
+      else
+      {
+       int *score_value = (int *)score_mapping[j].value;
+
+       *score_value = atoi(value);
+      }
+
+      server_scores.num_entries = i + 1;
+    }
+
+    server_scores.entry[i] = score_entry;
+  }
+
+  freeSetupFileHash(score_hash);
+}
+
+void LoadServerScore(int nr)
+{
+  // always start with reliable default values
+  setServerScoreInfoToDefaults();
+
+  DownloadServerScoreToCache(nr);
+  LoadServerScoreFromCache(nr);
+
+  MergeServerScore();
+}
+
 static char *get_file_base64(char *filename)
 {
   struct stat file_status;
index 982f20ec0b8a4b9b6bc2c3fb208d20c64e79f72a..803762db18bdcc9ae8d6cad4b1404a9688083227 100644 (file)
@@ -62,6 +62,7 @@ boolean SaveTapeChecked_LevelSolved(int);
 void LoadScore(int);
 void SaveScore(int);
 
+void LoadServerScore(int);
 void SaveServerScore(int);
 
 void LoadUserNames(void);
index 31ecdbdd6a551a580708cb332599b24c5df41865..470ccf5901defd4fe9407207f80a4309e48a7695 100644 (file)
@@ -5066,6 +5066,10 @@ static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry)
     boolean entry_is_empty = (entry->score == 0 &&
                              entry->time == 0);
 
+    // prevent adding server score entries if also existing in local score file
+    if (strEqual(new_entry->tape_basename, entry->tape_basename))
+      return -1;
+
     if (is_better || entry_is_empty)
     {
       // player has made it to the hall of fame
@@ -5135,6 +5139,22 @@ int NewHighScore(int level_nr)
   return scores.last_added;
 }
 
+void MergeServerScore(void)
+{
+  int i;
+
+  for (i = 0; i < server_scores.num_entries; i++)
+  {
+    int pos = addScoreEntry(&scores, &server_scores.entry[i]);
+
+    if (pos >= 0 && pos <= scores.last_added)
+      scores.last_added++;
+  }
+
+  if (scores.last_added >= MAX_SCORE_ENTRIES)
+    scores.last_added = -1;
+}
+
 static int getElementMoveStepsizeExt(int x, int y, int direction)
 {
   int element = Tile[x][y];
index 13bfdbc10000f13be3ae08863bee87e6b3b13b17..653a6454722ac39dfc79b2cb22c8184ed095bda2 100644 (file)
@@ -417,6 +417,8 @@ void UpdateEngineValues(int, int, int, int);
 void GameWon(void);
 void GameEnd(void);
 
+void MergeServerScore(void);
+
 void InitPlayerGfxAnimation(struct PlayerInfo *, int, int);
 void Moving2Blocked(int, int, int *, int *);
 void Blocked2Moving(int, int, int *, int *);
index 8265773358ca9d1806dc39e69b3c78f39a9cf61e..796db13e1450291c5bf079b80ff994ecf6fa318d 100644 (file)
 #define API_SERVER_PORT                        80
 #define API_SERVER_METHOD              "POST"
 #define API_SERVER_URI_ADD             "/api/scores/add"
+#define API_SERVER_URI_GET             "/api/scores/get"
 
 // values for touch control
 #define TOUCH_CONTROL_OFF              "off"
index 8d8a61a0f848a35ef9aa2dc011d1befc1bdb52ec..49f32600f8022728ab9c5ac8cdfc6f71acb48884 100644 (file)
@@ -128,7 +128,7 @@ boolean                     network_player_action_received = FALSE;
 
 struct LevelInfo       level, level_template;
 struct PlayerInfo      stored_player[MAX_PLAYERS], *local_player = NULL;
-struct ScoreInfo       scores;
+struct ScoreInfo       scores, server_scores;
 struct TapeInfo                tape;
 struct GameInfo                game;
 struct GlobalInfo      global;
index fbd2090631893dc103aeb7b848ebe0452f868570..2d4e61d632948ed15d5d25e509c683bec6c6efa3 100644 (file)
@@ -3766,7 +3766,7 @@ extern boolean                    network_player_action_received;
 extern int                     graphics_action_mapping[];
 
 extern struct LevelInfo                level, level_template;
-extern struct ScoreInfo                scores;
+extern struct ScoreInfo                scores, server_scores;
 extern struct TapeInfo         tape;
 extern struct GlobalInfo       global;
 extern struct BorderInfo       border;
index c60881afae76b2c85850c6196788f7238780847b..d86fa57267864ca83e626f6917683fddb8966c8a 100644 (file)
@@ -5051,6 +5051,12 @@ void DrawHallOfFame(int level_nr, int highlight_position)
   else
     SetAnimStatus(GAME_MODE_PSEUDO_SCORESNEW);
 
+  LoadServerScore(level_nr);
+
+  // correct highlight position after adding server scores
+  if (highlight_position >= 0)
+    highlight_position = scores.last_added;
+
   FadeSetEnterScreen();
 
   FadeOut(fade_mask);