X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fscreens.c;h=0937577f26b411e625a05550634257599f7482c8;hb=d0c5c90b881c5764e2da6ca4ff7e5bc66f9a5913;hp=9c5a86fd482db0b9defdac9d88de2982695fa427;hpb=737e6b265c7b282ab560e98be0de6eb95b529066;p=rocksndiamonds.git diff --git a/src/screens.c b/src/screens.c index 9c5a86fd..7a6bfe41 100644 --- a/src/screens.c +++ b/src/screens.c @@ -63,30 +63,31 @@ #define SETUP_MODE_CHOOSE_OTHER 16 // sub-screens on the setup screen (specific) -#define SETUP_MODE_CHOOSE_GAME_SPEED 17 -#define SETUP_MODE_CHOOSE_SCROLL_DELAY 18 -#define SETUP_MODE_CHOOSE_SNAPSHOT_MODE 19 -#define SETUP_MODE_CHOOSE_WINDOW_SIZE 20 -#define SETUP_MODE_CHOOSE_SCALING_TYPE 21 -#define SETUP_MODE_CHOOSE_RENDERING 22 -#define SETUP_MODE_CHOOSE_VSYNC 23 -#define SETUP_MODE_CHOOSE_GRAPHICS 24 -#define SETUP_MODE_CHOOSE_SOUNDS 25 -#define SETUP_MODE_CHOOSE_MUSIC 26 -#define SETUP_MODE_CHOOSE_VOLUME_SIMPLE 27 -#define SETUP_MODE_CHOOSE_VOLUME_LOOPS 28 -#define SETUP_MODE_CHOOSE_VOLUME_MUSIC 29 -#define SETUP_MODE_CHOOSE_TOUCH_CONTROL 30 -#define SETUP_MODE_CHOOSE_MOVE_DISTANCE 31 -#define SETUP_MODE_CHOOSE_DROP_DISTANCE 32 -#define SETUP_MODE_CHOOSE_TRANSPARENCY 33 -#define SETUP_MODE_CHOOSE_GRID_XSIZE_0 34 -#define SETUP_MODE_CHOOSE_GRID_YSIZE_0 35 -#define SETUP_MODE_CHOOSE_GRID_XSIZE_1 36 -#define SETUP_MODE_CHOOSE_GRID_YSIZE_1 37 -#define SETUP_MODE_CONFIG_VIRT_BUTTONS 38 - -#define MAX_SETUP_MODES 39 +#define SETUP_MODE_CHOOSE_SCORES_TYPE 17 +#define SETUP_MODE_CHOOSE_GAME_SPEED 18 +#define SETUP_MODE_CHOOSE_SCROLL_DELAY 19 +#define SETUP_MODE_CHOOSE_SNAPSHOT_MODE 20 +#define SETUP_MODE_CHOOSE_WINDOW_SIZE 21 +#define SETUP_MODE_CHOOSE_SCALING_TYPE 22 +#define SETUP_MODE_CHOOSE_RENDERING 23 +#define SETUP_MODE_CHOOSE_VSYNC 24 +#define SETUP_MODE_CHOOSE_GRAPHICS 25 +#define SETUP_MODE_CHOOSE_SOUNDS 26 +#define SETUP_MODE_CHOOSE_MUSIC 27 +#define SETUP_MODE_CHOOSE_VOLUME_SIMPLE 28 +#define SETUP_MODE_CHOOSE_VOLUME_LOOPS 29 +#define SETUP_MODE_CHOOSE_VOLUME_MUSIC 30 +#define SETUP_MODE_CHOOSE_TOUCH_CONTROL 31 +#define SETUP_MODE_CHOOSE_MOVE_DISTANCE 32 +#define SETUP_MODE_CHOOSE_DROP_DISTANCE 33 +#define SETUP_MODE_CHOOSE_TRANSPARENCY 34 +#define SETUP_MODE_CHOOSE_GRID_XSIZE_0 35 +#define SETUP_MODE_CHOOSE_GRID_YSIZE_0 36 +#define SETUP_MODE_CHOOSE_GRID_XSIZE_1 37 +#define SETUP_MODE_CHOOSE_GRID_YSIZE_1 38 +#define SETUP_MODE_CONFIG_VIRT_BUTTONS 39 + +#define MAX_SETUP_MODES 40 #define MAX_MENU_MODES MAX(MAX_INFO_MODES, MAX_SETUP_MODES) @@ -104,6 +105,7 @@ #define STR_SETUP_EXIT "Exit" #define STR_SETUP_SAVE_AND_EXIT "Save and Exit" +#define STR_SETUP_CHOOSE_SCORES_TYPE "Scores Type" #define STR_SETUP_CHOOSE_GAME_SPEED "Game Speed" #define STR_SETUP_CHOOSE_SCROLL_DELAY "Scroll Delay" #define STR_SETUP_CHOOSE_SNAPSHOT_MODE "Snapshot Mode" @@ -123,6 +125,11 @@ #define STR_SETUP_CHOOSE_GRID_XSIZE_1 "Horiz. Buttons" #define STR_SETUP_CHOOSE_GRID_YSIZE_1 "Vert. Buttons" +// other screen text constants +#define STR_CHOOSE_TREE_EDIT "Edit" +#define MENU_CHOOSE_TREE_FONT(x) (FONT_TEXT_1 + (x)) +#define MENU_CHOOSE_TREE_COLOR(ti, a) TREE_COLOR(ti, a) + // for input setup functions #define SETUPINPUT_SCREEN_POS_START 0 #define SETUPINPUT_SCREEN_POS_EMPTY1 3 @@ -138,6 +145,7 @@ #define MENU_SCREEN_START_XPOS 1 #define MENU_SCREEN_START_YPOS 2 #define MENU_SCREEN_VALUE_XPOS (SCR_FIELDX - 3) +#define MENU_SCREEN_TEXT2_XPOS (SCR_FIELDX - 2) #define MENU_SCREEN_MAX_XPOS (SCR_FIELDX - 1) #define MENU_TITLE1_YPOS 8 #define MENU_TITLE2_YPOS 46 @@ -176,26 +184,32 @@ // screen gadget identifiers #define SCREEN_CTRL_ID_PREV_LEVEL 0 #define SCREEN_CTRL_ID_NEXT_LEVEL 1 -#define SCREEN_CTRL_ID_FIRST_LEVEL 2 -#define SCREEN_CTRL_ID_LAST_LEVEL 3 -#define SCREEN_CTRL_ID_LEVEL_NUMBER 4 -#define SCREEN_CTRL_ID_PREV_PLAYER 5 -#define SCREEN_CTRL_ID_NEXT_PLAYER 6 -#define SCREEN_CTRL_ID_INSERT_SOLUTION 7 -#define SCREEN_CTRL_ID_PLAY_SOLUTION 8 -#define SCREEN_CTRL_ID_SWITCH_ECS_AGA 9 -#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE 10 -#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE 11 -#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2 12 -#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2 13 -#define SCREEN_CTRL_ID_SCROLL_UP 14 -#define SCREEN_CTRL_ID_SCROLL_DOWN 15 -#define SCREEN_CTRL_ID_SCROLL_VERTICAL 16 -#define SCREEN_CTRL_ID_NETWORK_SERVER 17 - -#define NUM_SCREEN_GADGETS 18 - -#define NUM_SCREEN_MENUBUTTONS 14 +#define SCREEN_CTRL_ID_PREV_LEVEL2 2 +#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 NUM_SCREEN_SCROLLBUTTONS 2 #define NUM_SCREEN_SCROLLBARS 1 #define NUM_SCREEN_TEXTINPUT 1 @@ -205,6 +219,8 @@ #define SCREEN_MASK_INPUT (1 << 2) #define SCREEN_MASK_TOUCH (1 << 3) #define SCREEN_MASK_TOUCH2 (1 << 4) +#define SCREEN_MASK_SCORES (1 << 5) +#define SCREEN_MASK_SCORES_INFO (1 << 6) // graphic position and size values for buttons and scrollbars #define SC_MENUBUTTON_XSIZE TILEX @@ -248,31 +264,47 @@ static void execSetupTouch(void); static void execSetupArtwork(void); static void HandleChooseTree(int, int, int, int, int, TreeInfo **); +static void DrawChoosePlayerName(void); static void DrawChooseLevelSet(void); static void DrawChooseLevelNr(void); +static void DrawScoreInfo(int); +static void DrawScoreInfo_Content(int); static void DrawInfoScreen(void); static void DrawSetupScreen(void); +static void DrawTypeName(void); static void DrawInfoScreen_NotAvailable(char *, char *); static void DrawInfoScreen_HelpAnim(int, int, boolean); static void DrawInfoScreen_HelpText(int, int, int, int); static void HandleInfoScreen_Main(int, int, int, int, int); -static void HandleInfoScreen_TitleScreen(int); -static void HandleInfoScreen_Elements(int); -static void HandleInfoScreen_Music(int); -static void HandleInfoScreen_Credits(int); +static void HandleInfoScreen_TitleScreen(int, int, int); +static void HandleInfoScreen_Elements(int, int, int); +static void HandleInfoScreen_Music(int, int, int); +static void HandleInfoScreen_Credits(int, int, int); static void HandleInfoScreen_Program(int); static void HandleInfoScreen_Version(int); static void ModifyGameSpeedIfNeeded(void); static void DisableVsyncIfNeeded(void); +static void RedrawScreenMenuGadgets(int); static void MapScreenMenuGadgets(int); static void UnmapScreenMenuGadgets(int); static void MapScreenGadgets(int); +static void UnmapScreenGadgets(void); static void MapScreenTreeGadgets(TreeInfo *); +static void UnmapScreenTreeGadgets(void); static void UpdateScreenMenuGadgets(int, boolean); +static void AdjustScoreInfoButtons(int, int, int); + +static boolean OfferUploadTapes(void); +static void execOfferUploadTapes(void); + +static void DrawHallOfFame_setScoreEntries(void); +static void HandleHallOfFame_SelectLevel(int, int); +static char *getHallOfFameRankText(int, int); +static char *getHallOfFameScoreText(int, int); static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS]; @@ -297,6 +329,9 @@ static TreeInfo *scroll_delay_current = NULL; static TreeInfo *snapshot_modes = NULL; static TreeInfo *snapshot_mode_current = NULL; +static TreeInfo *scores_types = NULL; +static TreeInfo *scores_type_current = NULL; + static TreeInfo *game_speeds_normal = NULL; static TreeInfo *game_speeds_extended = NULL; static TreeInfo *game_speeds = NULL; @@ -326,9 +361,15 @@ static TreeInfo *transparency_current = NULL; static TreeInfo *grid_sizes[2][2] = { { NULL, NULL }, { NULL, NULL } }; static TreeInfo *grid_size_current[2][2] = { { NULL, NULL }, { NULL, NULL } }; +static TreeInfo *player_name = NULL; +static TreeInfo *player_name_current = NULL; + static TreeInfo *level_number = NULL; static TreeInfo *level_number_current = NULL; +static TreeInfo *score_entries = NULL; +static TreeInfo *score_entry_current = NULL; + static struct ValueTextInfo window_sizes_list[] = { { 50, "50 %" }, @@ -378,6 +419,15 @@ static struct StringValueTextInfo vsync_modes_list[] = { NULL, NULL }, }; +static struct StringValueTextInfo scores_types_list[] = +{ + { STR_SCORES_TYPE_LOCAL_ONLY, "Local scores only" }, + { STR_SCORES_TYPE_SERVER_ONLY, "Server scores only" }, + { STR_SCORES_TYPE_LOCAL_AND_SERVER, "Local and server scores" }, + + { NULL, NULL }, +}; + static struct ValueTextInfo game_speeds_list_normal[] = { { 30, "Very Slow" }, @@ -540,7 +590,9 @@ static int align_yoffset = 0; #define DRAW_MODE(s) ((s) >= GAME_MODE_MAIN && \ (s) <= GAME_MODE_SETUP ? (s) : \ (s) == GAME_MODE_PSEUDO_TYPENAME ? \ - GAME_MODE_MAIN : GAME_MODE_DEFAULT) + GAME_MODE_MAIN : \ + (s) == GAME_MODE_PSEUDO_TYPENAMES ? \ + GAME_MODE_NAMES : GAME_MODE_DEFAULT) // (there are no draw offset definitions needed for INFO_MODE_TITLE) #define DRAW_MODE_INFO(i) ((i) >= INFO_MODE_TITLE && \ @@ -574,6 +626,10 @@ static int align_yoffset = 0; menu.extra_spacing[GAME_MODE_SETUP] : \ menu.extra_spacing_setup[DRAW_MODE_SETUP(i)]) +#define EXTRA_SPACING_SCORES(i) (EXTRA_SPACING_INFO(i)) + +#define EXTRA_SPACING_SCOREINFO(i) (menu.extra_spacing[GAME_MODE_SCOREINFO]) + #define DRAW_XOFFSET(s) ((s) == GAME_MODE_INFO ? \ DRAW_XOFFSET_INFO(info_mode) : \ (s) == GAME_MODE_SETUP ? \ @@ -588,6 +644,8 @@ static int align_yoffset = 0; EXTRA_SPACING_INFO(info_mode) : \ (s) == GAME_MODE_SETUP ? \ EXTRA_SPACING_SETUP(setup_mode) : \ + (s) == GAME_MODE_SCORES ? \ + EXTRA_SPACING_SCORES(info_mode) : \ menu.extra_spacing[DRAW_MODE(s)]) #define mSX (SX + DRAW_XOFFSET(game_status)) @@ -625,6 +683,13 @@ struct TitleControlInfo struct TitleControlInfo title_controls[MAX_NUM_TITLE_SCREENS]; + +// credits screens definitions + +static int num_credits_screens = 0; +static boolean use_global_credits_screens = FALSE; + + // main menu display and control definitions #define MAIN_CONTROL_NAME 0 @@ -680,6 +745,8 @@ static char *main_text_title_1 = NULL; static char *main_text_title_2 = NULL; static char *main_text_title_3 = NULL; +extern char debug_xsn_mode[]; + struct MainControlInfo { int nr; @@ -928,10 +995,10 @@ static int getTitleSound(struct TitleControlInfo *tci) int sound_local = base + nr; #if 0 - printf("::: %d, %d, %d: %d ['%s'], %d ['%s']\n", - nr, initial, is_image, - sound_global, getSoundListEntry(sound_global)->filename, - sound_local, getSoundListEntry(sound_local)->filename); + Debug("screens:getTitleSound", "%d, %d, %d: %d ['%s'], %d ['%s']", + nr, initial, is_image, + sound_global, getSoundListEntry(sound_global)->filename, + sound_local, getSoundListEntry(sound_local)->filename); #endif if (!strEqual(getSoundListEntry(sound_local)->filename, UNDEFINED_FILENAME)) @@ -958,10 +1025,10 @@ static int getTitleMusic(struct TitleControlInfo *tci) int music_local = base + nr; #if 0 - printf("::: %d, %d, %d: %d ['%s'], %d ['%s']\n", - nr, initial, is_image, - music_global, getMusicListEntry(music_global)->filename, - music_local, getMusicListEntry(music_local)->filename); + Debug("screens:getTitleMusic", "%d, %d, %d: %d ['%s'], %d ['%s']", + nr, initial, is_image, + music_global, getMusicListEntry(music_global)->filename, + music_local, getMusicListEntry(music_local)->filename); #endif if (!strEqual(getMusicListEntry(music_local)->filename, UNDEFINED_FILENAME)) @@ -1326,10 +1393,11 @@ static boolean insideTextPosRect(struct TextPosInfo *rect, int x, int y) int rect_y = ALIGNED_TEXT_YPOS(rect); #if 0 - printf("::: insideTextPosRect: (%d, %d), (%d, %d) [%d, %d] (%d, %d) => %d\n", - x, y, rect_x, rect_y, rect->x, rect->y, rect->width, rect->height, - (x >= rect_x && x < rect_x + rect->width && - y >= rect_y && y < rect_y + rect->height)); + Debug("screens:insideTextPosRect", + "(%d, %d), (%d, %d) [%d, %d] (%d, %d) => %d", + x, y, rect_x, rect_y, rect->x, rect->y, rect->width, rect->height, + (x >= rect_x && x < rect_x + rect->width && + y >= rect_y && y < rect_y + rect->height)); #endif return (x >= rect_x && x < rect_x + rect->width && @@ -1360,10 +1428,10 @@ static void AdjustScrollbar(int id, int items_max, int items_visible, GDI_SCROLLBAR_ITEM_POSITION, item_position, GDI_END); } -static void AdjustChooseTreeScrollbar(int id, int first_entry, TreeInfo *ti) +static void AdjustChooseTreeScrollbar(TreeInfo *ti, int id) { AdjustScrollbar(id, numTreeInfoInGroup(ti), NUM_MENU_ENTRIES_ON_SCREEN, - first_entry); + ti->cl_first); } static void clearMenuListArea(void) @@ -1420,6 +1488,40 @@ static void drawChooseTreeCursor(int ypos, boolean active) drawCursorExt(0, ypos, active, -1); } +static int getChooseTreeEditFont(boolean active) +{ + return (active ? FONT_MENU_2_ACTIVE : FONT_MENU_2); +} + +static int getChooseTreeEditXPos(int pos) +{ + boolean has_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->mapped; + int xoffset = (has_scrollbar ? -1 : 0); + int xpos = MENU_SCREEN_TEXT2_XPOS + xoffset; + int sx = amSX + xpos * TILEX; + int font_nr = getChooseTreeEditFont(FALSE); + int width = getTextWidth(STR_CHOOSE_TREE_EDIT, font_nr); + + return (pos == POS_RIGHT ? sx + width - 1 : sx); +} + +static int getChooseTreeEditYPos(int ypos_raw) +{ + int ypos = MENU_SCREEN_START_YPOS + ypos_raw; + int sy = amSY + ypos * TILEY; + + return sy; +} + +static void drawChooseTreeEdit(int ypos_raw, boolean active) +{ + int sx = getChooseTreeEditXPos(POS_LEFT); + int sy = getChooseTreeEditYPos(ypos_raw); + int font_nr = getChooseTreeEditFont(active); + + DrawText(sx, sy, STR_CHOOSE_TREE_EDIT, font_nr); +} + static void DrawHeadline(void) { DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, main_text_title_1); @@ -1580,22 +1682,19 @@ void DrawMainMenu(void) return; } - // needed if last screen was the setup screen and fullscreen state changed - // (moved to "execSetupGraphics()" to change fullscreen state directly) - // ToggleFullscreenOrChangeWindowScalingIfNeeded(); - - // leveldir_current may be invalid (level group, parent link) - if (!validLevelSeries(leveldir_current)) - leveldir_current = getFirstValidTreeInfoEntry(leveldir_last_valid); + // 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) + { + UpdateLastPlayedLevels_TreeInfo(); + levelset_has_changed = TRUE; + } // store valid level series information leveldir_last_valid = leveldir_current; - init_last = init; // switch to new busy animation - // needed if last screen (level choice) changed graphics, sounds or music ReloadCustomArtwork(0); @@ -1689,6 +1788,11 @@ void DrawMainMenu(void) SetMouseCursor(CURSOR_DEFAULT); OpenDoor(DOOR_CLOSE_1 | DOOR_OPEN_2); + + SyncEmscriptenFilesystem(); + + // needed once after program start or after user change + CheckApiServerTasks(); } static void gotoTopLevelDir(void) @@ -1701,7 +1805,7 @@ static void gotoTopLevelDir(void) if (leveldir_current->node_parent->node_group->cl_first == -1) { int num_leveldirs = numTreeInfoInGroup(leveldir_current); - int leveldir_pos = posTreeInfo(leveldir_current); + int leveldir_pos = getPosFromTreeInfo(leveldir_current); int num_page_entries = MIN(num_leveldirs, NUM_MENU_ENTRIES_ON_SCREEN); int cl_first, cl_cursor; @@ -1821,7 +1925,7 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button) { return_to_main_menu = TRUE; } - else if (button == MB_MENU_CHOICE) + else if (button == MB_MENU_CHOICE || dx) { if (game_status_last_screen == GAME_MODE_INFO && num_title_screens == 0) { @@ -1834,9 +1938,10 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button) return; } - title_screen_nr++; + title_screen_nr += + (game_status_last_screen == GAME_MODE_INFO && dx < 0 ? -1 : +1); - if (title_screen_nr < num_title_screens) + if (title_screen_nr >= 0 && title_screen_nr < num_title_screens) { tci = &title_controls[title_screen_nr]; @@ -1906,6 +2011,16 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button) } } +static void HandleMainMenu_ToggleTeamMode(void) +{ + setup.team_mode = !setup.team_mode; + + InitializeMainControls(); + DrawCursorAndText_Main(MAIN_CONTROL_NAME, TRUE, FALSE); + + DrawPreviewPlayers(); +} + static void HandleMainMenu_SelectLevel(int step, int direction, int selected_level_nr) { @@ -2055,9 +2170,16 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) } else if (dx != 0) { - if (choice != MAIN_CONTROL_INFO && - choice != MAIN_CONTROL_SETUP) + if (choice == MAIN_CONTROL_NAME) + { + // special case: cursor left or right pressed -- toggle team mode + HandleMainMenu_ToggleTeamMode(); + } + else if (choice != MAIN_CONTROL_INFO && + choice != MAIN_CONTROL_SETUP) + { HandleMainMenu_SelectLevel(1, dx, NO_DIRECT_LEVEL_SELECT); + } } } else @@ -2070,18 +2192,24 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) insideTextPosRect(main_controls[i].pos_text, mx - mSX, my - mSY)) { // special case: menu text "name/team" clicked -- toggle team mode - setup.team_mode = !setup.team_mode; - - InitializeMainControls(); - DrawCursorAndText_Main(choice, TRUE, FALSE); - - DrawPreviewPlayers(); + HandleMainMenu_ToggleTeamMode(); } else { - SetGameStatus(GAME_MODE_PSEUDO_TYPENAME); + if (setup.multiple_users) + { + CloseDoor(DOOR_CLOSE_2); + + SetGameStatus(GAME_MODE_NAMES); - HandleTypeName(strlen(setup.player_name), 0); + DrawChoosePlayerName(); + } + else + { + SetGameStatus(GAME_MODE_PSEUDO_TYPENAME); + + DrawTypeName(); + } } } else if (pos == MAIN_CONTROL_LEVELS) @@ -2095,6 +2223,9 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) SaveLevelSetup_LastSeries(); SaveLevelSetup_SeriesInfo(); + // restore level set if chosen from "last played level set" menu + RestoreLastPlayedLevels(&leveldir_current); + if (setup.internal.choose_from_top_leveldir) gotoTopLevelDir(); @@ -2107,15 +2238,15 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) SetGameStatus(GAME_MODE_SCORES); - DrawHallOfFame(level_nr, -1); + DrawHallOfFame(level_nr); } else if (pos == MAIN_CONTROL_EDITOR) { if (leveldir_current->readonly && - !strEqual(setup.player_name, "Artsoft")) - Request("This level is read only!", REQ_CONFIRM); + setup.editor.show_read_only_warning) + Request("This level is read-only!", REQ_CONFIRM | REQ_STAY_OPEN); - CloseDoor(DOOR_CLOSE_2); + CloseDoor(DOOR_CLOSE_ALL); SetGameStatus(GAME_MODE_EDITOR); @@ -2152,8 +2283,13 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) SaveLevelSetup_LastSeries(); SaveLevelSetup_SeriesInfo(); - if (Request("Do you really want to quit?", REQ_ASK | REQ_STAY_CLOSED)) +#if defined(PLATFORM_EMSCRIPTEN) + Request("Close the browser window to quit!", REQ_CONFIRM); +#else + if (!setup.ask_on_quit_program || + Request("Do you really want to quit?", REQ_ASK | REQ_STAY_CLOSED)) SetGameStatus(GAME_MODE_QUIT); +#endif } } } @@ -2962,9 +3098,9 @@ static void DrawInfoScreen_TitleScreen(void) DrawTitleScreen(); } -void HandleInfoScreen_TitleScreen(int button) +void HandleInfoScreen_TitleScreen(int dx, int dy, int button) { - HandleTitleScreen(0, 0, 0, 0, button); + HandleTitleScreen(0, 0, dx, dy, button); } static void DrawInfoScreen_Elements(void) @@ -2976,12 +3112,12 @@ static void DrawInfoScreen_Elements(void) LoadHelpAnimInfo(); LoadHelpTextInfo(); - HandleInfoScreen_Elements(MB_MENU_INITIALIZE); + HandleInfoScreen_Elements(0, 0, MB_MENU_INITIALIZE); FadeIn(REDRAW_FIELD); } -void HandleInfoScreen_Elements(int button) +void HandleInfoScreen_Elements(int dx, int dy, int button) { static unsigned int info_delay = 0; static int num_anims; @@ -3020,16 +3156,16 @@ void HandleInfoScreen_Elements(int button) return; } - else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE) + else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE || dx) { if (button != MB_MENU_INITIALIZE) { PlaySound(SND_MENU_ITEM_SELECTING); - page++; + page += (dx < 0 ? -1 : +1); } - if (page >= num_pages) + if (page < 0 || page >= num_pages) { FadeMenuSoundsAndMusic(); @@ -3039,7 +3175,7 @@ void HandleInfoScreen_Elements(int button) return; } - if (page > 0) + if (button != MB_MENU_INITIALIZE) FadeSetNextScreen(); if (button != MB_MENU_INITIALIZE) @@ -3071,12 +3207,12 @@ static void DrawInfoScreen_Music(void) LoadMusicInfo(); - HandleInfoScreen_Music(MB_MENU_INITIALIZE); + HandleInfoScreen_Music(0, 0, MB_MENU_INITIALIZE); FadeIn(REDRAW_FIELD); } -void HandleInfoScreen_Music(int button) +void HandleInfoScreen_Music(int dx, int dy, int button) { static struct MusicFileInfo *list = NULL; int font_title = MENU_INFO_FONT_TITLE; @@ -3122,14 +3258,14 @@ void HandleInfoScreen_Music(int button) return; } - else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE) + else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE || dx) { if (button != MB_MENU_INITIALIZE) { PlaySound(SND_MENU_ITEM_SELECTING); if (list != NULL) - list = list->next; + list = (dx < 0 ? list->prev : list->next); } if (list == NULL) @@ -3157,7 +3293,7 @@ void HandleInfoScreen_Music(int button) { int sound = list->music; - if (sound_info[sound].loop) + if (IS_LOOP_SOUND(sound)) PlaySoundLoop(sound); else PlaySound(sound); @@ -3168,7 +3304,7 @@ void HandleInfoScreen_Music(int button) { int music = list->music; - if (music_info[music].loop) + if (IS_LOOP_MUSIC(music)) PlayMusicLoop(music); else PlayMusic(music); @@ -3236,24 +3372,18 @@ void HandleInfoScreen_Music(int button) FadeIn(REDRAW_FIELD); } - if (list != NULL && list->is_sound && sound_info[list->music].loop) + if (list != NULL && list->is_sound && IS_LOOP_SOUND(list->music)) PlaySoundLoop(list->music); } static void DrawInfoScreen_CreditsScreen(int screen_nr) { int font_title = MENU_INFO_FONT_TITLE; - int font_head = MENU_INFO_FONT_HEAD; int font_text = MENU_INFO_FONT_TEXT; int font_foot = MENU_INFO_FONT_FOOT; int spacing_title = menu.headline1_spacing_info[info_mode]; - int spacing_head = menu.headline2_spacing_info[info_mode]; - int spacing_para = menu.paragraph_spacing_info[info_mode]; int spacing_line = menu.line_spacing_info[info_mode]; int ystep_title = getMenuTextStep(spacing_title, font_title); - int ystep_head = getMenuTextStep(spacing_head, font_head); - int ystep_para = getMenuTextStep(spacing_para, font_text); - int ystep_line = getMenuTextStep(spacing_line, font_text); int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART1; int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; @@ -3261,211 +3391,21 @@ static void DrawInfoScreen_CreditsScreen(int screen_nr) DrawHeadline(); DrawTextSCentered(ystart, font_title, "Credits:"); - ystart += ystep_title; - if (screen_nr == 0) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Peter Liepa"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for creating"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "\"Boulder Dash\""); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "in the year"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "1984"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "published by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "First Star Software"); - } - else if (screen_nr == 1) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Klaus Heinz & Volker Wertich"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for creating"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "\"Emerald Mine\""); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "in the year"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "1987"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "published by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Kingsoft"); - } - else if (screen_nr == 2) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Michael Stopp & Philip Jespersen"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for creating"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "\"Supaplex\""); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "in the year"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "1991"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "published by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Digital Integration"); - } - else if (screen_nr == 3) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Hiroyuki Imabayashi"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for creating"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "\"Sokoban\""); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "in the year"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "1982"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "published by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Thinking Rabbit"); - } - else if (screen_nr == 4) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Alan Bond"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "and"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "J\xfcrgen Bonhagen"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for the continuous creation"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_head, - "of outstanding level sets"); - } - else if (screen_nr == 5) - { - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Peter Elzner"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for ideas and inspiration by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Diamond Caves"); - ystart += ystep_para; - - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Steffest"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for ideas and inspiration by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "DX-Boulderdash"); - } - else if (screen_nr == 6) - { - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "David Tritscher"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for the code base used for the"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_head, - "native Emerald Mine engine"); - } - else if (screen_nr == 7) - { - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Guido Schulz"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for the initial DOS port"); - ystart += ystep_para; - - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Karl H\xf6rnell"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for some additional toons"); - } - else if (screen_nr == 8) - { - DrawTextSCentered(ystart, font_head, - "And not to forget:"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "Many thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "All those who contributed"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_text, - "levels to this game"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_text, - "since 1995"); - } + char *filename = getCreditsFilename(screen_nr, use_global_credits_screens); + int width = SXSIZE; + int height = MENU_SCREEN_INFO_YBOTTOM - MENU_SCREEN_INFO_YSTART1; + int chars = width / getFontWidth(font_text); + int lines = height / getFontHeight(font_text); + int padx = (width - chars * getFontWidth(font_text)) / 2; + int line_spacing = getMenuTextSpacing(spacing_line, font_text); + boolean autowrap = FALSE; + boolean centered = TRUE; + boolean parse_comments = TRUE; + + DrawTextFile(mSX + padx, mSY + MENU_SCREEN_INFO_YSTART1 + ystep_title, + filename, font_text, chars, -1, lines, line_spacing, -1, + autowrap, centered, parse_comments); DrawTextSCentered(ybottom, font_foot, "Press any key or button for next page"); @@ -3479,24 +3419,57 @@ static void DrawInfoScreen_Credits(void) FadeOut(REDRAW_FIELD); - HandleInfoScreen_Credits(MB_MENU_INITIALIZE); + HandleInfoScreen_Credits(0, 0, MB_MENU_INITIALIZE); FadeIn(REDRAW_FIELD); } -void HandleInfoScreen_Credits(int button) +void HandleInfoScreen_Credits(int dx, int dy, int button) { static int screen_nr = 0; - int num_screens = 9; if (button == MB_MENU_INITIALIZE) { + int i; + + // determine number of (global or level set specific) credits screens + for (i = 0; i < 2; i++) + { + num_credits_screens = 0; + use_global_credits_screens = i; + + while (getCreditsFilename(num_credits_screens, + use_global_credits_screens) != NULL) + num_credits_screens++; + + if (num_credits_screens > 0) + break; + } + + if (num_credits_screens == 0) + { + int font_title = MENU_INFO_FONT_TITLE; + int font_foot = MENU_INFO_FONT_FOOT; + int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART1; + int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; + + ClearField(); + DrawHeadline(); + + DrawTextSCentered(ystart, font_title, + "No credits for this level set."); + + DrawTextSCentered(ybottom, font_foot, + "Press any key or button for info menu"); + + return; + } + screen_nr = 0; - // DrawInfoScreen_CreditsScreen(screen_nr); + DrawInfoScreen_CreditsScreen(screen_nr); } - - if (button == MB_MENU_LEAVE) + else if (button == MB_MENU_LEAVE) { PlaySound(SND_MENU_ITEM_SELECTING); @@ -3505,16 +3478,13 @@ void HandleInfoScreen_Credits(int button) return; } - else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE) + else if (button == MB_MENU_CHOICE || dx) { - if (button != MB_MENU_INITIALIZE) - { - PlaySound(SND_MENU_ITEM_SELECTING); + PlaySound(SND_MENU_ITEM_SELECTING); - screen_nr++; - } + screen_nr += (dx < 0 ? -1 : +1); - if (screen_nr >= num_screens) + if (screen_nr < 0 || screen_nr >= num_credits_screens) { FadeMenuSoundsAndMusic(); @@ -3524,16 +3494,13 @@ void HandleInfoScreen_Credits(int button) return; } - if (screen_nr > 0) - FadeSetNextScreen(); + FadeSetNextScreen(); - if (button != MB_MENU_INITIALIZE) - FadeOut(REDRAW_FIELD); + FadeOut(REDRAW_FIELD); DrawInfoScreen_CreditsScreen(screen_nr); - if (button != MB_MENU_INITIALIZE) - FadeIn(REDRAW_FIELD); + FadeIn(REDRAW_FIELD); } else { @@ -3695,6 +3662,10 @@ static void DrawInfoScreen_Version(void) DrawTextF(xstart1, ystart, font_head, "Source date"); DrawTextF(xstart2, ystart, font_text, getSourceDateString()); + ystart += ystep_line; + + DrawTextF(xstart1, ystart, font_head, "Commit hash"); + DrawTextF(xstart2, ystart, font_text, getSourceHashString()); ystart += ystep_para; DrawTextF(xstart1, ystart, font_head, "Library"); @@ -3935,13 +3906,13 @@ static void DrawInfoScreen(void) void HandleInfoScreen(int mx, int my, int dx, int dy, int button) { if (info_mode == INFO_MODE_TITLE) - HandleInfoScreen_TitleScreen(button); + HandleInfoScreen_TitleScreen(dx, dy, button); else if (info_mode == INFO_MODE_ELEMENTS) - HandleInfoScreen_Elements(button); + HandleInfoScreen_Elements(dx, dy, button); else if (info_mode == INFO_MODE_MUSIC) - HandleInfoScreen_Music(button); + HandleInfoScreen_Music(dx, dy, button); else if (info_mode == INFO_MODE_CREDITS) - HandleInfoScreen_Credits(button); + HandleInfoScreen_Credits(dx, dy, button); else if (info_mode == INFO_MODE_PROGRAM) HandleInfoScreen_Program(button); else if (info_mode == INFO_MODE_VERSION) @@ -3954,219 +3925,925 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button) // ============================================================================ -// type name functions +// rename player API functions // ============================================================================ -void HandleTypeName(int newxpos, Key key) +struct ApiRenamePlayerThreadData { - static char last_player_name[MAX_PLAYER_NAME_LEN + 1]; - struct MainControlInfo *mci = getMainControlInfo(MAIN_CONTROL_NAME); - struct TextPosInfo *pos = mci->pos_input; - int startx = mSX + ALIGNED_TEXT_XPOS(pos); - int starty = mSY + ALIGNED_TEXT_YPOS(pos); - static int xpos = 0; - int font_nr = pos->font; - int font_active_nr = FONT_ACTIVE(font_nr); - int font_width = getFontWidth(font_active_nr); - char key_char = getValidConfigValueChar(getCharFromKey(key)); - boolean is_valid_key_char = (key_char != 0 && (key_char != ' ' || xpos > 0)); - boolean is_active = TRUE; + char *player_name; + char *player_uuid; +}; - DrawBackgroundForFont(startx,starty, pos->width, pos->height, font_active_nr); +static void *CreateThreadData_ApiRenamePlayer(void) +{ + struct ApiRenamePlayerThreadData *data = + checked_malloc(sizeof(struct ApiRenamePlayerThreadData)); - if (newxpos) - { - strcpy(last_player_name, setup.player_name); + data->player_name = getStringCopy(setup.player_name); + data->player_uuid = getStringCopy(setup.player_uuid); - xpos = newxpos; + return data; +} - StartTextInput(startx, starty, pos->width, pos->height); - } - else if (is_valid_key_char && xpos < MAX_PLAYER_NAME_LEN) - { - setup.player_name[xpos] = key_char; - setup.player_name[xpos + 1] = 0; +static void FreeThreadData_ApiRenamePlayer(void *data_raw) +{ + struct ApiRenamePlayerThreadData *data = data_raw; - xpos++; - } - else if ((key == KSYM_Delete || key == KSYM_BackSpace) && xpos > 0) - { - xpos--; + checked_free(data->player_name); + checked_free(data->player_uuid); + checked_free(data); +} - setup.player_name[xpos] = 0; - } - else if (key == KSYM_Return && xpos > 0) - { - SaveSetup(); +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; - is_active = FALSE; + request->hostname = setup.api_server_hostname; + request->port = API_SERVER_PORT; + request->method = API_SERVER_METHOD; + request->uri = API_SERVER_URI_RENAME; - SetGameStatus(GAME_MODE_MAIN); - } - else if (key == KSYM_Escape) - { - strcpy(setup.player_name, last_player_name); + char *player_name = getEscapedJSON(player_name_raw); + char *player_uuid = getEscapedJSON(player_uuid_raw); - is_active = FALSE; + 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); - SetGameStatus(GAME_MODE_MAIN); - } + checked_free(player_name); + checked_free(player_uuid); + + ConvertHttpRequestBodyToServerEncoding(request); + + return TRUE; +} - if (is_active) +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) { - pos->width = (strlen(setup.player_name) + 1) * font_width; - startx = mSX + ALIGNED_TEXT_XPOS(pos); + HandleResponse_ApiRenamePlayer(response, data_raw); - DrawText(startx, starty, setup.player_name, font_active_nr); - DrawText(startx + xpos * font_width, starty, "_", font_active_nr); + checked_free(response); } else { - pos->width = strlen(setup.player_name) * font_width; - startx = mSX + ALIGNED_TEXT_XPOS(pos); - - DrawText(startx, starty, setup.player_name, font_nr); - - StopTextInput(); + Error("server response too large to handle (%d bytes)", size); } -} - -// ============================================================================ -// tree menu functions -// ============================================================================ + FreeThreadData_ApiRenamePlayer(data_raw); +} -static int getAlignXOffsetFromTreeInfo(TreeInfo *ti) +static void Emscripten_ApiRenamePlayer_Failed(unsigned handle, void *data_raw, + int code, const char *status) { - if (game_status != GAME_MODE_SETUP || - DRAW_MODE_SETUP(setup_mode) != SETUP_MODE_CHOOSE_OTHER) - return 0; - - int max_text_size = 0; - TreeInfo *node; - - for (node = getTreeInfoFirstGroupEntry(ti); node != NULL; node = node->next) - max_text_size = MAX(max_text_size, strlen(node->name)); + Error("server failed to handle request: %d %s", code, status); - int num_entries = numTreeInfoInGroup(ti); - boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN); - int text_width = max_text_size * getFontWidth(FONT_TEXT_1); - int button_width = SC_MENUBUTTON_XSIZE; - int scrollbar_xpos = SC_SCROLLBAR_XPOS + menu.scrollbar_xoffset; - int screen_width = (scrollbar_needed ? scrollbar_xpos : SXSIZE); - int align = menu.list_setup[SETUP_MODE_CHOOSE_OTHER].align; - int x = ALIGNED_XPOS(0, screen_width, align) * -1; - int align_xoffset_raw = ALIGNED_XPOS(x, button_width + text_width, align); - int align_xoffset = MAX(0, align_xoffset_raw); + FreeThreadData_ApiRenamePlayer(data_raw); +} - return align_xoffset; +static void Emscripten_ApiRenamePlayer_Progress(unsigned handle, void *data_raw, + int bytes, int size) +{ + // nothing to do here } -static int getAlignYOffsetFromTreeInfo(TreeInfo *ti) +static void Emscripten_ApiRenamePlayer_HttpRequest(struct HttpRequest *request, + void *data_raw) { - if (game_status != GAME_MODE_SETUP || - DRAW_MODE_SETUP(setup_mode) != SETUP_MODE_CHOOSE_OTHER) - return 0; + if (!SetRequest_ApiRenamePlayer(request, data_raw)) + { + FreeThreadData_ApiRenamePlayer(data_raw); - int num_entries = numTreeInfoInGroup(ti); - int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN); - int font_height = getFontHeight(FONT_TEXT_1); - int text_height = font_height * num_page_entries; - int page_height = font_height * NUM_MENU_ENTRIES_ON_SCREEN; - int align = menu.list_setup[SETUP_MODE_CHOOSE_OTHER].valign; - int y = ALIGNED_YPOS(0, page_height, align) * -1; - int align_yoffset_raw = ALIGNED_YPOS(y, text_height, align); - int align_yoffset = MAX(0, align_yoffset_raw); + return; + } - return align_yoffset; + emscripten_async_wget2_data(request->uri, + request->method, + request->body, + data_raw, + TRUE, + Emscripten_ApiRenamePlayer_Loaded, + Emscripten_ApiRenamePlayer_Failed, + Emscripten_ApiRenamePlayer_Progress); } -static void DrawChooseTree(TreeInfo **ti_ptr) -{ - int fade_mask = REDRAW_FIELD; +#else - if (CheckFadeAll()) - fade_mask = REDRAW_ALL; +static void ApiRenamePlayer_HttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + if (!SetRequest_ApiRenamePlayer(request, data_raw)) + return; - if (strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY)) + if (!DoHttpRequest(request, response)) { - SetGameStatus(GAME_MODE_MAIN); - - DrawMainMenu(); + Error("HTTP request failed: %s", GetHttpError()); return; } - UnmapAllGadgets(); + if (!HTTP_SUCCESS(response->status_code)) + { + Error("server failed to handle request: %d %s", + response->status_code, + response->status_text); - FreeScreenGadgets(); - CreateScreenGadgets(); + return; + } - FadeOut(fade_mask); + HandleResponse_ApiRenamePlayer(response, data_raw); +} - // needed if different viewport properties defined for choosing level (set) - ChangeViewportPropertiesIfNeeded(); +static void ApiRenamePlayer_HttpRequest(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + ApiRenamePlayer_HttpRequestExt(request, response, data_raw); - if (game_status == GAME_MODE_LEVELNR) - SetMainBackgroundImage(IMG_BACKGROUND_LEVELNR); - else if (game_status == GAME_MODE_LEVELS) - SetMainBackgroundImage(IMG_BACKGROUND_LEVELS); + FreeThreadData_ApiRenamePlayer(data_raw); +} +#endif - ClearField(); +static int ApiRenamePlayerThread(void *data_raw) +{ + struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); - OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW); + program.api_thread_count++; - HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, ti_ptr); - MapScreenTreeGadgets(*ti_ptr); +#if defined(PLATFORM_EMSCRIPTEN) + Emscripten_ApiRenamePlayer_HttpRequest(request, data_raw); +#else + ApiRenamePlayer_HttpRequest(request, response, data_raw); +#endif - DrawMaskedBorder(fade_mask); + program.api_thread_count--; - FadeIn(fade_mask); + checked_free(request); + checked_free(response); + + return 0; } -static void drawChooseTreeList(int first_entry, int num_page_entries, - TreeInfo *ti) +static void ApiRenamePlayerAsThread(void) { - int num_entries = numTreeInfoInGroup(ti); - boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN); - int scrollbar_xpos = SC_SCROLLBAR_XPOS + menu.scrollbar_xoffset; - int screen_width = (scrollbar_needed ? scrollbar_xpos : SXSIZE); - int font_nr = FONT_TEXT_1; - int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset; - int i; - char *title_string = NULL; - int yoffset_sets = MENU_TITLE1_YPOS; - int yoffset_setup = 16; - int yoffset = (ti->type == TREE_TYPE_LEVEL_DIR || - ti->type == TREE_TYPE_LEVEL_NR ? yoffset_sets : yoffset_setup); + struct ApiRenamePlayerThreadData *data = CreateThreadData_ApiRenamePlayer(); - title_string = ti->infotext; + ExecuteAsThread(ApiRenamePlayerThread, + "ApiRenamePlayer", data, + "rename player on server"); +} - DrawTextSCentered(mSY - SY + yoffset, FONT_TITLE_1, title_string); - clearMenuListArea(); +// ============================================================================ +// reset player UUID API functions +// ============================================================================ - for (i = 0; i < num_page_entries; i++) - { - TreeInfo *node, *node_first; - int entry_pos = first_entry + i; - int xpos = MENU_SCREEN_START_XPOS; - int ypos = MENU_SCREEN_START_YPOS + i; - int startx = amSX + xpos * 32; - int starty = amSY + ypos * 32; - int startx_text = startx + font_xoffset; - int endx_text = amSX + screen_width; - int max_text_size = endx_text - startx_text; - int max_buffer_len = max_text_size / getFontWidth(font_nr); - char buffer[max_buffer_len + 1]; +struct ApiResetUUIDThreadData +{ + char *player_name; + char *player_uuid_old; + char *player_uuid_new; +}; - node_first = getTreeInfoFirstGroupEntry(ti); - node = getTreeInfoFromPos(node_first, entry_pos); +static void *CreateThreadData_ApiResetUUID(char *uuid_new) +{ + struct ApiResetUUIDThreadData *data = + checked_malloc(sizeof(struct ApiResetUUIDThreadData)); - strncpy(buffer, node->name, max_buffer_len); - buffer[max_buffer_len] = '\0'; + data->player_name = getStringCopy(setup.player_name); + data->player_uuid_old = getStringCopy(setup.player_uuid); + data->player_uuid_new = getStringCopy(uuid_new); - DrawText(startx, starty, buffer, font_nr + node->color); + 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 +// ============================================================================ + +static TreeInfo *type_name_node = NULL; +static char type_name_last[MAX_PLAYER_NAME_LEN + 1] = { 0 }; +static int type_name_nr = 0; + +static int getPlayerNameColor(char *name) +{ + return (strEqual(name, EMPTY_PLAYER_NAME) ? FC_BLUE : FC_RED); +} + +static void drawTypeNameText(char *name, struct TextPosInfo *pos, + boolean active) +{ + char text[MAX_PLAYER_NAME_LEN + 2] = { 0 }; + boolean multiple_users = (game_status == GAME_MODE_PSEUDO_TYPENAMES); + int sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(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); + + DrawBackgroundForFont(sx, sy, pos->width, pos->height, font_nr); + + sprintf(text, "%s%c", name, (active ? '_' : '\0')); + + pos->width = strlen(text) * font_width; + sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(pos)); + + DrawText(sx, sy, text, font_nr); +} + +static void getTypeNameValues(char *name, struct TextPosInfo *pos, int *xpos) +{ + struct MainControlInfo *mci = getMainControlInfo(MAIN_CONTROL_NAME); + + *pos = *mci->pos_input; + + if (game_status == GAME_MODE_PSEUDO_TYPENAMES) + { + TreeInfo *ti = player_name_current; + int first_entry = ti->cl_first; + int entry_pos = first_entry + ti->cl_cursor; + TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti); + int xpos = MENU_SCREEN_START_XPOS; + int ypos = MENU_SCREEN_START_YPOS + ti->cl_cursor; + + type_name_node = getTreeInfoFromPos(node_first, entry_pos); + type_name_nr = entry_pos; + + strcpy(name, type_name_node->name); + + pos->x = xpos * 32; + pos->y = ypos * 32; + pos->width = MAX_PLAYER_NAME_LEN * 32; + } + else + { + type_name_nr = user.nr; + + strcpy(name, setup.player_name); + } + + strcpy(type_name_last, name); + + if (strEqual(name, EMPTY_PLAYER_NAME)) + strcpy(name, ""); + + *xpos = strlen(name); +} + +static void setTypeNameValues_Name(char *name, struct TextPosInfo *pos) +{ + // change name of edited user in global list of user names + setString(&global.user_names[type_name_nr], name); + + if (game_status == GAME_MODE_PSEUDO_TYPENAMES) + { + TreeInfo *node = type_name_node; + + // change name of edited user in local menu tree structure + setString(&node->name, name); + setString(&node->name_sorting, name); + + node->color = getPlayerNameColor(name); + pos->font = MENU_CHOOSE_TREE_FONT(node->color); + } +} + +static void setTypeNameValues(char *name, struct TextPosInfo *pos, + boolean changed) +{ + boolean reset_setup = strEqual(name, ""); + boolean remove_user = strEqual(name, EMPTY_PLAYER_NAME); + boolean create_user = strEqual(type_name_last, EMPTY_PLAYER_NAME); + + if (!changed) + strcpy(name, type_name_last); + + if (strEqual(name, "")) + strcpy(name, EMPTY_PLAYER_NAME); + + setTypeNameValues_Name(name, pos); + + // if player name not changed, no further action required + if (strEqual(name, type_name_last)) + return; + + // redraw player name before (possibly) opening request dialogs + drawTypeNameText(name, pos, FALSE); + + int last_user_nr = user.nr; + + if (game_status == GAME_MODE_PSEUDO_TYPENAMES) + { + // save setup of currently active user (may differ from edited user) + SaveSetup(); + + // temporarily change active user to edited user + user.nr = type_name_nr; + + if (create_user && + Request("Use current setup values for the new player?", REQ_ASK)) + { + // use current setup values for new user, but create new player UUID + setup.player_uuid = getStringCopy(getUUID()); + } + else + { + // load setup for existing user (or start with defaults for new user) + LoadSetup(); + } + } + + char *setup_filename = getSetupFilename(); + boolean setup_exists = fileExists(setup_filename); + + // change name of edited user in setup structure + strcpy(setup.player_name, name); + + // save setup of edited user + SaveSetup(); + + // change name of edited user on score server + ApiRenamePlayerAsThread(); + + if (game_status == GAME_MODE_PSEUDO_TYPENAMES || reset_setup) + { + if (reset_setup) + { + if (Request("Reset setup values for this player?", REQ_ASK)) + { + // remove setup config file + unlink(setup_filename); + + // set player name to default player name + LoadSetup(); + + // update player name used by name typing functions + strcpy(name, setup.player_name); + + setTypeNameValues_Name(name, pos); + } + } + else if (remove_user && type_name_nr != 0) + { + if (Request("Remove settings and tapes for deleted player?", REQ_ASK)) + { + char *user_dir = getUserGameDataDir(); + char *user_dir_removed = + getStringCat3WithSeparator(user_dir, "REMOVED", + getCurrentTimestamp(), "."); + + if (rename(user_dir, user_dir_removed) != 0) + Request("Removing settings and tapes failed!", REQ_CONFIRM); + + checked_free(user_dir_removed); + } + } + else if (create_user && type_name_nr != 0 && !setup_exists) + { + if (Request("Create empty level set for the new player?", REQ_ASK)) + { + char *levelset_subdir = getNewUserLevelSubdir(); + + if (CreateUserLevelSet(levelset_subdir, name, name, 100, FALSE)) + { + AddUserLevelSetToLevelInfo(levelset_subdir); + + LevelDirTree *leveldir_current_last = leveldir_current; + + leveldir_current = getTreeInfoFromIdentifier(leveldir_first, + levelset_subdir); + + // set level number of newly created level set to default value + LoadLevelSetup_SeriesInfo(); + + // set newly created level set as current level set for new user + SaveLevelSetup_LastSeries(); + SaveLevelSetup_SeriesInfo(); + + leveldir_current = leveldir_current_last; + } + else + { + Request("Creating new level set failed!", REQ_CONFIRM); + } + } + } + + // restore currently active user + user.nr = last_user_nr; + + // restore setup of currently active user + LoadSetup(); + + // restore last level set of currently active user + LoadLevelSetup_LastSeries(); + LoadLevelSetup_SeriesInfo(); + } +} + +static void HandleTypeNameExt(boolean initialize, Key key) +{ + static struct TextPosInfo pos_name = { 0 }; + static char name[MAX_PLAYER_NAME_LEN + 1] = { 0 }; + static int xpos = 0; + struct TextPosInfo *pos = &pos_name; + char key_char = getValidConfigValueChar(getCharFromKey(key)); + boolean is_valid_key_char = (key_char != 0 && (key_char != ' ' || xpos > 0)); + boolean active = TRUE; + + if (initialize) + { + getTypeNameValues(name, pos, &xpos); + + int sx = mSX + ALIGNED_TEXT_XPOS(pos); + int sy = mSY + ALIGNED_TEXT_YPOS(pos); + + StartTextInput(sx, sy, pos->width, pos->height); + } + else if (is_valid_key_char && xpos < MAX_PLAYER_NAME_LEN) + { + name[xpos] = key_char; + name[xpos + 1] = 0; + + xpos++; + } + else if ((key == KSYM_Delete || key == KSYM_BackSpace) && xpos > 0) + { + xpos--; + + name[xpos] = 0; + } + else if (key == KSYM_Return || key == KSYM_Escape) + { + boolean changed = (key == KSYM_Return); + + StopTextInput(); + + setTypeNameValues(name, pos, changed); + + active = FALSE; + } + + drawTypeNameText(name, pos, active); + + if (!active) + { + SetGameStatus(game_status_last_screen); + + if (game_status == GAME_MODE_MAIN) + InitializeMainControls(); + } +} + +static void DrawTypeName(void) +{ + HandleTypeNameExt(TRUE, 0); +} + +void HandleTypeName(Key key) +{ + HandleTypeNameExt(FALSE, key); +} + + +// ============================================================================ +// tree menu functions +// ============================================================================ + +static int getAlignXOffsetFromTreeInfo(TreeInfo *ti) +{ + if (game_status != GAME_MODE_SETUP || + DRAW_MODE_SETUP(setup_mode) != SETUP_MODE_CHOOSE_OTHER) + return 0; + + int max_text_size = 0; + TreeInfo *node; + + for (node = getTreeInfoFirstGroupEntry(ti); node != NULL; node = node->next) + max_text_size = MAX(max_text_size, strlen(node->name)); + + int num_entries = numTreeInfoInGroup(ti); + boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN); + int font_nr = MENU_CHOOSE_TREE_FONT(FC_RED); + int text_width = max_text_size * getFontWidth(font_nr); + int button_width = SC_MENUBUTTON_XSIZE; + int scrollbar_xpos = SC_SCROLLBAR_XPOS + menu.scrollbar_xoffset; + int screen_width = (scrollbar_needed ? scrollbar_xpos : SXSIZE); + int align = menu.list_setup[SETUP_MODE_CHOOSE_OTHER].align; + int x = ALIGNED_XPOS(0, screen_width, align) * -1; + int align_xoffset_raw = ALIGNED_XPOS(x, button_width + text_width, align); + int align_xoffset = MAX(0, align_xoffset_raw); + + return align_xoffset; +} + +static int getAlignYOffsetFromTreeInfo(TreeInfo *ti) +{ + if (game_status != GAME_MODE_SETUP || + DRAW_MODE_SETUP(setup_mode) != SETUP_MODE_CHOOSE_OTHER) + return 0; + + int num_entries = numTreeInfoInGroup(ti); + int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN); + int font_nr = MENU_CHOOSE_TREE_FONT(FC_RED); + int font_height = getFontHeight(font_nr); + int text_height = font_height * num_page_entries; + int page_height = font_height * NUM_MENU_ENTRIES_ON_SCREEN; + int align = menu.list_setup[SETUP_MODE_CHOOSE_OTHER].valign; + int y = ALIGNED_YPOS(0, page_height, align) * -1; + int align_yoffset_raw = ALIGNED_YPOS(y, text_height, align); + int align_yoffset = MAX(0, align_yoffset_raw); + + return align_yoffset; +} + +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); + + if (CheckFadeAll()) + fade_mask = REDRAW_ALL; + + if (*ti_ptr != NULL && strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY)) + { + if (game_status == GAME_MODE_SETUP) + { + execSetupArtwork(); + } + else // GAME_MODE_LEVELS + { + SetGameStatus(GAME_MODE_MAIN); + + DrawMainMenu(); + } + + return; + } + + UnmapAllGadgets(); + + FreeScreenGadgets(); + CreateScreenGadgets(); + + if (restart_music) + FadeMenuSoundsAndMusic(); + + FadeOut(fade_mask); + + // needed if different viewport properties defined for this screen + ChangeViewportPropertiesIfNeeded(); + + if (game_status == GAME_MODE_NAMES) + SetMainBackgroundImage(IMG_BACKGROUND_NAMES); + else if (game_status == GAME_MODE_LEVELNR) + SetMainBackgroundImage(IMG_BACKGROUND_LEVELNR); + else if (game_status == GAME_MODE_LEVELS) + SetMainBackgroundImage(IMG_BACKGROUND_LEVELS); + else if (game_status == GAME_MODE_SCORES) + SetMainBackgroundImage(IMG_BACKGROUND_SCORES); + + ClearField(); + + OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW); + + // map gadgets for high score screen + if (game_status == GAME_MODE_SCORES) + MapScreenMenuGadgets(SCREEN_MASK_SCORES); + + MapScreenTreeGadgets(*ti_ptr); + + HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, ti_ptr); + + DrawMaskedBorder(fade_mask); + + if (restart_music) + PlayMenuSoundsAndMusic(); + + FadeIn(fade_mask); +} + +static int getChooseTreeFont(TreeInfo *node, boolean active) +{ + if (game_status == GAME_MODE_SCORES) + return (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1); + else + return MENU_CHOOSE_TREE_FONT(MENU_CHOOSE_TREE_COLOR(node, active)); +} + +static void drawChooseTreeText(TreeInfo *ti, int y, boolean active) +{ + int num_entries = numTreeInfoInGroup(ti); + boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN); + int scrollbar_xpos = SC_SCROLLBAR_XPOS + menu.scrollbar_xoffset; + int screen_width = (scrollbar_needed ? scrollbar_xpos : SXSIZE); + int first_entry = ti->cl_first; + int entry_pos = first_entry + y; + TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti); + TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos); + int font_nr = getChooseTreeFont(node, active); + int font_xoffset = getFontDrawOffsetX(font_nr); + int xpos = MENU_SCREEN_START_XPOS; + int ypos = MENU_SCREEN_START_YPOS + y; + int startdx = xpos * 32; + int startdy = ypos * 32; + int startx = amSX + startdx; + int starty = amSY + startdy; + int startx_text = startx + font_xoffset; + int endx_text = amSX + screen_width; + int max_text_size = endx_text - startx_text; + int max_buffer_len = max_text_size / getFontWidth(font_nr); + char buffer[max_buffer_len + 1]; + + if (game_status == GAME_MODE_SCORES && !node->parent_link) + { + int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1); + int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2); + int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3); + int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4); + int font_size_1 = getFontWidth(font_nr1); + int font_size_3 = getFontWidth(font_nr3); + int font_size_4 = getFontWidth(font_nr4); + int text_size_1 = 4 * font_size_1; + int text_size_4 = 5 * font_size_4; + int border = amSX - SX + getFontDrawOffsetX(font_nr1); + int dx1 = 0; + int dx3 = text_size_1; + int dx4 = SXSIZE - 2 * startdx - 2 * border - text_size_4; + int num_dots = (dx4 - dx3) / font_size_3; + int startx1 = startx + dx1; + int startx3 = startx + dx3; + int startx4 = startx + dx4; + int pos = node->pos; + char *pos_text = getHallOfFameRankText(pos, 3); + int i; + + DrawText(startx1, starty, pos_text, font_nr1); + + for (i = 0; i < num_dots; i++) + DrawText(startx3 + i * font_size_3, starty, ".", font_nr3); + + if (!strEqual(scores.entry[pos].name, EMPTY_PLAYER_NAME)) + DrawText(startx3, starty, scores.entry[pos].name, font_nr2); + + DrawText(startx4, starty, getHallOfFameScoreText(pos, 5), font_nr4); + } + else + { + strncpy(buffer, node->name, max_buffer_len); + buffer[max_buffer_len] = '\0'; + + DrawText(startx, starty, buffer, font_nr); + } +} + +static void drawChooseTreeHeadExt(int type, char *title_string) +{ + int yoffset_sets = MENU_TITLE1_YPOS; + int yoffset_setup = 16; + int yoffset = (type == TREE_TYPE_SCORE_ENTRY || + type == TREE_TYPE_LEVEL_DIR || + type == TREE_TYPE_LEVEL_NR ? yoffset_sets : yoffset_setup); + + DrawTextSCentered(mSY - SY + yoffset, FONT_TITLE_1, title_string); +} + +static void drawChooseTreeHead(TreeInfo *ti) +{ + drawChooseTreeHeadExt(ti->type, ti->infotext); +} + +static void drawChooseTreeList(TreeInfo *ti) +{ + int first_entry = ti->cl_first; + int num_entries = numTreeInfoInGroup(ti); + int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN); + int i; + + clearMenuListArea(); + + for (i = 0; i < num_page_entries; i++) + { + TreeInfo *node, *node_first; + int entry_pos = first_entry + i; + + node_first = getTreeInfoFirstGroupEntry(ti); + node = getTreeInfoFromPos(node_first, entry_pos); + + drawChooseTreeText(ti, i, FALSE); if (node->parent_link) initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU); @@ -4174,44 +4851,130 @@ static void drawChooseTreeList(int first_entry, int num_page_entries, initCursor(i, IMG_MENU_BUTTON_ENTER_MENU); else initCursor(i, IMG_MENU_BUTTON); + + if (game_status == GAME_MODE_SCORES && node->pos == scores.last_added) + initCursor(i, IMG_MENU_BUTTON_ENTER_MENU); + + if (game_status == GAME_MODE_NAMES) + drawChooseTreeEdit(i, FALSE); } - redraw_mask |= REDRAW_FIELD; + redraw_mask |= REDRAW_FIELD; +} + +static void drawChooseTreeInfo(TreeInfo *ti) +{ + int entry_pos = ti->cl_first + ti->cl_cursor; + int last_redraw_mask = redraw_mask; + int ypos = MENU_TITLE2_YPOS; + int font_nr = FONT_TITLE_2; + int x; + + if (ti->type == TREE_TYPE_LEVEL_NR) + DrawTextFCentered(ypos, font_nr, leveldir_current->name); + + if (ti->type == TREE_TYPE_SCORE_ENTRY) + DrawTextFCentered(ypos, font_nr, "HighScores of Level %d", + scores.last_level_nr); + + if (ti->type != TREE_TYPE_LEVEL_DIR) + return; + + TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti); + TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos); + + DrawBackgroundForFont(SX, SY + ypos, SXSIZE, getFontHeight(font_nr), font_nr); + + if (node->parent_link) + DrawTextFCentered(ypos, font_nr, "leave \"%s\"", + node->node_parent->name); + else if (node->level_group) + DrawTextFCentered(ypos, font_nr, "enter \"%s\"", + node->name); + else if (ti->type == TREE_TYPE_LEVEL_DIR) + DrawTextFCentered(ypos, font_nr, "%3d %s (%s)", + node->levels, (node->levels > 1 ? "levels" : "level"), + node->class_desc); + + // let BackToFront() redraw only what is needed + redraw_mask = last_redraw_mask; + for (x = 0; x < SCR_FIELDX; x++) + MarkTileDirty(x, 1); +} + +static void drawChooseTreeCursorAndText(TreeInfo *ti, boolean active) +{ + drawChooseTreeCursor(ti->cl_cursor, active); + drawChooseTreeText(ti, ti->cl_cursor, active); +} + +static void drawChooseTreeScreen(TreeInfo *ti) +{ + drawChooseTreeHead(ti); + drawChooseTreeList(ti); + drawChooseTreeInfo(ti); + drawChooseTreeCursorAndText(ti, TRUE); + + AdjustChooseTreeScrollbar(ti, SCREEN_CTRL_ID_SCROLL_VERTICAL); + + // scroll bar and buttons may just have been added after reloading scores + if (game_status == GAME_MODE_SCORES) + MapScreenTreeGadgets(ti); +} + +static void drawChooseTreeScreen_Scores_NotAvailable(void) +{ + // dirty workaround to use spacing definitions from info screen + info_mode = INFO_MODE_TITLE; + + char *text_info = "HighScores of Level %d"; + char *text_title = "Score information:"; + char *text_error = "No scores for this level."; + char *text_foot = "Press any key or button for main menu"; + int font_info = FONT_TITLE_2; + int font_title = FONT_INITIAL_3; + int font_error = FONT_INITIAL_4; + int font_foot = FONT_INITIAL_2; + int spacing_title = menu.headline1_spacing_info[INFO_MODE_TITLE]; + int ystep_title = getMenuTextStep(spacing_title, font_title); + int ystart1 = mSY - SY + MENU_SCREEN_INFO_YSTART1; + int ystart2 = ystart1 + ystep_title; + int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; + int ystart0 = MENU_TITLE2_YPOS; + + drawChooseTreeHeadExt(TREE_TYPE_SCORE_ENTRY, INFOTEXT_SCORE_ENTRY); + DrawTextFCentered(ystart0, font_info, text_info, scores.last_level_nr); + + DrawTextSCentered(ystart1, font_title, text_title); + DrawTextSCentered(ystart2, font_error, text_error); + + DrawTextSCentered(ybottom, font_foot, text_foot); } -static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti) +static TreeInfo *setHallOfFameActiveEntry(TreeInfo **ti_ptr) { - TreeInfo *node, *node_first; - int x, last_redraw_mask = redraw_mask; - int ypos = MENU_TITLE2_YPOS; - int font_nr = FONT_TITLE_2; + int score_pos = scores.last_added; - if (ti->type == TREE_TYPE_LEVEL_NR) - DrawTextFCentered(ypos, font_nr, leveldir_current->name); + if (game_status_last_screen == GAME_MODE_SCOREINFO) + score_pos = scores.last_entry_nr; - if (ti->type != TREE_TYPE_LEVEL_DIR) - return; + // set current tree entry to last added score entry + *ti_ptr = getTreeInfoFromIdentifier(score_entries, i_to_a(score_pos)); - node_first = getTreeInfoFirstGroupEntry(ti); - node = getTreeInfoFromPos(node_first, entry_pos); + // if that fails, set current tree entry to first entry (back link) + if (*ti_ptr == NULL) + *ti_ptr = score_entries->node_group; - DrawBackgroundForFont(SX, SY + ypos, SXSIZE, getFontHeight(font_nr), font_nr); + int num_entries = numTreeInfoInGroup(*ti_ptr); + int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN); + int pos_score = getPosFromTreeInfo(*ti_ptr); + int pos_first_raw = pos_score - (num_page_entries + 1) / 2 + 1; + int pos_first = MIN(MAX(0, pos_first_raw), num_entries - num_page_entries); - if (node->parent_link) - DrawTextFCentered(ypos, font_nr, "leave \"%s\"", - node->node_parent->name); - else if (node->level_group) - DrawTextFCentered(ypos, font_nr, "enter \"%s\"", - node->name); - else if (ti->type == TREE_TYPE_LEVEL_DIR) - DrawTextFCentered(ypos, font_nr, "%3d %s (%s)", - node->levels, (node->levels > 1 ? "levels" : "level"), - node->class_desc); + (*ti_ptr)->cl_first = pos_first; + (*ti_ptr)->cl_cursor = pos_score - pos_first; - // let BackToFront() redraw only what is needed - redraw_mask = last_redraw_mask; - for (x = 0; x < SCR_FIELDX; x++) - MarkTileDirty(x, 1); + return *ti_ptr; } static void HandleChooseTree(int mx, int my, int dx, int dy, int button, @@ -4221,26 +4984,75 @@ 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 x = 0; - int y = ti->cl_cursor; + int y = (ti != NULL ? ti->cl_cursor : 0); int step = (button == 1 ? 1 : button == 2 ? 5 : 10); int num_entries = numTreeInfoInGroup(ti); int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN); boolean position_set_by_scrollbar = (dx == 999); + boolean button_action = (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE); + boolean button_is_valid = (mx >= 0 && my >= 0); + boolean button_screen_clicked = (button_action && button_is_valid); + + if (game_status == GAME_MODE_SCORES) + { + if (server_scores.updated) + { + // reload scores, using updated server score cache file + LoadLocalAndServerScore(scores.last_level_nr, FALSE); + + server_scores.updated = FALSE; + + DrawHallOfFame_setScoreEntries(); + + if (score_entries != NULL) + { + ti = setHallOfFameActiveEntry(ti_ptr); + + if (button != MB_MENU_INITIALIZE) + drawChooseTreeScreen(ti); + } + } + + if (score_entries == NULL) + { + if (button == MB_MENU_INITIALIZE) + { + drawChooseTreeScreen_Scores_NotAvailable(); + } + else if (button_screen_clicked) + { + PlaySound(SND_MENU_ITEM_SELECTING); + + SetGameStatus(GAME_MODE_MAIN); + + DrawMainMenu(); + } + + return; + } + } if (button == MB_MENU_INITIALIZE) { int num_entries = numTreeInfoInGroup(ti); - int entry_pos = posTreeInfo(ti); + int entry_pos = getPosFromTreeInfo(ti); align_xoffset = getAlignXOffsetFromTreeInfo(ti); align_yoffset = getAlignYOffsetFromTreeInfo(ti); - if (ti->cl_first == -1) + if (game_status == GAME_MODE_SCORES) + { + ti = setHallOfFameActiveEntry(ti_ptr); + } + else if (ti->cl_first == -1) { // only on initialization ti->cl_first = MAX(0, entry_pos - num_page_entries + 1); ti->cl_cursor = entry_pos - ti->cl_first; + } else if (ti->cl_cursor >= num_page_entries || (num_entries > num_page_entries && @@ -4253,19 +5065,15 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, if (position_set_by_scrollbar) ti->cl_first = dy; - else - AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, - ti->cl_first, ti); - drawChooseTreeList(ti->cl_first, num_page_entries, ti); - drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti); - drawChooseTreeCursor(ti->cl_cursor, TRUE); + drawChooseTreeScreen(ti); return; } else if (button == MB_MENU_LEAVE) { - FadeSetLeaveMenu(); + if (game_status != GAME_MODE_SCORES) + FadeSetLeaveMenu(); PlaySound(SND_MENU_ITEM_SELECTING); @@ -4276,7 +5084,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, } else if (game_status == GAME_MODE_SETUP) { - if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || + if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE || + setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY || setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE) execSetupGame(); @@ -4322,6 +5131,9 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, { x = (mx - amSX) / 32; y = (my - amSY) / 32 - MENU_SCREEN_START_YPOS; + + if (game_status == GAME_MODE_NAMES) + drawChooseTreeEdit(ti->cl_cursor, FALSE); } else if (dx || dy) // keyboard or scrollbar/scrollbutton input { @@ -4363,14 +5175,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, } if (redraw) - { - drawChooseTreeList(ti->cl_first, num_page_entries, ti); - drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti); - drawChooseTreeCursor(ti->cl_cursor, TRUE); - - AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, - ti->cl_first, ti); - } + drawChooseTreeScreen(ti); return; } @@ -4379,7 +5184,13 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, y = ti->cl_cursor + dy; } - if (dx == 1) + if (game_status == GAME_MODE_SCORES && ABS(dx) == 1) + { + HandleHallOfFame_SelectLevel(1, dx); + + return; + } + else if (dx == 1) { TreeInfo *node_first, *node_cursor; int entry_pos = ti->cl_first + y; @@ -4420,21 +5231,30 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, { if (button) { + if (game_status == GAME_MODE_NAMES) + { + if (mx >= sx1_edit_name && mx <= sx2_edit_name) + drawChooseTreeEdit(y, TRUE); + } + if (y != ti->cl_cursor) { PlaySound(SND_MENU_ITEM_ACTIVATING); - drawChooseTreeCursor(ti->cl_cursor, FALSE); - drawChooseTreeCursor(y, TRUE); - drawChooseTreeInfo(ti->cl_first + y, ti); + drawChooseTreeCursorAndText(ti, FALSE); ti->cl_cursor = y; + + drawChooseTreeCursorAndText(ti, TRUE); + + drawChooseTreeInfo(ti); } else if (dx < 0) { if (game_status == GAME_MODE_SETUP) { - if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || + if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE || + setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY || setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE) execSetupGame(); @@ -4482,14 +5302,16 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, } else if (node_cursor->parent_link) { - FadeSetLeaveMenu(); + if (game_status != GAME_MODE_SCORES) + FadeSetLeaveMenu(); *ti_ptr = node_cursor->node_parent; DrawChooseTree(ti_ptr); } else { - FadeSetEnterMenu(); + if (game_status != GAME_MODE_SCORES) + FadeSetEnterMenu(); node_cursor->cl_first = ti->cl_first; node_cursor->cl_cursor = ti->cl_cursor; @@ -4506,7 +5328,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, if (game_status == GAME_MODE_SETUP) { - if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || + if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE || + setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY || setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE) execSetupGame(); @@ -4539,6 +5362,67 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, HandleMainMenu_SelectLevel(0, 0, new_level_nr); } + else if (game_status == GAME_MODE_LEVELS) + { + // store level set if chosen from "last played level set" menu + StoreLastPlayedLevels(leveldir_current); + } + else if (game_status == GAME_MODE_NAMES) + { + if (mx >= sx1_edit_name && mx <= sx2_edit_name) + { + SetGameStatus(GAME_MODE_PSEUDO_TYPENAMES); + + DrawTypeName(); + + return; + } + + // change active user to selected user + user.nr = entry_pos; + + // save number of new active user + SaveUserSetup(); + + // load setup of new active user + LoadSetup(); + + // load last level set of new active user + LoadLevelSetup_LastSeries(); + LoadLevelSetup_SeriesInfo(); + + // update list of last played level sets + UpdateLastPlayedLevels_TreeInfo(); + + TapeErase(); + + ToggleFullscreenIfNeeded(); + ChangeWindowScalingIfNeeded(); + + ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS); + ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS); + ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC); + } + 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) + { + StartGameActions(network.enabled, setup.autorecord, + level.random_seed); + return; + } + else + { + SetGameStatus(GAME_MODE_SCOREINFO); + + DrawScoreInfo(node_cursor->pos); + + return; + } + } SetGameStatus(GAME_MODE_MAIN); @@ -4547,15 +5431,64 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, } } } + + if (game_status == GAME_MODE_SCORES) + PlayMenuSoundIfLoop(); } -void DrawChooseLevelSet(void) +void DrawChoosePlayerName(void) { - FadeMenuSoundsAndMusic(); + int i; - DrawChooseTree(&leveldir_current); + if (player_name != NULL) + { + freeTreeInfo(player_name); - PlayMenuSoundsAndMusic(); + player_name = NULL; + } + + for (i = 0; i < MAX_PLAYER_NAMES; i++) + { + TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_PLAYER_NAME); + char identifier[32], name[MAX_PLAYER_NAME_LEN + 1]; + int value = i; + + ti->node_top = &player_name; + ti->sort_priority = 10000 + value; + ti->color = getPlayerNameColor(global.user_names[i]); + + snprintf(identifier, sizeof(identifier), "%d", value); + snprintf(name, sizeof(name), "%s", global.user_names[i]); + + setString(&ti->identifier, identifier); + setString(&ti->name, name); + setString(&ti->name_sorting, name); + + pushTreeInfo(&player_name, ti); + } + + // sort player entries by player number + sortTreeInfo(&player_name); + + // set current player entry to selected player entry + player_name_current = + getTreeInfoFromIdentifier(player_name, i_to_a(user.nr)); + + // if that fails, set current player name to first available name + if (player_name_current == NULL) + player_name_current = player_name; + + DrawChooseTree(&player_name_current); +} + +void HandleChoosePlayerName(int mx, int my, int dx, int dy, int button) +{ + HandleChooseTree(mx, my, dx, dy, button, &player_name_current); +} + +void DrawChooseLevelSet(void) +{ + DrawChooseTree(&leveldir_current); } void HandleChooseLevelSet(int mx, int my, int dx, int dy, int button) @@ -4567,8 +5500,6 @@ void DrawChooseLevelNr(void) { int i; - FadeMenuSoundsAndMusic(); - if (level_number != NULL) { freeTreeInfo(level_number); @@ -4614,8 +5545,6 @@ void DrawChooseLevelNr(void) level_number_current = level_number; DrawChooseTree(&level_number_current); - - PlayMenuSoundsAndMusic(); } void HandleChooseLevelNr(int mx, int my, int dx, int dy, int button) @@ -4623,155 +5552,362 @@ void HandleChooseLevelNr(int mx, int my, int dx, int dy, int button) HandleChooseTree(mx, my, dx, dy, button, &level_number_current); } -void DrawHallOfFame(int level_nr, int highlight_position) +static void DrawHallOfFame_setScoreEntries(void) { - int fade_mask = REDRAW_FIELD; + int max_empty_entries = 10; // at least show "top ten" list, if empty + int max_visible_entries = NUM_MENU_ENTRIES_ON_SCREEN - 1; // w/o back link + int min_score_entries = MIN(max_empty_entries, max_visible_entries); + int score_pos = (scores.last_added >= 0 ? scores.last_added : 0); + int i; - if (CheckFadeAll()) - fade_mask = REDRAW_ALL; + if (score_entries != NULL) + { + freeTreeInfo(score_entries); + + score_entries = NULL; + } + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + // do not add empty score entries if off-screen + if (scores.entry[i].score == 0 && + scores.entry[i].time == 0 && + i >= min_score_entries) + break; + + TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_SCORE_ENTRY); + char identifier[32], name[64]; + int value = i; + + ti->node_top = &score_entries; + ti->sort_priority = 10000 + value; + ti->color = FC_YELLOW; + ti->pos = i; + + snprintf(identifier, sizeof(identifier), "%d", value); + snprintf(name, sizeof(name), "%03d.", value + 1); + + setString(&ti->identifier, identifier); + setString(&ti->name, name); + setString(&ti->name_sorting, name); + + pushTreeInfo(&score_entries, ti); + } + + // sort score entries to start with highest score entry + sortTreeInfo(&score_entries); + + // add top tree node to create back link to main menu + score_entries = addTopTreeInfoNode(score_entries); + + // set current score entry to last added or highest score entry + score_entry_current = + getTreeInfoFromIdentifier(score_entries, i_to_a(score_pos)); + + // if that fails, set current score entry to first valid score entry + if (score_entry_current == NULL) + score_entry_current = getFirstValidTreeInfoEntry(score_entries); + + // ("score_entries" and "score_entry_current" may be NULL here) +} + +void DrawHallOfFame(int level_nr) +{ + scores.last_level_nr = level_nr; + + // (this is needed when called from GameEnd() after winning a game) + KeyboardAutoRepeatOn(); + + // (this is needed when called from GameEnd() after winning a game) + SetDrawDeactivationMask(REDRAW_NONE); + SetDrawBackgroundMask(REDRAW_FIELD); + + LoadLocalAndServerScore(level_nr, TRUE); + + DrawHallOfFame_setScoreEntries(); + + if (scores.last_added >= 0) + SetAnimStatus(GAME_MODE_PSEUDO_SCORESNEW); + + FadeSetEnterScreen(); + + DrawChooseTree(&score_entry_current); +} + +static char *getHallOfFameRankText(int nr, int size) +{ + static char rank_text[10]; + boolean forced = (scores.force_last_added && nr == scores.last_added); + char *rank_text_raw = (forced ? "???" : int2str(nr + 1, size)); + + sprintf(rank_text, "%s%s", rank_text_raw, (size > 0 || !forced ? "." : "")); + + return rank_text; +} + +static char *getHallOfFameTimeText(int nr) +{ + static char score_text[10]; + int time_seconds = scores.entry[nr].time / FRAMES_PER_SECOND; + int mm = (time_seconds / 60) % 60; + int ss = (time_seconds % 60); + + sprintf(score_text, "%02d:%02d", mm, ss); // show playing time + + return score_text; +} + +static char *getHallOfFameScoreText(int nr, int size) +{ + if (!level.rate_time_over_score) + return int2str(scores.entry[nr].score, size); // show normal score + else if (level.use_step_counter) + return int2str(scores.entry[nr].time, size); // show number of steps + else + return getHallOfFameTimeText(nr); // show playing time +} + +static void HandleHallOfFame_SelectLevel(int step, int direction) +{ + int old_level_nr = scores.last_level_nr; + int new_level_nr = old_level_nr + step * direction; + + if (new_level_nr < leveldir_current->first_level) + new_level_nr = leveldir_current->first_level; + if (new_level_nr > leveldir_current->last_level) + new_level_nr = leveldir_current->last_level; + + if (setup.handicap && new_level_nr > leveldir_current->handicap_level) + new_level_nr = leveldir_current->handicap_level; + + if (new_level_nr != old_level_nr) + { + PlaySound(SND_MENU_ITEM_SELECTING); + + scores.last_level_nr = level_nr = new_level_nr; + scores.last_entry_nr = 0; + + LoadLevel(level_nr); + LoadLocalAndServerScore(level_nr, TRUE); + + DrawHallOfFame_setScoreEntries(); + + if (game_status == GAME_MODE_SCORES) + { + // force remapping optional gadgets (especially scroll bar) + UnmapScreenTreeGadgets(); + + // redraw complete high score screen, as sub-title has changed + ClearField(); + + // redraw level selection buttons (which have just been erased) + RedrawScreenMenuGadgets(SCREEN_MASK_SCORES); + + HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, &score_entry_current); + } + else + { + DrawScoreInfo_Content(scores.last_entry_nr); + } + + SaveLevelSetup_SeriesInfo(); + } +} + +void HandleHallOfFame(int mx, int my, int dx, int dy, int button) +{ + HandleChooseTree(mx, my, dx, dy, button, &score_entry_current); +} + +static void DrawScoreInfo_Content(int entry_nr) +{ + struct ScoreEntry *entry = &scores.entry[entry_nr]; + char *pos_text = getHallOfFameRankText(entry_nr, 0); + int font_title = MENU_INFO_FONT_TITLE; + int font_head = MENU_INFO_FONT_HEAD; + int font_text = MENU_INFO_FONT_TEXT; + int font_foot = MENU_INFO_FONT_FOOT; + int spacing_title = menu.headline1_spacing[GAME_MODE_SCOREINFO]; + int spacing_para = menu.paragraph_spacing[GAME_MODE_SCOREINFO]; + int spacing_line = menu.line_spacing[GAME_MODE_SCOREINFO]; + int xstep = getFontWidth(font_text); + 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 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 font_width = getFontWidth(font_text); + int font_height = getFontHeight(font_text); + int pad_left = xstart2; + int pad_right = MENU_SCREEN_INFO_SPACE_RIGHT; + 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) + RedrawScreenMenuGadgets(SCREEN_MASK_SCORES); + + if (score_entries == NULL) + { + drawChooseTreeScreen_Scores_NotAvailable(); + + return; + } + + drawChooseTreeHead(score_entries); + drawChooseTreeInfo(score_entries); + + DrawTextSCentered(ystart, font_title, "Score Information:"); + ystart += ystep_title; + + DrawTextF(xstart1, ystart, font_head, "Level Set"); + lines = DrawTextBufferS(xstart2, ystart, leveldir_current->name, font_text, + max_chars_per_line, -1, max_lines_per_text, 0, -1, + TRUE, FALSE, FALSE); + ystart += ystep_line + (lines > 0 ? lines - 1 : 0) * font_height; - UnmapAllGadgets(); - FadeMenuSoundsAndMusic(); + DrawTextF(xstart1, ystart, font_head, "Level"); + lines = DrawTextBufferS(xstart2, ystart, level.name, font_text, + max_chars_per_line, -1, max_lines_per_text, 0, -1, + TRUE, FALSE, FALSE); + ystart += ystep_para + (lines > 0 ? lines - 1 : 0) * font_height; - // (this is needed when called from GameEnd() after winning a game) - KeyboardAutoRepeatOn(); + button_y1 = SY + ystart; + ystart += graphic_info[IMG_MENU_BUTTON_PREV_SCORE].height; - // (this is needed when called from GameEnd() after winning a game) - SetDrawDeactivationMask(REDRAW_NONE); - SetDrawBackgroundMask(REDRAW_FIELD); + DrawTextF(xstart1, ystart, font_head, "Rank"); + DrawTextF(xstart2, ystart, font_text, pos_text); + ystart += ystep_line; - if (highlight_position < 0) - LoadScore(level_nr); + DrawTextF(xstart1, ystart, font_head, "Player"); + DrawTextF(xstart2, ystart, font_text, entry->name); + ystart += ystep_line; + + if (level.use_step_counter) + { + DrawTextF(xstart1, ystart, font_head, "Steps"); + DrawTextF(xstart2, ystart, font_text, int2str(entry->time, 5)); + ystart += ystep_line; + } else - SetAnimStatus(GAME_MODE_PSEUDO_SCORESNEW); + { + DrawTextF(xstart1, ystart, font_head, "Time"); + DrawTextF(xstart2, ystart, font_text, getHallOfFameTimeText(entry_nr)); + ystart += ystep_line; + } - FadeSetEnterScreen(); + if (!level.rate_time_over_score || entry->score > 0) + { + DrawTextF(xstart1, ystart, font_head, "Score"); + DrawTextF(xstart2, ystart, font_text, int2str(entry->score, 5)); + ystart += ystep_line; + } - FadeOut(fade_mask); + ystart += ystep_line; - // needed if different viewport properties defined for scores - ChangeViewportPropertiesIfNeeded(); + DrawTextF(xstart1, ystart, font_head, "Tape Date"); + DrawTextF(xstart2, ystart, font_text, entry->tape_date); + ystart += ystep_line; - PlayMenuSoundsAndMusic(); + DrawTextF(xstart1, ystart, font_head, "Platform"); + DrawTextF(xstart2, ystart, font_text, entry->platform); + ystart += ystep_line; - OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW); + DrawTextF(xstart1, ystart, font_head, "Version"); + DrawTextF(xstart2, ystart, font_text, entry->version); + ystart += ystep_line; - HandleHallOfFame(level_nr, highlight_position, 0, 0, MB_MENU_INITIALIZE); + DrawTextF(xstart1, ystart, font_head, "Country"); + lines = DrawTextBufferS(xstart2, ystart, entry->country_name, font_text, + max_chars_per_line, -1, max_lines_per_text, 0, -1, + TRUE, FALSE, FALSE); + ystart += ystep_line; - DrawMaskedBorder(fade_mask); + button_y2 = SY + ystart; - FadeIn(fade_mask); + DrawTextSCentered(ybottom, font_foot, "Press any key or button to go back"); + + AdjustScoreInfoButtons(button_x, button_y1, button_y2); } -static void drawHallOfFameList(int level_nr, int first_entry, - int highlight_position) +static void DrawScoreInfo(int entry_nr) { - int i, j; - - SetMainBackgroundImage(IMG_BACKGROUND_SCORES); - ClearField(); - - DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, "Hall Of Fame"); - DrawTextFCentered(MENU_TITLE2_YPOS, FONT_TITLE_2, - "HighScores of Level %d", level_nr); + scores.last_entry_nr = entry_nr; - for (i = 0; i < NUM_MENU_ENTRIES_ON_SCREEN; i++) - { - int entry = first_entry + i; - boolean active = (entry == highlight_position); - int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1); - int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2); - int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3); - int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4); - int dxoff = getFontDrawOffsetX(font_nr1); - int dx1 = 3 * getFontWidth(font_nr1); - int dx2 = dx1 + getFontWidth(font_nr1); - int dx3 = SXSIZE - 2 * (mSX - SX + dxoff) - 5 * getFontWidth(font_nr4); - int num_dots = (dx3 - dx2) / getFontWidth(font_nr3); - int sy = mSY + 64 + i * 32; + SetMainBackgroundImageIfDefined(IMG_BACKGROUND_SCOREINFO); - DrawText(mSX, sy, int2str(entry + 1, 3), font_nr1); - DrawText(mSX + dx1, sy, ".", font_nr1); + UnmapAllGadgets(); - for (j = 0; j < num_dots; j++) - DrawText(mSX + dx2 + j * getFontWidth(font_nr3), sy, ".", font_nr3); + FadeOut(REDRAW_FIELD); - if (!strEqual(highscore[entry].Name, EMPTY_PLAYER_NAME)) - DrawText(mSX + dx2, sy, highscore[entry].Name, font_nr2); + DrawScoreInfo_Content(entry_nr); - DrawText(mSX + dx3, sy, int2str(highscore[entry].Score, 5), font_nr4); - } + // map gadgets for score info screen + MapScreenMenuGadgets(SCREEN_MASK_SCORES_INFO); - redraw_mask |= REDRAW_FIELD; + FadeIn(REDRAW_FIELD); } -void HandleHallOfFame(int mx, int my, int dx, int dy, int button) +static void HandleScoreInfo_SelectScore(int step, int direction) { - static int level_nr = 0; - static int first_entry = 0; - static int highlight_position = 0; - int step = (button == 1 ? 1 : button == 2 ? 5 : 10); + int old_entry_nr = scores.last_entry_nr; + int new_entry_nr = old_entry_nr + step * direction; + int num_nodes = numTreeInfoInGroup(score_entry_current); + int num_entries = num_nodes - 1; // score nodes only, without back link - if (button == MB_MENU_INITIALIZE) - { - level_nr = mx; - first_entry = 0; - highlight_position = my; + if (new_entry_nr < 0) + new_entry_nr = 0; + if (new_entry_nr > num_entries - 1) + new_entry_nr = num_entries - 1; - drawHallOfFameList(level_nr, first_entry, highlight_position); + if (new_entry_nr != old_entry_nr) + { + scores.last_entry_nr = new_entry_nr; - return; + DrawScoreInfo_Content(new_entry_nr); } +} - if (ABS(dy) == SCROLL_PAGE) // handle scrolling one page - step = NUM_MENU_ENTRIES_ON_SCREEN - 1; +void HandleScoreInfo(int mx, int my, int dx, int dy, int button) +{ + boolean button_action = (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE); + boolean button_is_valid = (mx >= 0 && my >= 0); + boolean button_screen_clicked = (button_action && button_is_valid); - if (dy < 0) + if (server_scores.updated) { - if (first_entry > 0) - { - first_entry -= step; - if (first_entry < 0) - first_entry = 0; + // reload scores, using updated server score cache file + LoadLocalAndServerScore(scores.last_level_nr, FALSE); - drawHallOfFameList(level_nr, first_entry, highlight_position); - } - } - else if (dy > 0) - { - if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN < MAX_SCORE_ENTRIES) - { - first_entry += step; - if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES) - first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN); + server_scores.updated = FALSE; - drawHallOfFameList(level_nr, first_entry, highlight_position); - } + DrawHallOfFame_setScoreEntries(); + + DrawScoreInfo_Content(scores.last_entry_nr); } - else if (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE) + + if (button_screen_clicked) { PlaySound(SND_MENU_ITEM_SELECTING); - FadeSound(SND_BACKGROUND_SCORES); - - if (button == MB_MENU_CHOICE && - game_status_last_screen == GAME_MODE_PLAYING && - setup.auto_play_next_level && setup.increment_levels && - level_nr < leveldir_current->last_level && - !network_playing) - { - StartGameActions(network.enabled, setup.autorecord, level.random_seed); - } - else - { - SetGameStatus(GAME_MODE_MAIN); + SetGameStatus(GAME_MODE_SCORES); - DrawMainMenu(); - } + DrawHallOfFame(level_nr); + } + else if (dx) + { + HandleHallOfFame_SelectLevel(1, SIGN(dx) * (ABS(dx) > 1 ? 10 : 1)); + } + else if (dy) + { + HandleScoreInfo_SelectScore(1, SIGN(dy) * (ABS(dy) > 1 ? 10 : 1)); } - - if (game_status == GAME_MODE_SCORES) - PlayMenuSoundIfLoop(); } @@ -4790,6 +5926,7 @@ static char *vsync_mode_text; static char *scroll_delay_text; static char *snapshot_mode_text; static char *game_speed_text; +static char *scores_type_text; static char *network_server_text; static char *graphics_set_name; static char *sounds_set_name; @@ -4810,6 +5947,56 @@ static void execSetupMain(void) DrawSetupScreen(); } +static void execSetupGame_setScoresType(void) +{ + if (scores_types == NULL) + { + int i; + + for (i = 0; scores_types_list[i].value != NULL; i++) + { + TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED); + char identifier[32], name[32]; + char *value = scores_types_list[i].value; + char *text = scores_types_list[i].text; + + ti->node_top = &scores_types; + ti->sort_priority = i; + + sprintf(identifier, "%s", value); + sprintf(name, "%s", text); + + setString(&ti->identifier, identifier); + setString(&ti->name, name); + setString(&ti->name_sorting, name); + setString(&ti->infotext, STR_SETUP_CHOOSE_SCORES_TYPE); + + pushTreeInfo(&scores_types, ti); + } + + // sort scores type values to start with lowest scores type value + sortTreeInfo(&scores_types); + + // set current scores type value to configured scores type value + scores_type_current = + getTreeInfoFromIdentifier(scores_types, setup.scores_in_highscore_list); + + // if that fails, set current scores type to reliable default value + if (scores_type_current == NULL) + scores_type_current = + getTreeInfoFromIdentifier(scores_types, STR_SCORES_TYPE_DEFAULT); + + // if that also fails, set current scores type to first available value + if (scores_type_current == NULL) + scores_type_current = scores_types; + } + + setup.scores_in_highscore_list = scores_type_current->identifier; + + // needed for displaying scores type text instead of identifier + scores_type_text = scores_type_current->name; +} + static void execSetupGame_setGameSpeeds(boolean update_value) { if (setup.game_speed_extended) @@ -5003,11 +6190,15 @@ static void execSetupGame(void) boolean check_vsync_mode = (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED); execSetupGame_setGameSpeeds(FALSE); + execSetupGame_setScoresType(); execSetupGame_setScrollDelays(); execSetupGame_setSnapshotModes(); execSetupGame_setNetworkServerText(); + if (!setup.provide_uploading_tapes) + setHideSetupEntry(execOfferUploadTapes); + setup_mode = SETUP_MODE_GAME; DrawSetupScreen(); @@ -5017,6 +6208,13 @@ static void execSetupGame(void) DisableVsyncIfNeeded(); } +static void execSetupChooseScoresType(void) +{ + setup_mode = SETUP_MODE_CHOOSE_SCORES_TYPE; + + DrawSetupScreen(); +} + static void execSetupChooseGameSpeed(void) { setup_mode = SETUP_MODE_CHOOSE_GAME_SPEED; @@ -5302,13 +6500,21 @@ static void execSetupGraphics(void) if (setup_mode == SETUP_MODE_CHOOSE_WINDOW_SIZE) execSetupGraphics_setWindowSizes(FALSE); + // update "setup.vsync_mode" from list selection + // (in this case, vsync mode was changed on setup screen) + if (setup_mode == SETUP_MODE_CHOOSE_VSYNC) + execSetupGraphics_setVsyncModes(FALSE); + // update list selection from "setup.window_scaling_percent" // (window scaling may have changed by resizing the window) execSetupGraphics_setWindowSizes(TRUE); + // update list selection from "setup.vsync_mode" + // (vsync_mode may have changed by re-creating the renderer) + execSetupGraphics_setVsyncModes(TRUE); + execSetupGraphics_setScalingTypes(); execSetupGraphics_setRenderingModes(); - execSetupGraphics_setVsyncModes(FALSE); setup_mode = SETUP_MODE_GRAPHICS; @@ -5319,7 +6525,7 @@ static void execSetupGraphics(void) ModifyGameSpeedIfNeeded(); // window scaling may have changed at this point - ToggleFullscreenOrChangeWindowScalingIfNeeded(); + ChangeWindowScalingIfNeeded(); // window scaling quality may have changed at this point if (!strEqual(setup.window_scaling_quality, video.window_scaling_quality)) @@ -5328,8 +6534,23 @@ static void execSetupGraphics(void) // screen rendering mode may have changed at this point SDLSetScreenRenderingMode(setup.screen_rendering_mode); + int setup_vsync_mode = VSYNC_MODE_STR_TO_INT(setup.vsync_mode); + int video_vsync_mode = video.vsync_mode; + // screen vsync mode may have changed at this point - SDLSetScreenVsyncMode(setup.vsync_mode); + ChangeVsyncModeIfNeeded(); + + // check if setting vsync mode to selected value failed + if (setup_vsync_mode != video_vsync_mode && + setup_vsync_mode != video.vsync_mode) + { + // changing vsync mode to selected value failed -- reset displayed value + execSetupGraphics_setVsyncModes(TRUE); + + Request("Setting VSync failed!", REQ_CONFIRM); + + DrawSetupScreen(); + } } static void execSetupChooseWindowSize(void) @@ -5931,11 +7152,28 @@ static void execSetupTouch(void) static void execSetupArtwork(void) { + static ArtworkDirTree *gfx_last_valid = NULL; + static ArtworkDirTree *snd_last_valid = NULL; + static ArtworkDirTree *mus_last_valid = NULL; + + // current artwork directory may be invalid (level group, parent link) + if (!validLevelSeries(artwork.gfx_current)) + artwork.gfx_current = getFirstValidTreeInfoEntry(gfx_last_valid); + if (!validLevelSeries(artwork.snd_current)) + artwork.snd_current = getFirstValidTreeInfoEntry(snd_last_valid); + if (!validLevelSeries(artwork.mus_current)) + artwork.mus_current = getFirstValidTreeInfoEntry(mus_last_valid); + + // store valid artwork directory information + gfx_last_valid = artwork.gfx_current; + snd_last_valid = artwork.snd_current; + mus_last_valid = artwork.mus_current; + #if 0 - printf("::: '%s', '%s', '%s'\n", - artwork.gfx_current->subdir, - artwork.gfx_current->fullpath, - artwork.gfx_current->basepath); + Debug("screens:execSetupArtwork", "'%s', '%s', '%s'", + artwork.gfx_current->subdir, + artwork.gfx_current->fullpath, + artwork.gfx_current->basepath); #endif setup.graphics_set = artwork.gfx_current->identifier; @@ -6053,6 +7291,11 @@ static void execGadgetNetworkServer(void) ClickOnGadget(gi, MB_LEFTBUTTON); } +static void execOfferUploadTapes(void) +{ + OfferUploadTapes(); +} + static void ToggleNetworkModeIfNeeded(void) { int font_title = FONT_TITLE_1; @@ -6111,6 +7354,17 @@ static void ToggleGameSpeedsListIfNeeded(void) DrawSetupScreen(); } +static void ToggleUseApiServerIfNeeded(void) +{ + if (runtime.use_api_server == setup.use_api_server) + return; + + runtime.use_api_server = setup.use_api_server; + + if (runtime.use_api_server) + CheckApiServerTasks(); +} + static void ModifyGameSpeedIfNeeded(void) { if (strEqual(setup.vsync_mode, STR_VSYNC_MODE_OFF) || @@ -6158,6 +7412,9 @@ static struct void *related_value; } hide_related_entry_list[] = { + { &setup.scores_in_highscore_list, execSetupChooseScoresType }, + { &setup.scores_in_highscore_list, &scores_type_text }, + { &setup.game_frame_delay, execSetupChooseGameSpeed }, { &setup.game_frame_delay, &game_speed_text }, @@ -6270,15 +7527,24 @@ static struct TokenInfo setup_info_game[] = { TYPE_PLAYER, &setup.network_player_nr,"Preferred Network Player:" }, { TYPE_TEXT_INPUT, execGadgetNetworkServer, "Network Server Hostname:" }, { TYPE_STRING, &network_server_text, "" }, + { TYPE_SWITCH, &setup.use_api_server, "Use Highscore Server:" }, + { TYPE_ENTER_LIST, execSetupChooseScoresType,"Scores in Highscore List:" }, + { TYPE_STRING, &scores_type_text, "" }, + { TYPE_ENTER_LIST, execOfferUploadTapes, "Upload Tapes to Server" }, + { 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.increment_levels,"Increment Solved Levels:" }, { TYPE_SWITCH, &setup.auto_play_next_level,"Auto-play Next Level:" }, - { TYPE_SWITCH, &setup.skip_scores_after_game,"Skip Scores After Game:" }, + { TYPE_SWITCH, &setup.count_score_after_game,"Count Score After Game:" }, + { TYPE_SWITCH, &setup.show_scores_after_game,"Show Scores After Game:" }, { TYPE_YES_NO, &setup.ask_on_game_over, "Ask on Game Over:" }, + { TYPE_YES_NO, &setup.ask_on_quit_game, "Ask on Quit Game:" }, + { TYPE_YES_NO, &setup.ask_on_quit_program, "Ask on Quit Program:" }, { TYPE_SWITCH, &setup.autorecord, "Auto-Record Tapes:" }, + { TYPE_SWITCH, &setup.auto_pause_on_start, "Start Game in Pause Mode:" }, { TYPE_ENTER_LIST, execSetupChooseGameSpeed, "Game Speed:" }, { TYPE_STRING, &game_speed_text, "" }, { TYPE_SWITCH, &setup.game_speed_extended, "Game Speed Extended List:" }, @@ -6288,7 +7554,8 @@ static struct TokenInfo setup_info_game[] = #endif { TYPE_ENTER_LIST, execSetupChooseSnapshotMode,"Game Engine Snapshot Mode:" }, { TYPE_STRING, &snapshot_mode_text, "" }, - { TYPE_SWITCH, &setup.show_snapshot_buttons,"Show Snapshot Buttons:" }, + { TYPE_SWITCH, &setup.show_load_save_buttons,"Show Load/Save Buttons:" }, + { TYPE_SWITCH, &setup.show_undo_redo_buttons,"Show Undo/Redo Buttons:" }, { TYPE_EMPTY, NULL, "" }, { TYPE_LEAVE_MENU, execSetupMain, "Back" }, @@ -6301,6 +7568,7 @@ static struct TokenInfo setup_info_engines[] = { TYPE_SWITCH, &setup.forced_scroll_delay, "Scroll Delay:" }, { TYPE_ECS_AGA, &setup.prefer_aga_graphics, "Amiga Graphics Chipset:" }, { TYPE_SWITCH, &setup.prefer_lowpass_sounds,"Low-Pass Filter Sounds:" }, + { TYPE_SWITCH, &setup.prefer_extra_panel_items,"Show Dynamite and Keys:" }, { TYPE_EMPTY, NULL, "" }, { TYPE_HEADLINE, NULL, "Supaplex" }, { TYPE_SWITCH, &setup.sp_show_border_elements, "Border Elements:" }, @@ -6339,6 +7607,8 @@ static struct TokenInfo setup_info_editor[] = #endif { TYPE_SWITCH, &setup.editor.show_element_token, "Show element token:" }, { TYPE_EMPTY, NULL, "" }, + { TYPE_SWITCH, &setup.editor.show_read_only_warning, "Show read-only warning:" }, + { TYPE_EMPTY, NULL, "" }, { TYPE_LEAVE_MENU, execSetupMain, "Back" }, { 0, NULL, NULL } @@ -6367,6 +7637,7 @@ static struct TokenInfo setup_info_graphics[] = { TYPE_SWITCH, &setup.show_titlescreen,"Show Title Screens:" }, { TYPE_SWITCH, &setup.toons, "Show Menu Animations:" }, { TYPE_SWITCH, &setup.small_game_graphics, "Small Game Graphics:" }, + { TYPE_YES_NO_AUTO, &setup.debug.xsn_mode, debug_xsn_mode }, { TYPE_EMPTY, NULL, "" }, { TYPE_LEAVE_MENU, execSetupMain, "Back" }, @@ -6521,6 +7792,10 @@ static struct TokenInfo setup_info_shortcuts_1[] = { TYPE_KEY, &setup.shortcut.save_game, "" }, { TYPE_KEYTEXT, NULL, "Quick Load Game from Tape:", }, { TYPE_KEY, &setup.shortcut.load_game, "" }, + { TYPE_KEYTEXT, NULL, "Restart Game:", }, + { TYPE_KEY, &setup.shortcut.restart_game, "" }, + { TYPE_KEYTEXT, NULL, "Replay & Pause Before End:", }, + { TYPE_KEY, &setup.shortcut.pause_before_end, "" }, { TYPE_KEYTEXT, NULL, "Start Game & Toggle Pause:", }, { TYPE_KEY, &setup.shortcut.toggle_pause, "" }, { TYPE_EMPTY, NULL, "" }, @@ -6729,14 +8004,18 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw) if (scrollbar_needed && xpos > MENU_SCREEN_START_XPOS) { int max_menu_text_length = 26; // maximum text length for classic menu - int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset; + int font_xoffset = getFontDrawOffsetX(font_nr); int text_startx = mSX + MENU_SCREEN_START_XPOS * 32; int text_font_nr = getMenuTextFont(FONT_MENU_2); - int text_font_xoffset = getFontBitmapInfo(text_font_nr)->draw_xoffset; + int text_font_xoffset = getFontDrawOffsetX(text_font_nr); int text_width = max_menu_text_length * getFontWidth(text_font_nr); if (startx + font_xoffset < text_startx + text_width + text_font_xoffset) { + // when using narrow font, left-shifting text "auto" not needed + if (type & TYPE_YES_NO_AUTO) + xpos += 1; + xpos += 1; startx = mSX + xpos * 32; @@ -6751,11 +8030,11 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw) MENU_SCREEN_START_XPOS); int max_menu_text_length_medium = max_menu_text_length_big * 2; int check_font_nr = FONT_OPTION_ON; // known font that needs correction - int font1_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset; - int font2_xoffset = getFontBitmapInfo(check_font_nr)->draw_xoffset; + int font1_xoffset = getFontDrawOffsetX(font_nr); + int font2_xoffset = getFontDrawOffsetX(check_font_nr); int text_startx = mSX + MENU_SCREEN_START_XPOS * 32; int text_font_nr = getMenuTextFont(FONT_MENU_2); - int text_font_xoffset = getFontBitmapInfo(text_font_nr)->draw_xoffset; + int text_font_xoffset = getFontDrawOffsetX(text_font_nr); int text_width = max_menu_text_length_medium * getFontWidth(text_font_nr); boolean correct_font_draw_xoffset = FALSE; @@ -6771,7 +8050,7 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw) // (this can happen for extreme/wrong values for font draw offset) if (correct_font_draw_xoffset) { - font_draw_xoffset_old = getFontBitmapInfo(font_nr)->draw_xoffset; + font_draw_xoffset_old = getFontDrawOffsetX(font_nr); font_draw_xoffset_modified = TRUE; if (type & TYPE_KEY) @@ -6853,12 +8132,16 @@ static void changeSetupValue(int screen_pos, int setup_info_pos_raw, int dx) // fullscreen state may have changed at this point if (si->value == &setup.fullscreen) - ToggleFullscreenOrChangeWindowScalingIfNeeded(); + ToggleFullscreenIfNeeded(); // network mode may have changed at this point if (si->value == &setup.network_mode) ToggleNetworkModeIfNeeded(); + // API server mode may have changed at this point + if (si->value == &setup.use_api_server) + ToggleUseApiServerIfNeeded(); + // game speed list may have changed at this point if (si->value == &setup.game_speed_extended) ToggleGameSpeedsListIfNeeded(); @@ -7523,7 +8806,7 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) Event event; int alpha = 200, alpha_step = -1; int alpha_ticks = 0; - char mapping[4096], temp[4096]; + char mapping[4096], temp[256]; int font_name = MENU_SETUP_FONT_TITLE; int font_info = MENU_SETUP_FONT_TEXT; int spacing_name = menu.line_spacing_setup[SETUP_MODE_INPUT]; @@ -7586,9 +8869,9 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) #if DEBUG_JOYSTICKS // print info about the joystick we are watching - Error(ERR_DEBUG, "watching joystick %d: (%s)\n", + Debug("joystick", "watching joystick %d: (%s)", SDL_JoystickInstanceID(joystick), name); - Error(ERR_DEBUG, "joystick has %d axes, %d hats, %d balls, and %d buttons\n", + Debug("joystick", "joystick has %d axes, %d hats, %d balls, and %d buttons", SDL_JoystickNumAxes(joystick), SDL_JoystickNumHats(joystick), SDL_JoystickNumBalls(joystick), SDL_JoystickNumButtons(joystick)); #endif @@ -7845,7 +9128,7 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) if (success) { #if DEBUG_JOYSTICKS - Error(ERR_DEBUG, "New game controller mapping:\n\n%s\n\n", mapping); + Debug("joystick", "New game controller mapping:\n\n%s\n\n", mapping); #endif // activate mapping for this game @@ -8274,6 +9557,8 @@ void DrawSetupScreen(void) if (setup_mode == SETUP_MODE_INPUT) DrawSetupScreen_Input(); + else if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE) + DrawChooseTree(&scores_type_current); else if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED) DrawChooseTree(&game_speed_current); else if (setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY) @@ -8356,6 +9641,8 @@ void HandleSetupScreen(int mx, int my, int dx, int dy, int button) { if (setup_mode == SETUP_MODE_INPUT) HandleSetupScreen_Input(mx, my, dx, dy, button); + else if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE) + HandleChooseTree(mx, my, dx, dy, button, &scores_type_current); else if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED) HandleChooseTree(mx, my, dx, dy, button, &game_speed_current); else if (setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY) @@ -8417,10 +9704,10 @@ void HandleGameActions(void) if (game_status != GAME_MODE_PLAYING) return; - GameActions(); // main game loop + GameActions(); // main game loop if (tape.auto_play && !tape.playing) - AutoPlayTape(); // continue automatically playing next tape + AutoPlayTapesContinue(); // continue automatically playing next tape } @@ -8454,6 +9741,38 @@ static struct GD_EVENT_PRESSED | GD_EVENT_REPEATED, FALSE, "next level" }, + { + IMG_MENU_BUTTON_PREV_LEVEL2, IMG_MENU_BUTTON_PREV_LEVEL2_ACTIVE, + &menu.scores.button.prev_level, NULL, + SCREEN_CTRL_ID_PREV_LEVEL2, + SCREEN_MASK_SCORES | SCREEN_MASK_SCORES_INFO, + GD_EVENT_PRESSED | GD_EVENT_REPEATED, + FALSE, "previous level" + }, + { + IMG_MENU_BUTTON_NEXT_LEVEL2, IMG_MENU_BUTTON_NEXT_LEVEL2_ACTIVE, + &menu.scores.button.next_level, NULL, + SCREEN_CTRL_ID_NEXT_LEVEL2, + SCREEN_MASK_SCORES | SCREEN_MASK_SCORES_INFO, + GD_EVENT_PRESSED | GD_EVENT_REPEATED, + FALSE, "next level" + }, + { + IMG_MENU_BUTTON_PREV_SCORE, IMG_MENU_BUTTON_PREV_SCORE_ACTIVE, + &menu.scores.button.prev_score, NULL, + SCREEN_CTRL_ID_PREV_SCORE, + SCREEN_MASK_SCORES_INFO, + GD_EVENT_PRESSED | GD_EVENT_REPEATED, + FALSE, "previous score" + }, + { + IMG_MENU_BUTTON_NEXT_SCORE, IMG_MENU_BUTTON_NEXT_SCORE_ACTIVE, + &menu.scores.button.next_score, NULL, + SCREEN_CTRL_ID_NEXT_SCORE, + SCREEN_MASK_SCORES_INFO, + GD_EVENT_PRESSED | GD_EVENT_REPEATED, + FALSE, "next score" + }, { IMG_MENU_BUTTON_FIRST_LEVEL, IMG_MENU_BUTTON_FIRST_LEVEL_ACTIVE, &menu.main.button.first_level, NULL, @@ -8623,8 +9942,10 @@ static void CreateScreenMenubuttons(void) for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++) { struct MenuPosInfo *pos = menubutton_info[i].pos; + int screen_mask = menubutton_info[i].screen_mask; 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); Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed; int gfx_unpressed, gfx_pressed; int x, y, width, height; @@ -8672,6 +9993,22 @@ static void CreateScreenMenubuttons(void) checked = *menubutton_info[i].check_value; } + if (is_score_button) + { + // if x/y set to -1, dynamically place buttons next to title text + int title_width = getTextWidth(INFOTEXT_SCORE_ENTRY, FONT_TITLE_1); + + 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); + + if (pos->y == -1) + y = (id == SCREEN_CTRL_ID_PREV_LEVEL2 || + id == SCREEN_CTRL_ID_NEXT_LEVEL2 ? mSY + MENU_TITLE1_YPOS : 0); + } + gi = CreateGadget(GDI_CUSTOM_ID, id, GDI_CUSTOM_TYPE_ID, i, GDI_IMAGE_ID, gfx_unpressed, @@ -8694,7 +10031,7 @@ static void CreateScreenMenubuttons(void) GDI_END); if (gi == NULL) - Error(ERR_EXIT, "cannot create gadget"); + Fail("cannot create gadget"); screen_gadget[id] = gi; } @@ -8762,7 +10099,7 @@ static void CreateScreenScrollbuttons(void) GDI_END); if (gi == NULL) - Error(ERR_EXIT, "cannot create gadget"); + Fail("cannot create gadget"); screen_gadget[id] = gi; } @@ -8843,7 +10180,7 @@ static void CreateScreenScrollbars(void) GDI_END); if (gi == NULL) - Error(ERR_EXIT, "cannot create gadget"); + Fail("cannot create gadget"); screen_gadget[id] = gi; } @@ -8889,7 +10226,7 @@ static void CreateScreenTextInputGadgets(void) GDI_END); if (gi == NULL) - Error(ERR_EXIT, "cannot create gadget"); + Fail("cannot create gadget"); screen_gadget[id] = gi; } @@ -8913,6 +10250,15 @@ void FreeScreenGadgets(void) FreeGadget(screen_gadget[i]); } +static void RedrawScreenMenuGadgets(int screen_mask) +{ + int i; + + for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++) + if (screen_mask & menubutton_info[i].screen_mask) + RedrawGadget(screen_gadget[menubutton_info[i].gadget_id]); +} + static void MapScreenMenuGadgets(int screen_mask) { int i; @@ -8963,11 +10309,41 @@ static void MapScreenGadgets(int num_entries) MapGadget(screen_gadget[scrollbar_info[i].gadget_id]); } +static void UnmapScreenGadgets() +{ + int i; + + for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++) + UnmapGadget(screen_gadget[scrollbutton_info[i].gadget_id]); + + for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++) + UnmapGadget(screen_gadget[scrollbar_info[i].gadget_id]); +} + static void MapScreenTreeGadgets(TreeInfo *ti) { MapScreenGadgets(numTreeInfoInGroup(ti)); } +static void UnmapScreenTreeGadgets(void) +{ + UnmapScreenGadgets(); +} + +static void AdjustScoreInfoButtons(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]; + struct MenuPosInfo *pos_1 = menubutton_info[SCREEN_CTRL_ID_PREV_SCORE].pos; + struct MenuPosInfo *pos_2 = menubutton_info[SCREEN_CTRL_ID_NEXT_SCORE].pos; + + if (pos_1->x == -1 && pos_1->y == -1) + ModifyGadget(gi_1, GDI_X, x, GDI_Y, y1, GDI_END); + + if (pos_2->x == -1 && pos_2->y == -1) + ModifyGadget(gi_2, GDI_X, x, GDI_Y, y2, GDI_END); +} + static void HandleScreenGadgets(struct GadgetInfo *gi) { int id = gi->custom_id; @@ -8986,6 +10362,22 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) HandleMainMenu_SelectLevel(step, +1, NO_DIRECT_LEVEL_SELECT); break; + case SCREEN_CTRL_ID_PREV_LEVEL2: + HandleHallOfFame_SelectLevel(step, -1); + break; + + case SCREEN_CTRL_ID_NEXT_LEVEL2: + HandleHallOfFame_SelectLevel(step, +1); + break; + + case SCREEN_CTRL_ID_PREV_SCORE: + HandleScoreInfo_SelectScore(step, -1); + break; + + case SCREEN_CTRL_ID_NEXT_SCORE: + HandleScoreInfo_SelectScore(step, +1); + break; + case SCREEN_CTRL_ID_FIRST_LEVEL: HandleMainMenu_SelectLevel(MAX_LEVELS, -1, NO_DIRECT_LEVEL_SELECT); break; @@ -9029,7 +10421,9 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) break; case SCREEN_CTRL_ID_SCROLL_UP: - if (game_status == GAME_MODE_LEVELS) + if (game_status == GAME_MODE_NAMES) + HandleChoosePlayerName(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); + else if (game_status == GAME_MODE_LEVELS) HandleChooseLevelSet(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_LEVELNR) HandleChooseLevelNr(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); @@ -9037,10 +10431,14 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) HandleSetupScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_INFO) HandleInfoScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); + else if (game_status == GAME_MODE_SCORES) + HandleHallOfFame(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); break; case SCREEN_CTRL_ID_SCROLL_DOWN: - if (game_status == GAME_MODE_LEVELS) + if (game_status == GAME_MODE_NAMES) + HandleChoosePlayerName(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); + else if (game_status == GAME_MODE_LEVELS) HandleChooseLevelSet(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_LEVELNR) HandleChooseLevelNr(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); @@ -9048,10 +10446,14 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) HandleSetupScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_INFO) HandleInfoScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); + else if (game_status == GAME_MODE_SCORES) + HandleHallOfFame(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); break; case SCREEN_CTRL_ID_SCROLL_VERTICAL: - if (game_status == GAME_MODE_LEVELS) + if (game_status == GAME_MODE_NAMES) + HandleChoosePlayerName(0,0,999,gi->event.item_position,MB_MENU_INITIALIZE); + else if (game_status == GAME_MODE_LEVELS) HandleChooseLevelSet(0,0,999,gi->event.item_position,MB_MENU_INITIALIZE); else if (game_status == GAME_MODE_LEVELNR) HandleChooseLevelNr(0,0,999,gi->event.item_position,MB_MENU_INITIALIZE); @@ -9059,6 +10461,8 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) HandleSetupScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE); else if (game_status == GAME_MODE_INFO) HandleInfoScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE); + else if (game_status == GAME_MODE_SCORES) + HandleHallOfFame(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE); break; case SCREEN_CTRL_ID_NETWORK_SERVER: @@ -9205,3 +10609,139 @@ void DrawScreenAfterAddingSet(char *tree_subdir_new, int tree_type) } } } + +static int UploadTapes(void) +{ + SetGameStatus(GAME_MODE_LOADING); + + FadeSetEnterScreen(); + FadeOut(REDRAW_ALL); + + ClearRectangle(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE); + + FadeIn(REDRAW_ALL); + + DrawInitTextHead("Uploading tapes"); + + global.autoplay_mode = AUTOPLAY_MODE_UPLOAD; + global.autoplay_leveldir = "ALL"; + global.autoplay_all = TRUE; + + int num_tapes_uploaded = AutoPlayTapes(); + + global.autoplay_mode = AUTOPLAY_MODE_NONE; + global.autoplay_leveldir = NULL; + global.autoplay_all = FALSE; + + SetGameStatus(GAME_MODE_MAIN); + + DrawMainMenu(); + + return num_tapes_uploaded; +} + +static boolean OfferUploadTapes(void) +{ + if (!Request(setup.has_remaining_tapes ? + "Upload missing tapes to the high score server now?" : + "Upload all your tapes to the high score server now?", REQ_ASK)) + return FALSE; + + int num_tapes_uploaded = UploadTapes(); + char message[100]; + + if (num_tapes_uploaded < 0) + { + num_tapes_uploaded = -num_tapes_uploaded - 1; + + if (num_tapes_uploaded == 0) + sprintf(message, "Upload failed! No tapes uploaded!"); + else if (num_tapes_uploaded == 1) + sprintf(message, "Upload failed! Only 1 tape uploaded!"); + else + sprintf(message, "Upload failed! Only %d tapes uploaded!", + num_tapes_uploaded); + + Request(message, REQ_CONFIRM); + + // if uploading tapes failed, add tape upload entry to setup menu + setup.provide_uploading_tapes = TRUE; + setup.has_remaining_tapes = TRUE; + + SaveSetup_ServerSetup(); + + return FALSE; + } + + if (num_tapes_uploaded == 0) + sprintf(message, "No tapes uploaded!"); + else if (num_tapes_uploaded == 1) + sprintf(message, "1 tape uploaded!"); + else + sprintf(message, "%d tapes uploaded!", num_tapes_uploaded); + + Request(message, REQ_CONFIRM); + + if (num_tapes_uploaded > 0) + Request("New scores will be visible after a few minutes!", REQ_CONFIRM); + + // after all tapes have been uploaded, remove entry from setup menu + setup.provide_uploading_tapes = FALSE; + setup.has_remaining_tapes = FALSE; + + SaveSetup_ServerSetup(); + + return TRUE; +} + +static void CheckUploadTapes(void) +{ + if (!setup.ask_for_uploading_tapes) + return; + + // after asking for uploading tapes, do not ask again + setup.ask_for_uploading_tapes = FALSE; + setup.ask_for_remaining_tapes = FALSE; + + if (directoryExists(getTapeDir(NULL))) + { + boolean tapes_uploaded = OfferUploadTapes(); + + if (!tapes_uploaded) + { + Request(setup.has_remaining_tapes ? + "You can upload missing tapes from the setup menu later!" : + "You can upload your tapes from the setup menu later!", + REQ_CONFIRM); + } + } + else + { + // if tapes directory does not exist yet, never offer uploading all tapes + setup.provide_uploading_tapes = FALSE; + } + + SaveSetup_ServerSetup(); +} + +static void UpgradePlayerUUID(void) +{ + ApiResetUUIDAsThread(getUUID()); +} + +static void CheckUpgradePlayerUUID(void) +{ + if (setup.player_version > 1) + return; + + UpgradePlayerUUID(); +} + +void CheckApiServerTasks(void) +{ + // check if the player's UUID has to be upgraded + CheckUpgradePlayerUUID(); + + // check if there are any tapes to be uploaded + CheckUploadTapes(); +}