diff --git a/autogen/convert_constants.py b/autogen/convert_constants.py index 409cd8427..31758cc82 100644 --- a/autogen/convert_constants.py +++ b/autogen/convert_constants.py @@ -20,6 +20,7 @@ in_files = [ "src/game/characters.h", "src/pc/network/network.h", "src/pc/network/network_player.h", + "src/pc/network/network_utils.h", "include/PR/os_cont.h", "src/game/interaction.c", "src/game/interaction.h", diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua index 1d7dfb2dc..9a2e363f5 100644 --- a/autogen/lua_definitions/constants.lua +++ b/autogen/lua_definitions/constants.lua @@ -4777,6 +4777,15 @@ NPT_CLIENT = 3 --- @type NetworkPlayerType --- | `NPT_SERVER` --- | `NPT_CLIENT` +DC_LEAVE = 0 --- @type DisconnectType +DC_KICK = 1 --- @type DisconnectType +DC_BAN = 2 --- @type DisconnectType + +--- @alias DisconnectType +--- | `DC_LEAVE` +--- | `DC_KICK` +--- | `DC_BAN` + --- @type integer OBJ_COL_FLAG_GROUNDED = (1 << 0) diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua index 00d52fad7..8d54a73f0 100644 --- a/autogen/lua_definitions/functions.lua +++ b/autogen/lua_definitions/functions.lua @@ -4194,6 +4194,13 @@ function djui_menu_get_rainbow_string_color(color) -- ... end +--- @param message string +--- @param paddingLines integer +--- Creates an auto-scaling popup that says `message` which will always show the entire message with padding lines of 'paddingLines' +function djui_popup_create_auto_scaling(message, paddingLines) + -- ... +end + --- @param message string --- @param lines integer --- Creates a popup that says `message` and has `lines` @@ -7955,6 +7962,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() @@ -7968,6 +7982,13 @@ function network_discord_id_from_local_index(localIndex) -- ... end +--- @param dcType? DisconnectType +--- @param reason? string +--- Disconnects the local player with DisconnectType `dcType` (default is DC_LEAVE) because of `reason` (optional). +function network_disconnect(dcType, reason) + -- ... +end + --- Resets Yoshi as being alive function set_yoshi_as_not_dead() -- ... diff --git a/docs/lua/constants.md b/docs/lua/constants.md index cf00279f7..29e9e0715 100644 --- a/docs/lua/constants.md +++ b/docs/lua/constants.md @@ -64,6 +64,8 @@ - [enum PvpType](#enum-PvpType) - [network_player.h](#network_playerh) - [enum NetworkPlayerType](#enum-NetworkPlayerType) +- [network_utils.h](#network_utilsh) + - [enum DisconnectType](#enum-DisconnectType) - [obj_behaviors.c](#obj_behaviorsc) - [obj_behaviors_2.h](#obj_behaviors_2h) - [object_constants.h](#object_constantsh) @@ -2257,6 +2259,19 @@
+## [network_utils.h](#network_utils.h) + +### [enum DisconnectType](#DisconnectType) +| Identifier | Value | +| :--------- | :---- | +| DC_LEAVE | 0 | +| DC_KICK | 1 | +| DC_BAN | 2 | + +[:arrow_up_small:](#) + +
+ ## [obj_behaviors.c](#obj_behaviors.c) - OBJ_COL_FLAG_GROUNDED - OBJ_COL_FLAG_HIT_WALL diff --git a/docs/lua/functions-3.md b/docs/lua/functions-3.md index 572b46f94..e327945ad 100644 --- a/docs/lua/functions-3.md +++ b/docs/lua/functions-3.md @@ -3966,6 +3966,30 @@ Gets the header hex color code from a `DJUI_RAINBOW_COLOR_*` constant
+## [djui_popup_create_auto_scaling](#djui_popup_create_auto_scaling) + +### Description +Creates an auto-scaling popup that says `message` which will always show the entire message with padding lines of 'paddingLines' + +### Lua Example +`djui_popup_create_auto_scaling(message, paddingLines)` + +### Parameters +| Field | Type | +| ----- | ---- | +| message | `string` | +| paddingLines | `integer` | + +### Returns +- None + +### C Prototype +`void djui_popup_create_auto_scaling(const char* message, int paddingLines);` + +[:arrow_up_small:](#) + +
+ ## [djui_popup_create](#djui_popup_create) ### Description diff --git a/docs/lua/functions-5.md b/docs/lua/functions-5.md index 91636f399..b6ac6cd85 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 @@ -2802,6 +2825,30 @@ Gets a Discord ID corresponding to the network player with `localIndex`
+## [network_disconnect](#network_disconnect) + +### Description +Disconnects the local player with DisconnectType `dcType` (default is DC_LEAVE) because of `reason` (optional). + +### Lua Example +`network_disconnect(dcType, reason)` + +### Parameters +| Field | Type | +| ----- | ---- | +| dcType | [enum DisconnectType](constants.md#enum-DisconnectType) | +| reason | `string` | + +### Returns +- None + +### C Prototype +`void network_disconnect(OPTIONAL enum DisconnectType dcType, OPTIONAL const char* reason);` + +[:arrow_up_small:](#) + +
+ --- # functions from obj_behaviors.c diff --git a/docs/lua/functions.md b/docs/lua/functions.md index cd60e96f9..b386a660b 100644 --- a/docs/lua/functions.md +++ b/docs/lua/functions.md @@ -815,6 +815,7 @@
- djui_popup.h + - [djui_popup_create_auto_scaling](functions-3.md#djui_popup_create_auto_scaling) - [djui_popup_create](functions-3.md#djui_popup_create)
@@ -1438,8 +1439,10 @@ - [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) + - [network_disconnect](functions-5.md#network_disconnect)
diff --git a/lang/English.ini b/lang/English.ini index 0560d9ac2..ff3f317c7 100644 --- a/lang/English.ini +++ b/lang/English.ini @@ -9,7 +9,9 @@ DISCORD_ERROR = "Discord threw an error.\nTo fix, try: \n1. Close the game.\n2. DISCORD_DETECT = "\\#ffa0a0\\Error:\\#dcdcdc\\ Could not detect Discord.\n\\#a0a0a0\\Try closing the game, restarting Discord, and opening the game again." DISCONNECT_FULL = "\\#ffa0a0\\Disconnected:\\#dcdcdc\\ The server is full." DISCONNECT_KICK = "\\#ffa0a0\\Disconnected:\\#dcdcdc\\ You have been kicked." +DISCONNECT_KICK_REASON = "\\#ffa0a0\\Disconnected:\\#dcdcdc\\ You have been kicked. Reason: @" DISCONNECT_BAN = "\\#ffa0a0\\Disconnected:\\#dcdcdc\\ You have been banned." +DISCONNECT_BAN_REASON = "\\#ffa0a0\\Disconnected:\\#dcdcdc\\ You have been banned. Reason: @" DISCONNECT_REJOIN = "\\#ffa0a0\\Disconnected:\\#dcdcdc\\ Rejoining..." DISCONNECT_CLOSED = "\\#ffa0a0\\Disconnected:\\#dcdcdc\\ Host closed the connection." DISCONNECT_BIG_MOD = "Server had too large of a mod.\nQuitting." @@ -58,6 +60,7 @@ MOD_DESC = "/moderator [NAME|ID] - Make this player able to use commands like /k NAMETAGS_DESC = "/nametags [show-tag|show-health] - Change whether or not you see your own nametag and whether or not you see health" UNRECOGNIZED = "Unrecognized chat command." MOD_GRANTED = "\\#fff982\\You are now a Moderator." +MOD_REVOKED = "\\#FE7F7F\\You are no longer a Moderator." [MENU] BACK = "Back" @@ -266,6 +269,7 @@ SAVE_SLOT = "Save Slot" SETTINGS = "Settings" MODS = "Mods" ROMHACKS = "Rom-Hacks" +MODERATION_LISTS = "Moderation Lists" APPLY = "Apply" HOST = "Host" @@ -339,6 +343,38 @@ CONSOLE = "CONSOLE" [MODLIST] MODS = "MODS" +[MODERATION] +MODERATOR_MENU_TITLE = "MODERATION" +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_INFO = "Reason: @" +REASON = "Reason" +PERMANENT = "Permanent" + [OPTIONS] OPTIONS = "OPTIONS" PLAYER = "Player" @@ -360,6 +396,7 @@ DYNOS_PACKS = "DynOS Packs" OPTIONS = "Options" CHEATS = "Cheats" SERVER_SETTINGS = "Server Settings" +MODERATOR_MENU = "Moderation" RESUME = "Resume" STOP_HOSTING = "Stop Hosting" DISCONNECT = "Disconnect" diff --git a/lang/German.ini b/lang/German.ini index 1fb8031d7..8b7649aae 100644 --- a/lang/German.ini +++ b/lang/German.ini @@ -9,7 +9,9 @@ DISCORD_ERROR = "Discord hat einen Fehler verursacht.\nUm dies zu vermeiden, ver DISCORD_DETECT = "\\#ffa0a0\\Fehlermeldung:\\#dcdcdc\\ Discord konnte nicht gefunden werden.\n\\#a0a0a0\\Versuche, das Spiel zu schließen, Discord neu zu starten und dann das Spiel anschließend wieder zu öffnen." DISCONNECT_FULL = "\\#ffa0a0\\Verbindung getrennt:\\#dcdcdc\\ Der Server ist bereits voll." DISCONNECT_KICK = "\\#ffa0a0\\Verbindung getrennt:\\#dcdcdc\\ Du wurdest vom Server gekickt." +DISCONNECT_KICK_REASON = "\\#ffa0a0\\Verbindung getrennt:\\#dcdcdc\\ Du wurdest gekickt. Grund: @" DISCONNECT_BAN = "\\#ffa0a0\\Verbindung getrennt:\\#dcdcdc\\ Der Bann-Hammer hat gesprochen." +DISCONNECT_BAN_REASON = "\\#ffa0a0\\Verbindung getrennt:\\#dcdcdc\\ Du wurdest gebannt. Grund: @" DISCONNECT_REJOIN = "\\#ffa0a0\\Verbindung getrennt:\\#dcdcdc\\ Erneut verbinden..." DISCONNECT_CLOSED = "\\#ffa0a0\\Verbindung getrennt:\\#dcdcdc\\ Der Hoster hat den Server geschlosse." DISCONNECT_BIG_MOD = "Es konnte keine Verbindung hergestellt werden, da zu viele oder zu große Mods auf dem Server vorhanden sind!" @@ -58,12 +60,14 @@ MOD_DESC = "/moderator [NAME|ID] - Gebe einem Spieler Moderator rechte wie /kick NAMETAGS_DESC = "/nametags [show-tag|show-health] - Sichtbarkeit von Spielernamen sowie der KP/Kraft aktivieren oder deaktivieren " UNRECOGNIZED = "Unbekannter Befehl!" MOD_GRANTED = "\\#fff982\\Du bist jetzt ein Moderator." +MOD_REVOKED = "\\#FE7F7F\\Du bist kein Moderator mehr." [MENU] BACK = "Zurück" CANCEL = "Abbrechen" NO = "Nein" YES = "Ja" +MODERATION_LISTS = "Moderationslisten" [CAMERA] CAMERA = "KAMERA" @@ -339,6 +343,38 @@ CONSOLE = "KONSOLE" [MODLIST] MODS = "MODS" +[MODERATION] +MODERATOR_MENU_TITLE = "MODERATION" +MODERATION_LISTS_TITLE = "MODERATIONSLISTEN" +MODERATION_LISTS = "Moderationslisten" +NO_PLAYERS_CONNECTED = "Keine Spieler verbunden." +KICK = "Kicken" +BAN = "Bannen" +UNBAN = "Entbannen" +MOD = "Mod geben" +UNMOD = "Mod entfernen" +KICK_PLAYER_TITLE = "SPIELER KICKEN" +BAN_PLAYER_TITLE = "SPIELER BANNEN" +UNBAN_PLAYER_TITLE = "SPIELER ENTBANNEN" +MOD_PLAYER_TITLE = "SPIELER ZUM MODERATOR MACHEN" +UNMOD_PLAYER_TITLE = "MODERATOR ENTFERNEN" +KICK_CONFIRM = "Bist du sicher, dass du @\\#dcdcdc\\ kicken möchtest?" +BAN_CONFIRM = "Bist du sicher, dass du @\\#dcdcdc\\ bannen möchtest?" +UNBAN_CONFIRM = "Bist du sicher, dass du @\\#dcdcdc\\ entbannen möchtest?" +MOD_CONFIRM = "Bist du sicher, dass du @\\#dcdcdc\\ zum Moderator machen möchtest?" +UNMOD_CONFIRM = "Bist du sicher, dass du @\\#dcdcdc\\ als Moderator entfernen möchtest?" +BAN_LIST = "Bann-Liste" +MODERATOR_LIST = "Moderatorliste" +LIST = "Liste" +NO_PLAYERS_IN_LIST = "Keine Spieler in der Liste." +INSPECT = "Untersuchen" +INSPECTOR_TITLE = "INSPEKTOR" +DATE = "Hinzugefügt: @" +DISCORD_ID = "Discord-ID: @" +REASON_INFO = "Grund: @" +REASON = "Grund" +PERMANENT = "Permanent" + [OPTIONS] OPTIONS = "OPTIONEN" PLAYER = "Spieler" @@ -365,6 +401,7 @@ STOP_HOSTING = "Hosting beenden" DISCONNECT = "Verbindung trennen" MOD_MENU = "Mod-Menü" MOD_MENU_TITLE = "MOD-MENÜ" +MODERATOR_MENU = "Moderator-Menü" [PLAYER] PLAYER_TITLE = "SPIELER" diff --git a/src/pc/chat_commands.c b/src/pc/chat_commands.c index adb2bec1e..11fdaeb05 100644 --- a/src/pc/chat_commands.c +++ b/src/pc/chat_commands.c @@ -4,8 +4,7 @@ #include "pc/djui/djui_language.h" #include "pc/djui/djui_chat_message.h" #include "chat_commands.h" -#include "pc/network/ban_list.h" -#include "pc/network/moderator_list.h" +#include "pc/network/moderation.h" #include "pc/debuglog.h" #include "pc/lua/utils/smlua_level_utils.h" #include "pc/mods/mods_utils.h" @@ -55,17 +54,21 @@ bool exec_chat_command(char* command) { enum ChatConfirmCommand ccc = sConfirming; sConfirming = CCC_NONE; - if (ccc != CCC_NONE && strcmp("/confirm", command) == 0) { + if (ccc != CCC_NONE && (strcmp("/confirm", command) == 0 || str_starts_with(command, "/confirm "))) { struct NetworkPlayer* np = &gNetworkPlayers[sConfirmPlayerIndex]; if (!np->connected) return true; + char* reason = NULL; + if (str_starts_with(command, "/confirm ")) { + reason = &command[9]; + } if (gNetworkType == NT_SERVER || npl->moderator) { if (ccc == CCC_KICK) { chat_construct_player_message(np, DLANG(CHAT, KICKING)); if (gNetworkType == NT_SERVER) { - network_send_kick(np->localIndex, EKT_KICKED); + network_send_kick(np->localIndex, EKT_KICKED, reason); network_player_disconnected(np->localIndex); } else { - network_send_chat_command(np->globalIndex, CCC_KICK); + network_send_chat_command(np->globalIndex, CCC_KICK, reason); } return true; } @@ -74,19 +77,20 @@ bool exec_chat_command(char* command) { if (ccc == CCC_BAN) { chat_construct_player_message(np, DLANG(CHAT, BANNING)); if (gNetworkType == NT_SERVER) { - network_send_kick(np->localIndex, EKT_BANNED); - ban_list_add(gNetworkSystem->get_id_str(np->localIndex), false); + network_send_kick(np->localIndex, EKT_BANNED, reason); + + moderation_list_add(MODERATION_LIST_TYPE_BAN, np->localIndex, reason, false); network_player_disconnected(np->localIndex); } else { - network_send_chat_command(np->globalIndex, CCC_BAN); + network_send_chat_command(np->globalIndex, CCC_BAN, reason); } return true; } } if (gNetworkType == NT_SERVER && ccc == CCC_PERMBAN) { chat_construct_player_message(np, DLANG(CHAT, PERM_BANNING)); - network_send_kick(np->localIndex, EKT_BANNED); - ban_list_add(gNetworkSystem->get_id_str(np->localIndex), true); + network_send_kick(np->localIndex, EKT_BANNED, reason); + moderation_list_add(MODERATION_LIST_TYPE_BAN, np->localIndex, reason, true); network_player_disconnected(np->localIndex); return true; } @@ -94,7 +98,7 @@ bool exec_chat_command(char* command) { chat_construct_player_message(np, DLANG(CHAT, ADD_MODERATOR)); np->moderator = true; network_send_moderator(np->localIndex); - moderator_list_add(gNetworkSystem->get_id_str(np->localIndex), true); + moderation_list_add(MODERATION_LIST_TYPE_MODERATOR, np->localIndex, reason, true); return true; } } @@ -120,7 +124,7 @@ bool exec_chat_command(char* command) { djui_chat_message_create(DLANG(CHAT, PLAYER_NOT_FOUND)); return true; } - + if (str_starts_with(command, "/kick ")) { if (gNetworkType != NT_SERVER && !npl->moderator) { djui_chat_message_create(DLANG(CHAT, NO_PERMS)); @@ -205,7 +209,7 @@ bool exec_chat_command(char* command) { return true; } - if (str_starts_with(command, "/moderator ")) { + if (str_starts_with(command, "/moderator ")) { if (gNetworkType != NT_SERVER) { djui_chat_message_create(DLANG(CHAT, SERVER_ONLY)); return true; diff --git a/src/pc/djui/djui_base.c b/src/pc/djui/djui_base.c index e4d85a506..46e81d405 100644 --- a/src/pc/djui/djui_base.c +++ b/src/pc/djui/djui_base.c @@ -353,6 +353,11 @@ void djui_base_destroy(struct DjuiBase* base) { gInteractableBinding = NULL; } + // remove input controlled base + if (gInputControlledBase == base) { + gInputControlledBase = NULL; + } + // remove myself from parent's linked list if (base->parent != NULL) { struct DjuiBaseChild* child = base->parent->child; @@ -391,10 +396,12 @@ void djui_base_destroy(struct DjuiBase* base) { } // deallocate interactable - if (base->interactable != NULL) { - free(base->interactable); - base->interactable = NULL; - } + free(base->interactable); + base->interactable = NULL; + + // deallocate ctag + free(base->cTag); + base->cTag = NULL; // remove from interactable variable if (base == gDjuiHovered) { gDjuiHovered = NULL; } diff --git a/src/pc/djui/djui_base.h b/src/pc/djui/djui_base.h index 60174824e..2864c3875 100644 --- a/src/pc/djui/djui_base.h +++ b/src/pc/djui/djui_base.h @@ -43,7 +43,9 @@ struct DjuiBase { bool abandonAfterChildRenderFail; bool gradient; s64 tag; + u64 uTag; bool bTag; + char* cTag; void (*get_cursor_hover_location)(struct DjuiBase*, f32* x, f32* y); void (*on_child_render)(struct DjuiBase*, struct DjuiBase*); void (*on_render_pre)(struct DjuiBase*, bool*); diff --git a/src/pc/djui/djui_cursor.c b/src/pc/djui/djui_cursor.c index 7c336a74f..4c7dfde7f 100644 --- a/src/pc/djui/djui_cursor.c +++ b/src/pc/djui/djui_cursor.c @@ -12,7 +12,7 @@ extern ALIGNED8 u8 gd_texture_hand_closed[]; struct DjuiImage* sMouseCursor = NULL; static bool sCursorMouseControlled = false; -static struct DjuiBase* sInputControlledBase = NULL; +struct DjuiBase* gInputControlledBase = NULL; static f32 sSavedMouseX = 0; static f32 sSavedMouseY = 0; @@ -55,7 +55,7 @@ static void djui_cursor_base_hover_location(struct DjuiBase* base, f32* x, f32* void djui_cursor_input_controlled_center(struct DjuiBase* base) { if (!sCursorMouseControlled && (!base || (base && base->interactable && base->interactable->enabled))) { - sInputControlledBase = base; + gInputControlledBase = base; djui_cursor_set_visible(base != NULL); } } @@ -147,8 +147,8 @@ static void djui_cursor_update_position(void) { if (sCursorMouseControlled) { gCursorX = mouse_window_x / djui_gfx_get_scale(); gCursorY = mouse_window_y / djui_gfx_get_scale(); - } else if (sInputControlledBase != NULL) { - djui_cursor_base_hover_location(sInputControlledBase, &gCursorX, &gCursorY); + } else if (gInputControlledBase != NULL) { + djui_cursor_base_hover_location(gInputControlledBase, &gCursorX, &gCursorY); } // set cursor position diff --git a/src/pc/djui/djui_cursor.h b/src/pc/djui/djui_cursor.h index bc9374060..e485ba4b2 100644 --- a/src/pc/djui/djui_cursor.h +++ b/src/pc/djui/djui_cursor.h @@ -5,6 +5,8 @@ extern f32 gCursorX; extern f32 gCursorY; +extern struct DjuiBase* gInputControlledBase; + void djui_cursor_set_visible(bool visible); bool djui_cursor_inside_base(struct DjuiBase* base); void djui_cursor_input_controlled_center(struct DjuiBase* base); diff --git a/src/pc/djui/djui_language.c b/src/pc/djui/djui_language.c index 1de53e651..370ad6d48 100644 --- a/src/pc/djui/djui_language.c +++ b/src/pc/djui/djui_language.c @@ -45,7 +45,7 @@ void djui_language_replace(char* src, char* dst, int size, char key, char* value char* o = dst; while (*c != '\0') { if (*c == key) { - snprintf(o, size - (o - dst), "%s", value); + snprintf(o, size - (o - dst), "%s", value == NULL ? "" : value); } else { djui_unicode_get_char(c, tmpChar); snprintf(o, size - (o - dst), "%s", tmpChar); 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_confirm.c b/src/pc/djui/djui_panel_confirm.c index fc9dc948c..ea4fcfe9f 100644 --- a/src/pc/djui/djui_panel_confirm.c +++ b/src/pc/djui/djui_panel_confirm.c @@ -27,4 +27,4 @@ void djui_panel_confirm_create(struct DjuiBase* caller, char* title, char* messa } djui_panel_add(caller, panel, NULL); -} +} \ No newline at end of file diff --git a/src/pc/djui/djui_panel_host.c b/src/pc/djui/djui_panel_host.c index b1b069e4b..28a3fb663 100644 --- a/src/pc/djui/djui_panel_host.c +++ b/src/pc/djui/djui_panel_host.c @@ -6,6 +6,7 @@ #include "djui_panel_host_settings.h" #include "djui_panel_host_save.h" #include "djui_panel_host_message.h" +#include "djui_panel_moderation_list.h" #include "djui_panel_rules.h" #include "game/save_file.h" #include "pc/network/network.h" @@ -181,6 +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); + 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..cd5b9944a --- /dev/null +++ b/src/pc/djui/djui_panel_moderation_confirm_action.c @@ -0,0 +1,152 @@ +#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; +static bool sPermanent = false; +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, sPermanent); break; + case MODERATION_ACTION_UNBAN: network_unban_player(address); break; + case MODERATION_ACTION_MOD: network_mod_player(player, sReason, sPermanent); break; + case MODERATION_ACTION_UNMOD: network_unmod_player(address); break; + default: break; + } + + free(sReason); + sReason = NULL; + sPermanent = false; + 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; + } +} + +static void djui_panel_moderation_confirm_destroy(struct DjuiBase* base) { + struct DjuiThreePanel* threePanel = (struct DjuiThreePanel*)base; + free(threePanel); + free(sReason); + sReason = NULL; + sPermanent = false; + sOnYesClick = NULL; +} + +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*)) { + sPermanent = permanent; + + 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_KICK || 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, DLANG(MODERATION, 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.4f, 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.75, 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); + } + + if (action != MODERATION_ACTION_KICK && gNetworkType == NT_SERVER) djui_checkbox_create(body, DLANG(MODERATION, PERMANENT), &sPermanent, NULL); + } + + 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.cTag = strdup(address); + sOnYesClick = on_yes_click; + } + } + + panel->base.destroy = djui_panel_moderation_confirm_destroy; + 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; + struct NetworkPlayer* np = &gNetworkPlayers[localIndex]; + if (!np->connected) 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..93c24fcc9 --- /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(UNUSED struct DjuiBase* caller) { + 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(NULL); + } + 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..646119d9c --- /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(UNUSED struct DjuiBase* caller); +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..f12c97117 --- /dev/null +++ b/src/pc/djui/djui_panel_moderation_list_inspector.c @@ -0,0 +1,120 @@ +#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; + +// I don't like you Windows +static void djui_panel_moderation_list_inspect_save(UNUSED struct DjuiBase* caller) { + moderation_list_save(); +} + +static void djui_panel_moderation_list_inspect_action_exit(UNUSED struct DjuiBase* caller) { + djui_panel_back_by(2); + djui_panel_moderation_list_reload(NULL); +} + +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[0] != '\0') { + int reasonStrLen = MAX_REASON_LENGTH + strlen(DLANG(MODERATION, REASON_INFO)) + 1; + char reasonStr[reasonStrLen]; + djui_language_replace(DLANG(MODERATION, REASON_INFO), reasonStr, reasonStrLen, '@', 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; + 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, djui_panel_moderation_list_inspect_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..783b092d0 --- /dev/null +++ b/src/pc/djui/djui_panel_moderator_menu.c @@ -0,0 +1,107 @@ + +#include +#include "djui.h" +#include "djui_panel.h" +#include "djui_panel_menu.h" +#include "djui_panel_moderator_menu.h" +#include "djui_panel_moderator_menu_inspect.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, caller->bTag, 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, djui_panel_moderator_menu_inspector_create); + 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); + playerButton->base.tag = i; + + 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.bTag = true; + 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(UNUSED struct DjuiBase* caller) { + 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..63b26b3fc --- /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(UNUSED struct DjuiBase* caller); +void djui_panel_moderator_menu_create(struct DjuiBase* caller); \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderator_menu_inspect.c b/src/pc/djui/djui_panel_moderator_menu_inspect.c new file mode 100644 index 000000000..e32446d5f --- /dev/null +++ b/src/pc/djui/djui_panel_moderator_menu_inspect.c @@ -0,0 +1,82 @@ + +#include +#include "djui.h" +#include "djui_panel.h" +#include "djui_panel_menu.h" +#include "djui_panel_moderator_menu.h" +#include "djui_panel_moderator_menu_inspect.h" +#include "djui_panel_moderation_list.h" +#include "djui_panel_moderation_confirm_action.h" +#include "pc/network/network.h" +#include "pc/network/moderation.h" + +struct DjuiButton* sModButton = NULL; +static u8 sSelectedIndex = 0; + +static void djui_panel_moderator_inspector_validate_and_reload(UNUSED struct DjuiBase* caller); + +static void djui_panel_moderator_menu_action_button_click(struct DjuiBase* caller) { + djui_panel_moderation_confirm_create(caller, caller->uTag, caller->tag, caller->bTag, djui_panel_moderator_inspector_validate_and_reload); +} + +static void djui_panel_moderator_menu_inspector_destroy(struct DjuiBase* base) { + struct DjuiThreePanel* threePanel = (struct DjuiThreePanel*)base; + free(threePanel); + sModButton = NULL; +} + +static void djui_panel_moderator_inspector_validate_and_reload(UNUSED struct DjuiBase* caller) { + djui_panel_moderator_menu_reload(caller); + if (sSelectedIndex >= MAX_PLAYERS) djui_panel_back_by(2); + struct NetworkPlayer* np = &gNetworkPlayers[sSelectedIndex]; + if (!np->connected) djui_panel_back_by(2); + djui_panel_moderator_inspector_reload(NULL); +} + +void djui_panel_moderator_inspector_reload(UNUSED struct DjuiBase* caller) { + if (!sModButton) return; + if (sSelectedIndex >= MAX_PLAYERS) return; + struct NetworkPlayer* np = &gNetworkPlayers[sSelectedIndex]; + + djui_text_set_text(sModButton->text, np->moderator ? DLANG(MODERATION, UNMOD) : DLANG(MODERATION, MOD)); + sModButton->base.uTag = np->moderator ? MODERATION_ACTION_UNMOD : MODERATION_ACTION_MOD; +} + +void djui_panel_moderator_menu_inspector_create(struct DjuiBase* caller) { + if (caller->tag <= 0 || caller->tag >= MAX_PLAYERS) return; + struct NetworkPlayer* np = &gNetworkPlayers[caller->tag]; + if (!np->connected) return; + sSelectedIndex = caller->tag; + struct DjuiThreePanel* panel = djui_panel_menu_create(DLANG(MODERATION, MODERATOR_MENU_TITLE), 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 \\#fff982\\- \\#82f9ff\\%u", network_get_complete_player_name(np->localIndex), gNetworkSystem->get_id_str(np->localIndex), np->localIndex); + struct DjuiText* playerText = djui_text_create(body, playerName); + 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); + + struct DjuiButton* kickButton = djui_button_create(body, DLANG(MODERATION, KICK), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderator_menu_action_button_click); + kickButton->base.uTag = MODERATION_ACTION_KICK; + kickButton->base.tag = np->localIndex; + + struct DjuiButton* banButton = djui_button_create(body, DLANG(MODERATION, BAN), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderator_menu_action_button_click); + banButton->base.uTag = MODERATION_ACTION_BAN; + banButton->base.tag = np->localIndex; + + if (gNetworkType == NT_SERVER) { + sModButton = djui_button_create(body, np->moderator ? DLANG(MODERATION, UNMOD) : DLANG(MODERATION, MOD), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderator_menu_action_button_click); + sModButton->base.uTag = np->moderator ? MODERATION_ACTION_UNMOD : MODERATION_ACTION_MOD; + sModButton->base.bTag = true; + sModButton->base.tag = np->localIndex; + } + + djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back); + } + + panel->base.destroy = djui_panel_moderator_menu_inspector_destroy; + djui_panel_add(caller, panel, NULL); +} \ No newline at end of file diff --git a/src/pc/djui/djui_panel_moderator_menu_inspect.h b/src/pc/djui/djui_panel_moderator_menu_inspect.h new file mode 100644 index 000000000..779abd968 --- /dev/null +++ b/src/pc/djui/djui_panel_moderator_menu_inspect.h @@ -0,0 +1,5 @@ +#pragma once +#include "djui.h" + +void djui_panel_moderator_inspector_reload(UNUSED struct DjuiBase* caller); +void djui_panel_moderator_menu_inspector_create(struct DjuiBase* caller); \ No newline at end of file diff --git a/src/pc/djui/djui_panel_pause.c b/src/pc/djui/djui_panel_pause.c index 9c993053d..4098305b8 100644 --- a/src/pc/djui/djui_panel_pause.c +++ b/src/pc/djui/djui_panel_pause.c @@ -5,6 +5,7 @@ #include "djui_panel_dynos.h" #include "djui_panel_options.h" #include "djui_panel_host.h" +#include "djui_panel_moderator_menu.h" #include "djui_panel_menu.h" #include "djui_panel_confirm.h" #include "djui_panel_mod_menu.h" @@ -72,6 +73,10 @@ void djui_panel_pause_create(struct DjuiBase* caller) { djui_button_create(body, DLANG(PAUSE, SERVER_SETTINGS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_create); } + if (gNetworkType == NT_SERVER || network_is_moderator()) { + djui_button_create(body, DLANG(PAUSE, MODERATOR_MENU), DJUI_BUTTON_STYLE_NORMAL, djui_panel_moderator_menu_create); + } + struct Mod* addedMods[MAX_HOOKED_MOD_MENU_ELEMENTS] = { 0 }; int modCount = 0; for (int i = 0; i < gHookedModMenuElementsCount; i++) { diff --git a/src/pc/djui/djui_popup.c b/src/pc/djui/djui_popup.c index 4d0225a45..ce55a912d 100644 --- a/src/pc/djui/djui_popup.c +++ b/src/pc/djui/djui_popup.c @@ -5,7 +5,9 @@ #include "pc/utils/misc.h" #include "pc/configfile.h" #include "pc/lua/utils/smlua_misc_utils.h" +#include "djui_hud_utils.h" +#define DJUI_POPUP_WIDTH 400.0f #define DJUI_POPUP_LIFETIME 6.0f struct DjuiPopupList { @@ -35,25 +37,27 @@ static void djui_popup_destroy(struct DjuiBase* base) { free(popup); } -void djui_popup_create(const char* message, int lines) { +static void djui_popup_create_internal(const char* message, int lines, int paddingLines) { if (djui_is_popup_disabled()) { return; } + if (paddingLines < 0) paddingLines = 0; struct DjuiPopup* popup = calloc(1, sizeof(struct DjuiPopup)); struct DjuiBase* base = &popup->base; - f32 height = lines * 32 + 32; + f32 height = (lines + paddingLines) * 32 + 32; djui_base_init(&gDjuiRoot->base, base, djui_popup_render, djui_popup_destroy); djui_base_set_alignment(base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_TOP); djui_base_set_location(base, 8, -height); - djui_base_set_size(base, 400, height); + djui_base_set_size(base, DJUI_POPUP_WIDTH, height); djui_base_set_border_width(base, 4); djui_base_set_color(base, 0, 0, 0, 220); djui_base_set_border_color(base, 0, 0, 0, 180); struct DjuiText* text = djui_text_create(base, message); - djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_RELATIVE); - djui_base_set_size(&text->base, 1.0f, 1.0f); + djui_base_set_alignment(&text->base, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER); + djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&text->base, 1.0f, lines * 32.0f); djui_base_set_color(&text->base, 220, 220, 220, 255); - djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER); + djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_TOP); djui_text_set_drop_shadow(text, 0, 0, 0, 64); popup->text = text; @@ -62,6 +66,19 @@ void djui_popup_create(const char* message, int lines) { play_sound(SOUND_MENU_PINCH_MARIO_FACE, gGlobalSoundSource); } +void djui_popup_create(const char* message, int lines) { + int linesReq = (int)ceilf(djui_hud_measure_text(djui_text_get_uncolored_string(NULL, strlen(message + 1), message)) / DJUI_POPUP_WIDTH); + if (linesReq < 1) linesReq = 1; + if (linesReq > lines) linesReq = lines; + djui_popup_create_internal(message, linesReq, lines - linesReq); +} + +void djui_popup_create_auto_scaling(const char* message, int paddingLines) { + int linesReq = (int)ceilf(djui_hud_measure_text(djui_text_get_uncolored_string(NULL, strlen(message + 1), message)) / DJUI_POPUP_WIDTH); + if (linesReq < 1) linesReq = 1; + djui_popup_create_internal(message, linesReq, paddingLines); +} + void djui_popup_update(void) { struct DjuiPopupList* node = sPopupListHead; struct DjuiPopupList* last = NULL; diff --git a/src/pc/djui/djui_popup.h b/src/pc/djui/djui_popup.h index c823baa87..eb62c5884 100644 --- a/src/pc/djui/djui_popup.h +++ b/src/pc/djui/djui_popup.h @@ -6,6 +6,9 @@ struct DjuiPopup { struct DjuiText* text; }; +/* |description|Creates an auto-scaling popup that says `message` which will always show the entire message with padding lines of 'paddingLines'|descriptionEnd| */ +void djui_popup_create_auto_scaling(const char* message, int paddingLines); /* |description|Creates a popup that says `message` and has `lines`|descriptionEnd| */ void djui_popup_create(const char* message, int lines); + void djui_popup_update(void); diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index de7da771c..ec92063c1 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -2346,6 +2346,9 @@ char gSmluaConstants[] = "" "NPT_LOCAL=1\n" "NPT_SERVER=2\n" "NPT_CLIENT=3\n" +"DC_LEAVE=0\n" +"DC_KICK=1\n" +"DC_BAN=2\n" "OBJ_COL_FLAG_GROUNDED=(1 << 0)\n" "OBJ_COL_FLAG_HIT_WALL=(1 << 1)\n" "OBJ_COL_FLAG_UNDERWATER=(1 << 2)\n" diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c index 47dcd1e4c..d64aea119 100644 --- a/src/pc/lua/smlua_functions_autogen.c +++ b/src/pc/lua/smlua_functions_autogen.c @@ -13191,6 +13191,25 @@ int smlua_func_djui_menu_get_rainbow_string_color(lua_State* L) { // djui_popup.h // ////////////////// +int smlua_func_djui_popup_create_auto_scaling(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 2) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "djui_popup_create_auto_scaling", 2, top); + return 0; + } + + const char* message = smlua_to_string(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "djui_popup_create_auto_scaling"); return 0; } + int paddingLines = smlua_to_integer(L, 2); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "djui_popup_create_auto_scaling"); return 0; } + + djui_popup_create_auto_scaling(message, paddingLines); + + return 1; +} + int smlua_func_djui_popup_create(lua_State* L) { if (L == NULL) { return 0; } @@ -23576,6 +23595,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; } @@ -23608,6 +23644,31 @@ int smlua_func_network_discord_id_from_local_index(lua_State* L) { return 1; } +int smlua_func_network_disconnect(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top < 0 || top > 2) { + LOG_LUA_LINE("Improper param count for '%s': Expected between %u and %u, Received %u", "network_disconnect", 0, 2, top); + return 0; + } + + int dcType = (int) 0; + if (top >= 1) { + dcType = smlua_to_integer(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "network_disconnect"); return 0; } + } + const char* reason = (const char*) NULL; + if (top >= 2) { + reason = smlua_to_string(L, 2); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "network_disconnect"); return 0; } + } + + network_disconnect(dcType, reason); + + return 1; +} + ///////////////////// // obj_behaviors.c // ///////////////////// @@ -37578,6 +37639,7 @@ void smlua_bind_functions_autogen(void) { smlua_bind_function(L, "djui_menu_get_rainbow_string_color", smlua_func_djui_menu_get_rainbow_string_color); // djui_popup.h + smlua_bind_function(L, "djui_popup_create_auto_scaling", smlua_func_djui_popup_create_auto_scaling); smlua_bind_function(L, "djui_popup_create", smlua_func_djui_popup_create); // external.h @@ -38146,8 +38208,10 @@ 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); + smlua_bind_function(L, "network_disconnect", smlua_func_network_disconnect); // obj_behaviors.c smlua_bind_function(L, "set_yoshi_as_not_dead", smlua_func_set_yoshi_as_not_dead); diff --git a/src/pc/lua/utils/smlua_misc_utils.c b/src/pc/lua/utils/smlua_misc_utils.c index faa10c1ae..00e74d22c 100644 --- a/src/pc/lua/utils/smlua_misc_utils.c +++ b/src/pc/lua/utils/smlua_misc_utils.c @@ -31,6 +31,7 @@ #include "game/rumble_init.h" #include "game/sound_init.h" #include "pc/lua/utils/smlua_audio_utils.h" +#include "pc/network/moderation.h" #ifdef DISCORD_SDK #include "pc/discord/discord.h" diff --git a/src/pc/lua/utils/smlua_misc_utils.h b/src/pc/lua/utils/smlua_misc_utils.h index 1b7d929cb..9ab99144c 100644 --- a/src/pc/lua/utils/smlua_misc_utils.h +++ b/src/pc/lua/utils/smlua_misc_utils.h @@ -51,7 +51,6 @@ struct DateTime { s32 second; }; - /* |description|Gets the current area's networked timer|descriptionEnd| */ u32 get_network_area_timer(void); /* |description|Gets the area update counter incremented when objects are updated|descriptionEnd| */ diff --git a/src/pc/network/moderation.c b/src/pc/network/moderation.c new file mode 100644 index 000000000..6277b6667 --- /dev/null +++ b/src/pc/network/moderation.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include "moderation.h" +#include "pc/djui/djui_panel_moderator_menu.h" +#include "pc/djui/djui_panel_moderation_list.h" +#include "pc/djui/djui_panel_moderator_menu_inspect.h" +#include "pc/debuglog.h" +#include "pc/ini.h" + +u8 gQueuedDisconnect = QUEUED_DISCONNECT_NONE; + +void djui_reload_moderation_panels() { + djui_panel_moderator_menu_reload(NULL); + djui_panel_moderation_list_reload(NULL); + djui_panel_moderator_inspector_reload(NULL); +} + +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, reason); + 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, reason); + 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..e4f9e8d5b --- /dev/null +++ b/src/pc/network/moderation.h @@ -0,0 +1,24 @@ +#pragma once +#include + +#define QUEUED_DISCONNECT_NONE 255 + +enum ModerationActions { + MODERATION_ACTION_KICK, + MODERATION_ACTION_BAN, + MODERATION_ACTION_UNBAN, + MODERATION_ACTION_MOD, + MODERATION_ACTION_UNMOD, + MODERATION_ACTION_COUNT, +}; + +extern u8 gQueuedDisconnect; + +void djui_reload_moderation_panels(); +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..7b4222e80 --- /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); + // windows <3 + fprintf(file, "time = %lld\n", (long long)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")); + snprintf(entry->reason, MAX_REASON_LENGTH, "%s", 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)); + snprintf(entry->reason, MAX_REASON_LENGTH, "%s", 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]); + + 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..1ca5a31ae --- /dev/null +++ b/src/pc/network/moderation_list.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include "moderation.h" + +#define MODERATION_LIST_FILEPATH "moderation_list.ini" +#define MAX_MODERATION_LIST_ENTRIES 1024 +#define MAX_REASON_LENGTH 256 + +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[MAX_REASON_LENGTH]; + 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.c b/src/pc/network/network.c index 6bd1d4882..16b00593f 100644 --- a/src/pc/network/network.c +++ b/src/pc/network/network.c @@ -6,6 +6,7 @@ #include "game/level_update.h" #include "object_constants.h" #include "behavior_table.h" +#include "moderation.h" #include "pc/configfile.h" #include "pc/djui/djui.h" #include "pc/djui/djui_panel.h" @@ -498,7 +499,7 @@ void network_rehost_begin(void) { struct NetworkPlayer* np = &gNetworkPlayers[i]; if (!np->connected) { continue; } - network_send_kick(i, EKT_REJOIN); + network_send_kick(i, EKT_REJOIN, NULL); network_player_disconnected(i); } @@ -635,6 +636,13 @@ void network_update(void) { network_reset_reconnect_and_rehost(); network_shutdown(true, false, false, false); } + + if (gNetworkType != NT_NONE && !gDjuiInMainMenu && gQueuedDisconnect != QUEUED_DISCONNECT_NONE) { + network_reset_reconnect_and_rehost(); + network_shutdown(true, false, false, false); + } else { + gQueuedDisconnect = QUEUED_DISCONNECT_NONE; + } } static inline void color_set(Color color, u8 r, u8 g, u8 b) { diff --git a/src/pc/network/network_player.c b/src/pc/network/network_player.c index 5e1661bbb..3f25b020f 100644 --- a/src/pc/network/network_player.c +++ b/src/pc/network/network_player.c @@ -17,6 +17,7 @@ #endif #include "game/mario.h" #include "pc/djui/djui_unicode.h" +#include "moderation.h" struct NetworkPlayer gNetworkPlayers[MAX_PLAYERS] = { 0 }; struct NetworkPlayer *gNetworkPlayerLocal = NULL; @@ -292,6 +293,8 @@ u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex, u8 mode np->palette = *palette; network_player_update_model(localIndex); + djui_reload_moderation_panels(); + snprintf(np->name, MAX_CONFIG_STRING, "%s", name); return localIndex; } @@ -327,6 +330,9 @@ u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex, u8 mode snprintf(np->discordId, 64, "%s", discordId); + // update moderation panels + djui_reload_moderation_panels(); + // clear networking fields np->lastReceived = clock_elapsed(); np->lastSent = clock_elapsed(); @@ -421,6 +427,9 @@ u8 network_player_disconnected(u8 globalIndex) { // reset mario state init_mario_single_from_save_file(&gMarioStates[i], i); + // reload moderation panels + djui_reload_moderation_panels(); + return i; } return UNKNOWN_GLOBAL_INDEX; diff --git a/src/pc/network/network_utils.c b/src/pc/network/network_utils.c index a461d6534..0b279a485 100644 --- a/src/pc/network/network_utils.c +++ b/src/pc/network/network_utils.c @@ -1,9 +1,12 @@ #include #include "network_utils.h" +#include "moderation.h" #include "game/camera.h" #include "game/level_update.h" #include "game/mario_misc.h" #include "pc/mods/mods.h" +#include "pc/debuglog.h" +#include "pc/lua/smlua.h" u8 network_global_index_from_local(u8 localIndex) { if (gNetworkType == NT_SERVER) { return localIndex; } @@ -53,6 +56,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)) && @@ -63,3 +74,25 @@ const char* network_discord_id_from_local_index(u8 localIndex) { if (localIndex >= MAX_PLAYERS) { return "0"; } return gNetworkPlayers[localIndex].discordId; } + +void network_disconnect(OPTIONAL enum DisconnectType dcType, OPTIONAL const char* reason) { + switch (dcType) { + case DC_KICK: + if (gNetworkType == NT_SERVER) { + LOG_LUA("network_disconnect: Cannot kick the server!"); + return; + } + network_send_moderation_action(MODERATION_ACTION_KICK, 0, (char*)reason, false); + break; + case DC_BAN: + if (gNetworkType == NT_SERVER) { + LOG_LUA("network_disconnect: Cannot ban the server!"); + return; + } + network_send_moderation_action(MODERATION_ACTION_BAN, 0, (char*)reason, false); + break; + default: + gQueuedDisconnect = dcType; + break; + } +} diff --git a/src/pc/network/network_utils.h b/src/pc/network/network_utils.h index a44b58674..4b0ef0c05 100644 --- a/src/pc/network/network_utils.h +++ b/src/pc/network/network_utils.h @@ -4,6 +4,12 @@ #include #include "network.h" +enum DisconnectType { + DC_LEAVE, + DC_KICK, + DC_BAN +}; + /* |description|Gets a player's global index from their local index|descriptionEnd| */ u8 network_global_index_from_local(u8 localIndex); /* |description|Gets a player's local index from their global index|descriptionEnd| */ @@ -17,6 +23,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); @@ -24,4 +32,7 @@ bool network_check_singleplayer_pause(void); /* |description|Gets a Discord ID corresponding to the network player with `localIndex`|descriptionEnd| */ const char* network_discord_id_from_local_index(u8 localIndex); +/* |description|Disconnects the local player with DisconnectType `dcType` (default is DC_LEAVE) because of `reason` (optional).|descriptionEnd| */ +void network_disconnect(OPTIONAL enum DisconnectType dcType, OPTIONAL const char* reason); + #endif \ No newline at end of file diff --git a/src/pc/network/packets/packet.c b/src/pc/network/packets/packet.c index 199a88e5a..b625e01a6 100644 --- a/src/pc/network/packets/packet.c +++ b/src/pc/network/packets/packet.c @@ -1,7 +1,7 @@ #include #include #include "../network.h" -#include "pc/network/ban_list.h" +#include "pc/network/moderation.h" #include "pc/debuglog.h" static u32 sCompBufferLen = 0; @@ -93,6 +93,7 @@ void packet_process(struct Packet* p) { case PACKET_KICK: network_receive_kick(p); break; case PACKET_COMMAND: network_receive_chat_command(p); break; case PACKET_MODERATOR: network_receive_moderator(p); break; + case PACKET_MODERATION_ACTION: network_receive_moderation_action(p); break; case PACKET_KEEP_ALIVE: network_receive_keep_alive(p); break; case PACKET_LEAVING: network_receive_leaving(p); break; case PACKET_SAVE_FILE: network_receive_save_file(p); break; @@ -153,9 +154,18 @@ void packet_receive(struct Packet* p) { // refuse packets from banned players if (gNetworkType == NT_SERVER) { - if (ban_list_contains(gNetworkSystem->get_id_str(p->localIndex))) { + if (moderation_list_contains(MODERATION_LIST_TYPE_BAN, gNetworkSystem->get_id_str(p->localIndex))) { + char* reason = NULL; + struct ModerationList* list = moderation_list_get_list_by_type(MODERATION_LIST_TYPE_BAN); + for (u16 i = 0; i < list->count; i++) { + struct ModerationEntry* entry = list->list[i]; + if (strcmp(entry->address, gNetworkSystem->get_id_str(p->localIndex)) == 0) { + reason = entry->reason; + break; + } + } LOG_INFO("kicking banned player"); - network_send_kick(0, EKT_BANNED); + network_send_kick(0, EKT_BANNED, reason); return; } } @@ -178,7 +188,7 @@ void packet_receive(struct Packet* p) { } if (packetType != PACKET_PLAYER) { LOG_INFO("closing connection for packetType: %d", packetType); - network_send_kick(0, EKT_CLOSE_CONNECTION); + network_send_kick(0, EKT_CLOSE_CONNECTION, NULL); } LOG_INFO("refusing packet from unknown player, packetType: %d", packetType); return; diff --git a/src/pc/network/packets/packet.h b/src/pc/network/packets/packet.h index 5ab859760..c72418119 100644 --- a/src/pc/network/packets/packet.h +++ b/src/pc/network/packets/packet.h @@ -74,9 +74,10 @@ enum PacketType { PACKET_LUA_CUSTOM, PACKET_LUA_CUSTOM_BYTESTRING, - + PACKET_COMMAND, PACKET_MODERATOR, + PACKET_MODERATION_ACTION, /// PACKET_CUSTOM = 255, @@ -237,17 +238,22 @@ void network_send_chat(char* message, u8 globalIndex); void network_receive_chat(struct Packet* p); // packet_kick.c -void network_send_kick(u8 localIndex, enum KickReasonType kickReason); +void network_create_kick_popup(enum KickReasonType kickReason, char* reason); +void network_send_kick(u8 localIndex, enum KickReasonType kickReason, char* reason); void network_receive_kick(struct Packet* p); // packet_command_mod.c -void network_send_chat_command(u8 localIndex, enum ChatConfirmCommand CCC); +void network_send_chat_command(u8 localIndex, enum ChatConfirmCommand CCC, char* reason); void network_receive_chat_command(struct Packet* p); // packet_moderator.c void network_send_moderator(u8 localIndex); void network_receive_moderator(struct Packet* p); +// packet_moderation.c +void network_send_moderation_action(u8 action, u8 localIndex, char* reason, bool permanent); +void network_receive_moderation_action(struct Packet* p); + // packet_keep_alive.c void network_send_keep_alive(u8 localIndex); void network_receive_keep_alive(struct Packet* p); diff --git a/src/pc/network/packets/packet_command_mod.c b/src/pc/network/packets/packet_command_mod.c index ce3fa0ba2..628b15a93 100644 --- a/src/pc/network/packets/packet_command_mod.c +++ b/src/pc/network/packets/packet_command_mod.c @@ -2,11 +2,10 @@ #include "../network.h" #include "pc/djui/djui_language.h" #include "pc/djui/djui_chat_message.h" -#include "pc/network/ban_list.h" -#include "pc/network/moderator_list.h" +#include "pc/network/moderation.h" #include "pc/debuglog.h" -void network_send_chat_command(u8 globalIndex, enum ChatConfirmCommand ccc) { +void network_send_chat_command(u8 globalIndex, enum ChatConfirmCommand ccc, char* reason) { if (!gNetworkPlayers[0].moderator) return; u8 cccType = ccc; struct Packet p = { 0 }; @@ -14,6 +13,14 @@ void network_send_chat_command(u8 globalIndex, enum ChatConfirmCommand ccc) { packet_init(&p, PACKET_COMMAND, false, PLMT_NONE); packet_write(&p, &globalIndex, sizeof(u8)); packet_write(&p, &cccType, sizeof(u8)); + u16 reasonLength = 0; + if (reason) { + u16 reasonLength = strlen(reason); + packet_write(&p, &reasonLength, sizeof(u16)); + packet_write(&p, reason, sizeof(u8) * reasonLength); + } else { + packet_write(&p, &reasonLength, sizeof(u16)); + } network_send_to(gNetworkPlayerServer->localIndex, &p); } @@ -23,13 +30,18 @@ void network_receive_chat_command(struct Packet *p) { return; } - if (!moderator_list_contains(gNetworkSystem->get_id_str(p->localIndex))) { + if (!moderation_list_contains(MODERATION_LIST_TYPE_MODERATOR, gNetworkSystem->get_id_str(p->localIndex))) { LOG_ERROR("recieved moderator command from non moderator"); return; } u8 CCC; u8 player; + u16 reasonLength = 0; + char reason[MAX_REASON_LENGTH] = { 0 }; packet_read(p, &player, sizeof(u8)); packet_read(p, &CCC, sizeof(u8)); + packet_read(p, &reasonLength, sizeof(u16)); + if (reasonLength >= MAX_REASON_LENGTH) reasonLength = MAX_REASON_LENGTH - 1; + packet_read(p, reason, sizeof(u8) * reasonLength); if (CCC != CCC_KICK && CCC != CCC_BAN) { LOG_ERROR("recieved an invalid chat command: %d", CCC); @@ -43,26 +55,33 @@ void network_receive_chat_command(struct Packet *p) { } char message[256] = { 0 }; if (CCC == CCC_KICK) { - network_send_kick(np->localIndex, EKT_KICKED); + network_send_kick(np->localIndex, EKT_KICKED, reason); snprintf(message, 256, "\\#fff982\\Kicked '%s%s\\#fff982\\'!", network_get_player_text_color_string(np->localIndex), np->name); } if (CCC == CCC_BAN) { - network_send_kick(np->localIndex, EKT_BANNED); - ban_list_add(gNetworkSystem->get_id_str(np->localIndex), false); + network_send_kick(np->localIndex, EKT_BANNED, reason); + moderation_list_add(MODERATION_LIST_TYPE_BAN, np->localIndex, reason, false); snprintf(message, 256, "\\#fff982\\Banned '%s%s\\#fff982\\'!", network_get_player_text_color_string(np->localIndex), np->name); } - network_player_disconnected(np->localIndex); + network_player_disconnected(np->globalIndex); djui_chat_message_create(message); } void network_send_moderator(u8 localIndex) { struct Packet p = { 0 }; packet_init(&p, PACKET_MODERATOR, false, PLMT_NONE); + packet_write(&p, &gNetworkPlayerLocal[localIndex].moderator, sizeof(bool)); network_send_to(localIndex, &p); } void network_receive_moderator(struct Packet *p) { - if (gNetworkPlayers[0].moderator || (network_player_any_connected() && gNetworkPlayers[p->localIndex].type != NPT_SERVER)) return; - gNetworkPlayers[0].moderator = true; - djui_chat_message_create(DLANG(CHAT, MOD_GRANTED)); + if (network_player_any_connected() && gNetworkPlayers[p->localIndex].type != NPT_SERVER) return; + bool moderator; + packet_read(p, &moderator, sizeof(bool)); + if (gNetworkPlayers[0].moderator == moderator) { + LOG_ERROR("Server moderator is telling me to be what I already am! Ignoring..."); + return; + } + gNetworkPlayers[0].moderator = moderator; + djui_chat_message_create(moderator ? DLANG(CHAT, MOD_GRANTED) : DLANG(CHAT, MOD_REVOKED)); } diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c index 0f1602352..abfd46b37 100644 --- a/src/pc/network/packets/packet_join.c +++ b/src/pc/network/packets/packet_join.c @@ -48,9 +48,12 @@ void network_send_join_request(void) { snprintf(version, MAX_VERSION_LENGTH, "%s", get_version()); packet_write(&p, &version, sizeof(u8) * MAX_VERSION_LENGTH); - packet_write(&p, &configPlayerModel, sizeof(u8)); - packet_write(&p, &configPlayerPalette, sizeof(struct PlayerPalette)); - packet_write(&p, &configPlayerName, sizeof(u8) * MAX_CONFIG_STRING); + packet_write(&p, &configPlayerModel, sizeof(u8)); + packet_write(&p, &configPlayerPalette, sizeof(struct PlayerPalette)); + packet_write(&p, &configPlayerName, sizeof(u8) * MAX_CONFIG_STRING); + char discordId[64]; + snprintf(discordId, 64, "%s", get_local_discord_id()); + packet_write(&p, &discordId, sizeof(u8) * 64); network_send_to((gNetworkPlayerServer != NULL) ? gNetworkPlayerServer->localIndex : 0, &p); LOG_INFO("sending join request"); @@ -66,10 +69,12 @@ void network_receive_join_request(struct Packet* p) { packet_read(p, &sJoinRequestPlayerModel, sizeof(u8)); packet_read(p, &sJoinRequestPlayerPalette, sizeof(struct PlayerPalette)); packet_read(p, &sJoinRequestPlayerName, sizeof(u8) * MAX_CONFIG_STRING); + packet_read(p, &sJoinRequestDiscordId, sizeof(u8) * 64); } else { sJoinRequestPlayerModel = 0; sJoinRequestPlayerPalette = DEFAULT_MARIO_PALETTE; snprintf(sJoinRequestPlayerName, MAX_CONFIG_STRING, "%s", "Player"); + snprintf(sJoinRequestDiscordId, 64, "%s", "0"); } network_send_join(p); @@ -91,7 +96,7 @@ void network_send_join(struct Packet* joinRequestPacket) { } } if (globalIndex == UNKNOWN_LOCAL_INDEX || connectedCount >= gServerSettings.maxPlayers) { - network_send_kick(0, EKT_FULL_PARTY); + network_send_kick(0, EKT_FULL_PARTY, NULL); return; } } diff --git a/src/pc/network/packets/packet_kick.c b/src/pc/network/packets/packet_kick.c index 9f2d006d2..53cf12fc2 100644 --- a/src/pc/network/packets/packet_kick.c +++ b/src/pc/network/packets/packet_kick.c @@ -1,5 +1,6 @@ #include #include "../network.h" +#include "../moderation.h" #include "pc/debuglog.h" #include "pc/djui/djui.h" #include "pc/utils/misc.h" @@ -7,12 +8,52 @@ f32 sLastReconnectTime = -9999999; f32 sLastNotifyTime = -9999999; -void network_send_kick(u8 localIndex, enum KickReasonType kickReason) { +void network_create_kick_popup(enum KickReasonType kickReason, char* reason) { + char* text = NULL; + + switch (kickReason) { + case EKT_FULL_PARTY: + text = DLANG(NOTIF, DISCONNECT_FULL); + break; + case EKT_KICKED: + if (reason && reason[0] != '\0') { + text = DLANG(NOTIF, DISCONNECT_KICK_REASON); + } else { + text = DLANG(NOTIF, DISCONNECT_KICK); + } + break; + case EKT_BANNED: + if (reason && reason[0] != '\0') { + text = DLANG(NOTIF, DISCONNECT_BAN_REASON); + } else { + text = DLANG(NOTIF, DISCONNECT_BAN); + } + break; + default: + text = DLANG(NOTIF, DISCONNECT_CLOSED); + break; + } + + char popupText[512] = { 0 }; + djui_language_replace(text, popupText, 512, '@', reason); + djui_popup_create_auto_scaling(popupText, 1); +} + +void network_send_kick(u8 localIndex, enum KickReasonType kickReason, char* reason) { u8 kickReasonType = kickReason; struct Packet p = { 0 }; packet_init(&p, PACKET_KICK, true, PLMT_NONE); p.keepSendingAfterDisconnect = (kickReason == EKT_REJOIN); packet_write(&p, &kickReasonType, sizeof(u8)); + if (reason) { + u16 reasonLength = strlen(reason); + if (reasonLength >= MAX_REASON_LENGTH) reasonLength = MAX_REASON_LENGTH - 1; + packet_write(&p, &reasonLength, sizeof(u16)); + packet_write(&p, reason, sizeof(u8) * reasonLength); + } else { + u16 reasonLength = 0; + packet_write(&p, &reasonLength, sizeof(u16)); + } network_send_to(localIndex, &p); } @@ -22,24 +63,24 @@ void network_receive_kick(struct Packet* p) { return; } - if (network_player_any_connected() && gNetworkPlayers[p->localIndex].type != NPT_SERVER) { - LOG_ERROR("Kick came from non-server... refuse!"); + if (network_player_any_connected() && gNetworkPlayers[p->localIndex].type != NPT_SERVER && !gNetworkPlayers[p->localIndex].moderator) { + LOG_ERROR("Kick came from non-server and non-moderator... refuse!"); return; } u8 kickReasonType; + u16 reasonLength = 0; + char reason[MAX_REASON_LENGTH] = { 0 }; packet_read(p, &kickReasonType, sizeof(u8)); enum KickReasonType kickReason = kickReasonType; + packet_read(p, &reasonLength, sizeof(u16)); + if (reasonLength >= MAX_REASON_LENGTH) reasonLength = MAX_REASON_LENGTH - 1; + if (reasonLength > 0) packet_read(p, reason, sizeof(u8) * reasonLength); f32 now = clock_elapsed(); if ((now - sLastNotifyTime) > 3) { sLastNotifyTime = now; - switch (kickReason) { - case EKT_FULL_PARTY: djui_popup_create(DLANG(NOTIF, DISCONNECT_FULL), 1); break; - case EKT_KICKED: djui_popup_create(DLANG(NOTIF, DISCONNECT_KICK), 1); break; - case EKT_BANNED: djui_popup_create(DLANG(NOTIF, DISCONNECT_BAN), 1); break; - default: djui_popup_create(DLANG(NOTIF, DISCONNECT_CLOSED), 1); break; - } + network_create_kick_popup(kickReasonType, reason); } if (kickReason == EKT_REJOIN) { diff --git a/src/pc/network/packets/packet_moderation.c b/src/pc/network/packets/packet_moderation.c new file mode 100644 index 000000000..821d24c1f --- /dev/null +++ b/src/pc/network/packets/packet_moderation.c @@ -0,0 +1,84 @@ +#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 && localIndex != 0) { + 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 && localIndex != 0) { + 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)); + u16 reasonLength = 0; + if (reason) { + u16 reasonLength = strlen(reason); + packet_write(&p, &reasonLength, sizeof(u16)); + packet_write(&p, reason, sizeof(u8) * reasonLength); + } else { + packet_write(&p, &reasonLength, sizeof(u16)); + } + packet_write(&p, &permanent, sizeof(bool)); + + network_send_to(gNetworkPlayerServer->localIndex, &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[MAX_REASON_LENGTH] = { 0 }; + 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)); + if (reasonLength >= MAX_REASON_LENGTH) reasonLength = MAX_REASON_LENGTH - 1; + 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 diff --git a/src/pc/network/packets/packet_network_players.c b/src/pc/network/packets/packet_network_players.c index c9e57e298..b8ac19e8f 100644 --- a/src/pc/network/packets/packet_network_players.c +++ b/src/pc/network/packets/packet_network_players.c @@ -5,7 +5,7 @@ #include "game/behavior_actions.h" #include "pc/debuglog.h" #include "pc/configfile.h" -#include "pc/network/moderator_list.h" +#include "pc/network/moderation.h" static void network_send_to_network_players(u8 sendToLocalIndex) { SOFT_ASSERT(gNetworkType == NT_SERVER); @@ -60,8 +60,9 @@ void network_receive_network_players_request(struct Packet* p) { } network_send_to_network_players(localIndex); - if (moderator_list_contains(gNetworkSystem->get_id_str(p->localIndex))) { - LOG_INFO("sending moderator packet to localIndex: %d", p->localIndex); + if (moderation_list_contains(MODERATION_LIST_TYPE_MODERATOR, gNetworkSystem->get_id_str(p->localIndex))) { + LOG_INFO("sending moderator packet to localIndex: %d", localIndex); + gNetworkPlayers[localIndex].moderator = true; network_send_moderator(p->localIndex); } } diff --git a/src/pc/network/packets/packet_player.c b/src/pc/network/packets/packet_player.c index 793def2db..7c79bf8ca 100644 --- a/src/pc/network/packets/packet_player.c +++ b/src/pc/network/packets/packet_player.c @@ -252,8 +252,8 @@ void network_receive_player(struct Packet* p) { construct_player_popup(np, DLANG(NOTIF, DEBUG_FLY), NULL); } #else - network_send_kick(np->localIndex, EKT_KICKED); - network_player_disconnected(np->localIndex); + network_send_kick(np->localIndex, EKT_KICKED, NULL); + network_player_disconnected(np->globalIndex); return; #endif } diff --git a/src/pc/network/socket/socket.c b/src/pc/network/socket/socket.c index edf179202..1791812e0 100644 --- a/src/pc/network/socket/socket.c +++ b/src/pc/network/socket/socket.c @@ -148,7 +148,7 @@ static bool ns_socket_initialize(enum NetworkType networkType, UNUSED bool recon int reuse = 1; if (setsockopt(sCurSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) { LOG_ERROR("setsockopt(SO_REUSEADDR) failed"); - } + } #ifdef SO_REUSEPORT if (setsockopt(sCurSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) { @@ -157,9 +157,9 @@ static bool ns_socket_initialize(enum NetworkType networkType, UNUSED bool recon #endif // bind the socket to any address and the specified port. int rc = socket_bind(sCurSocket, port); - if (rc != NO_ERROR) { + if (rc != NO_ERROR) { LOG_ERROR("bind returned an error."); - return false; + return false; } LOG_INFO("bound to port %u", port); } else if (networkType == NT_CLIENT) { diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index 91f689b6d..9af6c0ec1 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -42,6 +42,7 @@ #include "pc/network/version.h" #include "pc/network/socket/socket.h" #include "pc/network/network_player.h" +#include "pc/network/moderation.h" #include "pc/update_checker.h" #include "pc/djui/djui.h" #include "pc/djui/djui_unicode.h" @@ -510,6 +511,7 @@ int main(int argc, char *argv[]) { #endif configfile_load(); + moderation_list_load(); legacy_folder_handler();