mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-04-22 01:52:43 +00:00
Merge 7ffe8a12a2 into 7604ef9297
This commit is contained in:
commit
f6434751f5
27 changed files with 424 additions and 49 deletions
|
|
@ -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"
|
||||
};
|
||||
|
|
|
|||
|
|
@ -432,6 +432,11 @@ REFRESHING = "Obnovování..."
|
|||
ENTER_PASSWORD = "Zadejte heslo soukromé hry:"
|
||||
SEARCH = "Hledat"
|
||||
NO_LOBBIES_FOUND = "Nebyly nalezeny žádné hry."
|
||||
SORT_BY = "Seřadit podle"
|
||||
NONE = "Žádné"
|
||||
NAME = "Jméno"
|
||||
GAMEMODE = "Herní mód"
|
||||
PLAYERS = "Hráči"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "ZÁZNAM ZMĚN"
|
||||
|
|
|
|||
|
|
@ -432,6 +432,11 @@ REFRESHING = "herladen..."
|
|||
ENTER_PASSWORD = "Typ het wachtwoord van de privé lobby:"
|
||||
SEARCH = "Zoek"
|
||||
NO_LOBBIES_FOUND = "Er zijn geen lobby's gevonden."
|
||||
SORT_BY = "Sorteer op"
|
||||
NONE = "Geen"
|
||||
NAME = "Naam"
|
||||
GAMEMODE = "Spelmodus"
|
||||
PLAYERS = "Spelers"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "WIJZIGINGENLOGBOEK"
|
||||
|
|
|
|||
|
|
@ -432,6 +432,13 @@ REFRESHING = "Refreshing..."
|
|||
ENTER_PASSWORD = "Enter the private lobby's password:"
|
||||
SEARCH = "Search"
|
||||
NO_LOBBIES_FOUND = "No lobbies were found."
|
||||
SORT_BY = "Sort By"
|
||||
NONE = "None"
|
||||
NAME = "Name"
|
||||
GAMEMODE = "Gamemode"
|
||||
TIME = "Time"
|
||||
SIZE = "Size"
|
||||
PLAYERS = "Players"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "CHANGELOG"
|
||||
|
|
|
|||
|
|
@ -432,6 +432,11 @@ REFRESHING = "Actualisation..."
|
|||
ENTER_PASSWORD = "Entrez le mot de passe de la partie:"
|
||||
SEARCH = "Rechercher"
|
||||
NO_LOBBIES_FOUND = "Aucune partie n'a été trouvée."
|
||||
SORT_BY = "Trier par"
|
||||
NONE = "Aucun"
|
||||
NAME = "Pseudo"
|
||||
GAMEMODE = "Mode de jeu"
|
||||
PLAYERS = "Joueurs"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "MODIFICATIONS"
|
||||
|
|
|
|||
|
|
@ -432,6 +432,13 @@ REFRESHING = "Aktualisiere..."
|
|||
ENTER_PASSWORD = "Gebe das Lobby-Passwort ein:"
|
||||
SEARCH = "Suchen"
|
||||
NO_LOBBIES_FOUND = "Keine Lobbys gefunden."
|
||||
SORT_BY = "Sortieren nach"
|
||||
NONE = "Keine"
|
||||
NAME = "Name"
|
||||
GAMEMODE = "Spielmodus"
|
||||
TIME = "Zeit"
|
||||
SIZE = "Größe"
|
||||
PLAYERS = "Spieler"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "ÄNDERUNGSPROTOKOLL"
|
||||
|
|
|
|||
|
|
@ -430,6 +430,11 @@ REFRESHING = "Ricaricando..."
|
|||
ENTER_PASSWORD = "Scrivi la password della stanza privata:"
|
||||
SEARCH = "Cerca"
|
||||
NO_LOBBIES_FOUND = "Non è stata trovata alcuna stanza."
|
||||
SORT_BY = "Ordina per"
|
||||
NONE = "Nulla"
|
||||
NAME = "Nome"
|
||||
GAMEMODE = "Modalità di gioco"
|
||||
PLAYERS = "Giocatori"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "REGISTRO DELLE MODIFICHE"
|
||||
|
|
|
|||
|
|
@ -432,7 +432,13 @@ REFRESH = "更新する"
|
|||
REFRESHING = "更新中…"
|
||||
ENTER_PASSWORD = "ルームのパスワードを入力してください:"
|
||||
SEARCH = "検索"
|
||||
NONE_FOUND = "部屋が見つかりませんでした"
|
||||
NO_LOBBIES_FOUND = "ルームが見つかりませんでした。"
|
||||
SORT_BY = "並べ替え"
|
||||
NONE = "なし"
|
||||
NAME = "名前"
|
||||
GAMEMODE = "ゲームモード"
|
||||
PLAYERS = "プレイヤー数"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "CHANGELOG"
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ MUTE_FOCUS_LOSS = "Silenciar quando a janela estiver desfocada"
|
|||
|
||||
[LANGUAGE]
|
||||
LANGUAGE = "IDIOMA"
|
||||
Czech = "Tcheco (Čeština)"
|
||||
Czech = "Tcheco (Čeština)"
|
||||
Dutch = "Holandês (Nederlands)"
|
||||
French = "Francês (Français)"
|
||||
German = "Alemão (Deutsch)"
|
||||
|
|
@ -432,6 +432,13 @@ REFRESHING = "Recarregando..."
|
|||
ENTER_PASSWORD = "Digite a senha da sala privada:"
|
||||
SEARCH = "Pesquisar"
|
||||
NO_LOBBIES_FOUND = "Nenhuma sala encontrada."
|
||||
SORT_BY = "Ordenar por"
|
||||
NONE = "Nenhum"
|
||||
NAME = "Nome"
|
||||
GAMEMODE = "Modo de jogo"
|
||||
TIME = "Hora"
|
||||
SIZE = "Tamanho"
|
||||
PLAYERS = "Jogadores"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "ALTERAÇÕES"
|
||||
|
|
|
|||
|
|
@ -431,6 +431,13 @@ REFRESHING = "Обновление..."
|
|||
ENTER_PASSWORD = "Введите пароль закрытой группы:"
|
||||
SEARCH = "Поиск"
|
||||
NO_LOBBIES_FOUND = "Группы не найдены."
|
||||
SORT_BY = "Сортировка"
|
||||
NONE = "Нет"
|
||||
NAME = "По имени"
|
||||
GAMEMODE = "По игровому режиму"
|
||||
TIME = "По времени"
|
||||
SIZE = "По размеру"
|
||||
PLAYERS = "По игрокам"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "ЖУРНАЛ ИЗМЕНЕНИЙ"
|
||||
|
|
|
|||
|
|
@ -432,6 +432,13 @@ REFRESHING = "Refrescando..."
|
|||
ENTER_PASSWORD = "Escribe la contraseña de la partida privada:"
|
||||
SEARCH = "Buscar"
|
||||
NO_LOBBIES_FOUND = "No se han encontrado partidas."
|
||||
SORT_BY = "Ordenar por"
|
||||
NONE = "Ninguno"
|
||||
NAME = "Nombre"
|
||||
GAMEMODE = "Modo de juego"
|
||||
TIME = "Tiempo"
|
||||
SIZE = "Tamaño"
|
||||
PLAYERS = "Jugadores"
|
||||
|
||||
[CHANGELOG]
|
||||
CHANGELOG_TITLE = "REGISTRO DE CAMBIOS"
|
||||
|
|
|
|||
|
|
@ -8,9 +8,17 @@ class Connection;
|
|||
class Lobby;
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define COOPNET_MAX_GAME_LEN 32
|
||||
#define COOPNET_MAX_VERSION_LEN 32
|
||||
#define COOPNET_MAX_HOST_NAME_LEN 64
|
||||
#define COOPNET_MAX_MODE_LEN 64
|
||||
#define COOPNET_MAX_PASSWORD_LEN 64
|
||||
#define COOPNET_MAX_DESCRIPTION_LEN 1024
|
||||
|
||||
typedef enum {
|
||||
COOPNET_OK,
|
||||
COOPNET_FAILED,
|
||||
|
|
@ -34,7 +42,7 @@ typedef struct {
|
|||
void (*OnLobbyCreated)(uint64_t aLobbyId, const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, uint16_t aMaxConnections);
|
||||
void (*OnLobbyJoined)(uint64_t aLobbyId, uint64_t aUserId, uint64_t aOwnerId, uint64_t aDestId);
|
||||
void (*OnLobbyLeft)(uint64_t aLobbyId, uint64_t aUserId);
|
||||
void (*OnLobbyListGot)(uint64_t aLobbyId, uint64_t aOwnerId, uint16_t aConnections, uint16_t aMaxConnections, const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, const char* aDescription);
|
||||
void (*OnLobbyListGot)(uint64_t aLobbyId, uint64_t aOwnerId, uint16_t aConnections, uint16_t aMaxConnections, int64_t aTimestamp, const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, const char* aDescription, size_t aModSize);
|
||||
void (*OnLobbyListFinish)(void);
|
||||
void (*OnReceive)(uint64_t aFromUserId, const uint8_t* aData, uint64_t aSize);
|
||||
void (*OnError)(enum MPacketErrorNumber aErrorNumber, uint64_t tag);
|
||||
|
|
@ -60,8 +68,8 @@ bool coopnet_is_connected(void);
|
|||
CoopNetRc coopnet_begin(const char* aHost, uint32_t aPort, const char* aName, uint64_t aDestId);
|
||||
CoopNetRc coopnet_shutdown(void);
|
||||
CoopNetRc coopnet_update(void);
|
||||
CoopNetRc coopnet_lobby_create(const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, uint16_t aMaxConnections, const char* aPassword, const char* aDescription);
|
||||
CoopNetRc coopnet_lobby_update(uint64_t aLobbyId, const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, const char* aDescription);
|
||||
CoopNetRc coopnet_lobby_create(const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, uint16_t aMaxConnections, const char* aPassword, const char* aDescription, size_t aModSize);
|
||||
CoopNetRc coopnet_lobby_update(uint64_t aLobbyId, const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, const char* aDescription, size_t aModSize);
|
||||
CoopNetRc coopnet_lobby_join(uint64_t aLobbyId, const char* aPassword);
|
||||
CoopNetRc coopnet_lobby_leave(uint64_t aLobbyId);
|
||||
CoopNetRc coopnet_lobby_list_get(const char* aGame, const char* aPassword);
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -175,6 +175,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;
|
||||
|
|
@ -335,6 +337,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, .boolValue = &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},
|
||||
|
|
|
|||
|
|
@ -140,6 +140,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;
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ struct ModCategory sCategories[] = {
|
|||
// lang key, mod category
|
||||
{ "ALL", NULL },
|
||||
{ "MISC", NULL },
|
||||
{ "ROMHACKS", "romhack" },
|
||||
{ "GAMEMODES", "gamemode" },
|
||||
{ "ROMHACKS", "romhack" },
|
||||
{ "MOVESETS", "moveset" },
|
||||
{ "CHARACTER_SELECT", "cs" },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#include <stdio.h>
|
||||
#include "libcoopnet.h"
|
||||
#include "djui.h"
|
||||
#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,17 +19,99 @@
|
|||
|
||||
#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[] = {
|
||||
{
|
||||
"NONE",
|
||||
LOBBY_SORTING_NONE,
|
||||
},
|
||||
{
|
||||
"NAME",
|
||||
LOBBY_SORTING_NAME,
|
||||
},
|
||||
{
|
||||
"GAMEMODE",
|
||||
LOBBY_SORTING_GAMEMODE,
|
||||
},
|
||||
{
|
||||
"PLAYERS",
|
||||
LOBBY_SORTING_PLAYERS,
|
||||
},
|
||||
{
|
||||
"TIME",
|
||||
LOBBY_SORTING_TIME,
|
||||
},
|
||||
{
|
||||
"SIZE",
|
||||
LOBBY_SORTING_SIZE,
|
||||
},
|
||||
};
|
||||
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 djui_panel_join_lobby_description_create(void) {
|
||||
f32 bodyHeight = 600;
|
||||
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);
|
||||
}
|
||||
|
||||
struct DjuiThreePanel* panel = djui_three_panel_create(&gDjuiRoot->base, 64, bodyHeight, 0);
|
||||
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 = lobbyB->playerCount - lobbyA->playerCount;
|
||||
} else if (sortBy == LOBBY_SORTING_TIME) {
|
||||
retValue = lobbyA->timestamp > lobbyB->timestamp ? 1 : -1;
|
||||
} else if (sortBy == LOBBY_SORTING_SIZE) {
|
||||
retValue = lobbyA->modSize > lobbyB->modSize ? 1 : -1;
|
||||
} 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) {
|
||||
struct DjuiThreePanel* panel = djui_three_panel_create(&gDjuiRoot->base, 0, 1200, 0);
|
||||
struct DjuiThreePanelTheme theme = gDjuiThemes[configDjuiTheme]->threePanels;
|
||||
|
||||
djui_base_set_alignment(&panel->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_CENTER);
|
||||
|
|
@ -47,18 +131,25 @@ static void djui_panel_join_lobby_description_create(void) {
|
|||
djui_flow_layout_set_flow_direction(body, DJUI_FLOW_DIR_DOWN);
|
||||
|
||||
struct DjuiText* description = djui_text_create(&panel->base, "");
|
||||
djui_base_set_size_type(&description->base, DJUI_SVT_RELATIVE, DJUI_SVT_RELATIVE);
|
||||
djui_base_set_size(&description->base, 1.0f, 1.0f);
|
||||
djui_base_set_alignment(&description->base, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER);
|
||||
djui_base_set_size_type(&description->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&description->base, 1.0f, 32);
|
||||
djui_base_set_color(&description->base, 222, 222, 222, 255);
|
||||
djui_text_set_alignment(description, DJUI_HALIGN_LEFT, DJUI_VALIGN_CENTER);
|
||||
djui_text_set_alignment(description, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP);
|
||||
sTooltip = description;
|
||||
}
|
||||
sDescriptionPanel = panel;
|
||||
}
|
||||
|
||||
|
||||
static void djui_lobby_on_hover(struct DjuiBase* base) {
|
||||
struct DjuiLobbyEntry* entry = (struct DjuiLobbyEntry*)base;
|
||||
djui_text_set_text(sTooltip, entry->description);
|
||||
djui_base_compute_tree(&sDescriptionPanel->base);
|
||||
u16 lines = djui_text_count_lines(sTooltip, 48);
|
||||
f32 textHeight = 32 * 0.8125f * lines + 8;
|
||||
sDescriptionPanel->bodySize.value = textHeight;
|
||||
djui_base_set_size(&sTooltip->base, 1.0f, textHeight);
|
||||
}
|
||||
|
||||
static void djui_lobby_on_hover_end(UNUSED struct DjuiBase* base) {
|
||||
|
|
@ -74,35 +165,84 @@ void djui_panel_join_lobby(struct DjuiBase* caller) {
|
|||
djui_panel_join_message_create(caller);
|
||||
}
|
||||
|
||||
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) {
|
||||
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, int64_t aTimestamp, UNUSED const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, const char* aDescription, size_t aModSize) {
|
||||
if (!sLobbyLayout) { return; }
|
||||
if (!sLobbyPaginated) { return; }
|
||||
if (aMaxConnections > MAX_PLAYERS) { return; }
|
||||
|
||||
char playerText[64] = "";
|
||||
snprintf(playerText, 63, "%u/%u", aConnections, aMaxConnections);
|
||||
snprintf(playerText, 64, "%u/%u", aConnections, aMaxConnections);
|
||||
|
||||
|
||||
char mode[64] = "";
|
||||
snprintf(mode, 64, "%s", aMode);
|
||||
char mode[COOPNET_MAX_MODE_LEN] = "";
|
||||
snprintf(mode, COOPNET_MAX_MODE_LEN, "%s", aMode);
|
||||
|
||||
char version[MAX_VERSION_LENGTH] = { 0 };
|
||||
snprintf(version, MAX_VERSION_LENGTH, "%s", get_version());
|
||||
bool disabled = strcmp(version, aVersion) != 0;
|
||||
if (disabled) {
|
||||
snprintf(mode, 64, "\\#ff0000\\[%s]", aVersion);
|
||||
snprintf(mode, COOPNET_MAX_MODE_LEN, "\\#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->timestamp = aTimestamp;
|
||||
lobby->modSize = aModSize;
|
||||
lobby->disabled = disabled;
|
||||
|
||||
struct CoopnetLobby** lobbies = realloc(sCoopnetLobbies, (sCoopnetLobbyCount + 1) * sizeof(struct CoopnetLobby*));
|
||||
if (!lobbies) {
|
||||
LOG_ERROR("Failed to reallocate memory to lobbies!");
|
||||
free(lobby);
|
||||
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 +253,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 +264,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 +274,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 +294,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 +313,27 @@ 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);
|
||||
djui_base_set_size(&sSelectionbox->rect->base, 0.55, 1);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
enum LobbySorting {
|
||||
LOBBY_SORTING_NONE,
|
||||
LOBBY_SORTING_NAME,
|
||||
LOBBY_SORTING_GAMEMODE,
|
||||
LOBBY_SORTING_PLAYERS,
|
||||
LOBBY_SORTING_TIME,
|
||||
LOBBY_SORTING_SIZE,
|
||||
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;
|
||||
int64_t timestamp;
|
||||
size_t modSize;
|
||||
bool disabled;
|
||||
};
|
||||
|
||||
void djui_panel_join_lobbies_create(struct DjuiBase* caller, const char* password);
|
||||
|
|
|
|||
|
|
@ -29,12 +29,24 @@ struct LocalEnabledPath {
|
|||
struct LocalEnabledPath* sLocalEnabledPaths = NULL;
|
||||
|
||||
void mods_get_main_mod_name(char* destination, u32 maxSize) {
|
||||
struct Mod* selectedRomhack = NULL;
|
||||
struct Mod* picked = NULL;
|
||||
size_t pickedSize = 0;
|
||||
|
||||
for (u16 i = 0; i < gLocalMods.entryCount; i++) {
|
||||
struct Mod* mod = gLocalMods.entries[i];
|
||||
if (!mod->enabled) { continue; }
|
||||
// always make gamemodes the main mod
|
||||
if ((mod->category && strcmp(mod->category, "gamemode") == 0)
|
||||
|| (mod->incompatible && strcmp(mod->incompatible, "gamemode") == 0)) {
|
||||
picked = mod;
|
||||
break;
|
||||
}
|
||||
// prioritize romhacks
|
||||
if ((mod->category && strcmp(mod->category, "romhack") == 0)
|
||||
|| (mod->incompatible && strcmp(mod->incompatible, "romhack") == 0)) {
|
||||
selectedRomhack = mod;
|
||||
}
|
||||
size_t size = mod_get_lua_size(mod);
|
||||
if (size > pickedSize) {
|
||||
picked = mod;
|
||||
|
|
@ -42,6 +54,7 @@ void mods_get_main_mod_name(char* destination, u32 maxSize) {
|
|||
}
|
||||
}
|
||||
|
||||
if (selectedRomhack) { picked = selectedRomhack; }
|
||||
snprintf(destination, maxSize, "%s", picked ? picked->name : "Super Mario 64");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "pc/network/version.h"
|
||||
#include "pc/djui/djui_language.h"
|
||||
#include "pc/djui/djui_popup.h"
|
||||
#include "pc/djui/djui_panel_host_mods.h"
|
||||
#include "pc/mods/mods.h"
|
||||
#include "pc/utils/misc.h"
|
||||
#include "pc/debuglog.h"
|
||||
|
|
@ -15,11 +16,9 @@
|
|||
|
||||
#ifdef COOPNET
|
||||
|
||||
#define MAX_COOPNET_DESCRIPTION_LENGTH 1024
|
||||
|
||||
uint64_t gCoopNetDesiredLobby = 0;
|
||||
char gCoopNetPassword[64] = "";
|
||||
char sCoopNetDescription[MAX_COOPNET_DESCRIPTION_LENGTH] = "";
|
||||
char gCoopNetPassword[COOPNET_MAX_PASSWORD_LEN] = "";
|
||||
char sCoopNetDescription[COOPNET_MAX_DESCRIPTION_LEN] = "";
|
||||
|
||||
static uint64_t sLocalLobbyId = 0;
|
||||
static uint64_t sLocalLobbyOwnerId = 0;
|
||||
|
|
@ -173,32 +172,109 @@ bool ns_coopnet_is_connected(void) {
|
|||
return coopnet_is_connected();
|
||||
}
|
||||
|
||||
static char* get_size_string(size_t size) {
|
||||
static char buffer[32];
|
||||
char* sizeUnits[3] = { "B", "KB", "MB" };
|
||||
f64 convertedSize = (f64)size;
|
||||
u8 sizeType = 0;
|
||||
while (convertedSize >= 1024 && sizeType < 2) {
|
||||
convertedSize /= 1024;
|
||||
sizeType++;
|
||||
}
|
||||
snprintf(buffer, sizeof(buffer), "%.2f %s", convertedSize, sizeUnits[sizeType]);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void coopnet_populate_description(void) {
|
||||
char* buffer = sCoopNetDescription;
|
||||
int bufferLength = MAX_COOPNET_DESCRIPTION_LENGTH;
|
||||
int bufferLength = COOPNET_MAX_DESCRIPTION_LEN;
|
||||
// get version
|
||||
const char* version = get_version();
|
||||
int versionLength = strlen(version);
|
||||
snprintf(buffer, bufferLength, "%s", version);
|
||||
int versionLength = snprintf(buffer, bufferLength, "%s\n", get_version());
|
||||
buffer += versionLength;
|
||||
bufferLength -= versionLength;
|
||||
|
||||
// get mod strings
|
||||
if (gActiveMods.entryCount <= 0) { return; }
|
||||
char* strings[gActiveMods.entryCount];
|
||||
for (int i = 0; i < gActiveMods.entryCount; i++) {
|
||||
struct Mod* mod = gActiveMods.entries[i];
|
||||
strings[i] = mod->name;
|
||||
}
|
||||
// get mod size
|
||||
int modsSizeLength = snprintf(buffer, bufferLength, "\nTotal Mod Size: %s\n", get_size_string(gActiveMods.size));
|
||||
buffer += modsSizeLength;
|
||||
bufferLength -= modsSizeLength;
|
||||
|
||||
// add seperator
|
||||
char* sep = "\n\nMods:\n";
|
||||
snprintf(buffer, bufferLength, "%s", sep);
|
||||
buffer += strlen(sep);
|
||||
bufferLength -= strlen(sep);
|
||||
int sepLength = snprintf(buffer, bufferLength, "Mods:\n");
|
||||
buffer += sepLength;
|
||||
bufferLength -= sepLength;
|
||||
|
||||
// concat mod strings
|
||||
str_seperator_concat(buffer, bufferLength, strings, gActiveMods.entryCount, "\\#dcdcdc\\\n");
|
||||
struct ModCategory sCategories[] = {
|
||||
{ "GAMEMODES", "gamemode" },
|
||||
{ "ROMHACKS", "romhack" },
|
||||
{ "MOVESETS", "moveset" },
|
||||
{ "CHARACTER_SELECT", "cs" },
|
||||
};
|
||||
|
||||
if (gActiveMods.entryCount <= 0) { return; }
|
||||
|
||||
// add mods that are in a category
|
||||
for (size_t i = 0; i < sizeof(sCategories) / sizeof(sCategories[0]); i++) {
|
||||
struct ModCategory category = sCategories[i];
|
||||
|
||||
char* strings[gActiveMods.entryCount];
|
||||
int strIndex = 0;
|
||||
for (int j = 0; j < gActiveMods.entryCount; j++) {
|
||||
struct Mod* mod = gActiveMods.entries[j];
|
||||
char* modCategory = mod->category != NULL ? mod->category : mod->incompatible;
|
||||
if (modCategory && strcasestr(modCategory, sCategories[i].category)) {
|
||||
strings[strIndex++] = mod->name;
|
||||
}
|
||||
}
|
||||
|
||||
if (strIndex == 0) { continue; }
|
||||
int s = snprintf(buffer, bufferLength, "\n%s:\n", djui_language_get("HOST_MOD_CATEGORIES", category.langKey));
|
||||
buffer += s;
|
||||
bufferLength -= s;
|
||||
|
||||
for (int j = 0; j < strIndex; j++) {
|
||||
int s = snprintf(buffer, bufferLength, "%s\\#dcdcdc\\\n", strings[j]);
|
||||
if (s < 0 || s >= bufferLength) {
|
||||
LOG_ERROR("CoopNet description too long, some mods were not listed");
|
||||
break;
|
||||
}
|
||||
buffer += s;
|
||||
bufferLength -= s;
|
||||
}
|
||||
}
|
||||
|
||||
// add mods that are not in a category
|
||||
char* strings[gActiveMods.entryCount];
|
||||
int strIndex = 0;
|
||||
for (int j = 0; j < gActiveMods.entryCount; j++) {
|
||||
struct Mod* mod = gActiveMods.entries[j];
|
||||
char* modCategory = mod->category != NULL ? mod->category : mod->incompatible;
|
||||
bool doContinue = false;
|
||||
if (modCategory) {
|
||||
for (size_t i = 0; i < sizeof(sCategories) / sizeof(sCategories[0]); i++) {
|
||||
if (strstr(modCategory, sCategories[i].category)) {
|
||||
doContinue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (doContinue) { continue; }
|
||||
strings[strIndex++] = mod->name;
|
||||
}
|
||||
|
||||
if (strIndex == 0) { return; }
|
||||
int s = snprintf(buffer, bufferLength, "\n%s:\n", djui_language_get("HOST_MOD_CATEGORIES", "MISC"));
|
||||
buffer += s;
|
||||
bufferLength -= s;
|
||||
|
||||
for (int j = 0; j < strIndex; j++) {
|
||||
int s = snprintf(buffer, bufferLength, "%s\\#dcdcdc\\\n", strings[j]);
|
||||
if (s < 0 || s >= bufferLength) {
|
||||
LOG_ERROR("CoopNet description too long, some mods were not listed");
|
||||
break;
|
||||
}
|
||||
buffer += s;
|
||||
bufferLength -= s;
|
||||
}
|
||||
}
|
||||
|
||||
void ns_coopnet_update(void) {
|
||||
|
|
@ -207,17 +283,17 @@ void ns_coopnet_update(void) {
|
|||
coopnet_update();
|
||||
if (gNetworkType != NT_NONE && sNetworkType != NT_NONE) {
|
||||
if (sNetworkType == NT_SERVER) {
|
||||
char mode[64] = "";
|
||||
mods_get_main_mod_name(mode, 64);
|
||||
char mode[MOD_NAME_SIZE] = "";
|
||||
mods_get_main_mod_name(mode, MOD_NAME_SIZE);
|
||||
if (sReconnecting) {
|
||||
LOG_INFO("Update lobby");
|
||||
coopnet_populate_description();
|
||||
coopnet_lobby_update(sLocalLobbyId, GAME_NAME, get_version(), configPlayerName, mode, sCoopNetDescription);
|
||||
coopnet_lobby_update(sLocalLobbyId, GAME_NAME, get_version(), configPlayerName, mode, sCoopNetDescription, gActiveMods.size);
|
||||
} else {
|
||||
LOG_INFO("Create lobby");
|
||||
snprintf(gCoopNetPassword, 64, "%s", configPassword);
|
||||
snprintf(gCoopNetPassword, COOPNET_MAX_PASSWORD_LEN, "%s", configPassword);
|
||||
coopnet_populate_description();
|
||||
coopnet_lobby_create(GAME_NAME, get_version(), configPlayerName, mode, (uint16_t)configAmountOfPlayers, gCoopNetPassword, sCoopNetDescription);
|
||||
coopnet_lobby_create(GAME_NAME, get_version(), configPlayerName, mode, (uint16_t)configAmountOfPlayers, gCoopNetPassword, sCoopNetDescription, gActiveMods.size);
|
||||
}
|
||||
} else if (sNetworkType == NT_CLIENT) {
|
||||
LOG_INFO("Join lobby");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#define COOPNET_H
|
||||
#ifdef COOPNET
|
||||
|
||||
typedef void (*QueryCallbackPtr)(uint64_t aLobbyId, uint64_t aOwnerId, uint16_t aConnections, uint16_t aMaxConnections, const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, const char* aDescription);
|
||||
typedef void (*QueryCallbackPtr)(uint64_t aLobbyId, uint64_t aOwnerId, uint16_t aConnections, uint16_t aMaxConnections, int64_t aTimestamp, const char* aGame, const char* aVersion, const char* aHostName, const char* aMode, const char* aDescription, size_t aModSize);
|
||||
typedef void (*QueryFinishCallbackPtr)(void);
|
||||
|
||||
extern struct NetworkSystem gNetworkSystemCoopNet;
|
||||
|
|
|
|||
|
|
@ -593,4 +593,4 @@ void str_seperator_concat(char *output_buffer, int buffer_size, char** strings,
|
|||
buffer_index += seperator_length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
textures/segment2/custom_selectionbox_down_icon.rgba16.png
Normal file
BIN
textures/segment2/custom_selectionbox_down_icon.rgba16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/segment2/custom_selectionbox_up_icon.rgba16.png
Normal file
BIN
textures/segment2/custom_selectionbox_up_icon.rgba16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Loading…
Add table
Reference in a new issue