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
This commit is contained in:
EmeraldLockdown 2026-03-07 20:02:48 -06:00
parent 11ee2f6e82
commit c753cfe3f0
23 changed files with 986 additions and 2 deletions

View file

@ -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()

View file

@ -2758,6 +2758,29 @@ Gets the DJUI hex color code string for the player corresponding to `localIndex`
<br />
## [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:](#)
<br />
## [network_check_singleplayer_pause](#network_check_singleplayer_pause)
### Description

View file

@ -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)

View file

@ -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"

View file

@ -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;

View file

@ -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);

View file

@ -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);
{

View file

@ -0,0 +1,146 @@
#include <stdio.h>
#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);
}

View file

@ -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*));

View file

@ -0,0 +1,97 @@
#include <stdio.h>
#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);
}

View file

@ -0,0 +1,5 @@
#pragma once
#include "djui.h"
void djui_panel_moderation_list_reload();
void djui_panel_moderation_list_create(struct DjuiBase* caller);

View file

@ -0,0 +1,115 @@
#include <stdio.h>
#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);
}

View file

@ -0,0 +1,4 @@
#pragma once
#include "djui.h"
void djui_panel_moderation_list_inspect_create(struct DjuiBase* caller);

View file

@ -0,0 +1,104 @@
#include <stdio.h>
#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);
}

View file

@ -0,0 +1,5 @@
#pragma once
#include "djui.h"
void djui_panel_moderator_menu_reload();
void djui_panel_moderator_menu_create(struct DjuiBase* caller);

View file

@ -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);

View file

@ -0,0 +1,83 @@
#include <stdlib.h>
#include <string.h>
#include <PR/ultratypes.h>
#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);
}

View file

@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
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"

View file

@ -0,0 +1,161 @@
#include <stdlib.h>
#include <string.h>
#include <PR/ultratypes.h>
#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;
}

View file

@ -0,0 +1,43 @@
#pragma once
#include <stdbool.h>
#include <PR/ultratypes.h>
#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);

View file

@ -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)) &&

View file

@ -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);

View file

@ -0,0 +1,87 @@
#include <stdio.h>
#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;
}
}