diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua index f7b0aa63d..468a38f0a 100644 --- a/autogen/lua_definitions/functions.lua +++ b/autogen/lua_definitions/functions.lua @@ -7910,6 +7910,13 @@ function network_get_player_text_color_string(localIndex) -- ... end +--- @param localIndex integer +--- @return string +--- Gets the complete player name, including the player's starting hex code. +function network_get_complete_player_name(localIndex) + -- ... +end + --- @return boolean --- Checks if the game can currently be paused in singleplayer function network_check_singleplayer_pause() diff --git a/docs/lua/functions-5.md b/docs/lua/functions-5.md index 507dd7401..7dbb0c90f 100644 --- a/docs/lua/functions-5.md +++ b/docs/lua/functions-5.md @@ -2758,6 +2758,29 @@ Gets the DJUI hex color code string for the player corresponding to `localIndex`
+## [network_get_complete_player_name](#network_get_complete_player_name) + +### Description +Gets the complete player name, including the player's starting hex code. + +### Lua Example +`local stringValue = network_get_complete_player_name(localIndex)` + +### Parameters +| Field | Type | +| ----- | ---- | +| localIndex | `integer` | + +### Returns +- `string` + +### C Prototype +`const char* network_get_complete_player_name(u8 localIndex);` + +[:arrow_up_small:](#) + +
+ ## [network_check_singleplayer_pause](#network_check_singleplayer_pause) ### Description diff --git a/docs/lua/functions.md b/docs/lua/functions.md index e2f94b46b..da30dd6f9 100644 --- a/docs/lua/functions.md +++ b/docs/lua/functions.md @@ -1432,6 +1432,7 @@ - [network_is_server](functions-5.md#network_is_server) - [network_is_moderator](functions-5.md#network_is_moderator) - [network_get_player_text_color_string](functions-5.md#network_get_player_text_color_string) + - [network_get_complete_player_name](functions-5.md#network_get_complete_player_name) - [network_check_singleplayer_pause](functions-5.md#network_check_singleplayer_pause) - [network_discord_id_from_local_index](functions-5.md#network_discord_id_from_local_index) diff --git a/lang/English.ini b/lang/English.ini index 60553fc5d..e85f9890e 100644 --- a/lang/English.ini +++ b/lang/English.ini @@ -266,6 +266,7 @@ SAVE_SLOT = "Save Slot" SETTINGS = "Settings" MODS = "Mods" ROMHACKS = "Rom-Hacks" +MODERATION_LISTS = "Moderation Lists" APPLY = "Apply" HOST = "Host" @@ -339,8 +340,36 @@ CONSOLE = "CONSOLE" [MODLIST] MODS = "MODS" -[MODERATOR_MENU] +[MODERATION] MODERATOR_MENU_TITLE = "MODERATOR MENU" +MODERATION_LISTS_TITLE = "MODERATION LISTS" +MODERATION_LISTS = "Moderation Lists" +NO_PLAYERS_CONNECTED = "No players connected." +KICK = "Kick" +BAN = "Ban" +UNBAN = "Unban" +MOD = "Mod" +UNMOD = "Unmod" +KICK_PLAYER_TITLE = "KICK PLAYER" +BAN_PLAYER_TITLE = "BAN PLAYER" +UNBAN_PLAYER_TITLE = "UNBAN PLAYER" +MOD_PLAYER_TITLE = "MOD PLAYER" +UNMOD_PLAYER_TITLE = "UNMOD PLAYER" +KICK_CONFIRM = "Are you sure you want to kick @\\#dcdcdc\\?" +BAN_CONFIRM = "Are you sure you want to ban @\\#dcdcdc\\?" +UNBAN_CONFIRM = "Are you sure you want to unban @\\#dcdcdc\\?" +MOD_CONFIRM = "Are you sure you want to mod @\\#dcdcdc\\?" +UNMOD_CONFIRM = "Are you sure you want to unmod @\\#dcdcdc\\?" +BAN_LIST = "Ban List" +MODERATOR_LIST = "Moderator List" +LIST = "List" +NO_PLAYERS_IN_LIST = "No players in list." +INSPECT = "Inspect" +INSPECTOR_TITLE = "INSPECTOR" +DATE = "Added: @" +DISCORD_ID = "Discord ID: @" +REASON = "Reason: @" +PERMANENT = "Permanent" [OPTIONS] OPTIONS = "OPTIONS" diff --git a/src/pc/djui/djui_panel.c b/src/pc/djui/djui_panel.c index b25a251d2..1619ecb0a 100644 --- a/src/pc/djui/djui_panel.c +++ b/src/pc/djui/djui_panel.c @@ -11,6 +11,7 @@ static struct DjuiPanel* sPanelList = NULL; static struct DjuiPanel* sPanelRemoving = NULL; +static int sPanelBackQueue = 0; static f32 sMoveAmount = 0; bool gDjuiPanelDisableBack = false; @@ -136,6 +137,19 @@ void djui_panel_back(void) { gDjuiPanelJoinMessageVisible = false; } +void djui_panel_back_by(int amount) { + if (amount <= 0) { return; } + if (sPanelList == NULL) { return; } + if (gDjuiPanelDisableBack) { return; } + + sPanelBackQueue = amount - 1; + + if (sPanelRemoving == NULL) { + sPanelRemoving--; + djui_panel_back(); + } +} + void djui_panel_update(void) { if (sPanelList == NULL) { return; } if (sPanelList->base == NULL) { return; } @@ -175,6 +189,10 @@ void djui_panel_update(void) { djui_base_destroy(removingBase); free(panel); removingBase = NULL; + if (sPanelBackQueue > 0) { + sPanelBackQueue--; + djui_panel_back(); + } return; } } @@ -220,6 +238,7 @@ void djui_panel_shutdown(void) { sPanelList = NULL; sPanelRemoving = NULL; sMoveAmount = 0; + sPanelBackQueue = 0; gInteractableOverridePad = false; gDjuiPanelJoinMessageVisible = false; gDjuiPanelMainCreated = false; diff --git a/src/pc/djui/djui_panel.h b/src/pc/djui/djui_panel.h index dd752c953..71e61b879 100644 --- a/src/pc/djui/djui_panel.h +++ b/src/pc/djui/djui_panel.h @@ -19,5 +19,6 @@ extern bool gDjuiPanelDisableBack; bool djui_panel_is_active(void); struct DjuiPanel* djui_panel_add(struct DjuiBase* caller, struct DjuiThreePanel* threePanel, struct DjuiBase* defaultElementBase); void djui_panel_back(void); +void djui_panel_back_by(int amount); void djui_panel_update(void); void djui_panel_shutdown(void); diff --git a/src/pc/djui/djui_panel_host.c b/src/pc/djui/djui_panel_host.c index e9e3f682b..28a3fb663 100644 --- a/src/pc/djui/djui_panel_host.c +++ b/src/pc/djui/djui_panel_host.c @@ -182,7 +182,9 @@ void djui_panel_host_create(struct DjuiBase* caller) { djui_button_create(body, DLANG(HOST, SETTINGS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_settings_create); djui_button_create(body, DLANG(HOST, MODS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_mods_create); - djui_button_create(body, "Moderation Lists", DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderation_list_create); + if (gDjuiInMainMenu) { + djui_button_create(body, DLANG(HOST, MODERATION_LISTS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderation_list_create); + } struct DjuiRect* rect3 = djui_rect_container_create(body, 64); { diff --git a/src/pc/djui/djui_panel_moderation_confirm_action.c b/src/pc/djui/djui_panel_moderation_confirm_action.c new file mode 100644 index 000000000..a61dab4d3 --- /dev/null +++ b/src/pc/djui/djui_panel_moderation_confirm_action.c @@ -0,0 +1,146 @@ +#include +#include "djui.h" +#include "djui_panel.h" +#include "djui_panel_menu.h" +#include "djui_panel_confirm.h" +#include "djui_panel_moderation_list.h" +#include "pc/network/network.h" +#include "pc/network/moderation.h" + +static char* sReason = NULL; +void (*sOnYesClick)(struct DjuiBase*) = NULL; + +static void djui_panel_moderation_call_action(struct DjuiBase* caller) { + u8 player = caller->tag; + u8 action = caller->uTag; + char* address = caller->cTag; + + switch(action) { + case MODERATION_ACTION_KICK: + network_kick_player(player, sReason); + break; + case MODERATION_ACTION_BAN: + network_ban_player(player, sReason, false); + break; + case MODERATION_ACTION_UNBAN: + network_unban_player(address); + break; + case MODERATION_ACTION_MOD: + network_mod_player(player, sReason, true); + break; + case MODERATION_ACTION_UNMOD: + network_unmod_player(address); + break; + default: + break; + } + + free(sReason); + sReason = NULL; + djui_panel_menu_back(caller); + + if (sOnYesClick) sOnYesClick(caller); +} + +static void djui_panel_moderation_confirm_reason_text_change(struct DjuiBase* caller) { + struct DjuiInputbox* inputbox = (struct DjuiInputbox*)caller; + if (inputbox) { + sReason = strdup(inputbox->buffer); + } +} + +static void djui_panel_moderation_confirm_set_title_and_message(u8 action, char** title, char* message, char* playerName) { + switch (action) { + case MODERATION_ACTION_KICK: + *title = djui_language_get("MODERATION", "KICK_PLAYER_TITLE"); + djui_language_replace(DLANG(MODERATION, KICK_CONFIRM), message, 256, '@', playerName); + break; + case MODERATION_ACTION_BAN: + *title = djui_language_get("MODERATION", "BAN_PLAYER_TITLE"); + djui_language_replace(DLANG(MODERATION, BAN_CONFIRM), message, 256, '@', playerName); + break; + case MODERATION_ACTION_UNBAN: + *title = djui_language_get("MODERATION", "UNBAN_PLAYER_TITLE"); + djui_language_replace(DLANG(MODERATION, UNBAN_CONFIRM), message, 256, '@', playerName); + break; + case MODERATION_ACTION_MOD: + *title = djui_language_get("MODERATION", "MOD_PLAYER_TITLE"); + djui_language_replace(DLANG(MODERATION, MOD_CONFIRM), message, 256, '@', playerName); + break; + case MODERATION_ACTION_UNMOD: + *title = djui_language_get("MODERATION", "UNMOD_PLAYER_TITLE"); + djui_language_replace(DLANG(MODERATION, UNMOD_CONFIRM), message, 256, '@', playerName); + break; + default: + return; + } +} + +void djui_panel_moderation_confirm_create_body(struct DjuiBase* caller, char* title, char* message, u8 localIndex, u8 action, bool permanent, char* address, void (*on_yes_click)(struct DjuiBase*)) { + struct DjuiThreePanel* panel = djui_panel_menu_create(title, false); + struct DjuiBase* body = djui_three_panel_get_body(panel); + { + struct DjuiText* text = djui_text_create(body, message); + djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + + if (action == MODERATION_ACTION_BAN || action == MODERATION_ACTION_MOD) { + struct DjuiRect* rect1 = djui_rect_container_create(body, 32); + { + struct DjuiText* text1 = djui_text_create(&rect1->base, "Reason:"); + djui_base_set_size_type(&text1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_color(&text1->base, 220, 220, 220, 255); + djui_base_set_size(&text1->base, 0.585f, 64); + djui_base_set_alignment(&text1->base, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); + djui_text_set_drop_shadow(text1, 64, 64, 64, 100); + + struct DjuiInputbox* inputbox1 = djui_inputbox_create(&rect1->base, 256); + djui_base_set_size_type(&inputbox1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&inputbox1->base, 0.45f, 32); + djui_base_set_alignment(&inputbox1->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_TOP); + djui_interactable_hook_value_change(&inputbox1->base, djui_panel_moderation_confirm_reason_text_change); + } + } + + djui_base_set_size(&text->base, 1.0f, 64); + djui_base_compute_tree(&text->base); + u16 lines = djui_text_count_lines(text, 12); + f32 textHeight = 32 * 0.8125f * lines + 8; + djui_base_set_size(&text->base, 1.0f, textHeight); + + djui_base_set_color(&text->base, 220, 220, 220, 255); + djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_TOP); + + struct DjuiRect* rect2 = djui_rect_container_create(body, 64); + { + djui_button_left_create(&rect2->base, DLANG(MENU, NO), DJUI_BUTTON_STYLE_NORMAL, djui_panel_menu_back); + struct DjuiButton* yesButton = djui_button_right_create(&rect2->base, DLANG(MENU, YES), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderation_call_action); + yesButton->base.tag = localIndex; + yesButton->base.uTag = action; + yesButton->base.bTag = permanent; + yesButton->base.cTag = strdup(address); + sOnYesClick = on_yes_click; + } + } + + djui_panel_add(caller, panel, NULL); +} + +void djui_panel_moderation_confirm_create(struct DjuiBase* caller, u8 action, u8 localIndex, bool permanent, void (*on_yes_click)(struct DjuiBase*)) { + if (localIndex >= MAX_PLAYERS) return; + char* title = NULL; + char message[256] = { 0 }; + djui_panel_moderation_confirm_set_title_and_message(action, &title, message, (char*)network_get_complete_player_name(localIndex)); + djui_panel_moderation_confirm_create_body(caller, title, message, localIndex, action, permanent, gNetworkSystem->get_id_str(localIndex), on_yes_click); +} + +void djui_panel_moderation_confirm_create_using_list(struct DjuiBase* caller, u8 action, u8 listType, u16 listIndex, void (*on_yes_click)(struct DjuiBase*)) { + struct ModerationEntry* entry = moderation_list_get_list_by_type(listType)->list[listIndex]; + char* title = NULL; + char message[256] = { 0 }; + char colorString[10] = { 0 }; + snprintf(colorString, 10, "\\#%02x%02x%02x\\", entry->playerColor[0], entry->playerColor[1], entry->playerColor[2]); + char playerName[256] = { 0 }; + snprintf(playerName, 256, "%s%s", colorString, entry->playerName); + djui_panel_moderation_confirm_set_title_and_message(action, &title, message, playerName); + djui_panel_moderation_confirm_create_body(caller, title, message, 0, action, false, entry->address, on_yes_click); +} \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderation_confirm_action.h b/src/pc/djui/djui_panel_moderation_confirm_action.h new file mode 100644 index 000000000..e8fa14008 --- /dev/null +++ b/src/pc/djui/djui_panel_moderation_confirm_action.h @@ -0,0 +1,5 @@ +#pragma once +#include "djui.h" + +void djui_panel_moderation_confirm_create(struct DjuiBase* caller, u8 action, u8 localIndex, bool permanent, void (*on_yes_click)(struct DjuiBase*)); +void djui_panel_moderation_confirm_create_using_list(struct DjuiBase* caller, u8 action, u8 listType, u16 listIndex, void (*on_yes_click)(struct DjuiBase*)); \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderation_list.c b/src/pc/djui/djui_panel_moderation_list.c new file mode 100644 index 000000000..c4bd0f18c --- /dev/null +++ b/src/pc/djui/djui_panel_moderation_list.c @@ -0,0 +1,97 @@ + +#include +#include "djui.h" +#include "djui_panel.h" +#include "djui_panel_menu.h" +#include "djui_panel_moderator_menu.h" +#include "djui_panel_moderation_list.h" +#include "djui_panel_moderation_list_inspector.h" +#include "pc/network/network.h" +#include "pc/network/moderation.h" + +static struct DjuiFlowLayout* sLayout = NULL; +static struct DjuiPaginated* sPaginated = NULL; +static unsigned int sSelectedList = MODERATION_LIST_TYPE_BAN; + +static void djui_panel_moderation_list_inspect_player(struct DjuiBase* caller) { + djui_panel_moderation_list_inspect_create(caller); +} + +static void djui_panel_moderation_list_populate_list(struct DjuiBase* layoutBase) { + struct ModerationList* list = moderation_list_get_list_by_type(sSelectedList); + if (list->count == 0) { + struct DjuiText* text = djui_text_create(layoutBase, DLANG(MODERATION, NO_PLAYERS_IN_LIST)); + djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_RELATIVE); + djui_base_set_size(&text->base, 1, 1); + djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER); + djui_text_set_drop_shadow(text, 64, 64, 64, 100); + return; + } + for (int i = 0; i < list->count; i++) { + struct ModerationEntry* entry = list->list[i]; + struct DjuiRect* rectContainer = djui_rect_container_create(layoutBase, 32); + { + struct DjuiText* text = djui_text_create(&rectContainer->base, entry->playerName); + djui_text_set_alignment(text, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); + djui_text_set_drop_shadow(text, 64, 64, 64, 100); + djui_base_set_color(&text->base, entry->playerColor[0], entry->playerColor[1], entry->playerColor[2], 255); + djui_base_set_alignment(&text->base, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); + djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&text->base, 0.5, 32); + + struct DjuiButton* button = djui_button_create(&rectContainer->base, DLANG(MODERATION, INSPECT), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderation_list_inspect_player); + djui_base_set_alignment(&button->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_TOP); + djui_base_set_size_type(&button->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&button->base, 0.45, 32); + button->base.tag = sSelectedList; + button->base.uTag = i; + } + } +} + +void djui_panel_moderation_list_reload() { + if (!sLayout || !sPaginated) return; + djui_base_destroy_children(&sLayout->base); + djui_panel_moderation_list_populate_list(&sLayout->base); + djui_paginated_calculate_height(sPaginated); +} + +static void djui_panel_moderation_list_destroy(struct DjuiBase* base) { + struct DjuiThreePanel* threePanel = (struct DjuiThreePanel*)base; + free(threePanel); + sLayout = NULL; + sPaginated = NULL; +} + +static bool djui_panel_moderation_list_on_back(UNUSED struct DjuiBase* base) { + if (!gDjuiInMainMenu) { + djui_panel_moderator_menu_reload(); + } + return false; +} + +void djui_panel_moderation_list_create(struct DjuiBase* caller) { + struct DjuiThreePanel* panel = djui_panel_menu_create(DLANG(MODERATION, MODERATION_LISTS_TITLE), true); + + struct DjuiBase* body = djui_three_panel_get_body(panel); + { + char* choices[MODERATION_LIST_TYPE_COUNT] = { + DLANG(MODERATION, BAN_LIST), + DLANG(MODERATION, MODERATOR_LIST), + }; + djui_selectionbox_create(body, DLANG(MODERATION, LIST), choices, MODERATION_LIST_TYPE_COUNT, &sSelectedList, djui_panel_moderation_list_reload); + + struct DjuiPaginated* paginated = djui_paginated_create(body, 8); + paginated->showMaxCount = true; + sLayout = paginated->layout; + djui_panel_moderation_list_populate_list(&paginated->layout->base); + djui_paginated_calculate_height(paginated); + sPaginated = paginated; + + djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back); + } + + panel->on_back = djui_panel_moderation_list_on_back; + panel->base.destroy = djui_panel_moderation_list_destroy; + djui_panel_add(caller, panel, NULL); +} \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderation_list.h b/src/pc/djui/djui_panel_moderation_list.h new file mode 100644 index 000000000..4903b01cf --- /dev/null +++ b/src/pc/djui/djui_panel_moderation_list.h @@ -0,0 +1,5 @@ +#pragma once +#include "djui.h" + +void djui_panel_moderation_list_reload(); +void djui_panel_moderation_list_create(struct DjuiBase* caller); \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderation_list_inspector.c b/src/pc/djui/djui_panel_moderation_list_inspector.c new file mode 100644 index 000000000..277d077d1 --- /dev/null +++ b/src/pc/djui/djui_panel_moderation_list_inspector.c @@ -0,0 +1,115 @@ +#include +#include "djui.h" +#include "djui_panel.h" +#include "djui_panel_menu.h" +#include "djui_panel_moderator_menu.h" +#include "djui_panel_moderation_list.h" +#include "djui_panel_moderation_confirm_action.h" +#include "djui_panel_confirm.h" +#include "pc/network/network.h" +#include "pc/network/moderation.h" +#include "pc/debuglog.h" + +static u16 sListType = 0; +static u16 sListIndex = 0; + +static void djui_panel_moderation_list_inspect_action_exit(UNUSED struct DjuiBase* caller) { + djui_panel_back_by(2); + djui_panel_moderation_list_reload(); +} + +static void djui_panel_moderation_list_action_button_click(struct DjuiBase* caller) { + djui_panel_moderation_confirm_create_using_list(caller, caller->tag, sListType, sListIndex, djui_panel_moderation_list_inspect_action_exit); +} + +static void djui_panel_moderation_list_inspect_destroy(struct DjuiBase* base) { + struct DjuiThreePanel* threePanel = (struct DjuiThreePanel*)base; + free(threePanel); +} + +void djui_panel_moderation_list_inspect_create(struct DjuiBase* caller) { + struct ModerationList* list = moderation_list_get_list_by_type(caller->tag); + if (!list) return; + struct ModerationEntry* entry = list->list[caller->uTag]; + if (!entry) return; + struct tm* localTime = localtime(&entry->time); + + sListType = caller->tag; + sListIndex = caller->uTag; + + struct DjuiThreePanel* panel = djui_panel_menu_create(DLANG(MODERATION, INSPECTOR), true); + struct DjuiBase* body = djui_three_panel_get_body(panel); + { + char playerName[MAX_CONFIG_STRING + 128]; + snprintf(playerName, MAX_CONFIG_STRING + 128, "%s \\#fff982\\- \\#82f9ff\\%s", entry->playerName, entry->address); + struct DjuiText* playerText = djui_text_create(body, playerName); + djui_base_set_color(&playerText->base, entry->playerColor[0], entry->playerColor[1], entry->playerColor[2], 255); + djui_base_set_size_type(&playerText->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&playerText->base, 1, 32); + djui_text_set_alignment(playerText, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); + djui_text_set_drop_shadow(playerText, 64, 64, 64, 100); + + char date[128]; + strftime(date, sizeof(date), "%m/%d/%Y %I:%M:%S %p", localTime); + char dateStr[256]; + djui_language_replace(DLANG(MODERATION, DATE), dateStr, 256, '@', date); + struct DjuiText* dateText = djui_text_create(body, dateStr); + djui_base_set_color(&dateText->base, 220, 220, 220, 255); + djui_base_set_size_type(&dateText->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&dateText->base, 1, 32); + djui_text_set_alignment(dateText, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); + djui_text_set_drop_shadow(dateText, 64, 64, 64, 100); + + if (entry->discordId && strcmp(entry->discordId, "") != 0 && strcmp(entry->discordId, "0") != 0) { + char discordIdStr[128]; + djui_language_replace(DLANG(MODERATION, DISCORD_ID), discordIdStr, 128, '@', entry->discordId); + struct DjuiText* discordIdText = djui_text_create(body, discordIdStr); + djui_base_set_color(&discordIdText->base, 220, 220, 220, 255); + djui_base_set_size_type(&discordIdText->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&discordIdText->base, 1, 32); + djui_text_set_alignment(discordIdText, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); + djui_text_set_drop_shadow(discordIdText, 64, 64, 64, 100); + } + + if (entry->reason && strcmp(entry->reason, "") != 0) { + char reasonStr[512]; + djui_language_replace(DLANG(MODERATION, REASON), reasonStr, 512, '@', entry->reason); + struct DjuiText* reasonText = djui_text_create(body, reasonStr); + djui_base_set_color(&reasonText->base, 220, 220, 220, 255); + djui_base_set_size_type(&reasonText->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&reasonText->base, 1, 32); + djui_base_compute_tree(&reasonText->base); + u16 reasonLines = djui_text_count_lines(reasonText, 12); + f32 reasonHeight = 32 * reasonLines; + printf("Reason lines is %u, with reason height being %f\n", reasonLines, reasonHeight); + djui_base_set_size(&reasonText->base, 1, reasonHeight); + djui_text_set_alignment(reasonText, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); + djui_text_set_drop_shadow(reasonText, 64, 64, 64, 100); + } + + djui_checkbox_create(body, DLANG(MODERATION, PERMANENT), &entry->permanent, moderation_list_save); + + for (u8 i = 0; i < MODERATION_ACTION_COUNT; i++) { + if (!list->actions[i]) continue; + switch (i) { + case MODERATION_ACTION_UNBAN: { + struct DjuiButton* button = djui_button_create(body, DLANG(MODERATION, UNBAN), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderation_list_action_button_click); + button->base.tag = MODERATION_ACTION_UNBAN; + break; + } + case MODERATION_ACTION_UNMOD: { + struct DjuiButton* button = djui_button_create(body, DLANG(MODERATION, UNMOD), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderation_list_action_button_click); + button->base.tag = MODERATION_ACTION_UNMOD; + break; + } + default: + break; + } + } + } + + djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back); + + panel->base.destroy = djui_panel_moderation_list_inspect_destroy; + djui_panel_add(caller, panel, NULL); +} \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderation_list_inspector.h b/src/pc/djui/djui_panel_moderation_list_inspector.h new file mode 100644 index 000000000..248b36f18 --- /dev/null +++ b/src/pc/djui/djui_panel_moderation_list_inspector.h @@ -0,0 +1,4 @@ +#pragma once +#include "djui.h" + +void djui_panel_moderation_list_inspect_create(struct DjuiBase* caller); \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderator_menu.c b/src/pc/djui/djui_panel_moderator_menu.c new file mode 100644 index 000000000..98a552c79 --- /dev/null +++ b/src/pc/djui/djui_panel_moderator_menu.c @@ -0,0 +1,104 @@ + +#include +#include "djui.h" +#include "djui_panel.h" +#include "djui_panel_menu.h" +#include "djui_panel_moderator_menu.h" +#include "djui_panel_moderation_list.h" +#include "djui_panel_moderation_confirm_action.h" +#include "pc/network/network.h" +#include "pc/network/moderation.h" + +static struct DjuiFlowLayout* sLayout = NULL; +static struct DjuiPaginated* sPaginated = NULL; + +static void djui_panel_moderator_menu_action_button_click(struct DjuiBase* caller) { + djui_panel_moderation_confirm_create(caller, caller->uTag, caller->tag, false, djui_panel_moderator_menu_reload); +} + +static void djui_panel_moderator_add_players(struct DjuiBase* layoutBase) { + bool isPlayerConnected = false; + for (int i = 1; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected) continue; + if (gNetworkPlayerLocal->moderator && np->moderator) continue; + if (gNetworkPlayerLocal->moderator && np->globalIndex == 0) continue; + + isPlayerConnected = true; + + struct DjuiFlowLayout* flowLayout = djui_flow_layout_create(layoutBase); + djui_flow_layout_set_flow_direction(flowLayout, DJUI_FLOW_DIR_RIGHT); + 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, 32); + { + struct DjuiButton* playerButton = djui_button_create(&flowLayout->base, np->name, DJUI_BUTTON_STYLE_NORMAL, NULL); + u8 playerColor[3]; + memcpy(playerColor, network_get_player_text_color(i), 3); + djui_base_set_color(&playerButton->text->base, playerColor[0], playerColor[1], playerColor[2], 255); + djui_base_set_size_type(&playerButton->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&playerButton->base, 0.4, 32); + + struct DjuiButton* kickButton = djui_button_create(&flowLayout->base, DLANG(MODERATION, KICK), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderator_menu_action_button_click); + djui_base_set_size_type(&kickButton->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&kickButton->base, 0.333, 32); + kickButton->base.uTag = MODERATION_ACTION_KICK; + kickButton->base.tag = i; + + struct DjuiButton* banButton = djui_button_create(&flowLayout->base, DLANG(MODERATION, BAN), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderator_menu_action_button_click); + djui_base_set_size_type(&banButton->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&banButton->base, 0.5, 32); + banButton->base.uTag = MODERATION_ACTION_BAN; + banButton->base.tag = i; + + struct DjuiButton* modButton = djui_button_create(&flowLayout->base, np->moderator ? DLANG(MODERATION, UNMOD) : DLANG(MODERATION, MOD), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderator_menu_action_button_click); + djui_base_set_size_type(&modButton->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&modButton->base, 1.0, 32); + djui_base_set_enabled(&modButton->base, gNetworkType == NT_SERVER); + modButton->base.uTag = np->moderator ? MODERATION_ACTION_UNMOD : MODERATION_ACTION_MOD; + modButton->base.tag = i; + } + } + + if (!isPlayerConnected) { + struct DjuiText* text = djui_text_create(layoutBase, DLANG(MODERATION, NO_PLAYERS_CONNECTED)); + djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_RELATIVE); + djui_base_set_size(&text->base, 1, 1); + djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER); + djui_text_set_drop_shadow(text, 64, 64, 64, 100); + } +} + +static void djui_panel_moderator_menu_destroy(struct DjuiBase* base) { + struct DjuiThreePanel* threePanel = (struct DjuiThreePanel*)base; + free(threePanel); + sLayout = NULL; + sPaginated = NULL; +} + +void djui_panel_moderator_menu_reload() { + if (!sLayout || !sPaginated) return; + djui_base_destroy_children(&sLayout->base); + djui_panel_moderator_add_players(&sLayout->base); + djui_paginated_calculate_height(sPaginated); +} + +void djui_panel_moderator_menu_create(struct DjuiBase* caller) { + struct DjuiThreePanel* panel = djui_panel_menu_create(DLANG(MODERATION, MODERATOR_MENU_TITLE), true); + + struct DjuiBase* body = djui_three_panel_get_body(panel); + { + struct DjuiPaginated* paginated = djui_paginated_create(body, 8); + paginated->showMaxCount = true; + sLayout = paginated->layout; + djui_panel_moderator_add_players(&paginated->layout->base); + djui_paginated_calculate_height(paginated); + sPaginated = paginated; + + if (gNetworkType == NT_SERVER) djui_button_create(body, DLANG(MODERATION, MODERATION_LISTS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderation_list_create); + djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back); + } + + panel->base.destroy = djui_panel_moderator_menu_destroy; + djui_panel_add(caller, panel, NULL); +} \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderator_menu.h b/src/pc/djui/djui_panel_moderator_menu.h new file mode 100644 index 000000000..648441d15 --- /dev/null +++ b/src/pc/djui/djui_panel_moderator_menu.h @@ -0,0 +1,5 @@ +#pragma once +#include "djui.h" + +void djui_panel_moderator_menu_reload(); +void djui_panel_moderator_menu_create(struct DjuiBase* caller); \ No newline at end of file diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c index c4b6b4f0e..b0424d43f 100644 --- a/src/pc/lua/smlua_functions_autogen.c +++ b/src/pc/lua/smlua_functions_autogen.c @@ -23452,6 +23452,23 @@ int smlua_func_network_get_player_text_color_string(lua_State* L) { return 1; } +int smlua_func_network_get_complete_player_name(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 1) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "network_get_complete_player_name", 1, top); + return 0; + } + + u8 localIndex = smlua_to_integer(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "network_get_complete_player_name"); return 0; } + + lua_pushstring(L, network_get_complete_player_name(localIndex)); + + return 1; +} + int smlua_func_network_check_singleplayer_pause(UNUSED lua_State* L) { if (L == NULL) { return 0; } @@ -37997,6 +38014,7 @@ void smlua_bind_functions_autogen(void) { smlua_bind_function(L, "network_is_server", smlua_func_network_is_server); smlua_bind_function(L, "network_is_moderator", smlua_func_network_is_moderator); smlua_bind_function(L, "network_get_player_text_color_string", smlua_func_network_get_player_text_color_string); + smlua_bind_function(L, "network_get_complete_player_name", smlua_func_network_get_complete_player_name); smlua_bind_function(L, "network_check_singleplayer_pause", smlua_func_network_check_singleplayer_pause); smlua_bind_function(L, "network_discord_id_from_local_index", smlua_func_network_discord_id_from_local_index); diff --git a/src/pc/network/moderation.c b/src/pc/network/moderation.c new file mode 100644 index 000000000..6792ddc07 --- /dev/null +++ b/src/pc/network/moderation.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include "moderation.h" +#include "pc/debuglog.h" +#include "pc/ini.h" + +void network_kick_player(u8 localIndex, char* reason) { + if (gNetworkPlayerLocal->moderator) { + network_send_moderation_action(MODERATION_ACTION_KICK, localIndex, reason, false); + return; + } + if (gNetworkType != NT_SERVER) { + LOG_ERROR("Tried to kick player as non-server!"); + return; + } + struct NetworkPlayer* np = &gNetworkPlayers[localIndex]; + if (!np->connected) { + LOG_ERROR("Tried to perform moderation on disconnected player!"); + return; + } + network_send_kick(np->localIndex, EKT_KICKED); + network_player_disconnected(np->globalIndex); +} + +void network_ban_player(u8 localIndex, char* reason, bool permanent) { + if (gNetworkPlayerLocal->moderator) { + network_send_moderation_action(MODERATION_ACTION_BAN, localIndex, reason, permanent); + return; + } + if (gNetworkType != NT_SERVER) { + LOG_ERROR("Tried to ban player as non-server!"); + return; + } + struct NetworkPlayer* np = &gNetworkPlayers[localIndex]; + if (!np->connected) { + LOG_ERROR("Tried to perform moderation on disconnected player!"); + return; + } + moderation_list_add(MODERATION_LIST_TYPE_BAN, localIndex, reason, permanent); + network_send_kick(np->localIndex, EKT_BANNED); + network_player_disconnected(np->globalIndex); +} + +void network_unban_player(char* address) { + if (gNetworkType != NT_SERVER) { + LOG_ERROR("Tried to unban player as non-server!"); + return; + } + moderation_list_remove(MODERATION_LIST_TYPE_BAN, address); +} + +void network_mod_player(u8 localIndex, char* reason, bool permanent) { + if (gNetworkType != NT_SERVER) { + LOG_ERROR("Tried to mod player as non-server!"); + return; + } + struct NetworkPlayer* np = &gNetworkPlayers[localIndex]; + if (!np->connected) { + LOG_ERROR("Tried to perform moderation on disconnected player!"); + return; + } + np->moderator = true; + network_send_moderator(np->localIndex); + moderation_list_add(MODERATION_LIST_TYPE_MODERATOR, localIndex, reason, permanent); +} + +void network_unmod_player(char* address) { + if (gNetworkType != NT_SERVER) { + LOG_ERROR("Tried to unmod player as non-server!"); + return; + } + if (gNetworkSystem != NT_NONE) { + // loop thru to see if moderator is in the lobby + for (u8 i = 0; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np = &gNetworkPlayers[i]; + if (!np->connected || !np->moderator || !address || strcmp(gNetworkSystem->get_id_str(np->localIndex), address) != 0) continue; + np->moderator = false; + network_send_moderator(np->localIndex); + } + } + moderation_list_remove(MODERATION_LIST_TYPE_MODERATOR, address); +} \ No newline at end of file diff --git a/src/pc/network/moderation.h b/src/pc/network/moderation.h new file mode 100644 index 000000000..c3216b61f --- /dev/null +++ b/src/pc/network/moderation.h @@ -0,0 +1,19 @@ +#pragma once +#include + +enum ModerationActions { + MODERATION_ACTION_KICK, + MODERATION_ACTION_BAN, + MODERATION_ACTION_UNBAN, + MODERATION_ACTION_MOD, + MODERATION_ACTION_UNMOD, + MODERATION_ACTION_COUNT, +}; + +void network_kick_player(u8 localIndex, char* reason); +void network_ban_player(u8 localIndex, char* reason, bool permanent); +void network_unban_player(char* address); +void network_mod_player(u8 localIndex, char* reason, bool permanent); +void network_unmod_player(char* address); + +#include "moderation_list.h" \ No newline at end of file diff --git a/src/pc/network/moderation_list.c b/src/pc/network/moderation_list.c new file mode 100644 index 000000000..fa5d36e69 --- /dev/null +++ b/src/pc/network/moderation_list.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include "moderation.h" +#include "pc/debuglog.h" +#include "pc/ini.h" + +struct ModerationLists gModerationLists = { + .banList = { + .actions = { + [MODERATION_ACTION_UNBAN] = true + } + }, + .moderatorList = { + .actions = { + [MODERATION_ACTION_UNMOD] = true + } + } +}; + +static const char* safe_ini_get(ini_t* ini, const char* section, const char* key) { + const char* str = ini_get(ini, section, key); + return str ? str : ""; +} + +struct ModerationList* moderation_list_get_list_by_type(enum ModerationListType type) { + if (type == MODERATION_LIST_TYPE_BAN) return &gModerationLists.banList; + if (type == MODERATION_LIST_TYPE_MODERATOR) return &gModerationLists.moderatorList; + LOG_ERROR("Type %u is not a valid type", type); + return NULL; +} + +void moderation_list_save() { + FILE* file = fopen(fs_get_write_path(MODERATION_LIST_FILEPATH), "w"); + if (!file) return; + + for (u8 type = 0; type < MODERATION_LIST_TYPE_COUNT; type++) { + struct ModerationList* list = moderation_list_get_list_by_type(type); + if (!list) continue; + + fprintf(file, "[Type %u]\n", type); + fprintf(file, "count = %u\n\n", list->count); + + for (u16 i = 0; i < list->count; i++) { + struct ModerationEntry* entry = list->list[i]; + if (!entry) continue; + fprintf(file, "[Entry %u for %u]\n", i, type); + fprintf(file, "time = %ld\n", entry->time); + fprintf(file, "playerName = %s\n", entry->playerName); + fprintf(file, "playerColorR = %d\n", entry->playerColor[0]); + fprintf(file, "playerColorG = %d\n", entry->playerColor[1]); + fprintf(file, "playerColorB = %d\n", entry->playerColor[2]); + fprintf(file, "address = %s\n", entry->address); + fprintf(file, "discordId = %s\n", entry->discordId); + fprintf(file, "reason = %s\n", entry->reason); + fprintf(file, "permanent = %d\n\n", entry->permanent ? 1 : 0); + } + } + fclose(file); +} + +void moderation_list_load() { + ini_t* iniFile = ini_load(fs_get_write_path(MODERATION_LIST_FILEPATH)); + if (!iniFile) return; + + for (u8 type = 0; type < MODERATION_LIST_TYPE_COUNT; type++) { + struct ModerationList* list = moderation_list_get_list_by_type(type); + if (!list) continue; + + char typeSection[16]; + snprintf(typeSection, 16, "Type %u", type); + u16 totalInIni = strtol(safe_ini_get(iniFile, typeSection, "count"), NULL, 0); + + for (u16 i = 0; i < totalInIni && list->count < MAX_MODERATION_LIST_ENTRIES; i++) { + char entrySection[32]; + snprintf(entrySection, 32, "Entry %u for %u", i, type); + + struct ModerationEntry* entry = malloc(sizeof(struct ModerationEntry)); + if (!entry) continue; + + entry->permanent = (strtol(safe_ini_get(iniFile, entrySection, "permanent"), NULL, 0) != 0); + + if (!entry->permanent) { + free(entry); + continue; + } + + entry->time = strtol(safe_ini_get(iniFile, entrySection, "time"), NULL, 0); + entry->playerName = strdup(safe_ini_get(iniFile, entrySection, "playerName")); + entry->playerColor[0] = strtol(safe_ini_get(iniFile, entrySection, "playerColorR"), NULL, 0); + entry->playerColor[1] = strtol(safe_ini_get(iniFile, entrySection, "playerColorG"), NULL, 0); + entry->playerColor[2] = strtol(safe_ini_get(iniFile, entrySection, "playerColorB"), NULL, 0); + entry->address = strdup(safe_ini_get(iniFile, entrySection, "address")); + entry->discordId = strdup(safe_ini_get(iniFile, entrySection, "discordId")); + entry->reason = strdup(safe_ini_get(iniFile, entrySection, "reason")); + + list->list[list->count++] = entry; + } + } + ini_free(iniFile); + // wipe non-permanent players from list + moderation_list_save(); +} + +void moderation_list_add(enum ModerationListType type, u8 localIndex, char* reason, bool permanent) { + struct ModerationList* list = moderation_list_get_list_by_type(type); + if (!list || list->count >= MAX_MODERATION_LIST_ENTRIES) return; + + struct NetworkPlayer* np = &gNetworkPlayers[localIndex]; + struct ModerationEntry* entry = malloc(sizeof(struct ModerationEntry)); + if (!entry) return; + + entry->playerName = strdup(np->name); + memcpy(entry->playerColor, network_get_player_text_color(np->localIndex), 3); + entry->address = strdup(gNetworkSystem->get_id_str(localIndex)); + entry->discordId = strdup(network_discord_id_from_local_index(localIndex)); + entry->reason = strdup(reason ? reason : ""); + entry->permanent = permanent; + time(&entry->time); + + list->list[list->count++] = entry; + moderation_list_save(); +} + +void moderation_list_remove(enum ModerationListType type, char* address) { + struct ModerationList* list = moderation_list_get_list_by_type(type); + if (!list || !address) return; + + for (u16 i = 0; i < list->count; i++) { + if (list->list[i] && strcmp(list->list[i]->address, address) == 0) { + free(list->list[i]->playerName); + free(list->list[i]->address); + free(list->list[i]->discordId); + free(list->list[i]->reason); + free(list->list[i]); + + for (u16 j = i; j < list->count - 1; j++) { + list->list[j] = list->list[j + 1]; + } + + list->count--; + list->list[list->count] = NULL; + + moderation_list_save(); + return; + } + } + LOG_ERROR("Address %s not found in list %u", address, type); +} + +bool moderation_list_contains(enum ModerationListType type, char* address) { + struct ModerationList* list = moderation_list_get_list_by_type(type); + if (!list || !address) return false; + + for (u16 i = 0; i < list->count; i++) { + if (list->list[i] && strcmp(list->list[i]->address, address) == 0) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/src/pc/network/moderation_list.h b/src/pc/network/moderation_list.h new file mode 100644 index 000000000..cbbdd9be7 --- /dev/null +++ b/src/pc/network/moderation_list.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include "moderation.h" + +#define MODERATION_LIST_FILEPATH "moderation_list.ini" +#define MAX_MODERATION_LIST_ENTRIES 1024 + +enum ModerationListType { + MODERATION_LIST_TYPE_BAN, + MODERATION_LIST_TYPE_MODERATOR, + MODERATION_LIST_TYPE_COUNT +}; + +struct ModerationEntry { + time_t time; + char* playerName; + u8 playerColor[3]; + char* address; + char* discordId; + char* reason; + bool permanent; +}; + +struct ModerationList { + struct ModerationEntry* list[MAX_MODERATION_LIST_ENTRIES]; + u16 count; + bool actions[MODERATION_ACTION_COUNT]; +}; + +struct ModerationLists { + struct ModerationList banList; + struct ModerationList moderatorList; +}; + +extern struct ModerationLists gModerationLists; + +struct ModerationList* moderation_list_get_list_by_type(enum ModerationListType type); +void moderation_list_save(); +void moderation_list_load(); +void moderation_list_add(enum ModerationListType type, u8 localIndex, char* reason, bool permanent); +void moderation_list_remove(enum ModerationListType type, char* address); +bool moderation_list_contains(enum ModerationListType type, char* address); \ No newline at end of file diff --git a/src/pc/network/network_utils.c b/src/pc/network/network_utils.c index a461d6534..bbfc7f5fd 100644 --- a/src/pc/network/network_utils.c +++ b/src/pc/network/network_utils.c @@ -53,6 +53,14 @@ const char* network_get_player_text_color_string(u8 localIndex) { return sColorString; } +const char* network_get_complete_player_name(u8 localIndex) { + if (localIndex >= MAX_PLAYERS) { localIndex = 0; } + static char buffer[MAX_CONFIG_STRING + 10]; + const char* colorString = network_get_player_text_color_string(localIndex); + snprintf(buffer, MAX_CONFIG_STRING + 10, "%s%s", colorString, gNetworkPlayers[localIndex].name); + return buffer; +} + extern s16 gMenuMode; bool network_check_singleplayer_pause(void) { return ((gMenuMode != -1) || (gCameraMovementFlags & CAM_MOVE_PAUSE_SCREEN)) && diff --git a/src/pc/network/network_utils.h b/src/pc/network/network_utils.h index a44b58674..d33658520 100644 --- a/src/pc/network/network_utils.h +++ b/src/pc/network/network_utils.h @@ -17,6 +17,8 @@ bool network_is_moderator(void); u8* network_get_player_text_color(u8 localIndex); /* |description|Gets the DJUI hex color code string for the player corresponding to `localIndex`'s cap color|descriptionEnd| */ const char* network_get_player_text_color_string(u8 localIndex); +/* |description|Gets the complete player name, including the player's starting hex code.|descriptionEnd| */ +const char* network_get_complete_player_name(u8 localIndex); /* |description|Checks if the game can currently be paused in singleplayer|descriptionEnd| */ bool network_check_singleplayer_pause(void); diff --git a/src/pc/network/packets/packet_moderation.c b/src/pc/network/packets/packet_moderation.c new file mode 100644 index 000000000..ccabb4324 --- /dev/null +++ b/src/pc/network/packets/packet_moderation.c @@ -0,0 +1,87 @@ +#include +#include "../network.h" +#include "../moderation.h" +#include "pc/debuglog.h" + +bool sValidActions[MODERATION_ACTION_COUNT] = { + [MODERATION_ACTION_KICK] = true, + [MODERATION_ACTION_BAN] = true +}; + +void network_send_moderation_action(u8 action, u8 localIndex, char* reason, bool permanent) { + SOFT_ASSERT(gNetworkType != NT_SERVER); + if (!gNetworkPlayerLocal->moderator) { + LOG_ERROR("Tried to send moderation action as a non-moderator!"); + return; + } + struct NetworkPlayer* np = &gNetworkPlayers[localIndex]; + if (!np->connected) { + LOG_ERROR("Moderator tried to perform moderation on a disconnected player!"); + } + if (np->moderator) { + LOG_ERROR("Moderator tried to perform moderation on another moderator!"); + return; + } + + if (!sValidActions[action]) { + LOG_ERROR("Tried to send unimplemented action to the server!"); + return; + } + + struct Packet p = { 0 }; + packet_init(&p, PACKET_MODERATION_ACTION, false, PLMT_NONE); + packet_write(&p, &action, sizeof(u8)); + packet_write(&p, &np->globalIndex, sizeof(u8)); + if (reason) { + u16 reasonLength = strlen(reason); + packet_write(&p, &reasonLength, sizeof(u16)); + packet_write(&p, &reason, sizeof(u8) * reasonLength); + } else { + packet_write(&p, 0, sizeof(u16)); + } + packet_write(&p, &permanent, sizeof(bool)); + + network_send_to(gNetworkPlayerServer->globalIndex, &p); +} + +void network_receive_moderation_action(struct Packet* p) { + SOFT_ASSERT(gNetworkType == NT_SERVER); + + enum ModerationActions action = MODERATION_ACTION_COUNT; + u8 globalIndex = 0; + u16 reasonLength = 0; + char* reason = NULL; + bool permanent = false; + + packet_read(p, &action, sizeof(u8)); + if (!sValidActions[action]) { + LOG_ERROR("Received an invalid moderation action from a moderator!"); + return; + } + + packet_read(p, &globalIndex, sizeof(u8)); + if (globalIndex >= MAX_PLAYERS) { + LOG_ERROR("Received an out of range global index from a moderator!"); + return; + } + struct NetworkPlayer* np = network_player_from_global_index(globalIndex); + if (!np->connected) { + LOG_ERROR("Network player received from moderator is not connected!"); + return; + } + + packet_read(p, &reasonLength, sizeof(u16)); + packet_read(p, &reason, sizeof(u8) * reasonLength); + packet_read(p, &permanent, sizeof(bool)); + + switch (action) { + case MODERATION_ACTION_KICK: + network_kick_player(np->localIndex, reason); + break; + case MODERATION_ACTION_BAN: + network_ban_player(np->localIndex, reason, permanent); + break; + default: + break; + } +} \ No newline at end of file