X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Ffiles.c;h=4a980dffc748708fd4485a8f9b72d4e89252ab4e;hb=c87fc56ebe59b3d5158e7e6f01d7aa9bf667737e;hp=d6c9b5733668fe966c88d957568c9c9516720ad0;hpb=bcb1d6118c2d3484e1881a78fd859562745aa553;p=rocksndiamonds.git diff --git a/src/files.c b/src/files.c index d6c9b573..4a980dff 100644 --- a/src/files.c +++ b/src/files.c @@ -22,6 +22,7 @@ #include "tools.h" #include "tape.h" #include "config.h" +#include "api.h" #define ENABLE_UNUSED_CODE 0 // currently unused functions #define ENABLE_HISTORIC_CHUNKS 0 // only for historic reference @@ -51,6 +52,7 @@ // (element number only) #define LEVEL_CHUNK_GRPX_UNCHANGED 2 +#define LEVEL_CHUNK_EMPX_UNCHANGED 2 #define LEVEL_CHUNK_NOTE_UNCHANGED 2 // (nothing at all if unchanged) @@ -1376,6 +1378,26 @@ static struct LevelFileConfigInfo chunk_config_GRPX[] = } }; +static struct LevelFileConfigInfo chunk_config_EMPX[] = +{ + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &xx_ei.use_gfx_element, FALSE + }, + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_ei.gfx_element_initial, EL_EMPTY_SPACE + }, + + { + -1, -1, + -1, -1, + NULL, -1 + } +}; + static struct LevelFileConfigInfo chunk_config_CONF[] = // (OBSOLETE) { { @@ -1893,6 +1915,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) *group = xx_group; } + + if (IS_EMPTY_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) + { + xx_ei = *ei; // copy element data into temporary buffer + + setConfigToDefaultsFromConfigList(chunk_config_EMPX); + + *ei = xx_ei; + } } clipboard_elements_initialized = TRUE; @@ -2814,8 +2846,9 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level) for (x = 0; x < 3; x++) ei->content.e[x][y] = getMappedElement(getFile16BitBE(file)); + // bits 0 - 31 of "has_event[]" event_bits = getFile32BitBE(file); - for (j = 0; j < NUM_CHANGE_EVENTS; j++) + for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) if (event_bits & (1 << j)) ei->change->has_event[j] = TRUE; @@ -3371,6 +3404,30 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level) return real_chunk_size; } +static int LoadLevel_EMPX(File *file, int chunk_size, struct LevelInfo *level) +{ + int element = getMappedElement(getFile16BitBE(file)); + int real_chunk_size = 2; + struct ElementInfo *ei = &element_info[element]; + + xx_ei = *ei; // copy element data into temporary buffer + + while (!checkEndOfFile(file)) + { + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_EMPX, + -1, element); + + if (real_chunk_size >= chunk_size) + break; + } + + *ei = xx_ei; + + level->file_has_custom_elements = TRUE; + + return real_chunk_size; +} + static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, struct LevelFileInfo *level_file_info, boolean level_info_only) @@ -3493,6 +3550,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { "NOTE", -1, LoadLevel_NOTE }, { "CUSX", -1, LoadLevel_CUSX }, { "GRPX", -1, LoadLevel_GRPX }, + { "EMPX", -1, LoadLevel_EMPX }, { NULL, 0, NULL } }; @@ -6508,6 +6566,13 @@ static void LoadLevel_InitSettings(struct LevelInfo *level) { // adjust level settings for (non-native) Sokoban-style levels LoadLevel_InitSettings_SB(level); + + // rename levels with title "nameless level" or if renaming is forced + if (leveldir_current->empty_level_name != NULL && + (strEqual(level->name, NAMELESS_LEVEL_NAME) || + leveldir_current->force_level_name)) + snprintf(level->name, MAX_LEVEL_NAME_LEN + 1, + leveldir_current->empty_level_name, level_nr); } static void LoadLevel_InitStandardElements(struct LevelInfo *level) @@ -7537,6 +7602,22 @@ static int SaveLevel_GRPX(FILE *file, struct LevelInfo *level, int element) return chunk_size; } +static int SaveLevel_EMPX(FILE *file, struct LevelInfo *level, int element) +{ + struct ElementInfo *ei = &element_info[element]; + int chunk_size = 0; + int i; + + chunk_size += putFile16BitBE(file, element); + + xx_ei = *ei; // copy element data into temporary buffer + + for (i = 0; chunk_config_EMPX[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_EMPX[i], FALSE); + + return chunk_size; +} + static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, boolean save_as_template) { @@ -7628,6 +7709,18 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, SaveLevel_GRPX(file, level, element); } } + + for (i = 0; i < NUM_EMPTY_ELEMENTS_ALL; i++) + { + int element = GET_EMPTY_ELEMENT(i); + + chunk_size = SaveLevel_EMPX(NULL, level, element); + if (chunk_size > LEVEL_CHUNK_EMPX_UNCHANGED) // save if changed + { + putFileChunkBE(file, "EMPX", chunk_size); + SaveLevel_EMPX(file, level, element); + } + } } fclose(file); @@ -8240,6 +8333,20 @@ void LoadSolutionTape(int nr) CopyNativeTape_SP_to_RND(&level); } +void LoadScoreTape(char *score_tape_basename, int nr) +{ + char *filename = getScoreTapeFilename(score_tape_basename, nr); + + LoadTapeFromFilename(filename); +} + +void LoadScoreCacheTape(char *score_tape_basename, int nr) +{ + char *filename = getScoreCacheTapeFilename(score_tape_basename, nr); + + LoadTapeFromFilename(filename); +} + static boolean checkSaveTape_SCRN(struct TapeInfo *tape) { // chunk required for team mode tapes with non-default screen size @@ -8476,8 +8583,17 @@ void DumpTape(struct TapeInfo *tape) Print("[tas_keys]"); if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS) Print("[small_graphics]"); - Print("\n"); + + int year2 = tape->date / 10000; + int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2); + int month_index_raw = (tape->date / 100) % 100; + int month_index = month_index_raw % 12; // prevent invalid index + int month = month_index + 1; + int day = tape->date % 100; + + Print("Tape date: %04d-%02d-%02d\n", year4, month, day); + PrintLine("-", 79); tape_frame_counter = 0; @@ -8556,6 +8672,13 @@ static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME); scores->entry[i].score = 0; scores->entry[i].time = 0; + + scores->entry[i].id = -1; + strcpy(scores->entry[i].tape_date, UNKNOWN_NAME); + strcpy(scores->entry[i].platform, UNKNOWN_NAME); + strcpy(scores->entry[i].version, UNKNOWN_NAME); + strcpy(scores->entry[i].country_name, UNKNOWN_NAME); + strcpy(scores->entry[i].country_code, "??"); } scores->num_entries = 0; @@ -8564,7 +8687,15 @@ static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) scores->updated = FALSE; scores->uploaded = FALSE; + scores->tape_downloaded = FALSE; scores->force_last_added = FALSE; + + // The following values are intentionally not reset here: + // - last_level_nr + // - last_entry_nr + // - next_level_nr + // - continue_playing + // - continue_on_return } static void setScoreInfoToDefaults(void) @@ -8711,6 +8842,18 @@ static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores) return chunk_size; } +static int LoadScore_SC4R(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].score = getFile32BitBE(file); + + chunk_size = scores->num_entries * 4; + + return chunk_size; +} + static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores) { int i; @@ -8812,6 +8955,7 @@ void LoadScore(int nr) { "INFO", -1, LoadScore_INFO }, { "NAME", -1, LoadScore_NAME }, { "SCOR", -1, LoadScore_SCOR }, + { "SC4R", -1, LoadScore_SC4R }, { "TIME", -1, LoadScore_TIME }, { "TAPE", -1, LoadScore_TAPE }, @@ -8931,6 +9075,14 @@ static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores) putFile16BitBE(file, scores->entry[i].score); } +static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].score); +} + static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores) { int i; @@ -8958,8 +9110,11 @@ static void SaveScoreToFilename(char *filename) int info_chunk_size; int name_chunk_size; int scor_chunk_size; + int sc4r_chunk_size; int time_chunk_size; int tape_chunk_size; + boolean has_large_score_values; + int i; if (!(file = fopen(filename, MODE_WRITE))) { @@ -8971,9 +9126,15 @@ static void SaveScoreToFilename(char *filename) info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2; name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN; scor_chunk_size = scores.num_entries * 2; + sc4r_chunk_size = scores.num_entries * 4; time_chunk_size = scores.num_entries * 4; tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + has_large_score_values = FALSE; + for (i = 0; i < scores.num_entries; i++) + if (scores.entry[i].score > 0xffff) + has_large_score_values = TRUE; + putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE); @@ -8986,8 +9147,16 @@ static void SaveScoreToFilename(char *filename) putFileChunkBE(file, "NAME", name_chunk_size); SaveScore_NAME(file, &scores); - putFileChunkBE(file, "SCOR", scor_chunk_size); - SaveScore_SCOR(file, &scores); + if (has_large_score_values) + { + putFileChunkBE(file, "SC4R", sc4r_chunk_size); + SaveScore_SC4R(file, &scores); + } + else + { + putFileChunkBE(file, "SCOR", scor_chunk_size); + SaveScore_SCOR(file, &scores); + } putFileChunkBE(file, "TIME", time_chunk_size); SaveScore_TIME(file, &scores); @@ -9029,257 +9198,6 @@ void SaveScore(int nr) SaveScoreToFilename(filename); } -void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data, - char *error) -{ -#if defined(PLATFORM_EMSCRIPTEN) - // threads currently not fully supported by Emscripten/SDL and some browsers - function(data); -#else - SDL_Thread *thread = SDL_CreateThread(function, name, data); - - if (thread != NULL) - SDL_DetachThread(thread); - else - Error("Cannot create thread to %s!", error); - - // nasty kludge to lower probability of intermingled thread error messages - Delay(1); -#endif -} - -char *getPasswordJSON(char *password) -{ - static char password_json[MAX_FILENAME_LEN] = ""; - static boolean initialized = FALSE; - - if (!initialized) - { - if (password != NULL && - !strEqual(password, "") && - !strEqual(password, UNDEFINED_PASSWORD)) - snprintf(password_json, MAX_FILENAME_LEN, - " \"password\": \"%s\",\n", - setup.api_server_password); - - initialized = TRUE; - } - - return password_json; -} - -struct ApiGetScoreThreadData -{ - int level_nr; - char *score_cache_filename; -}; - -static void *CreateThreadData_ApiGetScore(int nr) -{ - struct ApiGetScoreThreadData *data = - checked_malloc(sizeof(struct ApiGetScoreThreadData)); - char *score_cache_filename = getScoreCacheFilename(nr); - - data->level_nr = nr; - data->score_cache_filename = getStringCopy(score_cache_filename); - - return data; -} - -static void FreeThreadData_ApiGetScore(void *data_raw) -{ - struct ApiGetScoreThreadData *data = data_raw; - - checked_free(data->score_cache_filename); - checked_free(data); -} - -static boolean SetRequest_ApiGetScore(struct HttpRequest *request, - void *data_raw) -{ - struct ApiGetScoreThreadData *data = data_raw; - int level_nr = data->level_nr; - - request->hostname = setup.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" - "%s" - " \"game_version\": \"%s\",\n" - " \"game_platform\": \"%s\",\n" - " \"levelset_identifier\": \"%s\",\n" - " \"level_nr\": \"%d\"\n" - "}\n", - getPasswordJSON(setup.api_server_password), - getProgramRealVersionString(), - getProgramPlatformString(), - levelset.identifier, - level_nr); - - ConvertHttpRequestBodyToServerEncoding(request); - - return TRUE; -} - -static void HandleResponse_ApiGetScore(struct HttpResponse *response, - void *data_raw) -{ - struct ApiGetScoreThreadData *data = data_raw; - - if (response->body_size == 0) - { - // no scores available for this level - - return; - } - - ConvertHttpResponseBodyToClientEncoding(response); - - char *filename = data->score_cache_filename; - 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); - - server_scores.updated = TRUE; -} - -#if defined(PLATFORM_EMSCRIPTEN) -static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw, - void *buffer, unsigned int size) -{ - struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); - - if (response != NULL) - { - HandleResponse_ApiGetScore(response, data_raw); - - checked_free(response); - } - else - { - Error("server response too large to handle (%d bytes)", size); - } - - FreeThreadData_ApiGetScore(data_raw); -} - -static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw, - int code, const char *status) -{ - Error("server failed to handle request: %d %s", code, status); - - FreeThreadData_ApiGetScore(data_raw); -} - -static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw, - int bytes, int size) -{ - // nothing to do here -} - -static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request, - void *data_raw) -{ - if (!SetRequest_ApiGetScore(request, data_raw)) - { - FreeThreadData_ApiGetScore(data_raw); - - return; - } - - emscripten_async_wget2_data(request->uri, - request->method, - request->body, - data_raw, - TRUE, - Emscripten_ApiGetScore_Loaded, - Emscripten_ApiGetScore_Failed, - Emscripten_ApiGetScore_Progress); -} - -#else - -static void ApiGetScore_HttpRequestExt(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - if (!SetRequest_ApiGetScore(request, data_raw)) - return; - - 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; - } - - HandleResponse_ApiGetScore(response, data_raw); -} - -static void ApiGetScore_HttpRequest(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - ApiGetScore_HttpRequestExt(request, response, data_raw); - - FreeThreadData_ApiGetScore(data_raw); -} -#endif - -static int ApiGetScoreThread(void *data_raw) -{ - struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); - struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - -#if defined(PLATFORM_EMSCRIPTEN) - Emscripten_ApiGetScore_HttpRequest(request, data_raw); -#else - ApiGetScore_HttpRequest(request, response, data_raw); -#endif - - checked_free(request); - checked_free(response); - - return 0; -} - -static void ApiGetScoreAsThread(int nr) -{ - struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr); - - ExecuteAsThread(ApiGetScoreThread, - "ApiGetScore", data, - "download scores from server"); -} - static void LoadServerScoreFromCache(int nr) { struct ScoreEntry score_entry; @@ -9295,6 +9213,12 @@ static void LoadServerScoreFromCache(int nr) { &score_entry.time, FALSE, 0 }, { score_entry.name, TRUE, MAX_PLAYER_NAME_LEN }, { score_entry.tape_basename, TRUE, MAX_FILENAME_LEN }, + { score_entry.tape_date, TRUE, MAX_ISO_DATE_LEN }, + { &score_entry.id, FALSE, 0 }, + { score_entry.platform, TRUE, MAX_PLATFORM_TEXT_LEN }, + { score_entry.version, TRUE, MAX_VERSION_TEXT_LEN }, + { score_entry.country_code, TRUE, MAX_COUNTRY_CODE_LEN }, + { score_entry.country_name, TRUE, MAX_COUNTRY_NAME_LEN }, { NULL, FALSE, 0 } }; @@ -9366,338 +9290,57 @@ void LoadServerScore(int nr, boolean download_score) } } -static char *get_file_base64(char *filename) +void PrepareScoreTapesForUpload(char *leveldir_subdir) { - struct stat file_status; + MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir); - if (stat(filename, &file_status) != 0) - { - Error("cannot stat file '%s'", filename); + // if score tape not uploaded, ask for uploading missing tapes later + if (!setup.has_remaining_tapes) + setup.ask_for_remaining_tapes = TRUE; - return NULL; - } + setup.provide_uploading_tapes = TRUE; + setup.has_remaining_tapes = TRUE; - int buffer_size = file_status.st_size; - byte *buffer = checked_malloc(buffer_size); - FILE *file; - int i; + SaveSetup_ServerSetup(); +} - if (!(file = fopen(filename, MODE_READ))) +void SaveServerScore(int nr, boolean tape_saved) +{ + if (!runtime.use_api_server) { - Error("cannot open file '%s'", filename); + PrepareScoreTapesForUpload(leveldir_current->subdir); - checked_free(buffer); - - return NULL; + return; } - for (i = 0; i < buffer_size; i++) - { - int c = fgetc(file); + ApiAddScoreAsThread(nr, tape_saved, NULL); +} - if (c == EOF) - { - Error("cannot read from input file '%s'", filename); +void SaveServerScoreFromFile(int nr, boolean tape_saved, + char *score_tape_filename) +{ + if (!runtime.use_api_server) + return; - fclose(file); - checked_free(buffer); + ApiAddScoreAsThread(nr, tape_saved, score_tape_filename); +} - return NULL; - } +void LoadLocalAndServerScore(int nr, boolean download_score) +{ + int last_added_local = scores.last_added_local; + boolean force_last_added = scores.force_last_added; - buffer[i] = (byte)c; - } + // needed if only showing server scores + setScoreInfoToDefaults(); - fclose(file); + if (!strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_SERVER_ONLY)) + LoadScore(nr); - int buffer_encoded_size = base64_encoded_size(buffer_size); - char *buffer_encoded = checked_malloc(buffer_encoded_size); + // restore last added local score entry (before merging server scores) + scores.last_added = scores.last_added_local = last_added_local; - base64_encode(buffer_encoded, buffer, buffer_size); - - checked_free(buffer); - - return buffer_encoded; -} - -struct ApiAddScoreThreadData -{ - int level_nr; - char *score_tape_filename; - struct ScoreEntry score_entry; -}; - -static void *CreateThreadData_ApiAddScore(int nr, char *score_tape_filename) -{ - struct ApiAddScoreThreadData *data = - checked_malloc(sizeof(struct ApiAddScoreThreadData)); - struct ScoreEntry *score_entry = &scores.entry[scores.last_added]; - - if (score_tape_filename == NULL) - score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr); - - data->level_nr = nr; - data->score_entry = *score_entry; - data->score_tape_filename = getStringCopy(score_tape_filename); - - return data; -} - -static void FreeThreadData_ApiAddScore(void *data_raw) -{ - struct ApiAddScoreThreadData *data = data_raw; - - checked_free(data->score_tape_filename); - checked_free(data); -} - -static boolean SetRequest_ApiAddScore(struct HttpRequest *request, - void *data_raw) -{ - struct ApiAddScoreThreadData *data = data_raw; - struct ScoreEntry *score_entry = &data->score_entry; - char *score_tape_filename = data->score_tape_filename; - int level_nr = data->level_nr; - - request->hostname = setup.api_server_hostname; - request->port = API_SERVER_PORT; - request->method = API_SERVER_METHOD; - request->uri = API_SERVER_URI_ADD; - - char *tape_base64 = get_file_base64(score_tape_filename); - - if (tape_base64 == NULL) - { - Error("loading and base64 encoding score tape file failed"); - - return FALSE; - } - - char *player_name_raw = score_entry->name; - char *player_uuid_raw = setup.player_uuid; - - if (options.player_name != NULL && global.autoplay_leveldir != NULL) - { - player_name_raw = options.player_name; - player_uuid_raw = ""; - } - - char *levelset_identifier = getEscapedJSON(leveldir_current->identifier); - char *levelset_name = getEscapedJSON(leveldir_current->name); - char *levelset_author = getEscapedJSON(leveldir_current->author); - char *level_name = getEscapedJSON(level.name); - char *level_author = getEscapedJSON(level.author); - char *player_name = getEscapedJSON(player_name_raw); - char *player_uuid = getEscapedJSON(player_uuid_raw); - - snprintf(request->body, MAX_HTTP_BODY_SIZE, - "{\n" - "%s" - " \"game_version\": \"%s\",\n" - " \"game_platform\": \"%s\",\n" - " \"batch_time\": \"%d\",\n" - " \"levelset_identifier\": \"%s\",\n" - " \"levelset_name\": \"%s\",\n" - " \"levelset_author\": \"%s\",\n" - " \"levelset_num_levels\": \"%d\",\n" - " \"levelset_first_level\": \"%d\",\n" - " \"level_nr\": \"%d\",\n" - " \"level_name\": \"%s\",\n" - " \"level_author\": \"%s\",\n" - " \"rate_time_over_score\": \"%d\",\n" - " \"player_name\": \"%s\",\n" - " \"player_uuid\": \"%s\",\n" - " \"score\": \"%d\",\n" - " \"time\": \"%d\",\n" - " \"tape_basename\": \"%s\",\n" - " \"tape\": \"%s\"\n" - "}\n", - getPasswordJSON(setup.api_server_password), - getProgramRealVersionString(), - getProgramPlatformString(), - (int)global.autoplay_time, - levelset_identifier, - levelset_name, - levelset_author, - leveldir_current->levels, - leveldir_current->first_level, - level_nr, - level_name, - level_author, - level.rate_time_over_score, - player_name, - player_uuid, - score_entry->score, - score_entry->time, - score_entry->tape_basename, - tape_base64); - - checked_free(tape_base64); - - checked_free(levelset_identifier); - checked_free(levelset_name); - checked_free(levelset_author); - checked_free(level_name); - checked_free(level_author); - checked_free(player_name); - checked_free(player_uuid); - - ConvertHttpRequestBodyToServerEncoding(request); - - return TRUE; -} - -static void HandleResponse_ApiAddScore(struct HttpResponse *response, - void *data_raw) -{ - server_scores.uploaded = TRUE; -} - -#if defined(PLATFORM_EMSCRIPTEN) -static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw, - void *buffer, unsigned int size) -{ - struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); - - if (response != NULL) - { - HandleResponse_ApiAddScore(response, data_raw); - - checked_free(response); - } - else - { - Error("server response too large to handle (%d bytes)", size); - } - - FreeThreadData_ApiAddScore(data_raw); -} - -static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw, - int code, const char *status) -{ - Error("server failed to handle request: %d %s", code, status); - - FreeThreadData_ApiAddScore(data_raw); -} - -static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw, - int bytes, int size) -{ - // nothing to do here -} - -static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request, - void *data_raw) -{ - if (!SetRequest_ApiAddScore(request, data_raw)) - { - FreeThreadData_ApiAddScore(data_raw); - - return; - } - - emscripten_async_wget2_data(request->uri, - request->method, - request->body, - data_raw, - TRUE, - Emscripten_ApiAddScore_Loaded, - Emscripten_ApiAddScore_Failed, - Emscripten_ApiAddScore_Progress); -} - -#else - -static void ApiAddScore_HttpRequestExt(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - if (!SetRequest_ApiAddScore(request, data_raw)) - return; - - 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; - } - - HandleResponse_ApiAddScore(response, data_raw); -} - -static void ApiAddScore_HttpRequest(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - ApiAddScore_HttpRequestExt(request, response, data_raw); - - FreeThreadData_ApiAddScore(data_raw); -} -#endif - -static int ApiAddScoreThread(void *data_raw) -{ - struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); - struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - -#if defined(PLATFORM_EMSCRIPTEN) - Emscripten_ApiAddScore_HttpRequest(request, data_raw); -#else - ApiAddScore_HttpRequest(request, response, data_raw); -#endif - - checked_free(request); - checked_free(response); - - return 0; -} - -static void ApiAddScoreAsThread(int nr, char *score_tape_filename) -{ - struct ApiAddScoreThreadData *data = - CreateThreadData_ApiAddScore(nr, score_tape_filename); - - ExecuteAsThread(ApiAddScoreThread, - "ApiAddScore", data, - "upload score to server"); -} - -void SaveServerScore(int nr) -{ - if (!runtime.use_api_server) - return; - - ApiAddScoreAsThread(nr, NULL); -} - -void SaveServerScoreFromFile(int nr, char *score_tape_filename) -{ - if (!runtime.use_api_server) - return; - - ApiAddScoreAsThread(nr, score_tape_filename); -} - -void LoadLocalAndServerScore(int nr, boolean download_score) -{ - int last_added_local = scores.last_added_local; - - LoadScore(nr); - - // restore last added local score entry (before merging server scores) - scores.last_added = scores.last_added_local = last_added_local; - - if (setup.use_api_server && !setup.only_show_local_scores) + if (setup.use_api_server && + !strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_LOCAL_ONLY)) { // load server scores from cache file and trigger update from server LoadServerScore(nr, download_score); @@ -9705,6 +9348,9 @@ void LoadLocalAndServerScore(int nr, boolean download_score) // merge local scores with scores from server MergeServerScore(); } + + if (force_last_added) + scores.force_last_added = force_last_added; } @@ -9721,10 +9367,6 @@ static struct TokenInfo global_setup_tokens[] = TYPE_STRING, &setup.player_name, "player_name" }, - { - TYPE_STRING, - &setup.player_uuid, "player_uuid" - }, { TYPE_SWITCH, &setup.multiple_users, "multiple_users" @@ -9777,6 +9419,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.autorecord, "automatic_tape_recording" }, + { + TYPE_SWITCH, + &setup.auto_pause_on_start, "auto_pause_on_start" + }, { TYPE_SWITCH, &setup.show_titlescreen, "show_titlescreen" @@ -9902,8 +9548,8 @@ static struct TokenInfo global_setup_tokens[] = &setup.show_undo_redo_buttons, "show_undo_redo_buttons" }, { - TYPE_SWITCH, - &setup.only_show_local_scores, "only_show_local_scores" + TYPE_STRING, + &setup.scores_in_highscore_list, "scores_in_highscore_list" }, { TYPE_STRING, @@ -9953,26 +9599,6 @@ static struct TokenInfo global_setup_tokens[] = TYPE_STRING, &setup.network_server_hostname, "network_server_hostname" }, - { - TYPE_SWITCH, - &setup.use_api_server, TEST_PREFIX "use_api_server" - }, - { - TYPE_STRING, - &setup.api_server_hostname, TEST_PREFIX "api_server_hostname" - }, - { - TYPE_STRING, - &setup.api_server_password, TEST_PREFIX "api_server_password" - }, - { - TYPE_SWITCH, - &setup.ask_for_uploading_tapes, TEST_PREFIX "ask_for_uploading_tapes" - }, - { - TYPE_SWITCH, - &setup.provide_uploading_tapes, TEST_PREFIX "provide_uploading_tapes" - }, { TYPE_STRING, &setup.touch.control_type, "touch.control_type" @@ -10023,6 +9649,50 @@ static struct TokenInfo auto_setup_tokens[] = }, }; +static struct TokenInfo server_setup_tokens[] = +{ + { + TYPE_STRING, + &setup.player_uuid, "player_uuid" + }, + { + TYPE_INTEGER, + &setup.player_version, "player_version" + }, + { + TYPE_SWITCH, + &setup.use_api_server, TEST_PREFIX "use_api_server" + }, + { + TYPE_STRING, + &setup.api_server_hostname, TEST_PREFIX "api_server_hostname" + }, + { + TYPE_STRING, + &setup.api_server_password, TEST_PREFIX "api_server_password" + }, + { + TYPE_SWITCH, + &setup.ask_for_uploading_tapes, TEST_PREFIX "ask_for_uploading_tapes" + }, + { + TYPE_SWITCH, + &setup.ask_for_remaining_tapes, TEST_PREFIX "ask_for_remaining_tapes" + }, + { + TYPE_SWITCH, + &setup.provide_uploading_tapes, TEST_PREFIX "provide_uploading_tapes" + }, + { + TYPE_SWITCH, + &setup.ask_for_using_api_server,TEST_PREFIX "ask_for_using_api_server" + }, + { + TYPE_SWITCH, + &setup.has_remaining_tapes, TEST_PREFIX "has_remaining_tapes" + }, +}; + static struct TokenInfo editor_setup_tokens[] = { { @@ -10113,6 +9783,10 @@ static struct TokenInfo editor_cascade_setup_tokens[] = TYPE_SWITCH, &setup.editor_cascade.el_ge, "editor.cascade.el_ge" }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_es, "editor.cascade.el_es" + }, { TYPE_SWITCH, &setup.editor_cascade.el_ref, "editor.cascade.el_ref" @@ -10137,6 +9811,14 @@ static struct TokenInfo shortcut_setup_tokens[] = TYPE_KEY_X11, &setup.shortcut.load_game, "shortcut.load_game" }, + { + TYPE_KEY_X11, + &setup.shortcut.restart_game, "shortcut.restart_game" + }, + { + TYPE_KEY_X11, + &setup.shortcut.pause_before_end, "shortcut.pause_before_end" + }, { TYPE_KEY_X11, &setup.shortcut.toggle_pause, "shortcut.toggle_pause" @@ -10426,6 +10108,38 @@ static struct TokenInfo internal_setup_tokens[] = TYPE_BOOLEAN, &setup.internal.menu_save_and_exit, "menu_save_and_exit" }, + { + TYPE_BOOLEAN, + &setup.internal.info_title, "info_title" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_elements, "info_elements" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_music, "info_music" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_credits, "info_credits" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_program, "info_program" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_version, "info_version" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_levelset, "info_levelset" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_exit, "info_exit" + }, }; static struct TokenInfo debug_setup_tokens[] = @@ -10544,7 +10258,6 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) int i; si->player_name = getStringCopy(getDefaultUserName(user.nr)); - si->player_uuid = NULL; // (will be set later) si->multiple_users = TRUE; @@ -10560,6 +10273,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT; si->fade_screens = TRUE; si->autorecord = TRUE; + si->auto_pause_on_start = FALSE; si->show_titlescreen = TRUE; si->quick_doors = FALSE; si->team_mode = FALSE; @@ -10591,7 +10305,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->small_game_graphics = FALSE; si->show_load_save_buttons = FALSE; si->show_undo_redo_buttons = FALSE; - si->only_show_local_scores = FALSE; + si->scores_in_highscore_list = getStringCopy(STR_SCORES_TYPE_DEFAULT); si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR); si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); @@ -10609,12 +10323,6 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->network_player_nr = 0; // first player si->network_server_hostname = getStringCopy(STR_NETWORK_AUTO_DETECT); - si->use_api_server = TRUE; - si->api_server_hostname = getStringCopy(API_SERVER_HOSTNAME); - si->api_server_password = getStringCopy(UNDEFINED_PASSWORD); - si->ask_for_uploading_tapes = TRUE; - si->provide_uploading_tapes = TRUE; - si->touch.control_type = getStringCopy(TOUCH_CONTROL_DEFAULT); si->touch.move_distance = TOUCH_MOVE_DISTANCE_DEFAULT; // percent si->touch.drop_distance = TOUCH_DROP_DISTANCE_DEFAULT; // percent @@ -10702,6 +10410,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME; si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME; + si->shortcut.restart_game = DEFAULT_KEY_RESTART_GAME; + si->shortcut.pause_before_end = DEFAULT_KEY_PAUSE_BEFORE_END; si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE; si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1; @@ -10821,6 +10531,21 @@ static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si) si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE; } +static void setSetupInfoToDefaults_ServerSetup(struct SetupInfo *si) +{ + si->player_uuid = NULL; // (will be set later) + si->player_version = 1; // (will be set later) + + si->use_api_server = TRUE; + si->api_server_hostname = getStringCopy(API_SERVER_HOSTNAME); + si->api_server_password = getStringCopy(UNDEFINED_PASSWORD); + si->ask_for_uploading_tapes = TRUE; + si->ask_for_remaining_tapes = FALSE; + si->provide_uploading_tapes = TRUE; + si->ask_for_using_api_server = TRUE; + si->has_remaining_tapes = FALSE; +} + static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) { si->editor_cascade.el_bd = TRUE; @@ -10839,6 +10564,7 @@ static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) si->editor_cascade.el_steel_chars = FALSE; si->editor_cascade.el_ce = FALSE; si->editor_cascade.el_ge = FALSE; + si->editor_cascade.el_es = FALSE; si->editor_cascade.el_ref = FALSE; si->editor_cascade.el_user = FALSE; si->editor_cascade.el_dynamic = FALSE; @@ -10908,7 +10634,7 @@ static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash, token_info[token_nr].text); } -static void decodeSetupFileHash(SetupFileHash *setup_file_hash) +static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash) { int i, pnr; @@ -11008,6 +10734,19 @@ static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash) auto_setup_tokens[i].text)); } +static void decodeSetupFileHash_ServerSetup(SetupFileHash *setup_file_hash) +{ + int i; + + if (!setup_file_hash) + return; + + for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++) + setSetupInfo(server_setup_tokens, i, + getHashEntry(setup_file_hash, + server_setup_tokens[i].text)); +} + static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash) { int i; @@ -11064,7 +10803,7 @@ void LoadSetupFromFilename(char *filename) if (setup_file_hash) { - decodeSetupFileHash(setup_file_hash); + decodeSetupFileHash_Default(setup_file_hash); freeSetupFileHash(setup_file_hash); } @@ -11093,17 +10832,9 @@ static void LoadSetup_SpecialPostProcessing(void) // make sure that scroll delay value stays inside valid range setup.scroll_delay_value = MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY); - - if (setup.player_uuid == NULL) - { - // player UUID does not yet exist in setup file - setup.player_uuid = getStringCopy(getUUID()); - - SaveSetup(); - } } -void LoadSetup(void) +void LoadSetup_Default(void) { char *filename; @@ -11113,6 +10844,12 @@ void LoadSetup(void) // try to load setup values from default setup file filename = getDefaultSetupFilename(); + if (fileExists(filename)) + LoadSetupFromFilename(filename); + + // try to load setup values from platform setup file + filename = getPlatformSetupFilename(); + if (fileExists(filename)) LoadSetupFromFilename(filename); @@ -11144,6 +10881,35 @@ void LoadSetup_AutoSetup(void) free(filename); } +void LoadSetup_ServerSetup(void) +{ + char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME); + SetupFileHash *setup_file_hash = NULL; + + // always start with reliable default values + setSetupInfoToDefaults_ServerSetup(&setup); + + setup_file_hash = loadSetupFileHash(filename); + + if (setup_file_hash) + { + decodeSetupFileHash_ServerSetup(setup_file_hash); + + freeSetupFileHash(setup_file_hash); + } + + free(filename); + + if (setup.player_uuid == NULL) + { + // player UUID does not yet exist in setup file + setup.player_uuid = getStringCopy(getUUID()); + setup.player_version = 2; + + SaveSetup_ServerSetup(); + } +} + void LoadSetup_EditorCascade(void) { char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME); @@ -11164,6 +10930,14 @@ void LoadSetup_EditorCascade(void) free(filename); } +void LoadSetup(void) +{ + LoadSetup_Default(); + LoadSetup_AutoSetup(); + LoadSetup_ServerSetup(); + LoadSetup_EditorCascade(); +} + static void addGameControllerMappingToHash(SetupFileHash *mappings_hash, char *mapping_line) { @@ -11213,7 +10987,7 @@ static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash, fclose(file); } -void SaveSetup(void) +void SaveSetup_Default(void) { char *filename = getSetupFilename(); FILE *file; @@ -11238,7 +11012,6 @@ void SaveSetup(void) global_setup_tokens[i].value == &setup.graphics_set || global_setup_tokens[i].value == &setup.volume_simple || global_setup_tokens[i].value == &setup.network_mode || - global_setup_tokens[i].value == &setup.use_api_server || global_setup_tokens[i].value == &setup.touch.control_type || global_setup_tokens[i].value == &setup.touch.grid_xsize[0] || global_setup_tokens[i].value == &setup.touch.grid_xsize[1]) @@ -11345,6 +11118,41 @@ void SaveSetup_AutoSetup(void) free(filename); } +void SaveSetup_ServerSetup(void) +{ + char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME); + FILE *file; + int i; + + InitUserDataDirectory(); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot write server setup file '%s'", filename); + + free(filename); + + return; + } + + fprintFileHeader(file, SERVERSETUP_FILENAME); + + for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++) + { + // just to make things nicer :) + if (server_setup_tokens[i].value == &setup.use_api_server) + fprintf(file, "\n"); + + fprintf(file, "%s\n", getSetupLine(server_setup_tokens, "", i)); + } + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); + + free(filename); +} + void SaveSetup_EditorCascade(void) { char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME); @@ -11374,6 +11182,14 @@ void SaveSetup_EditorCascade(void) free(filename); } +void SaveSetup(void) +{ + SaveSetup_Default(); + SaveSetup_AutoSetup(); + SaveSetup_ServerSetup(); + SaveSetup_EditorCascade(); +} + static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash, char *filename) { @@ -11734,6 +11550,18 @@ static int get_anim_action_parameter_value(char *token) result = -(int)key; } + if (result == -1) + { + if (isURL(token)) + { + result = get_hash_from_key(token); // unsigned int => int + result = ABS(result); // may be negative now + result += (result < MAX_IMAGE_FILES ? MAX_IMAGE_FILES : 0); + + setHashEntry(anim_url_hash, int2str(result, 0), token); + } + } + if (result == -1) result = ANIM_EVENT_ACTION_NONE; @@ -11786,6 +11614,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type) string_has_parameter(value, "pingpong") ? ANIM_PINGPONG : string_has_parameter(value, "pingpong2") ? ANIM_PINGPONG2 : string_has_parameter(value, "random") ? ANIM_RANDOM : + string_has_parameter(value, "random_static") ? ANIM_RANDOM_STATIC : string_has_parameter(value, "ce_value") ? ANIM_CE_VALUE : string_has_parameter(value, "ce_score") ? ANIM_CE_SCORE : string_has_parameter(value, "ce_delay") ? ANIM_CE_DELAY : @@ -11793,6 +11622,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type) string_has_parameter(value, "vertical") ? ANIM_VERTICAL : string_has_parameter(value, "centered") ? ANIM_CENTERED : string_has_parameter(value, "all") ? ANIM_ALL : + string_has_parameter(value, "tiled") ? ANIM_TILED : ANIM_DEFAULT); if (string_has_parameter(value, "once")) @@ -11900,14 +11730,18 @@ static int get_token_parameter_value(char *token, char *value_raw) return get_parameter_value(value_raw, suffix, TYPE_INTEGER); } -void InitMenuDesignSettings_Static(void) +void InitMenuDesignSettings_FromHash(SetupFileHash *setup_file_hash, + boolean ignore_defaults) { int i; - // always start with reliable default values from static default config for (i = 0; image_config_vars[i].token != NULL; i++) { - char *value = getHashEntry(image_config_hash, image_config_vars[i].token); + char *value = getHashEntry(setup_file_hash, image_config_vars[i].token); + + // (ignore definitions set to "[DEFAULT]" which are already initialized) + if (ignore_defaults && strEqual(value, ARG_DEFAULT)) + continue; if (value != NULL) *image_config_vars[i].value = @@ -11915,6 +11749,12 @@ void InitMenuDesignSettings_Static(void) } } +void InitMenuDesignSettings_Static(void) +{ + // always start with reliable default values from static default config + InitMenuDesignSettings_FromHash(image_config_hash, FALSE); +} + static void InitMenuDesignSettings_SpecialPreProcessing(void) { int i; @@ -12713,15 +12553,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) *menu_config_players[i].value = TRUE; // read (and overwrite with) values that may be specified in config file - for (i = 0; image_config_vars[i].token != NULL; i++) - { - char *value = getHashEntry(setup_file_hash, image_config_vars[i].token); - - // (ignore definitions set to "[DEFAULT]" which are already initialized) - if (value != NULL && !strEqual(value, ARG_DEFAULT)) - *image_config_vars[i].value = - get_token_parameter_value(image_config_vars[i].token, value); - } + InitMenuDesignSettings_FromHash(setup_file_hash, TRUE); freeSetupFileHash(setup_file_hash); } @@ -13078,6 +12910,18 @@ void LoadMusicInfo(void) new = &(*new)->next; } } + + // add pointers to previous list nodes + + struct MusicFileInfo *node = music_file_info; + + while (node != NULL) + { + if (node->next) + node->next->prev = node; + + node = node->next; + } } static void add_helpanim_entry(int element, int action, int direction,