diff --git a/bin/custom_textures.c b/bin/custom_textures.c index 8136a1fcb..7ec6e78bc 100644 --- a/bin/custom_textures.c +++ b/bin/custom_textures.c @@ -31,6 +31,14 @@ ALIGNED8 const Texture texture_selectionbox_forward_icon[] = { #include "textures/segment2/custom_selectionbox_forward_icon.rgba16.inc.c" }; +ALIGNED8 const Texture texture_selectionbox_up_icon[] = { +#include "textures/segment2/custom_selectionbox_up_icon.rgba16.inc.c" +}; + +ALIGNED8 const Texture texture_selectionbox_down_icon[] = { +#include "textures/segment2/custom_selectionbox_down_icon.rgba16.inc.c" +}; + ALIGNED8 const Texture texture_coopdx_logo[] = { #include "textures/segment2/custom_coopdx_logo.rgba32.inc.c" }; diff --git a/lang/English.ini b/lang/English.ini index 14ec1d462..d0e44db51 100644 --- a/lang/English.ini +++ b/lang/English.ini @@ -431,6 +431,11 @@ REFRESHING = "Refreshing..." ENTER_PASSWORD = "Enter the private lobby's password:" SEARCH = "Search" NO_LOBBIES_FOUND = "No lobbies were found." +SORT_BY = "Sort By" +SORTING_NONE = "None" +SORTING_NAME = "Name" +SORTING_GAMEMODE = "Gamemode" +SORTING_PLAYERS = "Players" [CHANGELOG] CHANGELOG_TITLE = "CHANGELOG" diff --git a/src/pc/configfile.c b/src/pc/configfile.c index f2267a7db..8f23905c1 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -174,6 +174,8 @@ unsigned int configHostSaveSlot = 1; char configJoinIp[MAX_CONFIG_STRING] = ""; unsigned int configJoinPort = DEFAULT_PORT; unsigned int configNetworkSystem = 0; +unsigned int configCoopNetSortSelected = 0; +bool configCoopNetSortInverted = false; unsigned int configPlayerInteraction = 1; unsigned int configPlayerKnockbackStrength = 25; unsigned int configStayInLevelAfterStar = 0; @@ -333,6 +335,8 @@ static const struct ConfigOption options[] = { {.name = "coop_join_ip", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configJoinIp, .maxStringLength = MAX_CONFIG_STRING}, {.name = "coop_join_port", .type = CONFIG_TYPE_UINT, .uintValue = &configJoinPort}, {.name = "coop_network_system", .type = CONFIG_TYPE_UINT, .uintValue = &configNetworkSystem}, + {.name = "coop_coop_net_sort_selected", .type = CONFIG_TYPE_UINT, .uintValue = &configCoopNetSortSelected}, + {.name = "coop_coop_net_sort_inverted", .type = CONFIG_TYPE_BOOL, .uintValue = &configCoopNetSortInverted}, {.name = "coop_player_interaction", .type = CONFIG_TYPE_UINT, .uintValue = &configPlayerInteraction}, {.name = "coop_player_knockback_strength", .type = CONFIG_TYPE_UINT, .uintValue = &configPlayerKnockbackStrength}, {.name = "coop_stay_in_level_after_star", .type = CONFIG_TYPE_UINT, .uintValue = &configStayInLevelAfterStar}, diff --git a/src/pc/configfile.h b/src/pc/configfile.h index d537970e4..8374097cb 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -139,6 +139,8 @@ extern unsigned int configHostSaveSlot; extern char configJoinIp[MAX_CONFIG_STRING]; extern unsigned int configJoinPort; extern unsigned int configNetworkSystem; +extern unsigned int configCoopNetSortSelected; +extern bool configCoopNetSortInverted; extern unsigned int configPlayerInteraction; extern unsigned int configPlayerKnockbackStrength; extern unsigned int configStayInLevelAfterStar; diff --git a/src/pc/djui/djui_panel_join_lobbies.c b/src/pc/djui/djui_panel_join_lobbies.c index bc0d7514f..6005c8da8 100644 --- a/src/pc/djui/djui_panel_join_lobbies.c +++ b/src/pc/djui/djui_panel_join_lobbies.c @@ -3,6 +3,7 @@ #include "djui_panel.h" #include "djui_panel_menu.h" #include "djui_panel_join_message.h" +#include "djui_panel_join_lobbies.h" #include "djui_lobby_entry.h" #include "djui_panel_rules.h" #include "pc/network/network.h" @@ -17,13 +18,85 @@ #define DJUI_DESC_PANEL_WIDTH (410.0f + (16 * 2.0f)) +extern ALIGNED8 u8 texture_selectionbox_up_icon[]; +extern ALIGNED8 u8 texture_selectionbox_down_icon[]; + +static struct LobbySortType sLobbySorting[] = { + { + "SORTING_NONE", + LOBBY_SORTING_NONE, + }, + { + "SORTING_NAME", + LOBBY_SORTING_NAME, + }, + { + "SORTING_GAMEMODE", + LOBBY_SORTING_GAMEMODE, + }, + { + "SORTING_PLAYERS", + LOBBY_SORTING_PLAYERS, + }, +}; +static const int numSortOptions = sizeof(sLobbySorting) / sizeof(sLobbySorting[0]); + +static struct CoopnetLobby** sCoopnetLobbies = NULL; +static unsigned int sCoopnetLobbyCount = 0; + static struct DjuiPaginated* sLobbyPaginated = NULL; static struct DjuiFlowLayout* sLobbyLayout = NULL; static struct DjuiButton* sRefreshButton = NULL; static struct DjuiThreePanel* sDescriptionPanel = NULL; static struct DjuiText* sTooltip = NULL; +static struct DjuiSelectionbox* sSelectionbox = NULL; +static struct DjuiImage* sSortInvertImage = NULL; +static unsigned int sSavedLobbyStartCount = 0; + static char* sPassword = NULL; +static void free_coopnet_lobbies() { + for (unsigned int i = 0; i < sCoopnetLobbyCount; i++) + { + struct CoopnetLobby* lobby = sCoopnetLobbies[i]; + if (!lobby) { continue; } + free(lobby->playerText); + free(lobby->hostName); + free(lobby->mode); + free(lobby->description); + free(lobby); + } + + free(sCoopnetLobbies); + sCoopnetLobbies = NULL; + sCoopnetLobbyCount = 0; +} + +static int sort_coopnet_lobby_comp(const void* a, const void* b) { + const struct CoopnetLobby* lobbyA = *(const struct CoopnetLobby**)a; + const struct CoopnetLobby* lobbyB = *(const struct CoopnetLobby**)b; + + int retValue = 0; + enum LobbySorting sortBy = sLobbySorting[configCoopNetSortSelected].sortType; + if (sortBy == LOBBY_SORTING_NAME) { + retValue = strcmp(lobbyA->hostName, lobbyB->hostName); + } else if (sortBy == LOBBY_SORTING_GAMEMODE) { + retValue = strcmp(lobbyA->mode, lobbyB->mode); + } else if (sortBy == LOBBY_SORTING_PLAYERS) { + retValue = lobbyA->playerCount - lobbyB->playerCount; + } else if (sortBy == LOBBY_SORTING_NONE) { + retValue = lobbyA->lobbyId > lobbyB->lobbyId ? 1 : -1; + } + + retValue *= configCoopNetSortInverted ? -1 : 1; + if (lobbyA->disabled != lobbyB->disabled) { + retValue = lobbyA->disabled ? 1 : -1; + } else if (retValue == 0) { + retValue = lobbyA->lobbyId > lobbyB->lobbyId ? 1 : -1; + } + return retValue; +} + static void djui_panel_join_lobby_description_create(void) { f32 bodyHeight = 600; @@ -56,6 +129,7 @@ static void djui_panel_join_lobby_description_create(void) { sDescriptionPanel = panel; } + static void djui_lobby_on_hover(struct DjuiBase* base) { struct DjuiLobbyEntry* entry = (struct DjuiLobbyEntry*)base; djui_text_set_text(sTooltip, entry->description); @@ -74,6 +148,22 @@ void djui_panel_join_lobby(struct DjuiBase* caller) { djui_panel_join_message_create(caller); } +static void djui_panel_join_on_sorting_change(UNUSED struct DjuiBase* base) { + qsort(sCoopnetLobbies, sCoopnetLobbyCount, sizeof(sCoopnetLobbies[0]), sort_coopnet_lobby_comp); + djui_base_destroy_children(&sLobbyLayout->base); + for (unsigned int i = 0; i < sCoopnetLobbyCount; i++) { + struct CoopnetLobby* lobby = sCoopnetLobbies[i]; + struct DjuiLobbyEntry* entry = djui_lobby_entry_create(&sLobbyLayout->base, lobby->hostName, lobby->mode, lobby->playerText, lobby->description, lobby->disabled, djui_panel_join_lobby, djui_lobby_on_hover, djui_lobby_on_hover_end); + entry->base.tag = (s64)lobby->lobbyId; + } +} + +static void djui_panel_join_invert_sort(UNUSED struct DjuiBase* caller) { + configCoopNetSortInverted = !configCoopNetSortInverted; + sSortInvertImage->textureInfo.texture = configCoopNetSortInverted ? texture_selectionbox_up_icon : texture_selectionbox_down_icon; + djui_panel_join_on_sorting_change(NULL); +} + void djui_panel_join_query(uint64_t aLobbyId, UNUSED uint64_t aOwnerId, uint16_t aConnections, uint16_t aMaxConnections, UNUSED const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, const char* aDescription) { if (!sLobbyLayout) { return; } if (!sLobbyPaginated) { return; } @@ -82,7 +172,6 @@ void djui_panel_join_query(uint64_t aLobbyId, UNUSED uint64_t aOwnerId, uint16_t char playerText[64] = ""; snprintf(playerText, 63, "%u/%u", aConnections, aMaxConnections); - char mode[64] = ""; snprintf(mode, 64, "%s", aMode); @@ -93,16 +182,47 @@ void djui_panel_join_query(uint64_t aLobbyId, UNUSED uint64_t aOwnerId, uint16_t snprintf(mode, 64, "\\#ff0000\\[%s]", aVersion); } - struct DjuiBase* layoutBase = &sLobbyLayout->base; - struct DjuiLobbyEntry* entry = djui_lobby_entry_create(layoutBase, (char*)aHostName, (char*)mode, playerText, (char*)aDescription, disabled, djui_panel_join_lobby, djui_lobby_on_hover, djui_lobby_on_hover_end); - entry->base.tag = (s64)aLobbyId; - djui_paginated_update_page_buttons(sLobbyPaginated); + struct CoopnetLobby* lobby = malloc(sizeof(struct CoopnetLobby)); + + if (!lobby) { + LOG_ERROR("Failed to allocate memory to lobby!"); + return; + } + + lobby->lobbyId = aLobbyId; + lobby->playerCount = aConnections; + lobby->playerText = strdup(playerText); + lobby->hostName = strdup(aHostName); + lobby->mode = strdup(mode); + lobby->description = strdup(aDescription); + lobby->disabled = disabled; + + struct CoopnetLobby** lobbies = realloc(sCoopnetLobbies, (sCoopnetLobbyCount + 1) * sizeof(struct CoopnetLobby*)); + if (!lobbies) { + LOG_ERROR("Failed to reallocate memory to lobbies!"); + return; + } + sCoopnetLobbies = lobbies; + sCoopnetLobbies[sCoopnetLobbyCount] = lobby; + sCoopnetLobbyCount++; } void djui_panel_join_query_finish(void) { if (!sLobbyLayout) { return; } if (!sLobbyPaginated) { return; } if (!sRefreshButton) { return; } + + qsort(sCoopnetLobbies, sCoopnetLobbyCount, sizeof(sCoopnetLobbies[0]), sort_coopnet_lobby_comp); + + djui_base_destroy_children(&sLobbyLayout->base); + djui_base_set_enabled(&sLobbyLayout->base, true); + struct DjuiBase* layoutBase = &sLobbyLayout->base; + for (unsigned int i = 0; i < sCoopnetLobbyCount; i++) { + struct CoopnetLobby* lobby = sCoopnetLobbies[i]; + struct DjuiLobbyEntry* entry = djui_lobby_entry_create(layoutBase, lobby->hostName, lobby->mode, lobby->playerText, lobby->description, lobby->disabled, djui_panel_join_lobby, djui_lobby_on_hover, djui_lobby_on_hover_end); + entry->base.tag = (s64)lobby->lobbyId; + } + djui_text_set_text(sRefreshButton->text, DLANG(LOBBIES, REFRESH)); djui_base_set_enabled(&sRefreshButton->base, true); @@ -113,7 +233,9 @@ void djui_panel_join_query_finish(void) { djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER); djui_text_set_drop_shadow(text, 64, 64, 64, 100); } + sLobbyPaginated->startIndex = sSavedLobbyStartCount; djui_paginated_update_page_buttons(sLobbyPaginated); + djui_panel_join_on_sorting_change(NULL); } void djui_panel_join_lobbies_on_destroy(UNUSED struct DjuiBase* caller) { @@ -122,6 +244,8 @@ void djui_panel_join_lobbies_on_destroy(UNUSED struct DjuiBase* caller) { sRefreshButton = NULL; sLobbyLayout = NULL; sLobbyPaginated = NULL; + sSavedLobbyStartCount = 0; + free_coopnet_lobbies(); if (sDescriptionPanel != NULL) { djui_base_destroy(&sDescriptionPanel->base); @@ -130,10 +254,18 @@ void djui_panel_join_lobbies_on_destroy(UNUSED struct DjuiBase* caller) { } void djui_panel_join_lobbies_refresh(UNUSED struct DjuiBase* caller) { + sSavedLobbyStartCount = sLobbyPaginated->startIndex; + djui_base_set_enabled(&sLobbyLayout->base, false); djui_base_destroy_children(&sLobbyLayout->base); + for (unsigned int i = 0; i < sCoopnetLobbyCount; i++) { + struct CoopnetLobby* lobby = sCoopnetLobbies[i]; + struct DjuiLobbyEntry* entry = djui_lobby_entry_create(&sLobbyLayout->base, lobby->hostName, lobby->mode, lobby->playerText, lobby->description, true, djui_panel_join_lobby, djui_lobby_on_hover, djui_lobby_on_hover_end); + entry->base.tag = (s64)lobby->lobbyId; + } djui_text_set_text(sRefreshButton->text, DLANG(LOBBIES, REFRESHING)); djui_base_set_enabled(&sRefreshButton->base, false); djui_paginated_update_page_buttons(sLobbyPaginated); + free_coopnet_lobbies(); ns_coopnet_query(djui_panel_join_query, djui_panel_join_query_finish, sPassword); } @@ -142,6 +274,9 @@ void djui_panel_join_lobbies_value_changed(UNUSED struct DjuiBase* caller) { } void djui_panel_join_lobbies_create(struct DjuiBase* caller, const char* password) { + if (configCoopNetSortSelected > numSortOptions) { + configCoopNetSortSelected = numSortOptions; + } if (sPassword) { free(sPassword); sPassword = NULL; } sPassword = strdup(password); bool private = (strlen(password) > 0); @@ -158,6 +293,26 @@ void djui_panel_join_lobbies_create(struct DjuiBase* caller, const char* passwor true); struct DjuiBase* body = djui_three_panel_get_body(panel); { + char* sortChoices[sizeof(sLobbySorting)]; + for (int i = 0; i < numSortOptions; i++) { + sortChoices[i] = djui_language_get("LOBBIES", sLobbySorting[i].langKey); + } + struct DjuiFlowLayout* flowLayout = djui_flow_layout_create(body); + djui_base_set_color(&flowLayout->base, 0, 0, 0, 0); + djui_base_set_size_type(&flowLayout->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&flowLayout->base, 1.0f, 32.0f); + sSelectionbox = djui_selectionbox_create(&flowLayout->base, DLANG(LOBBIES, SORT_BY), sortChoices, numSortOptions, &configCoopNetSortSelected, djui_panel_join_on_sorting_change); + djui_base_set_size(&sSelectionbox->base, 0.925, 32); + struct DjuiButton* button = djui_button_create(&flowLayout->base, "", DJUI_BUTTON_STYLE_NORMAL, djui_panel_join_invert_sort); + djui_base_set_alignment(&button->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_BOTTOM); + djui_base_set_size_type(&button->base, DJUI_SVT_ABSOLUTE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&button->base, 32, 32); + sSortInvertImage = djui_image_create(&button->base, configCoopNetSortInverted ? texture_selectionbox_up_icon : texture_selectionbox_down_icon, 16, 16, G_IM_FMT_RGBA, G_IM_SIZ_16b); + djui_base_set_size(&sSortInvertImage->base, 16, 16); + djui_base_set_alignment(&sSortInvertImage->base, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER); + djui_flow_layout_set_margin(flowLayout, 16); + djui_flow_layout_set_flow_direction(flowLayout, DJUI_FLOW_DIR_RIGHT); + sLobbyPaginated = djui_paginated_create(body, 10); sLobbyLayout = sLobbyPaginated->layout; djui_flow_layout_set_margin(sLobbyLayout, 4); diff --git a/src/pc/djui/djui_panel_join_lobbies.h b/src/pc/djui/djui_panel_join_lobbies.h index cda56f95e..2e9921b08 100644 --- a/src/pc/djui/djui_panel_join_lobbies.h +++ b/src/pc/djui/djui_panel_join_lobbies.h @@ -1,3 +1,26 @@ #pragma once +enum LobbySorting { + LOBBY_SORTING_NONE, + LOBBY_SORTING_NAME, + LOBBY_SORTING_GAMEMODE, + LOBBY_SORTING_PLAYERS, + LOBBY_SORTING_COUNT, +}; + +struct LobbySortType { + const char* langKey; + enum LobbySorting sortType; +}; + +struct CoopnetLobby { + uint64_t lobbyId; + int playerCount; + char* hostName; + char* mode; + char* playerText; + char* description; + bool disabled; +}; + void djui_panel_join_lobbies_create(struct DjuiBase* caller, const char* password); diff --git a/src/pc/mods/mods.c b/src/pc/mods/mods.c index 9af7ab2b3..9f0d662ab 100644 --- a/src/pc/mods/mods.c +++ b/src/pc/mods/mods.c @@ -35,6 +35,13 @@ void mods_get_main_mod_name(char* destination, u32 maxSize) { for (u16 i = 0; i < gLocalMods.entryCount; i++) { struct Mod* mod = gLocalMods.entries[i]; if (!mod->enabled) { continue; } + LOG_CONSOLE("%s", mod->category) + // always make gamemodes the main mod + if ((mod->category && strcmp(mod->category, "gamemode") == 0) + || (mod->incompatible && strcmp(mod->incompatible, "gamemode") == 0)) { + picked = mod; + break; + } size_t size = mod_get_lua_size(mod); if (size > pickedSize) { picked = mod; diff --git a/textures/segment2/custom_selectionbox_down_icon.rgba16.png b/textures/segment2/custom_selectionbox_down_icon.rgba16.png new file mode 100644 index 000000000..ac6be8ddb Binary files /dev/null and b/textures/segment2/custom_selectionbox_down_icon.rgba16.png differ diff --git a/textures/segment2/custom_selectionbox_up_icon.rgba16.png b/textures/segment2/custom_selectionbox_up_icon.rgba16.png new file mode 100644 index 000000000..6bc626944 Binary files /dev/null and b/textures/segment2/custom_selectionbox_up_icon.rgba16.png differ