X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fscreens.c;h=b7ad82d4d4fcd2e8bbf7827b577d95001912a677;hb=9e5b242142cda73a8b1ae8ae52aa927999b5481d;hp=a80a51886a71520106fdda8ef1de1c5bc4fa5803;hpb=87c3e9a62515a7980a2453d030460ece1da87efb;p=rocksndiamonds.git diff --git a/src/screens.c b/src/screens.c index a80a5188..b7ad82d4 100644 --- a/src/screens.c +++ b/src/screens.c @@ -22,6 +22,7 @@ #include "network.h" #include "init.h" #include "config.h" +#include "api.h" #define DEBUG_JOYSTICKS 0 @@ -191,27 +192,28 @@ #define SCREEN_CTRL_ID_NEXT_LEVEL2 3 #define SCREEN_CTRL_ID_PREV_SCORE 4 #define SCREEN_CTRL_ID_NEXT_SCORE 5 -#define SCREEN_CTRL_ID_FIRST_LEVEL 6 -#define SCREEN_CTRL_ID_LAST_LEVEL 7 -#define SCREEN_CTRL_ID_LEVEL_NUMBER 8 -#define SCREEN_CTRL_ID_PREV_PLAYER 9 -#define SCREEN_CTRL_ID_NEXT_PLAYER 10 -#define SCREEN_CTRL_ID_INSERT_SOLUTION 11 -#define SCREEN_CTRL_ID_PLAY_SOLUTION 12 -#define SCREEN_CTRL_ID_SWITCH_ECS_AGA 13 -#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE 14 -#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE 15 -#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2 16 -#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2 17 - -#define NUM_SCREEN_MENUBUTTONS 18 - -#define SCREEN_CTRL_ID_SCROLL_UP 18 -#define SCREEN_CTRL_ID_SCROLL_DOWN 19 -#define SCREEN_CTRL_ID_SCROLL_VERTICAL 20 -#define SCREEN_CTRL_ID_NETWORK_SERVER 21 - -#define NUM_SCREEN_GADGETS 22 +#define SCREEN_CTRL_ID_PLAY_TAPE 6 +#define SCREEN_CTRL_ID_FIRST_LEVEL 7 +#define SCREEN_CTRL_ID_LAST_LEVEL 8 +#define SCREEN_CTRL_ID_LEVEL_NUMBER 9 +#define SCREEN_CTRL_ID_PREV_PLAYER 10 +#define SCREEN_CTRL_ID_NEXT_PLAYER 11 +#define SCREEN_CTRL_ID_INSERT_SOLUTION 12 +#define SCREEN_CTRL_ID_PLAY_SOLUTION 13 +#define SCREEN_CTRL_ID_SWITCH_ECS_AGA 14 +#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE 15 +#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE 16 +#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2 17 +#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2 18 + +#define NUM_SCREEN_MENUBUTTONS 19 + +#define SCREEN_CTRL_ID_SCROLL_UP 19 +#define SCREEN_CTRL_ID_SCROLL_DOWN 20 +#define SCREEN_CTRL_ID_SCROLL_VERTICAL 21 +#define SCREEN_CTRL_ID_NETWORK_SERVER 22 + +#define NUM_SCREEN_GADGETS 23 #define NUM_SCREEN_SCROLLBUTTONS 2 #define NUM_SCREEN_SCROLLBARS 1 @@ -299,7 +301,8 @@ static void MapScreenTreeGadgets(TreeInfo *); static void UnmapScreenTreeGadgets(void); static void UpdateScreenMenuGadgets(int, boolean); -static void AdjustScoreInfoButtons(int, int, int); +static void AdjustScoreInfoButtons_SelectScore(int, int, int); +static void AdjustScoreInfoButtons_PlayTape(int, int, boolean); static boolean OfferUploadTapes(void); static void execOfferUploadTapes(void); @@ -309,6 +312,8 @@ static void HandleHallOfFame_SelectLevel(int, int); static char *getHallOfFameRankText(int, int); static char *getHallOfFameScoreText(int, int); +static struct TokenInfo *getSetupInfoFinal(struct TokenInfo *); + static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS]; static int info_mode = INFO_MODE_MAIN; @@ -1453,6 +1458,11 @@ static void clearMenuListArea(void) // clear menu list area, but not title or scrollbar DrawBackground(mSX, mSY + MENU_SCREEN_START_YPOS * 32, scrollbar_xpos - mSX, NUM_MENU_ENTRIES_ON_SCREEN * 32); + + // special compatibility handling for "Snake Bite" graphics set + if (strPrefix(leveldir_current->identifier, "snake_bite")) + ClearRectangle(drawto, mSX, mSY + MENU_SCREEN_START_YPOS * 32, + scrollbar_xpos - mSX, NUM_MENU_ENTRIES_ON_SCREEN * 32); } static void drawCursorExt(int xpos, int ypos, boolean active, int graphic) @@ -1521,6 +1531,15 @@ static int getChooseTreeEditYPos(int ypos_raw) return sy; } +static int getChooseTreeEditXPosReal(int pos) +{ + int xpos = getChooseTreeEditXPos(pos); + int font_nr = getChooseTreeEditFont(FALSE); + int font_xoffset = getFontDrawOffsetX(font_nr); + + return xpos + font_xoffset; +} + static void drawChooseTreeEdit(int ypos_raw, boolean active) { int sx = getChooseTreeEditXPos(POS_LEFT); @@ -1690,11 +1709,31 @@ void DrawMainMenu(void) return; } + // needed if last screen was the playing screen, invoked from hall of fame + if (score_info_tape_play) + { + CloseDoor(DOOR_CLOSE_ALL); + + SetGameStatus(GAME_MODE_SCOREINFO); + + DrawScoreInfo(scores.last_entry_nr); + + return; + } + + // reset flag to continue playing next level from hall of fame + scores.continue_playing = FALSE; + // leveldir_current may be invalid (level group, parent link, node copy) leveldir_current = getValidLevelSeries(leveldir_current, leveldir_last_valid); if (leveldir_current != leveldir_last_valid) { + // level setup config may have been loaded to "last played" tree node copy, + // but "leveldir_current" now points to the "original" level set tree node, + // in which case "handicap_level" may still default to the first level + LoadLevelSetup_SeriesInfo(); + UpdateLastPlayedLevels_TreeInfo(); levelset_has_changed = TRUE; @@ -1838,7 +1877,7 @@ static unsigned int getAutoDelayCounter(struct TitleFadingInfo *fi) static boolean TitleAutoDelayReached(unsigned int *counter_var, struct TitleFadingInfo *fi) { - return DelayReachedExt(counter_var, fi->auto_delay, getAutoDelayCounter(fi)); + return DelayReachedExt2(counter_var, fi->auto_delay, getAutoDelayCounter(fi)); } static void ResetTitleAutoDelay(unsigned int *counter_var, @@ -2049,7 +2088,7 @@ static void HandleMainMenu_SelectLevel(int step, int direction, { // skipping levels is only allowed when trying to skip single level if (setup.skip_levels && new_level_nr == old_level_nr + 1 && - Request("Level still unsolved! Skip despite handicap?", REQ_ASK)) + Request("Level still unsolved! Skip it anyway?", REQ_ASK)) { leveldir_current->handicap_level++; SaveLevelSetup_SeriesInfo(); @@ -2542,6 +2581,9 @@ static void DrawInfoScreen_Main(void) info_info = info_info_main; + // use modified info screen info without info screen entries marked as hidden + info_info = getSetupInfoFinal(info_info); + // determine maximal number of info entries that can be displayed on screen num_info_info = 0; for (i = 0; info_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++) @@ -3124,13 +3166,15 @@ static void DrawInfoScreen_Elements(void) void HandleInfoScreen_Elements(int dx, int dy, int button) { - static unsigned int info_delay = 0; + static DelayCounter info_delay = { 0 }; static int num_anims; static int num_pages; static int page; int anims_per_page = NUM_INFO_ELEMENTS_ON_SCREEN; int i; + info_delay.value = GameFrameDelay; + if (button == MB_MENU_INITIALIZE) { boolean new_element = TRUE; @@ -3193,7 +3237,7 @@ void HandleInfoScreen_Elements(int dx, int dy, int button) } else { - if (DelayReached(&info_delay, GameFrameDelay)) + if (DelayReached(&info_delay)) if (page < num_pages) DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, FALSE); @@ -3959,412 +4003,6 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button) } -// ============================================================================ -// rename player API functions -// ============================================================================ - -struct ApiRenamePlayerThreadData -{ - char *player_name; - char *player_uuid; -}; - -static void *CreateThreadData_ApiRenamePlayer(void) -{ - struct ApiRenamePlayerThreadData *data = - checked_malloc(sizeof(struct ApiRenamePlayerThreadData)); - - data->player_name = getStringCopy(setup.player_name); - data->player_uuid = getStringCopy(setup.player_uuid); - - return data; -} - -static void FreeThreadData_ApiRenamePlayer(void *data_raw) -{ - struct ApiRenamePlayerThreadData *data = data_raw; - - checked_free(data->player_name); - checked_free(data->player_uuid); - checked_free(data); -} - -static boolean SetRequest_ApiRenamePlayer(struct HttpRequest *request, - void *data_raw) -{ - struct ApiRenamePlayerThreadData *data = data_raw; - char *player_name_raw = data->player_name; - char *player_uuid_raw = data->player_uuid; - - request->hostname = setup.api_server_hostname; - request->port = API_SERVER_PORT; - request->method = API_SERVER_METHOD; - request->uri = API_SERVER_URI_RENAME; - - 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" - " \"name\": \"%s\",\n" - " \"uuid\": \"%s\"\n" - "}\n", - getPasswordJSON(setup.api_server_password), - getProgramRealVersionString(), - getProgramPlatformString(), - player_name, - player_uuid); - - checked_free(player_name); - checked_free(player_uuid); - - ConvertHttpRequestBodyToServerEncoding(request); - - return TRUE; -} - -static void HandleResponse_ApiRenamePlayer(struct HttpResponse *response, - void *data_raw) -{ - // nothing to do here -} - -#if defined(PLATFORM_EMSCRIPTEN) -static void Emscripten_ApiRenamePlayer_Loaded(unsigned handle, void *data_raw, - void *buffer, unsigned int size) -{ - struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); - - if (response != NULL) - { - HandleResponse_ApiRenamePlayer(response, data_raw); - - checked_free(response); - } - else - { - Error("server response too large to handle (%d bytes)", size); - } - - FreeThreadData_ApiRenamePlayer(data_raw); -} - -static void Emscripten_ApiRenamePlayer_Failed(unsigned handle, void *data_raw, - int code, const char *status) -{ - Error("server failed to handle request: %d %s", code, status); - - FreeThreadData_ApiRenamePlayer(data_raw); -} - -static void Emscripten_ApiRenamePlayer_Progress(unsigned handle, void *data_raw, - int bytes, int size) -{ - // nothing to do here -} - -static void Emscripten_ApiRenamePlayer_HttpRequest(struct HttpRequest *request, - void *data_raw) -{ - if (!SetRequest_ApiRenamePlayer(request, data_raw)) - { - FreeThreadData_ApiRenamePlayer(data_raw); - - return; - } - - emscripten_async_wget2_data(request->uri, - request->method, - request->body, - data_raw, - TRUE, - Emscripten_ApiRenamePlayer_Loaded, - Emscripten_ApiRenamePlayer_Failed, - Emscripten_ApiRenamePlayer_Progress); -} - -#else - -static void ApiRenamePlayer_HttpRequestExt(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - if (!SetRequest_ApiRenamePlayer(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_ApiRenamePlayer(response, data_raw); -} - -static void ApiRenamePlayer_HttpRequest(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - ApiRenamePlayer_HttpRequestExt(request, response, data_raw); - - FreeThreadData_ApiRenamePlayer(data_raw); -} -#endif - -static int ApiRenamePlayerThread(void *data_raw) -{ - struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); - struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - - program.api_thread_count++; - -#if defined(PLATFORM_EMSCRIPTEN) - Emscripten_ApiRenamePlayer_HttpRequest(request, data_raw); -#else - ApiRenamePlayer_HttpRequest(request, response, data_raw); -#endif - - program.api_thread_count--; - - checked_free(request); - checked_free(response); - - return 0; -} - -static void ApiRenamePlayerAsThread(void) -{ - struct ApiRenamePlayerThreadData *data = CreateThreadData_ApiRenamePlayer(); - - ExecuteAsThread(ApiRenamePlayerThread, - "ApiRenamePlayer", data, - "rename player on server"); -} - - -// ============================================================================ -// reset player UUID API functions -// ============================================================================ - -struct ApiResetUUIDThreadData -{ - char *player_name; - char *player_uuid_old; - char *player_uuid_new; -}; - -static void *CreateThreadData_ApiResetUUID(char *uuid_new) -{ - struct ApiResetUUIDThreadData *data = - checked_malloc(sizeof(struct ApiResetUUIDThreadData)); - - data->player_name = getStringCopy(setup.player_name); - data->player_uuid_old = getStringCopy(setup.player_uuid); - data->player_uuid_new = getStringCopy(uuid_new); - - return data; -} - -static void FreeThreadData_ApiResetUUID(void *data_raw) -{ - struct ApiResetUUIDThreadData *data = data_raw; - - checked_free(data->player_name); - checked_free(data->player_uuid_old); - checked_free(data->player_uuid_new); - checked_free(data); -} - -static boolean SetRequest_ApiResetUUID(struct HttpRequest *request, - void *data_raw) -{ - struct ApiResetUUIDThreadData *data = data_raw; - char *player_name_raw = data->player_name; - char *player_uuid_old_raw = data->player_uuid_old; - char *player_uuid_new_raw = data->player_uuid_new; - - request->hostname = setup.api_server_hostname; - request->port = API_SERVER_PORT; - request->method = API_SERVER_METHOD; - request->uri = API_SERVER_URI_RESETUUID; - - char *player_name = getEscapedJSON(player_name_raw); - char *player_uuid_old = getEscapedJSON(player_uuid_old_raw); - char *player_uuid_new = getEscapedJSON(player_uuid_new_raw); - - snprintf(request->body, MAX_HTTP_BODY_SIZE, - "{\n" - "%s" - " \"game_version\": \"%s\",\n" - " \"game_platform\": \"%s\",\n" - " \"name\": \"%s\",\n" - " \"uuid_old\": \"%s\",\n" - " \"uuid_new\": \"%s\"\n" - "}\n", - getPasswordJSON(setup.api_server_password), - getProgramRealVersionString(), - getProgramPlatformString(), - player_name, - player_uuid_old, - player_uuid_new); - - checked_free(player_name); - checked_free(player_uuid_old); - checked_free(player_uuid_new); - - ConvertHttpRequestBodyToServerEncoding(request); - - return TRUE; -} - -static void HandleResponse_ApiResetUUID(struct HttpResponse *response, - void *data_raw) -{ - struct ApiResetUUIDThreadData *data = data_raw; - - // upgrade player UUID in server setup file - setup.player_uuid = getStringCopy(data->player_uuid_new); - setup.player_version = 2; - - SaveSetup_ServerSetup(); -} - -#if defined(PLATFORM_EMSCRIPTEN) -static void Emscripten_ApiResetUUID_Loaded(unsigned handle, void *data_raw, - void *buffer, unsigned int size) -{ - struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); - - if (response != NULL) - { - HandleResponse_ApiResetUUID(response, data_raw); - - checked_free(response); - } - else - { - Error("server response too large to handle (%d bytes)", size); - } - - FreeThreadData_ApiResetUUID(data_raw); -} - -static void Emscripten_ApiResetUUID_Failed(unsigned handle, void *data_raw, - int code, const char *status) -{ - Error("server failed to handle request: %d %s", code, status); - - FreeThreadData_ApiResetUUID(data_raw); -} - -static void Emscripten_ApiResetUUID_Progress(unsigned handle, void *data_raw, - int bytes, int size) -{ - // nothing to do here -} - -static void Emscripten_ApiResetUUID_HttpRequest(struct HttpRequest *request, - void *data_raw) -{ - if (!SetRequest_ApiResetUUID(request, data_raw)) - { - FreeThreadData_ApiResetUUID(data_raw); - - return; - } - - emscripten_async_wget2_data(request->uri, - request->method, - request->body, - data_raw, - TRUE, - Emscripten_ApiResetUUID_Loaded, - Emscripten_ApiResetUUID_Failed, - Emscripten_ApiResetUUID_Progress); -} - -#else - -static void ApiResetUUID_HttpRequestExt(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - if (!SetRequest_ApiResetUUID(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_ApiResetUUID(response, data_raw); -} - -static void ApiResetUUID_HttpRequest(struct HttpRequest *request, - struct HttpResponse *response, - void *data_raw) -{ - ApiResetUUID_HttpRequestExt(request, response, data_raw); - - FreeThreadData_ApiResetUUID(data_raw); -} -#endif - -static int ApiResetUUIDThread(void *data_raw) -{ - struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); - struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - - program.api_thread_count++; - -#if defined(PLATFORM_EMSCRIPTEN) - Emscripten_ApiResetUUID_HttpRequest(request, data_raw); -#else - ApiResetUUID_HttpRequest(request, response, data_raw); -#endif - - program.api_thread_count--; - - checked_free(request); - checked_free(response); - - return 0; -} - -static void ApiResetUUIDAsThread(char *uuid_new) -{ - struct ApiResetUUIDThreadData *data = CreateThreadData_ApiResetUUID(uuid_new); - - ExecuteAsThread(ApiResetUUIDThread, - "ApiResetUUID", data, - "reset UUID on server"); -} - - // ============================================================================ // type name functions // ============================================================================ @@ -4379,7 +4017,7 @@ static int getPlayerNameColor(char *name) } static void drawTypeNameText(char *name, struct TextPosInfo *pos, - boolean active) + boolean active) { char text[MAX_PLAYER_NAME_LEN + 2] = { 0 }; boolean multiple_users = (game_status == GAME_MODE_PSEUDO_TYPENAMES); @@ -4387,8 +4025,12 @@ static void drawTypeNameText(char *name, struct TextPosInfo *pos, int sy = (multiple_users ? amSY + pos->y : mSY + ALIGNED_TEXT_YPOS(pos)); int font_nr = (active ? FONT_ACTIVE(pos->font) : pos->font); int font_width = getFontWidth(font_nr); + int font_xoffset = getFontDrawOffsetX(font_nr); + int font_yoffset = getFontDrawOffsetY(font_nr); + int font_sx = sx + font_xoffset; + int font_sy = sy + font_yoffset; - DrawBackgroundForFont(sx, sy, pos->width, pos->height, font_nr); + DrawBackgroundForFont(font_sx, font_sy, pos->width, pos->height, font_nr); sprintf(text, "%s%c", name, (active ? '_' : '\0')); @@ -4704,12 +4346,23 @@ static int getAlignYOffsetFromTreeInfo(TreeInfo *ti) return align_yoffset; } +static void StartPlayingFromHallOfFame(void) +{ + level_nr = scores.next_level_nr; + LoadLevel(level_nr); + + StartGameActions(network.enabled, setup.autorecord, level.random_seed); +} + static void DrawChooseTree(TreeInfo **ti_ptr) { int fade_mask = REDRAW_FIELD; boolean restart_music = (game_status != game_status_last_screen && game_status_last_screen != GAME_MODE_SCOREINFO); + scores.continue_on_return = (game_status == GAME_MODE_SCORES && + game_status_last_screen == GAME_MODE_PLAYING); + if (CheckFadeAll()) fade_mask = REDRAW_ALL; @@ -4719,7 +4372,11 @@ static void DrawChooseTree(TreeInfo **ti_ptr) { execSetupArtwork(); } - else // GAME_MODE_LEVELS + else if (game_status == GAME_MODE_SCORES && scores.continue_playing) + { + StartPlayingFromHallOfFame(); + } + else { SetGameStatus(GAME_MODE_MAIN); @@ -5019,8 +4676,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, boolean has_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->mapped; int mx_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->x; int mx_right_border = (has_scrollbar ? mx_scrollbar : SX + SXSIZE); - int sx1_edit_name = getChooseTreeEditXPos(POS_LEFT); - int sx2_edit_name = getChooseTreeEditXPos(POS_RIGHT); + int sx1_edit_name = getChooseTreeEditXPosReal(POS_LEFT); + int sx2_edit_name = getChooseTreeEditXPosReal(POS_RIGHT); int x = 0; int y = (ti != NULL ? ti->cl_cursor : 0); int step = (button == 1 ? 1 : button == 2 ? 5 : 10); @@ -5162,6 +4819,23 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, return; } +#if defined(PLATFORM_ANDROID) + // directly continue when touching the screen after playing + if ((mx || my) && scores.continue_on_return) + { + // ignore touch events until released + mx = my = 0; + } +#endif + + // any mouse click or cursor key stops leaving scores by "Return" key + if ((mx || my || dx || dy) && scores.continue_on_return) + { + scores.continue_on_return = FALSE; + level_nr = scores.last_level_nr; + LoadLevel(level_nr); + } + if (mx || my) // mouse input { x = (mx - amSX) / 32; @@ -5241,15 +4915,17 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, node_cursor->cl_first = ti->cl_first; node_cursor->cl_cursor = ti->cl_cursor; + *ti_ptr = node_cursor->node_group; DrawChooseTree(ti_ptr); return; } } - else if (dx == -1 && ti->node_parent) + else if ((dx == -1 || button == MB_MENU_CONTINUE) && ti->node_parent) { - FadeSetLeaveMenu(); + if (game_status != GAME_MODE_SCORES) + FadeSetLeaveMenu(); PlaySound(SND_MENU_ITEM_SELECTING); @@ -5332,6 +5008,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, node_cursor->cl_first = ti->cl_first; node_cursor->cl_cursor = ti->cl_cursor; + *ti_ptr = node_cursor->node_group; DrawChooseTree(ti_ptr); } @@ -5350,6 +5027,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, node_cursor->cl_first = ti->cl_first; node_cursor->cl_cursor = ti->cl_cursor; + *ti_ptr = node_cursor; if (ti->type == TREE_TYPE_LEVEL_DIR) @@ -5440,16 +5118,13 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, } else if (game_status == GAME_MODE_SCORES) { - if (game_status_last_screen == GAME_MODE_PLAYING && - setup.auto_play_next_level && setup.increment_levels && - scores.last_level_nr < leveldir_current->last_level && - !network_playing) + if (scores.continue_playing && scores.continue_on_return) { - StartGameActions(network.enabled, setup.autorecord, - level.random_seed); + StartPlayingFromHallOfFame(); + return; } - else + else if (!scores.continue_on_return) { SetGameStatus(GAME_MODE_SCOREINFO); @@ -5513,6 +5188,9 @@ void DrawChoosePlayerName(void) if (player_name_current == NULL) player_name_current = player_name; + // set text size for main name input (also used on name selection screen) + InitializeMainControls(); + DrawChooseTree(&player_name_current); } @@ -5643,12 +5321,15 @@ static void DrawHallOfFame_setScoreEntries(void) if (score_entry_current == NULL) score_entry_current = getFirstValidTreeInfoEntry(score_entries); + if (score_entries != NULL && scores.continue_playing) + setString(&score_entries->node_group->name, BACKLINK_TEXT_NEXT); + // ("score_entries" and "score_entry_current" may be NULL here) } -void DrawHallOfFame(int level_nr) +void DrawHallOfFame(int nr) { - scores.last_level_nr = level_nr; + scores.last_level_nr = nr; // (this is needed when called from GameEnd() after winning a game) KeyboardAutoRepeatOn(); @@ -5657,7 +5338,7 @@ void DrawHallOfFame(int level_nr) SetDrawDeactivationMask(REDRAW_NONE); SetDrawBackgroundMask(REDRAW_FIELD); - LoadLocalAndServerScore(level_nr, TRUE); + LoadLocalAndServerScore(scores.last_level_nr, TRUE); DrawHallOfFame_setScoreEntries(); @@ -5702,6 +5383,28 @@ static char *getHallOfFameScoreText(int nr, int size) return getHallOfFameTimeText(nr); // show playing time } +static char *getHallOfFameTapeDateText(struct ScoreEntry *entry) +{ + static char tape_date[MAX_ISO_DATE_LEN + 1]; + int i, j; + + if (!strEqual(entry->tape_date, UNKNOWN_NAME) || + strEqual(entry->tape_basename, UNDEFINED_FILENAME)) + return entry->tape_date; + + for (i = 0, j = 0; i < 8; i++, j++) + { + tape_date[j] = entry->tape_basename[i]; + + if (i == 3 || i == 5) + tape_date[++j] = '-'; + } + + tape_date[MAX_ISO_DATE_LEN] = '\0'; + + return tape_date; +} + static void HandleHallOfFame_SelectLevel(int step, int direction) { int old_level_nr = scores.last_level_nr; @@ -5758,6 +5461,7 @@ static void DrawScoreInfo_Content(int entry_nr) { struct ScoreEntry *entry = &scores.entry[entry_nr]; char *pos_text = getHallOfFameRankText(entry_nr, 0); + char *tape_date = getHallOfFameTapeDateText(entry); int font_title = MENU_INFO_FONT_TITLE; int font_head = MENU_INFO_FONT_HEAD; int font_text = MENU_INFO_FONT_TEXT; @@ -5769,23 +5473,28 @@ static void DrawScoreInfo_Content(int entry_nr) int ystep_title = getMenuTextStep(spacing_title, font_title); int ystep_para = getMenuTextStep(spacing_para, font_text); int ystep_line = getMenuTextStep(spacing_line, font_text); + int xstart = mSX - SX + menu.left_spacing[GAME_MODE_SCOREINFO]; int ystart = mSY - SY + menu.top_spacing[GAME_MODE_SCOREINFO]; int ybottom = mSY - SY + SYSIZE - menu.bottom_spacing[GAME_MODE_SCOREINFO]; - int xstart1 = mSX - SX + 2 * xstep; - int xstart2 = mSX - SX + 13 * xstep; - int button_x = SX + xstart1; - int button_y1, button_y2; + int xstart1 = xstart + xstep; + int xstart2 = xstart + xstep * 12; + int select_x = SX + xstart1; + int select_y1, select_y2; + int play_x, play_y; + int play_height = screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]->height; + boolean play_visible = !strEqual(tape_date, UNKNOWN_NAME); int font_width = getFontWidth(font_text); int font_height = getFontHeight(font_text); + int tape_date_width = getTextWidth(tape_date, font_text); int pad_left = xstart2; - int pad_right = MENU_SCREEN_INFO_SPACE_RIGHT; + int pad_right = menu.right_spacing[GAME_MODE_SCOREINFO]; int max_chars_per_line = (SXSIZE - pad_left - pad_right) / font_width; int max_lines_per_text = 5; int lines; ClearField(); - // redraw score selection buttons (which have just been erased) + // redraw level selection buttons (which have just been erased) RedrawScreenMenuGadgets(SCREEN_MASK_SCORES); if (score_entries == NULL) @@ -5813,7 +5522,7 @@ static void DrawScoreInfo_Content(int entry_nr) TRUE, FALSE, FALSE); ystart += ystep_para + (lines > 0 ? lines - 1 : 0) * font_height; - button_y1 = SY + ystart; + select_y1 = SY + ystart; ystart += graphic_info[IMG_MENU_BUTTON_PREV_SCORE].height; DrawTextF(xstart1, ystart, font_head, "Rank"); @@ -5846,8 +5555,11 @@ static void DrawScoreInfo_Content(int entry_nr) ystart += ystep_line; + play_x = SX + xstart2 + tape_date_width + font_width; + play_y = SY + ystart + (font_height - play_height) / 2; + DrawTextF(xstart1, ystart, font_head, "Tape Date"); - DrawTextF(xstart2, ystart, font_text, entry->tape_date); + DrawTextF(xstart2, ystart, font_text, tape_date); ystart += ystep_line; DrawTextF(xstart1, ystart, font_head, "Platform"); @@ -5864,23 +5576,42 @@ static void DrawScoreInfo_Content(int entry_nr) TRUE, FALSE, FALSE); ystart += ystep_line; - button_y2 = SY + ystart; + select_y2 = SY + ystart; DrawTextSCentered(ybottom, font_foot, "Press any key or button to go back"); - AdjustScoreInfoButtons(button_x, button_y1, button_y2); + AdjustScoreInfoButtons_SelectScore(select_x, select_y1, select_y2); + AdjustScoreInfoButtons_PlayTape(play_x, play_y, play_visible); } static void DrawScoreInfo(int entry_nr) { scores.last_entry_nr = entry_nr; - - SetMainBackgroundImageIfDefined(IMG_BACKGROUND_SCOREINFO); + score_info_tape_play = FALSE; UnmapAllGadgets(); + FreeScreenGadgets(); + CreateScreenGadgets(); + FadeOut(REDRAW_FIELD); + // needed if different viewport properties defined after playing score tape + ChangeViewportPropertiesIfNeeded(); + + // set this after "ChangeViewportPropertiesIfNeeded()" (which may reset it) + SetDrawDeactivationMask(REDRAW_NONE); + SetDrawBackgroundMask(REDRAW_FIELD); + + // needed if different background image defined after playing score tape + SetMainBackgroundImage(IMG_BACKGROUND_SCORES); + SetMainBackgroundImageIfDefined(IMG_BACKGROUND_SCOREINFO); + + // special compatibility handling for "Snake Bite" graphics set + if (strPrefix(leveldir_current->identifier, "snake_bite")) + ClearRectangle(gfx.background_bitmap, gfx.real_sx, gfx.real_sy + 64, + gfx.full_sxsize, gfx.full_sysize - 64); + DrawScoreInfo_Content(entry_nr); // map gadgets for score info screen @@ -5909,6 +5640,16 @@ static void HandleScoreInfo_SelectScore(int step, int direction) } } +static void HandleScoreInfo_PlayTape(void) +{ + if (!PlayScoreTape(scores.last_entry_nr)) + { + DrawScoreInfo_Content(scores.last_entry_nr); + + FadeIn(REDRAW_FIELD); + } +} + void HandleScoreInfo(int mx, int my, int dx, int dy, int button) { boolean button_action = (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE); @@ -5933,7 +5674,7 @@ void HandleScoreInfo(int mx, int my, int dx, int dy, int button) SetGameStatus(GAME_MODE_SCORES); - DrawHallOfFame(level_nr); + DrawHallOfFame(scores.last_level_nr); } else if (dx) { @@ -7397,7 +7138,12 @@ static void ToggleUseApiServerIfNeeded(void) runtime.use_api_server = setup.use_api_server; if (runtime.use_api_server) + { + if (setup.has_remaining_tapes) + setup.ask_for_uploading_tapes = TRUE; + CheckApiServerTasks(); + } } static void ModifyGameSpeedIfNeeded(void) @@ -7525,6 +7271,15 @@ static struct { &setup.internal.menu_exit, execExitSetup }, { &setup.internal.menu_save_and_exit, execSaveAndExitSetup }, + { &setup.internal.info_title, execInfoTitleScreen }, + { &setup.internal.info_elements, execInfoElements }, + { &setup.internal.info_music, execInfoMusic }, + { &setup.internal.info_credits, execInfoCredits }, + { &setup.internal.info_program, execInfoProgram }, + { &setup.internal.info_version, execInfoVersion }, + { &setup.internal.info_levelset, execInfoLevelSet }, + { &setup.internal.info_exit, execExitInfo }, + { NULL, NULL } }; @@ -7569,8 +7324,8 @@ static struct TokenInfo setup_info_game[] = { TYPE_SWITCH, &setup.multiple_users, "Multiple Users/Teams:" }, { TYPE_YES_NO, &setup.input_on_focus, "Only Move Focussed Player:" }, { TYPE_SWITCH, &setup.time_limit, "Time Limit:" }, - { TYPE_SWITCH, &setup.handicap, "Handicap:" }, - { TYPE_SWITCH, &setup.skip_levels, "Skip Unsolved Levels:" }, + { TYPE_SWITCH, &setup.handicap, "Force Solving Levels:" }, + { TYPE_SWITCH, &setup.skip_levels, "Allow Skipping Levels:" }, { TYPE_SWITCH, &setup.increment_levels,"Increment Solved Levels:" }, { TYPE_SWITCH, &setup.auto_play_next_level,"Auto-play Next Level:" }, { TYPE_SWITCH, &setup.count_score_after_game,"Count Score After Game:" }, @@ -7651,7 +7406,7 @@ static struct TokenInfo setup_info_editor[] = static struct TokenInfo setup_info_graphics[] = { -#if !defined(PLATFORM_ANDROID) +#if !defined(PLATFORM_ANDROID) && !defined(PLATFORM_EMSCRIPTEN) { TYPE_SWITCH, &setup.fullscreen, "Fullscreen:" }, { TYPE_ENTER_LIST, execSetupChooseWindowSize, "Window Scaling:" }, { TYPE_STRING, &window_size_text, "" }, @@ -7664,8 +7419,10 @@ static struct TokenInfo setup_info_graphics[] = { TYPE_ENTER_LIST, execSetupChooseScrollDelay, "Scroll Delay:" }, { TYPE_STRING, &scroll_delay_text, "" }, #endif +#if !defined(PLATFORM_EMSCRIPTEN) { TYPE_ENTER_LIST, execSetupChooseVsyncMode, "Vertical Sync (VSync):" }, { TYPE_STRING, &vsync_mode_text, "" }, +#endif { TYPE_SWITCH, &setup.fade_screens, "Fade Screens:" }, { TYPE_SWITCH, &setup.quick_switch, "Quick Player Focus Switch:" }, { TYPE_SWITCH, &setup.quick_doors, "Quick Menu Doors:" }, @@ -7925,7 +7682,7 @@ static Key getSetupKey(void) { case EVENT_KEYPRESS: { - key = GetEventKey((KeyEvent *)&event, TRUE); + key = GetEventKey((KeyEvent *)&event); // press 'Escape' or 'Enter' to keep the existing key binding if (key == KSYM_Escape || key == KSYM_Return) @@ -8711,14 +8468,18 @@ static boolean CustomizeKeyboardMain(int player_nr) while (!finished) { Event event; + DelayCounter event_frame_delay = { GAME_FRAME_DELAY }; - if (NextValidEvent(&event)) + // reset frame delay counter directly after updating screen + ResetDelayCounter(&event_frame_delay); + + while (NextValidEvent(&event)) { switch (event.type) { case EVENT_KEYPRESS: { - Key key = GetEventKey((KeyEvent *)&event, FALSE); + Key key = GetEventKey((KeyEvent *)&event); // press 'Escape' to abort and keep the old key bindings if (key == KSYM_Escape) @@ -8783,6 +8544,10 @@ static boolean CustomizeKeyboardMain(int player_nr) HandleOtherEvents(&event); break; } + + // do not handle events for longer than standard frame delay period + if (DelayReached(&event_frame_delay)) + break; } BackToFront(); @@ -8805,8 +8570,7 @@ void CustomizeKeyboard(int player_nr) int font_height = getFontHeight(font_nr); int ypos1 = SYSIZE / 2 - font_height * 2; int ypos2 = SYSIZE / 2 - font_height * 1; - unsigned int wait_frame_delay = 0; - unsigned int wait_frame_delay_value = 2000; + DelayCounter wait_frame_delay = { 2000 }; ResetDelayCounter(&wait_frame_delay); @@ -8815,7 +8579,7 @@ void CustomizeKeyboard(int player_nr) DrawTextSCentered(ypos1, font_nr, "Keyboard"); DrawTextSCentered(ypos2, font_nr, "configured!"); - while (!DelayReached(&wait_frame_delay, wait_frame_delay_value)) + while (!DelayReached(&wait_frame_delay)) BackToFront(); ClearEventQueue(); @@ -8885,11 +8649,6 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) { 282, 210, MARKER_AXIS_Y, "righty", }, }; - unsigned int event_frame_delay = 0; - unsigned int event_frame_delay_value = GAME_FRAME_DELAY; - - ResetDelayCounter(&event_frame_delay); - if (!bitmaps_initialized) { controller = LoadCustomImage("joystick/controller.png"); @@ -9021,6 +8780,11 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) screen_initialized = TRUE; + DelayCounter event_frame_delay = { GAME_FRAME_DELAY }; + + // reset frame delay counter directly after updating screen + ResetDelayCounter(&event_frame_delay); + while (NextValidEvent(&event)) { switch (event.type) @@ -9154,7 +8918,7 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) } // do not handle events for longer than standard frame delay period - if (DelayReached(&event_frame_delay, event_frame_delay_value)) + if (DelayReached(&event_frame_delay)) break; } } @@ -9238,8 +9002,7 @@ void ConfigureJoystick(int player_nr) int font_height = getFontHeight(font_nr); int ypos1 = SYSIZE / 2 - font_height * 2; int ypos2 = SYSIZE / 2 - font_height * 1; - unsigned int wait_frame_delay = 0; - unsigned int wait_frame_delay_value = 2000; + DelayCounter wait_frame_delay = { 2000 }; ResetDelayCounter(&wait_frame_delay); @@ -9250,7 +9013,7 @@ void ConfigureJoystick(int player_nr) DrawTextSCentered(ypos1, font_nr, message1); DrawTextSCentered(ypos2, font_nr, message2); - while (!DelayReached(&wait_frame_delay, wait_frame_delay_value)) + while (!DelayReached(&wait_frame_delay)) BackToFront(); ClearEventQueue(); @@ -9367,7 +9130,7 @@ static boolean ConfigureVirtualButtonsMain(void) case EVENT_KEYPRESS: { - Key key = GetEventKey((KeyEvent *)&event, FALSE); + Key key = GetEventKey((KeyEvent *)&event); action = (key == KSYM_Escape ? ACTION_ESCAPE : key == KSYM_BackSpace || @@ -9568,8 +9331,7 @@ void ConfigureVirtualButtons(void) int font_height = getFontHeight(font_nr); int ypos1 = SYSIZE / 2 - font_height * 2; int ypos2 = SYSIZE / 2 - font_height * 1; - unsigned int wait_frame_delay = 0; - unsigned int wait_frame_delay_value = 2000; + DelayCounter wait_frame_delay = { 2000 }; ResetDelayCounter(&wait_frame_delay); @@ -9578,7 +9340,7 @@ void ConfigureVirtualButtons(void) DrawTextSCentered(ypos1, font_nr, "Virtual buttons"); DrawTextSCentered(ypos2, font_nr, "configured!"); - while (!DelayReached(&wait_frame_delay, wait_frame_delay_value)) + while (!DelayReached(&wait_frame_delay)) BackToFront(); ClearEventQueue(); @@ -9808,6 +9570,14 @@ static struct GD_EVENT_PRESSED | GD_EVENT_REPEATED, FALSE, "next score" }, + { + IMG_MENU_BUTTON_PLAY_TAPE, IMG_MENU_BUTTON_PLAY_TAPE, + &menu.scores.button.play_tape, NULL, + SCREEN_CTRL_ID_PLAY_TAPE, + SCREEN_MASK_SCORES_INFO, + GD_EVENT_RELEASED, + FALSE, "play tape" + }, { IMG_MENU_BUTTON_FIRST_LEVEL, IMG_MENU_BUTTON_FIRST_LEVEL_ACTIVE, &menu.main.button.first_level, NULL, @@ -9981,6 +9751,8 @@ static void CreateScreenMenubuttons(void) boolean is_touch_button = menubutton_info[i].is_touch_button; boolean is_check_button = menubutton_info[i].check_value != NULL; boolean is_score_button = (screen_mask & SCREEN_MASK_SCORES_INFO); + boolean has_gfx_pressed = (menubutton_info[i].gfx_pressed == + menubutton_info[i].gfx_unpressed); Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed; int gfx_unpressed, gfx_pressed; int x, y, width, height; @@ -9990,6 +9762,10 @@ static void CreateScreenMenubuttons(void) int type = GD_TYPE_NORMAL_BUTTON; boolean checked = FALSE; + // do not use touch buttons if overlay touch buttons are disabled + if (is_touch_button && !setup.touch.overlay_buttons) + continue; + event_mask = menubutton_info[i].event_mask; x = (is_touch_button ? pos->x : mSX + GDI_ACTIVE_POS(pos->x)); @@ -10011,7 +9787,7 @@ static void CreateScreenMenubuttons(void) gd_x2a = gd_x2; gd_y2a = gd_y2; - if (is_touch_button) + if (has_gfx_pressed) { gd_x2 += graphic_info[gfx_pressed].pressed_xoffset; gd_y2 += graphic_info[gfx_pressed].pressed_yoffset; @@ -10033,12 +9809,18 @@ static void CreateScreenMenubuttons(void) // if x/y set to -1, dynamically place buttons next to title text int title_width = getTextWidth(INFOTEXT_SCORE_ENTRY, FONT_TITLE_1); + // special compatibility handling for "Snake Bite" graphics set + if (strPrefix(leveldir_current->identifier, "snake_bite")) + title_width = strlen(INFOTEXT_SCORE_ENTRY) * 32; + + // use "SX" here to center buttons (ignore horizontal draw offset) if (pos->x == -1) x = (id == SCREEN_CTRL_ID_PREV_LEVEL2 ? SX + (SXSIZE - title_width) / 2 - width * 3 / 2 : id == SCREEN_CTRL_ID_NEXT_LEVEL2 ? SX + (SXSIZE + title_width) / 2 + width / 2 : 0); + // use "mSY" here to place buttons (respect vertical draw offset) if (pos->y == -1) y = (id == SCREEN_CTRL_ID_PREV_LEVEL2 || id == SCREEN_CTRL_ID_NEXT_LEVEL2 ? mSY + MENU_TITLE1_YPOS : 0); @@ -10365,7 +10147,7 @@ static void UnmapScreenTreeGadgets(void) UnmapScreenGadgets(); } -static void AdjustScoreInfoButtons(int x, int y1, int y2) +static void AdjustScoreInfoButtons_SelectScore(int x, int y1, int y2) { struct GadgetInfo *gi_1 = screen_gadget[SCREEN_CTRL_ID_PREV_SCORE]; struct GadgetInfo *gi_2 = screen_gadget[SCREEN_CTRL_ID_NEXT_SCORE]; @@ -10379,6 +10161,19 @@ static void AdjustScoreInfoButtons(int x, int y1, int y2) ModifyGadget(gi_2, GDI_X, x, GDI_Y, y2, GDI_END); } +static void AdjustScoreInfoButtons_PlayTape(int x, int y, boolean visible) +{ + struct GadgetInfo *gi = screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]; + struct MenuPosInfo *pos = menubutton_info[SCREEN_CTRL_ID_PLAY_TAPE].pos; + + // set gadget position dynamically, pre-defined or off-screen + int xx = (visible ? (pos->x == -1 ? x : pos->x) : POS_OFFSCREEN); + int yy = (visible ? (pos->y == -1 ? y : pos->y) : POS_OFFSCREEN); + + ModifyGadget(gi, GDI_X, xx, GDI_Y, yy, GDI_END); + MapGadget(gi); // (needed if deactivated on last score page) +} + static void HandleScreenGadgets(struct GadgetInfo *gi) { int id = gi->custom_id; @@ -10413,6 +10208,10 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) HandleScoreInfo_SelectScore(step, +1); break; + case SCREEN_CTRL_ID_PLAY_TAPE: + HandleScoreInfo_PlayTape(); + break; + case SCREEN_CTRL_ID_FIRST_LEVEL: HandleMainMenu_SelectLevel(MAX_LEVELS, -1, NO_DIRECT_LEVEL_SELECT); break; @@ -10530,6 +10329,12 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) } } +void HandleScreenGadgetKeys(Key key) +{ + if (key == setup.shortcut.tape_play) + HandleScreenGadgets(screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]); +} + void DumpScreenIdentifiers(void) { int i; @@ -10682,6 +10487,9 @@ static boolean OfferUploadTapes(void) "Upload all your tapes to the high score server now?", REQ_ASK)) return FALSE; + // when uploading tapes, make sure that high score server is enabled + runtime.use_api_server = setup.use_api_server = TRUE; + int num_tapes_uploaded = UploadTapes(); char message[100];