X-Git-Url: https://git.artsoft.org/?p=rocksndiamonds.git;a=blobdiff_plain;f=src%2Fscreens.c;h=a5b808a1969462bac0a1bc7b1aefd001a1b49cb3;hp=7f4de2c74a8487ffb73c5dda0dc35b728f97e6fd;hb=274876d86fc0cb9af7027df86bf4ac46020c1501;hpb=7899261335da12a5a88bf15e9bcd057e42423e3e diff --git a/src/screens.c b/src/screens.c index 7f4de2c7..a5b808a1 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" @@ -125,6 +127,8 @@ // 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 @@ -280,6 +284,9 @@ static void MapScreenTreeGadgets(TreeInfo *); static void UpdateScreenMenuGadgets(int, boolean); +static boolean OfferUploadTapes(void); +static void execOfferUploadTapes(void); + static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS]; static int info_mode = INFO_MODE_MAIN; @@ -303,6 +310,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; @@ -387,6 +397,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" }, @@ -1628,12 +1647,15 @@ void DrawMainMenu(void) return; } - // 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; @@ -1733,6 +1755,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) @@ -1745,7 +1772,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; @@ -1950,6 +1977,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) { @@ -2099,9 +2136,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 @@ -2114,12 +2158,7 @@ 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 { @@ -2150,6 +2189,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(); @@ -2162,15 +2204,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); @@ -2207,8 +2249,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 } } } @@ -3212,7 +3259,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); @@ -3223,7 +3270,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); @@ -3291,7 +3338,7 @@ 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); } @@ -4012,19 +4059,432 @@ void HandleInfoScreen(int mx, int my, int dx, int dy, int button) } +// ============================================================================ +// rename player API functions +// ============================================================================ + +struct ApiRenamePlayerThreadData +{ + char *player_name; + char *player_uuid; +}; + +static void *CreateThreadData_ApiRenamePlayer(void) +{ + struct ApiRenamePlayerThreadData *data = + checked_malloc(sizeof(struct ApiRenamePlayerThreadData)); + + data->player_name = getStringCopy(setup.player_name); + data->player_uuid = getStringCopy(setup.player_uuid); + + return data; +} + +static void FreeThreadData_ApiRenamePlayer(void *data_raw) +{ + struct ApiRenamePlayerThreadData *data = data_raw; + + checked_free(data->player_name); + checked_free(data->player_uuid); + checked_free(data); +} + +static boolean SetRequest_ApiRenamePlayer(struct HttpRequest *request, + void *data_raw) +{ + struct ApiRenamePlayerThreadData *data = data_raw; + char *player_name_raw = data->player_name; + char *player_uuid_raw = data->player_uuid; + + request->hostname = setup.api_server_hostname; + request->port = API_SERVER_PORT; + request->method = API_SERVER_METHOD; + request->uri = API_SERVER_URI_RENAME; + + char *player_name = getEscapedJSON(player_name_raw); + char *player_uuid = getEscapedJSON(player_uuid_raw); + + snprintf(request->body, MAX_HTTP_BODY_SIZE, + "{\n" + "%s" + " \"game_version\": \"%s\",\n" + " \"game_platform\": \"%s\",\n" + " \"name\": \"%s\",\n" + " \"uuid\": \"%s\"\n" + "}\n", + getPasswordJSON(setup.api_server_password), + getProgramRealVersionString(), + getProgramPlatformString(), + player_name, + player_uuid); + + checked_free(player_name); + checked_free(player_uuid); + + ConvertHttpRequestBodyToServerEncoding(request); + + return TRUE; +} + +static void HandleResponse_ApiRenamePlayer(struct HttpResponse *response, + void *data_raw) +{ + // nothing to do here +} + +#if defined(PLATFORM_EMSCRIPTEN) +static void Emscripten_ApiRenamePlayer_Loaded(unsigned handle, void *data_raw, + void *buffer, unsigned int size) +{ + struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + + if (response != NULL) + { + HandleResponse_ApiRenamePlayer(response, data_raw); + + checked_free(response); + } + else + { + Error("server response too large to handle (%d bytes)", size); + } + + FreeThreadData_ApiRenamePlayer(data_raw); +} + +static void Emscripten_ApiRenamePlayer_Failed(unsigned handle, void *data_raw, + int code, const char *status) +{ + Error("server failed to handle request: %d %s", code, status); + + FreeThreadData_ApiRenamePlayer(data_raw); +} + +static void Emscripten_ApiRenamePlayer_Progress(unsigned handle, void *data_raw, + int bytes, int size) +{ + // nothing to do here +} + +static void Emscripten_ApiRenamePlayer_HttpRequest(struct HttpRequest *request, + void *data_raw) +{ + if (!SetRequest_ApiRenamePlayer(request, data_raw)) + { + FreeThreadData_ApiRenamePlayer(data_raw); + + return; + } + + emscripten_async_wget2_data(request->uri, + request->method, + request->body, + data_raw, + TRUE, + Emscripten_ApiRenamePlayer_Loaded, + Emscripten_ApiRenamePlayer_Failed, + Emscripten_ApiRenamePlayer_Progress); +} + +#else + +static void ApiRenamePlayer_HttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + if (!SetRequest_ApiRenamePlayer(request, data_raw)) + return; + + if (!DoHttpRequest(request, response)) + { + Error("HTTP request failed: %s", GetHttpError()); + + return; + } + + if (!HTTP_SUCCESS(response->status_code)) + { + Error("server failed to handle request: %d %s", + response->status_code, + response->status_text); + + return; + } + + HandleResponse_ApiRenamePlayer(response, data_raw); +} + +static void ApiRenamePlayer_HttpRequest(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + ApiRenamePlayer_HttpRequestExt(request, response, data_raw); + + FreeThreadData_ApiRenamePlayer(data_raw); +} +#endif + +static int ApiRenamePlayerThread(void *data_raw) +{ + struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); + + program.api_thread_count++; + +#if defined(PLATFORM_EMSCRIPTEN) + Emscripten_ApiRenamePlayer_HttpRequest(request, data_raw); +#else + ApiRenamePlayer_HttpRequest(request, response, data_raw); +#endif + + program.api_thread_count--; + + checked_free(request); + checked_free(response); + + return 0; +} + +static void ApiRenamePlayerAsThread(void) +{ + struct ApiRenamePlayerThreadData *data = CreateThreadData_ApiRenamePlayer(); + + ExecuteAsThread(ApiRenamePlayerThread, + "ApiRenamePlayer", data, + "rename player on server"); +} + + +// ============================================================================ +// reset player UUID API functions +// ============================================================================ + +struct ApiResetUUIDThreadData +{ + char *player_name; + char *player_uuid_old; + char *player_uuid_new; +}; + +static void *CreateThreadData_ApiResetUUID(char *uuid_new) +{ + struct ApiResetUUIDThreadData *data = + checked_malloc(sizeof(struct ApiResetUUIDThreadData)); + + data->player_name = getStringCopy(setup.player_name); + data->player_uuid_old = getStringCopy(setup.player_uuid); + data->player_uuid_new = getStringCopy(uuid_new); + + return data; +} + +static void FreeThreadData_ApiResetUUID(void *data_raw) +{ + struct ApiResetUUIDThreadData *data = data_raw; + + checked_free(data->player_name); + checked_free(data->player_uuid_old); + checked_free(data->player_uuid_new); + checked_free(data); +} + +static boolean SetRequest_ApiResetUUID(struct HttpRequest *request, + void *data_raw) +{ + struct ApiResetUUIDThreadData *data = data_raw; + char *player_name_raw = data->player_name; + char *player_uuid_old_raw = data->player_uuid_old; + char *player_uuid_new_raw = data->player_uuid_new; + + request->hostname = setup.api_server_hostname; + request->port = API_SERVER_PORT; + request->method = API_SERVER_METHOD; + request->uri = API_SERVER_URI_RESETUUID; + + char *player_name = getEscapedJSON(player_name_raw); + char *player_uuid_old = getEscapedJSON(player_uuid_old_raw); + char *player_uuid_new = getEscapedJSON(player_uuid_new_raw); + + snprintf(request->body, MAX_HTTP_BODY_SIZE, + "{\n" + "%s" + " \"game_version\": \"%s\",\n" + " \"game_platform\": \"%s\",\n" + " \"name\": \"%s\",\n" + " \"uuid_old\": \"%s\",\n" + " \"uuid_new\": \"%s\"\n" + "}\n", + getPasswordJSON(setup.api_server_password), + getProgramRealVersionString(), + getProgramPlatformString(), + player_name, + player_uuid_old, + player_uuid_new); + + checked_free(player_name); + checked_free(player_uuid_old); + checked_free(player_uuid_new); + + ConvertHttpRequestBodyToServerEncoding(request); + + return TRUE; +} + +static void HandleResponse_ApiResetUUID(struct HttpResponse *response, + void *data_raw) +{ + struct ApiResetUUIDThreadData *data = data_raw; + + // upgrade player UUID in server setup file + setup.player_uuid = getStringCopy(data->player_uuid_new); + setup.player_version = 2; + + SaveSetup_ServerSetup(); +} + +#if defined(PLATFORM_EMSCRIPTEN) +static void Emscripten_ApiResetUUID_Loaded(unsigned handle, void *data_raw, + void *buffer, unsigned int size) +{ + struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + + if (response != NULL) + { + HandleResponse_ApiResetUUID(response, data_raw); + + checked_free(response); + } + else + { + Error("server response too large to handle (%d bytes)", size); + } + + FreeThreadData_ApiResetUUID(data_raw); +} + +static void Emscripten_ApiResetUUID_Failed(unsigned handle, void *data_raw, + int code, const char *status) +{ + Error("server failed to handle request: %d %s", code, status); + + FreeThreadData_ApiResetUUID(data_raw); +} + +static void Emscripten_ApiResetUUID_Progress(unsigned handle, void *data_raw, + int bytes, int size) +{ + // nothing to do here +} + +static void Emscripten_ApiResetUUID_HttpRequest(struct HttpRequest *request, + void *data_raw) +{ + if (!SetRequest_ApiResetUUID(request, data_raw)) + { + FreeThreadData_ApiResetUUID(data_raw); + + return; + } + + emscripten_async_wget2_data(request->uri, + request->method, + request->body, + data_raw, + TRUE, + Emscripten_ApiResetUUID_Loaded, + Emscripten_ApiResetUUID_Failed, + Emscripten_ApiResetUUID_Progress); +} + +#else + +static void ApiResetUUID_HttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + if (!SetRequest_ApiResetUUID(request, data_raw)) + return; + + if (!DoHttpRequest(request, response)) + { + Error("HTTP request failed: %s", GetHttpError()); + + return; + } + + if (!HTTP_SUCCESS(response->status_code)) + { + Error("server failed to handle request: %d %s", + response->status_code, + response->status_text); + + return; + } + + HandleResponse_ApiResetUUID(response, data_raw); +} + +static void ApiResetUUID_HttpRequest(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + ApiResetUUID_HttpRequestExt(request, response, data_raw); + + FreeThreadData_ApiResetUUID(data_raw); +} +#endif + +static int ApiResetUUIDThread(void *data_raw) +{ + struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); + + program.api_thread_count++; + +#if defined(PLATFORM_EMSCRIPTEN) + Emscripten_ApiResetUUID_HttpRequest(request, data_raw); +#else + ApiResetUUID_HttpRequest(request, response, data_raw); +#endif + + program.api_thread_count--; + + checked_free(request); + checked_free(response); + + return 0; +} + +static void ApiResetUUIDAsThread(char *uuid_new) +{ + struct ApiResetUUIDThreadData *data = CreateThreadData_ApiResetUUID(uuid_new); + + ExecuteAsThread(ApiResetUUIDThread, + "ApiResetUUID", data, + "reset UUID on server"); +} + + // ============================================================================ // type name functions // ============================================================================ 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 }; - int sx = mSX + ALIGNED_TEXT_XPOS(pos); - int sy = mSY + ALIGNED_TEXT_YPOS(pos); + 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); @@ -4033,7 +4493,7 @@ static void drawTypeNameText(char *name, struct TextPosInfo *pos, sprintf(text, "%s%c", name, (active ? '_' : '\0')); pos->width = strlen(text) * font_width; - sx = mSX + ALIGNED_TEXT_XPOS(pos); + sx = (multiple_users ? amSX + pos->x : mSX + ALIGNED_TEXT_XPOS(pos)); DrawText(sx, sy, text, font_nr); } @@ -4044,7 +4504,7 @@ static void getTypeNameValues(char *name, struct TextPosInfo *pos, int *xpos) *pos = *mci->pos_input; - if (setup.multiple_users) + if (game_status == GAME_MODE_PSEUDO_TYPENAMES) { TreeInfo *ti = player_name_current; int first_entry = ti->cl_first; @@ -4052,18 +4512,20 @@ static void getTypeNameValues(char *name, struct TextPosInfo *pos, int *xpos) TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti); int xpos = MENU_SCREEN_START_XPOS; int ypos = MENU_SCREEN_START_YPOS + ti->cl_cursor; - int font_width = getFontWidth(pos->font); type_name_node = getTreeInfoFromPos(node_first, entry_pos); + type_name_nr = entry_pos; strcpy(name, type_name_node->name); - pos->x = xpos * font_width; - pos->y = ypos * font_width; - pos->width = MAX_PLAYER_NAME_LEN * font_width; + 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); } @@ -4075,10 +4537,30 @@ static void getTypeNameValues(char *name, struct TextPosInfo *pos, int *xpos) *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) { - TreeInfo *node = type_name_node; + 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); @@ -4086,57 +4568,123 @@ static void setTypeNameValues(char *name, struct TextPosInfo *pos, if (strEqual(name, "")) strcpy(name, EMPTY_PLAYER_NAME); - if (setup.multiple_users) - { - if (node == NULL) // should not happen - return; - - if (changed) - node->color = (strEqual(name, EMPTY_PLAYER_NAME) ? FC_BLUE : FC_RED); - - pos->font = (node->color == FC_RED ? FONT_INPUT_1 : FONT_VALUE_OLD); - } + 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 (setup.multiple_users) + if (game_status == GAME_MODE_PSEUDO_TYPENAMES) { - int edit_user_nr = posTreeInfo(node); - - // change name of edited user in global list of user names - setString(&global.user_names[edit_user_nr], name); - - // change name of edited user in local menu tree structure - setString(&node->name, name); - setString(&node->name_sorting, name); - // save setup of currently active user (may differ from edited user) SaveSetup(); // temporarily change active user to edited user - user.nr = edit_user_nr; + user.nr = type_name_nr; - // load setup of edited user - LoadSetup(); + 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(); - if (setup.multiple_users) + // 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(); } } @@ -4172,15 +4720,13 @@ static void HandleTypeNameExt(boolean initialize, Key key) name[xpos] = 0; } - else if (key == KSYM_Return) + else if (key == KSYM_Return || key == KSYM_Escape) { - setTypeNameValues(name, pos, TRUE); + boolean changed = (key == KSYM_Return); - active = FALSE; - } - else if (key == KSYM_Escape) - { - setTypeNameValues(name, pos, FALSE); + StopTextInput(); + + setTypeNameValues(name, pos, changed); active = FALSE; } @@ -4189,9 +4735,10 @@ static void HandleTypeNameExt(boolean initialize, Key key) if (!active) { - StopTextInput(); - SetGameStatus(game_status_last_screen); + + if (game_status == GAME_MODE_MAIN) + InitializeMainControls(); } } @@ -4224,7 +4771,8 @@ static int getAlignXOffsetFromTreeInfo(TreeInfo *ti) 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 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); @@ -4244,7 +4792,8 @@ static int getAlignYOffsetFromTreeInfo(TreeInfo *ti) 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 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; @@ -4264,9 +4813,16 @@ static void DrawChooseTree(TreeInfo **ti_ptr) if (strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY)) { - SetGameStatus(GAME_MODE_MAIN); + if (game_status == GAME_MODE_SETUP) + { + execSetupArtwork(); + } + else // GAME_MODE_LEVELS + { + SetGameStatus(GAME_MODE_MAIN); - DrawMainMenu(); + DrawMainMenu(); + } return; } @@ -4300,15 +4856,38 @@ static void DrawChooseTree(TreeInfo **ti_ptr) FadeIn(fade_mask); } -static void drawChooseTreeList(int first_entry, int num_page_entries, - TreeInfo *ti) +static void drawChooseTreeText(int y, boolean active, TreeInfo *ti) { 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 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_color = MENU_CHOOSE_TREE_COLOR(node, active); + int font_nr = MENU_CHOOSE_TREE_FONT(font_color); int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset; + int xpos = MENU_SCREEN_START_XPOS; + int ypos = MENU_SCREEN_START_YPOS + y; + 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]; + + strncpy(buffer, node->name, max_buffer_len); + buffer[max_buffer_len] = '\0'; + + DrawText(startx, starty, buffer, font_nr); +} + +static void drawChooseTreeList(int first_entry, int num_page_entries, + TreeInfo *ti) +{ int i; char *title_string = NULL; int yoffset_sets = MENU_TITLE1_YPOS; @@ -4326,23 +4905,11 @@ static void drawChooseTreeList(int first_entry, int num_page_entries, { 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]; node_first = getTreeInfoFirstGroupEntry(ti); node = getTreeInfoFromPos(node_first, entry_pos); - strncpy(buffer, node->name, max_buffer_len); - buffer[max_buffer_len] = '\0'; - - DrawText(startx, starty, buffer, font_nr + node->color); + drawChooseTreeText(i, FALSE, ti); if (node->parent_link) initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU); @@ -4393,6 +4960,12 @@ static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti) MarkTileDirty(x, 1); } +static void drawChooseTreeCursorAndText(int y, boolean active, TreeInfo *ti) +{ + drawChooseTreeCursor(y, active); + drawChooseTreeText(y, active, ti); +} + static void HandleChooseTree(int mx, int my, int dx, int dy, int button, TreeInfo **ti_ptr) { @@ -4412,7 +4985,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, 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); @@ -4440,7 +5013,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, drawChooseTreeList(ti->cl_first, num_page_entries, ti); drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti); - drawChooseTreeCursor(ti->cl_cursor, TRUE); + drawChooseTreeCursorAndText(ti->cl_cursor, TRUE, ti); return; } @@ -4457,7 +5030,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(); @@ -4550,7 +5124,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, { drawChooseTreeList(ti->cl_first, num_page_entries, ti); drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti); - drawChooseTreeCursor(ti->cl_cursor, TRUE); + drawChooseTreeCursorAndText(ti->cl_cursor, TRUE, ti); AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, ti->cl_first, ti); @@ -4614,8 +5188,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, { PlaySound(SND_MENU_ITEM_ACTIVATING); - drawChooseTreeCursor(ti->cl_cursor, FALSE); - drawChooseTreeCursor(y, TRUE); + drawChooseTreeCursorAndText(ti->cl_cursor, FALSE, ti); + drawChooseTreeCursorAndText(y, TRUE, ti); drawChooseTreeInfo(ti->cl_first + y, ti); ti->cl_cursor = y; @@ -4624,7 +5198,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(); @@ -4696,7 +5271,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(); @@ -4729,6 +5305,11 @@ 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) @@ -4753,6 +5334,9 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, LoadLevelSetup_LastSeries(); LoadLevelSetup_SeriesInfo(); + // update list of last played level sets + UpdateLastPlayedLevels_TreeInfo(); + TapeErase(); ToggleFullscreenIfNeeded(); @@ -4787,18 +5371,13 @@ void DrawChoosePlayerName(void) for (i = 0; i < MAX_PLAYER_NAMES; i++) { - boolean team_mode = (!network.enabled && setup.team_mode); - int tree_type = (team_mode ? TREE_TYPE_PLAYER_TEAM : TREE_TYPE_PLAYER_NAME); - TreeInfo *ti = newTreeInfo_setDefaults(tree_type); + 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 = FC_RED; - - if (strEqual(global.user_names[i], EMPTY_PLAYER_NAME)) - ti->color = FC_BLUE; + ti->color = getPlayerNameColor(global.user_names[i]); snprintf(identifier, sizeof(identifier), "%d", value); snprintf(name, sizeof(name), "%s", global.user_names[i]); @@ -4905,7 +5484,7 @@ 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) +void DrawHallOfFame(int level_nr) { int fade_mask = REDRAW_FIELD; @@ -4922,9 +5501,9 @@ void DrawHallOfFame(int level_nr, int highlight_position) SetDrawDeactivationMask(REDRAW_NONE); SetDrawBackgroundMask(REDRAW_FIELD); - if (highlight_position < 0) - LoadScore(level_nr); - else + LoadLocalAndServerScore(level_nr, TRUE); + + if (scores.last_added >= 0) SetAnimStatus(GAME_MODE_PSEUDO_SCORESNEW); FadeSetEnterScreen(); @@ -4938,15 +5517,47 @@ void DrawHallOfFame(int level_nr, int highlight_position) OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW); - HandleHallOfFame(level_nr, highlight_position, 0, 0, MB_MENU_INITIALIZE); + HandleHallOfFame(level_nr, 0, 0, 0, MB_MENU_INITIALIZE); DrawMaskedBorder(fade_mask); FadeIn(fade_mask); } -static void drawHallOfFameList(int level_nr, int first_entry, - int highlight_position) +static int getHallOfFameFirstEntry(int first_entry, int step) +{ + if (step == 0) + first_entry = scores.last_added - (NUM_MENU_ENTRIES_ON_SCREEN + 1) / 2 + 1; + else + first_entry += step; + + if (first_entry < 0) + first_entry = 0; + else if (first_entry > MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN) + first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN); + + return first_entry; +} + +static char *getHallOfFameScoreText(int nr) +{ + if (!level.rate_time_over_score) + return int2str(scores.entry[nr].score, 5); // show normal score + + if (level.use_step_counter) + return int2str(scores.entry[nr].time, 5); // show number of steps + + 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 void drawHallOfFameList(int level_nr, int first_entry) { int i, j; @@ -4960,7 +5571,8 @@ static void drawHallOfFameList(int level_nr, int first_entry, for (i = 0; i < NUM_MENU_ENTRIES_ON_SCREEN; i++) { int entry = first_entry + i; - boolean active = (entry == highlight_position); + boolean active = (entry == scores.last_added); + boolean forced = (scores.force_last_added && active); 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); @@ -4971,17 +5583,18 @@ static void drawHallOfFameList(int level_nr, int first_entry, 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; + char *pos_text = (forced ? "???" : int2str(entry + 1, 3)); - DrawText(mSX, sy, int2str(entry + 1, 3), font_nr1); + DrawText(mSX, sy, pos_text, font_nr1); DrawText(mSX + dx1, sy, ".", font_nr1); for (j = 0; j < num_dots; j++) DrawText(mSX + dx2 + j * getFontWidth(font_nr3), sy, ".", font_nr3); - if (!strEqual(highscore[entry].Name, EMPTY_PLAYER_NAME)) - DrawText(mSX + dx2, sy, highscore[entry].Name, font_nr2); + if (!strEqual(scores.entry[entry].name, EMPTY_PLAYER_NAME)) + DrawText(mSX + dx2, sy, scores.entry[entry].name, font_nr2); - DrawText(mSX + dx3, sy, int2str(highscore[entry].Score, 5), font_nr4); + DrawText(mSX + dx3, sy, getHallOfFameScoreText(entry), font_nr4); } redraw_mask |= REDRAW_FIELD; @@ -4991,16 +5604,23 @@ void HandleHallOfFame(int mx, int my, int dx, int dy, int button) { static int level_nr = 0; static int first_entry = 0; - static int highlight_position = 0; int step = (button == 1 ? 1 : button == 2 ? 5 : 10); if (button == MB_MENU_INITIALIZE) { level_nr = mx; - first_entry = 0; - highlight_position = my; - drawHallOfFameList(level_nr, first_entry, highlight_position); + if (server_scores.updated) + { + // reload scores, using updated server score cache file + LoadLocalAndServerScore(level_nr, FALSE); + + server_scores.updated = FALSE; + } + + first_entry = getHallOfFameFirstEntry(0, 0); + + drawHallOfFameList(level_nr, first_entry); return; } @@ -5010,25 +5630,15 @@ void HandleHallOfFame(int mx, int my, int dx, int dy, int button) if (dy < 0) { - if (first_entry > 0) - { - first_entry -= step; - if (first_entry < 0) - first_entry = 0; + first_entry = getHallOfFameFirstEntry(first_entry, -step); - drawHallOfFameList(level_nr, first_entry, highlight_position); - } + drawHallOfFameList(level_nr, first_entry); } 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); + first_entry = getHallOfFameFirstEntry(first_entry, step); - drawHallOfFameList(level_nr, first_entry, highlight_position); - } + drawHallOfFameList(level_nr, first_entry); } else if (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE) { @@ -5051,6 +5661,17 @@ void HandleHallOfFame(int mx, int my, int dx, int dy, int button) DrawMainMenu(); } } + else if (server_scores.updated) + { + // reload scores, using updated server score cache file + LoadLocalAndServerScore(level_nr, FALSE); + + server_scores.updated = FALSE; + + first_entry = getHallOfFameFirstEntry(0, 0); + + drawHallOfFameList(level_nr, first_entry); + } if (game_status == GAME_MODE_SCORES) PlayMenuSoundIfLoop(); @@ -5072,6 +5693,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; @@ -5092,6 +5714,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) @@ -5285,11 +5957,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(); @@ -5299,6 +5975,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; @@ -6236,6 +6919,23 @@ 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 Debug("screens:execSetupArtwork", "'%s', '%s', '%s'", artwork.gfx_current->subdir, @@ -6358,6 +7058,11 @@ static void execGadgetNetworkServer(void) ClickOnGadget(gi, MB_LEFTBUTTON); } +static void execOfferUploadTapes(void) +{ + OfferUploadTapes(); +} + static void ToggleNetworkModeIfNeeded(void) { int font_title = FONT_TITLE_1; @@ -6416,6 +7121,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) || @@ -6463,6 +7179,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 }, @@ -6575,6 +7294,10 @@ 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:" }, @@ -6582,8 +7305,11 @@ static struct TokenInfo setup_info_game[] = { 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_ENTER_LIST, execSetupChooseGameSpeed, "Game Speed:" }, { TYPE_STRING, &game_speed_text, "" }, @@ -6594,7 +7320,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" }, @@ -6607,6 +7334,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:" }, @@ -6645,6 +7373,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 } @@ -7170,6 +7900,10 @@ static void changeSetupValue(int screen_pos, int setup_info_pos_raw, int dx) 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(); @@ -8585,6 +9319,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) @@ -8667,6 +9403,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) @@ -8728,10 +9466,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) - AutoPlayTapes(); // continue automatically playing next tape + AutoPlayTapesContinue(); // continue automatically playing next tape } @@ -9522,3 +10260,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(); +}