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