Huge console and terminal rewrite

This commit is contained in:
EmeraldLockdown 2026-03-24 22:57:46 -05:00
parent fb8fbd1136
commit ca897a1ef2
37 changed files with 2913 additions and 430 deletions

View file

@ -517,7 +517,7 @@ ifeq ($(DISCORD_SDK),1)
SRC_DIRS += src/pc/discord
endif
SRC_DIRS += src/pc/mumble
SRC_DIRS += src/pc/mumble src/pc/linenoise
ULTRA_SRC_DIRS := lib/src lib/src/math lib/asm lib/data
ULTRA_BIN_DIRS := lib/bin

View file

@ -35,6 +35,7 @@ in_files = [
"src/game/mario_step.h",
"src/game/mario.h",
"src/game/rumble_init.h",
"src/pc/commands.h",
"src/pc/djui/djui_popup.h",
"src/pc/network/network_utils.h",
"src/pc/djui/djui_console.h",
@ -86,6 +87,7 @@ in_files = [
override_allowed_functions = {
"src/audio/external.h": [ " play_", "fade", "current_background", "stop_", "sound_banks", "drop_queued_background_music", "set_sound_moving_speed", "background_music_default_volume", "get_sound_pan", "sound_get_level_intensity", "set_audio_muted" ],
"src/game/rumble_init.h": [ "queue_rumble_", "reset_rumble_timers" ],
"src/pc/commands.h": [ "command_message_create" ],
"src/pc/djui/djui_popup.h": [ "create" ],
"src/pc/djui/djui_language.h": [ "djui_language_get" ],
"src/pc/djui/djui_panel_menu.h": [ "djui_menu_get_rainbow_string_color" ],

View file

@ -2751,6 +2751,9 @@ DIALOG_COUNT = 170 --- @type DialogId
--- | `DIALOG_169`
--- | `DIALOG_COUNT`
--- @type integer
MAX_CONSOLE_INPUT_LENGTH = 500
CONSOLE_MESSAGE_INFO = 0 --- @type ConsoleMessageLevel
CONSOLE_MESSAGE_WARNING = 1 --- @type ConsoleMessageLevel
CONSOLE_MESSAGE_ERROR = 2 --- @type ConsoleMessageLevel

View file

@ -3789,6 +3789,12 @@ function update_character_anim_offset(m)
-- ...
end
--- @param message string
--- @param level? ConsoleMessageLevel
function command_message_create(message, level)
-- ...
end
--- @param message string
--- Creates a `message` in the game's chat box
function djui_chat_message_create(message)

View file

@ -11,6 +11,7 @@ extern "C" {
#include "game/moving_texture.h"
#include "pc/djui/djui_console.h"
#include "pc/fs/fmem.h"
#include "pc/debuglog.h"
}
#define FUNCTION_CODE (u32) 0x434E5546
@ -730,13 +731,13 @@ T *CopyBytes(const T *aPtr, u64 aSize) {
template <typename... Args>
void PrintNoNewLine(const char *aFmt, Args... aArgs) {
printf(aFmt, aArgs...);
log_to_terminal(aFmt, aArgs...);
fflush(stdout);
}
template <typename... Args>
void Print(const char *aFmt, Args... aArgs) {
printf(aFmt, aArgs...);
log_to_terminal(aFmt, aArgs...);
printf("\r\n");
fflush(stdout);
}
@ -750,7 +751,7 @@ void PrintConsole(enum ConsoleMessageLevel level, const char *aFmt, Args... aArg
template <typename... Args>
void PrintError(const char *aFmt, Args... aArgs) {
printf(aFmt, aArgs...);
log_to_terminal(aFmt, aArgs...);
printf("\r\n");
fflush(stdout);
PrintConsole(CONSOLE_MESSAGE_ERROR, aFmt, aArgs...);

View file

@ -1136,6 +1136,7 @@
<br />
## [djui_console.h](#djui_console.h)
- MAX_CONSOLE_INPUT_LENGTH
### [enum ConsoleMessageLevel](#ConsoleMessageLevel)
| Identifier | Value |

View file

@ -2661,6 +2661,33 @@ Updates Mario's current animation offset. This adjusts Mario's position based on
<br />
---
# functions from commands.h
<br />
## [command_message_create](#command_message_create)
### Lua Example
`command_message_create(message, level)`
### Parameters
| Field | Type |
| ----- | ---- |
| message | `string` |
| level | [enum ConsoleMessageLevel](constants.md#enum-ConsoleMessageLevel) |
### Returns
- None
### C Prototype
`void command_message_create(const char* message, OPTIONAL enum ConsoleMessageLevel level);`
[:arrow_up_small:](#)
<br />
---
# functions from djui_chat_message.h

View file

@ -740,6 +740,11 @@
<br />
- commands.h
- [command_message_create](functions-3.md#command_message_create)
<br />
- djui_chat_message.h
- [djui_chat_message_create](functions-3.md#djui_chat_message_create)

View file

@ -1,6 +1,7 @@
#include <stdlib.h>
#include <ctype.h>
#include "pc/ini.h"
#include "pc/debuglog.h"
#include "pc/mods/mods.h"
#include "pc/mods/mods_utils.h"
#include "player_palette.h"
@ -118,7 +119,7 @@ void player_palettes_read(const char* palettesPath, bool appendPalettes) {
if (!player_palette_init(palettesPath, path, appendPalettes)) {
#ifdef DEVELOPMENT
printf("Failed to load palette '%s.ini'\n", path);
LOG_ERROR("Failed to load palette '%s.ini'\n", path);
#endif
continue;
}
@ -140,7 +141,7 @@ void player_palettes_read(const char* palettesPath, bool appendPalettes) {
gPresetPalettes[gPresetPaletteCount].palette = palette;
gPresetPaletteCount++;
#ifdef DEVELOPMENT
printf("Loaded palette '%s.ini'\n", path);
LOG_INFO("Loaded palette '%s.ini'\n", path);
#endif
if (gPresetPaletteCount >= MAX_PRESET_PALETTES) { break; }
}
@ -160,7 +161,7 @@ void player_palette_export(char* name) {
snprintf(ppath, SYS_MAX_PATH, "%s/%s.ini", palettesPath, name);
fs_sys_mkdir(palettesPath);
printf("Saving palette as '%s.ini'\n", name);
LOG_INFO("Saving palette as '%s.ini'\n", name);
FILE* file = fopen(ppath, "w");
fprintf(file, "[PALETTE]\n\
PANTS_R = %d\n\
@ -224,7 +225,7 @@ bool player_palette_delete(const char* palettesPath, char* name, bool appendPale
}
if (remove(ppath) == 0) {
printf("Deleting palette '%s.ini'\n", name);
LOG_INFO("Deleting palette '%s.ini'\n", name);
return true;
}
return false;

View file

@ -1,276 +0,0 @@
#include "pc/network/network.h"
#include "pc/network/socket/socket.h"
#include "pc/lua/smlua_hooks.h"
#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/debuglog.h"
#include "pc/lua/utils/smlua_level_utils.h"
#include "pc/mods/mods_utils.h"
#include "level_table.h"
#ifdef DEVELOPMENT
#include "pc/dev/chat.h"
#endif
static enum ChatConfirmCommand sConfirming = CCC_NONE;
static u8 sConfirmPlayerIndex = 0;
static struct NetworkPlayer* chat_get_network_player(const char* name) {
// check for id
for (s32 i = 0; i < MAX_PLAYERS; i++) {
if (!gNetworkPlayers[i].connected) { continue; }
char id[16] = { 0 };
if (snprintf(id, 16, "%d", i) < 0) {
// do nothing
}
if (strcmp(id, name) == 0) {
return &gNetworkPlayers[i];
}
}
// check for name
for (s32 i = 0; i < MAX_PLAYERS; i++) {
if (!gNetworkPlayers[i].connected) { continue; }
if (strcmp(gNetworkPlayers[i].name, name) == 0) {
return &gNetworkPlayers[i];
}
}
return NULL;
}
static void chat_construct_player_message(struct NetworkPlayer* np, char* msg) {
char built[256] = { 0 };
snprintf(built, 256, "\\#fff982\\");
char player[128] = { 0 };
snprintf(player, 128, "%s%s\\#fff982\\", network_get_player_text_color_string(np->localIndex), np->name);
djui_language_replace(msg, &built[9], 256 - 9, '@', player);
djui_chat_message_create(built);
}
bool exec_chat_command(char* command) {
struct NetworkPlayer* npl = &gNetworkPlayers[0];
enum ChatConfirmCommand ccc = sConfirming;
sConfirming = CCC_NONE;
if (ccc != CCC_NONE && strcmp("/confirm", command) == 0) {
struct NetworkPlayer* np = &gNetworkPlayers[sConfirmPlayerIndex];
if (!np->connected) return true;
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_player_disconnected(np->localIndex);
} else {
network_send_chat_command(np->globalIndex, CCC_KICK);
}
return true;
}
}
if (gNetworkType == NT_SERVER || npl->moderator) {
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_player_disconnected(np->localIndex);
} else {
network_send_chat_command(np->globalIndex, CCC_BAN);
}
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_player_disconnected(np->localIndex);
return true;
}
if (gNetworkType == NT_SERVER && ccc == CCC_MODERATOR) {
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);
return true;
}
}
if (strcmp("/players", command) == 0) {
char line[128] = { 0 };
snprintf(line, 127, "\\#fff982\\%s:\n", DLANG(CHAT, PLAYERS));
djui_chat_message_create(line);
for (s32 i = 0; i < MAX_PLAYERS; i++) {
struct NetworkPlayer* np = &gNetworkPlayers[i];
if (!np->connected) { continue; }
if (gNetworkSystem == &gNetworkSystemSocket) {
snprintf(line, 127, "\\#82f9ff\\%u\\#fff982\\ - %s%s\n", np->globalIndex, network_get_player_text_color_string(np->localIndex), np->name);
} else {
snprintf(line, 127, "\\#82f9ff\\%u\\#fff982\\ - \\#82f9ff\\%s\\#fff982\\ - %s%s\n", np->globalIndex, gNetworkSystem->get_id_str(np->localIndex), network_get_player_text_color_string(np->localIndex), np->name);
}
djui_chat_message_create(line);
}
return true;
}
if (strcmp("/kick", command) == 0) {
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));
return true;
}
struct NetworkPlayer* np = chat_get_network_player(&command[6]);
if (np == NULL) {
djui_chat_message_create(DLANG(CHAT, PLAYER_NOT_FOUND));
return true;
}
if (np->localIndex == 0) {
djui_chat_message_create(DLANG(CHAT, SELF_KICK));
return true;
}
chat_construct_player_message(np, DLANG(CHAT, KICK_CONFIRM));
sConfirming = CCC_KICK;
sConfirmPlayerIndex = np->localIndex;
return true;
}
if (strcmp("/ban", command) == 0) {
djui_chat_message_create(DLANG(CHAT, PLAYER_NOT_FOUND));
return true;
}
if (str_starts_with(command, "/ban ")) {
if (gNetworkType != NT_SERVER && !npl->moderator) {
djui_chat_message_create(DLANG(CHAT, NO_PERMS));
return true;
}
struct NetworkPlayer* np = chat_get_network_player(&command[5]);
if (np == NULL) {
djui_chat_message_create(DLANG(CHAT, PLAYER_NOT_FOUND));
return true;
}
if (np->localIndex == 0) {
djui_chat_message_create(DLANG(CHAT, SELF_BAN));
return true;
}
chat_construct_player_message(np, DLANG(CHAT, BAN_CONFIRM));
sConfirming = CCC_BAN;
sConfirmPlayerIndex = np->localIndex;
return true;
}
if (strcmp("/permban", command) == 0) {
djui_chat_message_create(DLANG(CHAT, PLAYER_NOT_FOUND));
return true;
}
if (str_starts_with(command, "/permban ")) {
if (gNetworkType != NT_SERVER && !npl->moderator) {
djui_chat_message_create(DLANG(CHAT, NO_PERMS));
return true;
}
struct NetworkPlayer* np = chat_get_network_player(&command[9]);
if (np == NULL) {
djui_chat_message_create(DLANG(CHAT, PLAYER_NOT_FOUND));
return true;
}
if (np->localIndex == 0) {
djui_chat_message_create(DLANG(CHAT, SELF_BAN));
return true;
}
chat_construct_player_message(np, DLANG(CHAT, PERM_BAN_CONFIRM));
sConfirming = CCC_PERMBAN;
sConfirmPlayerIndex = np->localIndex;
return true;
}
if (strcmp("/moderator", command) == 0) {
djui_chat_message_create(DLANG(CHAT, PLAYER_NOT_FOUND));
return true;
}
if (str_starts_with(command, "/moderator ")) {
if (gNetworkType != NT_SERVER) {
djui_chat_message_create(DLANG(CHAT, SERVER_ONLY));
return true;
}
struct NetworkPlayer* np = chat_get_network_player(&command[11]);
if (np == NULL) {
djui_chat_message_create(DLANG(CHAT, PLAYER_NOT_FOUND));
return true;
}
if (np->localIndex == 0) {
djui_chat_message_create(DLANG(CHAT, SELF_MOD));
return true;
}
chat_construct_player_message(np, DLANG(CHAT, MOD_CONFIRM));
sConfirming = CCC_MODERATOR;
sConfirmPlayerIndex = np->localIndex;
return true;
}
if (gServerSettings.nametags) {
if (strcmp("/nametags", command) == 0) {
djui_chat_message_create(DLANG(CHAT, NAMETAGS_MISSING_PARAMETERS));
return true;
}
if (str_starts_with(command, "/nametags ")) {
char *option = &command[10];
if (strcmp("show-tag", option) == 0) {
gNametagsSettings.showSelfTag = !gNametagsSettings.showSelfTag;
} else if (strcmp("show-health", option) == 0) {
gNametagsSettings.showHealth = !gNametagsSettings.showHealth;
}
return true;
}
}
#ifdef DEVELOPMENT
if (exec_dev_chat_command(command)) {
return true;
}
#endif
return smlua_call_chat_command_hook(command);
}
void display_chat_commands(void) {
djui_chat_message_create(DLANG(CHAT, PLAYERS_DESC));
if (gNetworkType == NT_SERVER || gNetworkPlayers[0].moderator) {
djui_chat_message_create(DLANG(CHAT, KICK_DESC));
djui_chat_message_create(DLANG(CHAT, BAN_DESC));
if (gNetworkType == NT_SERVER) {
djui_chat_message_create(DLANG(CHAT, PERM_BAN_DESC));
djui_chat_message_create(DLANG(CHAT, MOD_DESC));
}
}
if (gServerSettings.nametags) {
djui_chat_message_create(DLANG(CHAT, NAMETAGS_DESC));
}
#ifdef DEVELOPMENT
dev_display_chat_commands();
#endif
if (sConfirming != CCC_NONE) { djui_chat_message_create("/confirm"); }
smlua_display_chat_commands();
}

View file

@ -1,7 +0,0 @@
#ifndef CHAT_COMMANDS_H
#define CHAT_COMMANDS_H
bool exec_chat_command(char* command);
void display_chat_commands(void);
#endif

View file

@ -3,6 +3,7 @@
#include "pc_main.h"
#include "platform.h"
#include "macros.h"
#include "debuglog.h"
#include <strings.h>
#include <stdlib.h>
@ -13,28 +14,28 @@
struct CLIOptions gCLIOpts;
static void print_help(void) {
printf("sm64coopdx\n");
log_to_terminal("sm64coopdx\n");
#if defined(_WIN32) || defined(_WIN64)
printf("--console Enables the Windows console.\n");
log_to_terminal("--console Enables the Windows console.\n");
#endif
printf("--savepath SAVEPATH Overrides the default save/config path ('!' expands to executable path).\n");
printf("--configfile CONFIGNAME Saves the configuration file as CONFIGNAME.\n");
printf("--hide-loading-screen Hides the loading screen before the menu boots up.\n");
printf("--fullscreen Starts the game in full screen mode.\n");
printf("--windowed Starts the game in windowed mode.\n");
printf("--width WIDTH Sets the window width.\n");
printf("--height HEIGHT Sets the window height.\n");
printf("--skip-intro Skips the Peach and Lakitu intros when on a zero star save.\n");
printf("--server PORT Starts the game and creates a new server on PORT.\n");
printf("--client IP PORT Starts the game and joins an existing server.\n");
printf("--coopnet PASSWORD Starts the game and creates a new CoopNet server.\n");
printf("--playername PLAYERNAME Starts the game with a specific playername.\n");
printf("--playercount PLAYERCOUNT Starts the game with a specific player count limit.\n");
printf("--skip-update-check Skips the update check when loading the game.\n");
printf("--no-discord Disables discord integration.\n");
printf("--disable-mods Disables all mods that are already enabled.\n");
printf("--enable-mod MODNAME Enables a mod.\n");
printf("--headless Enable Headless mode.");
log_to_terminal("--savepath SAVEPATH Overrides the default save/config path ('!' expands to executable path).\n");
log_to_terminal("--configfile CONFIGNAME Saves the configuration file as CONFIGNAME.\n");
log_to_terminal("--hide-loading-screen Hides the loading screen before the menu boots up.\n");
log_to_terminal("--fullscreen Starts the game in full screen mode.\n");
log_to_terminal("--windowed Starts the game in windowed mode.\n");
log_to_terminal("--width WIDTH Sets the window width.\n");
log_to_terminal("--height HEIGHT Sets the window height.\n");
log_to_terminal("--skip-intro Skips the Peach and Lakitu intros when on a zero star save.\n");
log_to_terminal("--server PORT Starts the game and creates a new server on PORT.\n");
log_to_terminal("--client IP PORT Starts the game and joins an existing server.\n");
log_to_terminal("--coopnet PASSWORD Starts the game and creates a new CoopNet server.\n");
log_to_terminal("--playername PLAYERNAME Starts the game with a specific playername.\n");
log_to_terminal("--playercount PLAYERCOUNT Starts the game with a specific player count limit.\n");
log_to_terminal("--skip-update-check Skips the update check when loading the game.\n");
log_to_terminal("--no-discord Disables discord integration.\n");
log_to_terminal("--disable-mods Disables all mods that are already enabled.\n");
log_to_terminal("--enable-mod MODNAME Enables a mod.\n");
log_to_terminal("--headless Enable Headless mode.");
}
static inline int arg_string(const char *name, const char *value, char *target, int maxLength) {

483
src/pc/commands.c Normal file
View file

@ -0,0 +1,483 @@
#include "pc/network/network.h"
#include "pc/network/socket/socket.h"
#include "pc/lua/smlua_hooks.h"
#include "pc/djui/djui_language.h"
#include "pc/djui/djui_chat_message.h"
#include "pc/djui/djui_console.h"
#include "commands.h"
#include "pc/network/ban_list.h"
#include "pc/network/moderator_list.h"
#include "pc/debuglog.h"
#include "pc/lua/utils/smlua_level_utils.h"
#include "pc/mods/mods_utils.h"
#include "pc/pc_main.h"
#include "level_table.h"
#ifdef DEVELOPMENT
#include "pc/dev/chat.h"
#endif
static bool command_help(UNUSED const char* message);
static bool command_players(UNUSED const char* message);
static bool command_kick(const char* message);
static bool command_ban(const char* message);
static bool command_permaban(const char* message);
static bool command_mod(const char* message);
static bool command_confirm(UNUSED const char* message);
static bool command_nametags(const char* message);
static bool command_clear(UNUSED const char* message);
static bool command_quit(UNUSED const char* message);
static bool command_host(UNUSED const char* message);
static bool command_rehost(UNUSED const char* message);
static bool command_stop_hosting(UNUSED const char* message);
static bool command_disconnect(UNUSED const char* message);
static struct Command sCommands[] = {
{
.command = "players",
.description = "PLAYERS_DESC",
.action = command_players,
.active = true,
.isChatCommand = true
},
{
.command = "kick",
.description = "KICK_DESC",
.action = command_kick,
.active = true,
.isChatCommand = true
},
{
.command = "ban",
.description = "BAN_DESC",
.action = command_ban,
.active = true,
.isChatCommand = true
},
{
.command = "permaban",
.description = "PERM_BAN_DESC",
.action = command_permaban,
.active = true,
.isChatCommand = true
},
{
.command = "moderator",
.description = "MOD_DESC",
.action = command_mod,
.active = true,
.isChatCommand = true
},
{
.command = "confirm",
.description = "",
.action = command_confirm,
.active = false,
.isChatCommand = true
},
{
.command = "nametags",
.description = "NAMETAGS_DESC",
.action = command_nametags,
.active = false,
.isChatCommand = true
},
{
.command = "clear",
.description = "/clear - Clears the current console's text",
.action = command_clear,
.active = true,
.isChatCommand = false
},
{
.command = "quit",
.description = "/quit - Quits the game",
.action = command_quit,
.active = true,
.isChatCommand = false
},
{
.command = "host",
.description = "/host - Hosts a new game using your saved server settings and mod list",
.action = command_host,
.active = true,
.isChatCommand = false
},
{
.command = "rehost",
.description = "/rehost - Rehosts a currently active game",
.action = command_rehost,
.active = false,
.isChatCommand = false
},
{
.command = "stop-hosting",
.description = "/stop-hosting - Stop hosting a currently active game",
.action = command_stop_hosting,
.active = false,
.isChatCommand = false
},
{
.command = "disconnect",
.description = "/disconnect - Disconnects from a currently active game",
.action = command_disconnect,
.active = false,
.isChatCommand = false
},
};
static unsigned int sCommandCount = sizeof(sCommands) / sizeof(struct Command);
static enum ChatConfirmCommand sConfirming = CCC_NONE;
static u8 sConfirmPlayerIndex = 0;
static struct NetworkPlayer* chat_get_network_player(const char* name) {
// check for id
for (s32 i = 0; i < MAX_PLAYERS; i++) {
if (!gNetworkPlayers[i].connected) { continue; }
char id[16] = { 0 };
if (snprintf(id, 16, "%d", i) < 0) {
// do nothing
}
if (strcmp(id, name) == 0) {
return &gNetworkPlayers[i];
}
}
// check for name
for (s32 i = 0; i < MAX_PLAYERS; i++) {
if (!gNetworkPlayers[i].connected) { continue; }
if (strcmp(gNetworkPlayers[i].name, name) == 0) {
return &gNetworkPlayers[i];
}
}
return NULL;
}
static void chat_construct_player_message(struct NetworkPlayer* np, char* msg) {
char built[256] = { 0 };
snprintf(built, 256, "\\#fff982\\");
char player[128] = { 0 };
snprintf(player, 128, "%s%s\\#fff982\\", network_get_player_text_color_string(np->localIndex), np->name);
djui_language_replace(msg, &built[9], 256 - 9, '@', player);
command_message_create(built, CONSOLE_MESSAGE_INFO);
}
static bool command_help(UNUSED const char* message) {
for (unsigned int i = 0; i < sCommandCount; i++) {
if (!sCommands[i].active) continue;
if (!sCommands[i].isChatCommand && gDjuiChatBoxFocus) continue;
command_message_create(djui_language_get("CHAT", sCommands[i].description), CONSOLE_MESSAGE_INFO);
}
#ifdef DEVELOPMENT
dev_display_chat_commands();
#endif
smlua_display_chat_commands(gDjuiConsoleFocus);
return true;
}
static bool command_players(UNUSED const char* message) {
char line[128] = { 0 };
snprintf(line, 127, "\\#fff982\\%s:\n", DLANG(CHAT, PLAYERS));
command_message_create(line, CONSOLE_MESSAGE_INFO);
for (s32 i = 0; i < MAX_PLAYERS; i++) {
struct NetworkPlayer* np = &gNetworkPlayers[i];
if (!np->connected) { continue; }
if (gNetworkSystem == &gNetworkSystemSocket) {
snprintf(line, 127, "\\#82f9ff\\%u\\#fff982\\ - %s%s\n", np->globalIndex, network_get_player_text_color_string(np->localIndex), np->name);
} else {
snprintf(line, 127, "\\#82f9ff\\%u\\#fff982\\ - \\#82f9ff\\%s\\#fff982\\ - %s%s\n", np->globalIndex, gNetworkSystem->get_id_str(np->localIndex), network_get_player_text_color_string(np->localIndex), np->name);
}
command_message_create(line, CONSOLE_MESSAGE_INFO);
}
return true;
}
static bool command_kick(const char* message) {
struct NetworkPlayer* npl = &gNetworkPlayers[0];
if (gNetworkType != NT_SERVER && !npl->moderator) {
command_message_create(DLANG(CHAT, NO_PERMS), CONSOLE_MESSAGE_ERROR);
return true;
}
struct NetworkPlayer* np = chat_get_network_player(message);
if (np == NULL) {
command_message_create(DLANG(CHAT, PLAYER_NOT_FOUND), CONSOLE_MESSAGE_ERROR);
return true;
}
if (np->localIndex == 0) {
command_message_create(DLANG(CHAT, SELF_KICK), CONSOLE_MESSAGE_ERROR);
return true;
}
chat_construct_player_message(np, DLANG(CHAT, KICK_CONFIRM));
sConfirming = CCC_KICK;
sConfirmPlayerIndex = np->localIndex;
struct Command* confirmCommand = get_command("confirm");
if (confirmCommand) confirmCommand->active = true;
return true;
}
static bool command_ban(const char* message) {
struct NetworkPlayer* npl = &gNetworkPlayers[0];
if (gNetworkType != NT_SERVER && !npl->moderator) {
command_message_create(DLANG(CHAT, NO_PERMS), CONSOLE_MESSAGE_ERROR);
return true;
}
struct NetworkPlayer* np = chat_get_network_player(message);
if (np == NULL) {
command_message_create(DLANG(CHAT, PLAYER_NOT_FOUND), CONSOLE_MESSAGE_ERROR);
return true;
}
if (np->localIndex == 0) {
command_message_create(DLANG(CHAT, SELF_BAN), CONSOLE_MESSAGE_ERROR);
return true;
}
chat_construct_player_message(np, DLANG(CHAT, BAN_CONFIRM));
sConfirming = CCC_BAN;
sConfirmPlayerIndex = np->localIndex;
struct Command* confirmCommand = get_command("confirm");
if (confirmCommand) confirmCommand->active = true;
return true;
}
static bool command_permaban(const char* message) {
struct NetworkPlayer* npl = &gNetworkPlayers[0];
if (gNetworkType != NT_SERVER && !npl->moderator) {
command_message_create(DLANG(CHAT, NO_PERMS), CONSOLE_MESSAGE_ERROR);
return true;
}
struct NetworkPlayer* np = chat_get_network_player(message);
if (np == NULL) {
command_message_create(DLANG(CHAT, PLAYER_NOT_FOUND), CONSOLE_MESSAGE_ERROR);
return true;
}
if (np->localIndex == 0) {
command_message_create(DLANG(CHAT, SELF_BAN), CONSOLE_MESSAGE_ERROR);
return true;
}
chat_construct_player_message(np, DLANG(CHAT, PERM_BAN_CONFIRM));
sConfirming = CCC_PERMBAN;
sConfirmPlayerIndex = np->localIndex;
struct Command* confirmCommand = get_command("confirm");
if (confirmCommand) confirmCommand->active = true;
return true;
}
static bool command_mod(const char* message) {
if (gNetworkType != NT_SERVER) {
command_message_create(DLANG(CHAT, SERVER_ONLY), CONSOLE_MESSAGE_ERROR);
return true;
}
struct NetworkPlayer* np = chat_get_network_player(message);
if (np == NULL) {
command_message_create(DLANG(CHAT, PLAYER_NOT_FOUND), CONSOLE_MESSAGE_ERROR);
return true;
}
if (np->localIndex == 0) {
command_message_create(DLANG(CHAT, SELF_MOD), CONSOLE_MESSAGE_ERROR);
return true;
}
chat_construct_player_message(np, DLANG(CHAT, MOD_CONFIRM));
sConfirming = CCC_MODERATOR;
sConfirmPlayerIndex = np->localIndex;
struct Command* confirmCommand = get_command("confirm");
if (confirmCommand) confirmCommand->active = true;
return true;
}
static bool command_confirm(UNUSED const char* message) {
// deactivate command
struct Command* confirmCommand = get_command("confirm");
if (confirmCommand) confirmCommand->active = false;
enum ChatConfirmCommand ccc = sConfirming;
sConfirming = CCC_NONE;
struct NetworkPlayer* npl = &gNetworkPlayers[0];
struct NetworkPlayer* np = &gNetworkPlayers[sConfirmPlayerIndex];
if (!np->connected) return true;
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_player_disconnected(np->localIndex);
} else {
network_send_chat_command(np->globalIndex, CCC_KICK);
}
return true;
}
}
if (gNetworkType == NT_SERVER || npl->moderator) {
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_player_disconnected(np->localIndex);
} else {
network_send_chat_command(np->globalIndex, CCC_BAN);
}
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_player_disconnected(np->localIndex);
return true;
}
if (gNetworkType == NT_SERVER && ccc == CCC_MODERATOR) {
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);
return true;
}
return false;
}
static bool command_nametags(const char* message) {
if (strcmp("show-tag", message) == 0) {
gNametagsSettings.showSelfTag = !gNametagsSettings.showSelfTag;
} else if (strcmp("show-health", message) == 0) {
gNametagsSettings.showHealth = !gNametagsSettings.showHealth;
}
command_message_create(DLANG(CHAT, NAMETAGS_MISSING_PARAMETERS), CONSOLE_MESSAGE_ERROR);
return true;
}
static bool command_clear(UNUSED const char* message) {
djui_console_clear();
terminal_clear();
return true;
}
static bool command_quit(UNUSED const char* message) {
game_exit();
return true;
}
extern void djui_panel_do_host(bool reconnecting, bool playSound);
static bool command_host(UNUSED const char* message) {
djui_panel_do_host(false, true);
return true;
}
static bool command_rehost(UNUSED const char* message) {
network_rehost_begin();
return true;
}
static bool command_stop_hosting(UNUSED const char* message) {
network_reset_reconnect_and_rehost();
network_shutdown(true, false, false, false);
return true;
}
static bool command_disconnect(UNUSED const char* message) {
network_reset_reconnect_and_rehost();
network_shutdown(true, false, false, false);
return true;
}
static void set_command_active(const char* name, bool active) {
struct Command* command = get_command(name);
if (command) command->active = active;
}
struct Command* get_command(const char* name) {
for (unsigned int i = 0; i < sCommandCount; i++) {
if (strcmp(sCommands[i].command, name) == 0) {
return &sCommands[i];
}
}
return NULL;
}
void command_message_create(const char* message, OPTIONAL enum ConsoleMessageLevel level) {
if (gDjuiChatBoxFocus) {
size_t newMsgLength = strlen(message) + 12;
char newMsg[newMsgLength];
switch (level) {
case CONSOLE_MESSAGE_INFO:
snprintf(newMsg, newMsgLength, "\\#dcdcdc\\%s", message);
break;
case CONSOLE_MESSAGE_WARNING:
snprintf(newMsg, newMsgLength, "\\#ffffa0\\%s", message);
break;
case CONSOLE_MESSAGE_ERROR:
snprintf(newMsg, newMsgLength, "\\#ffa0a0\\%s", message);
break;
default:
snprintf(newMsg, newMsgLength, "\\#dcdcdc\\%s", message);
break;
}
djui_chat_message_create(newMsg);
} else {
djui_console_message_create(message, level);
char* colorCode;
switch (level) {
case CONSOLE_MESSAGE_WARNING: colorCode = "\x1b[33m"; break;
case CONSOLE_MESSAGE_ERROR: colorCode = "\x1b[31m"; break;
default: colorCode = "\x1b[0m"; break;
}
log_to_terminal("%s%s\x1b[0m\n", colorCode, message);
}
}
void run_command(char* command) {
// directly set active state of certain commands
set_command_active("nametags", gServerSettings.nametags);
set_command_active("host", gDjuiInMainMenu);
set_command_active("rehost", gNetworkType == NT_SERVER && !gDjuiInMainMenu);
set_command_active("stop-hosting", gNetworkType == NT_SERVER && !gDjuiInMainMenu);
set_command_active("disconnect", gNetworkType == NT_CLIENT && !gDjuiInMainMenu);
// directly check help command
if (strcmp(command, "help") == 0 || strcmp(command, "?") == 0 || strcmp(command, "") == 0) {
command_help(NULL);
return;
}
// loop through builtin commands first
for (unsigned int i = 0; i < sCommandCount; i++) {
// sanity checks
if (sCommands[i].command[0] == '\0') continue;
if (!sCommands[i].action) continue;
if (!sCommands[i].active) continue;
if (!sCommands[i].isChatCommand && gDjuiChatBoxFocus) continue;
// compare strings
size_t commandLength = strlen(sCommands[i].command);
if (!str_starts_with(command, sCommands[i].command)) continue;
if (command[commandLength] != '\0' && command[commandLength] != ' ') continue;
// get args
char* arguments = command + commandLength;
if (*arguments != '\0') arguments++;
// run action
if (sCommands[i].action(arguments)) return;
}
#ifdef DEVELOPMENT
// check development commands
if (exec_dev_chat_command(command)) return;
#endif
// check lua commands
if (smlua_call_chat_command_hook(command)) return;
// no command exists, alert the user
char extendedUnknownCommandMessage[MAX_CONSOLE_INPUT_LENGTH];
snprintf(extendedUnknownCommandMessage, sizeof(extendedUnknownCommandMessage), "%s (/help)", DLANG(CHAT, UNRECOGNIZED));
command_message_create(extendedUnknownCommandMessage, CONSOLE_MESSAGE_INFO);
}

17
src/pc/commands.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#define MAX_COMMAND_LEN 128
// only applies to builtin commands
#define MAX_COMMAND_DESC_LEN 512
struct Command {
char command[MAX_COMMAND_LEN];
char description[MAX_COMMAND_DESC_LEN];
bool (*action)(const char*);
bool active;
bool isChatCommand;
};
struct Command* get_command(const char* name);
void run_command(char* command);
void command_message_create(const char* message, OPTIONAL enum ConsoleMessageLevel level);

View file

@ -877,7 +877,7 @@ void configfile_save(const char *filename) {
return;
}
printf("Saving configuration to '%s'\n", filename);
LOG_INFO("Saving configuration to '%s'\n", filename);
for (unsigned int i = 0; i < ARRAY_LEN(options); i++) {
const struct ConfigOption *option = &options[i];

View file

@ -5,8 +5,11 @@
#include <time.h>
#include "pc/network/network.h"
#include "pc/djui/djui_console.h"
#include "pc/terminal.h"
static void _debuglog_print_timestamp(void) {
#define MAX_LOG_SIZE 8192
static int _debuglog_print_timestamp(char* buffer, size_t bufferSize) {
time_t ltime = time(NULL);
#if defined(_WIN32)
char* str = asctime(localtime(&ltime));
@ -15,32 +18,60 @@ static void _debuglog_print_timestamp(void) {
localtime_r(&ltime, &ltime2);
char* str = asctime(&ltime2);
#endif
printf("%.*s", (int)strlen(str) - 1, str);
return snprintf(buffer, bufferSize, "%.*s", (int)strlen(str) - 1, str);
}
static void _debuglog_print_network_type(void) {
printf(" [%02d] ", (gNetworkPlayerLocal != NULL) ? gNetworkPlayerLocal->globalIndex : -1);
static int _debuglog_print_network_type(char* buffer, size_t bufferSize) {
return snprintf(buffer, bufferSize, " [%02d] ", (gNetworkPlayerLocal != NULL) ? gNetworkPlayerLocal->globalIndex : -1);
}
static void _debuglog_print_log_type(const char* logType) {
printf("[%s] ", logType);
static int _debuglog_print_log_type(const char* logType, char* buffer, size_t bufferSize) {
return snprintf(buffer, bufferSize, "[%s] ", logType);
}
static void _debuglog_print_short_filename(const char* filename) {
static int _debuglog_print_short_filename(const char* filename, char* buffer, size_t bufferSize) {
const char* last = strrchr(filename, '/');
if (last != NULL) {
printf("%s: ", last + 1);
return snprintf(buffer, bufferSize, "%s: ", last + 1);
}
else {
printf("???: ");
return snprintf(buffer, bufferSize, "???: ");
}
}
static void _debuglog_print_log(const char* logType, char* filename) {
_debuglog_print_timestamp();
_debuglog_print_network_type();
_debuglog_print_log_type(logType);
_debuglog_print_short_filename(filename);
static inline void _debuglog_print_log(const char* logType, const char* filename, const char* fmt, ...) {
char log[MAX_LOG_SIZE];
size_t capacity = MAX_LOG_SIZE;
char* buffer = log;
int len;
len = _debuglog_print_timestamp(buffer, capacity);
if (len < 0 || (size_t)len >= capacity) return;
buffer += len; capacity -= len;
len = _debuglog_print_network_type(buffer, capacity);
if (len < 0 || (size_t)len >= capacity) return;
buffer += len; capacity -= len;
len = _debuglog_print_log_type(logType, buffer, capacity);
if (len < 0 || (size_t)len >= capacity) return;
buffer += len; capacity -= len;
len = _debuglog_print_short_filename(filename, buffer, capacity);
if (len < 0 || (size_t)len >= capacity) return;
buffer += len; capacity -= len;
va_list args;
va_start(args, fmt);
len = vsnprintf(buffer, capacity, fmt, args);
va_end(args);
if (len < 0) return;
log_to_terminal("%s\n", log);
}
#if defined(DISABLE_MODULE_LOG)
@ -48,9 +79,9 @@ static void _debuglog_print_log(const char* logType, char* filename) {
#define LOG_INFO(...)
#define LOG_ERROR(...)
#else
#define LOG_DEBUG(...) (configDebugPrint ? ( _debuglog_print_log("DEBUG", __FILE__), printf(__VA_ARGS__), printf("\n") ) : 0)
#define LOG_INFO(...) ((configDebugInfo || gCLIOpts.headless) ? ( _debuglog_print_log("INFO", __FILE__), printf(__VA_ARGS__), printf("\n") ) : 0)
#define LOG_ERROR(...) (configDebugError ? ( _debuglog_print_log("ERROR", __FILE__), printf(__VA_ARGS__), printf("\n") ) : 0)
#define LOG_DEBUG(...) (configDebugPrint ? ( _debuglog_print_log("DEBUG", __FILE__, __VA_ARGS__) ) : 0)
#define LOG_INFO(...) ((configDebugInfo || gCLIOpts.headless) ? ( _debuglog_print_log("INFO", __FILE__, __VA_ARGS__) ) : 0)
#define LOG_ERROR(...) (configDebugError ? ( _debuglog_print_log("ERROR", __FILE__, __VA_ARGS__) ) : 0)
#endif
#define LOG_CONSOLE(...) { snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__), djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_INFO); }

View file

@ -3,7 +3,7 @@
#include "pc/lua/smlua_hooks.h"
#include "pc/djui/djui_language.h"
#include "pc/djui/djui_chat_message.h"
#include "pc/chat_commands.h"
#include "pc/commands.h"
#include "pc/network/ban_list.h"
#include "pc/network/moderator_list.h"
#include "pc/debuglog.h"
@ -66,12 +66,12 @@ static s32 get_level_abbreviation_alt(const char *str) {
}
bool exec_dev_chat_command(char* command) {
if (strcmp("/warp", command) == 0) {
if (strcmp("warp", command) == 0) {
djui_chat_message_create("Missing parameters: [LEVEL] [AREA] [ACT]");
return true;
}
if (str_starts_with(command, "/warp ")) {
if (str_starts_with(command, "warp ")) {
static const struct { const char *name; s32 num; } sLevelNumByName[] = {
#undef STUB_LEVEL
#undef DEFINE_LEVEL
@ -85,7 +85,7 @@ bool exec_dev_chat_command(char* command) {
s32 act = 0;
// Params
char *paramLevel = command + 6;
char *paramLevel = command + 5;
if (*paramLevel == 0 || *paramLevel == ' ') {
djui_chat_message_create("Missing parameters: [LEVEL]");
return true;
@ -161,23 +161,23 @@ bool exec_dev_chat_command(char* command) {
return true;
}
if (strcmp("/lua", command) == 0) {
if (strcmp("lua", command) == 0) {
djui_chat_message_create("Missing parameter: [LUA]");
return true;
}
if (str_starts_with(command, "/lua ")) {
smlua_exec_str(&command[5]);
if (str_starts_with(command, "lua ")) {
smlua_exec_str(&command[4]);
return true;
}
if (strcmp("/luaf", command) == 0) {
if (strcmp("luaf", command) == 0) {
djui_chat_message_create("Missing parameter: [FILENAME]");
return true;
}
if (str_starts_with(command, "/luaf ")) {
smlua_exec_file(&command[6]);
if (str_starts_with(command, "luaf ")) {
smlua_exec_file(&command[5]);
return true;
}
@ -185,8 +185,8 @@ bool exec_dev_chat_command(char* command) {
}
void dev_display_chat_commands(void) {
djui_chat_message_create("/warp [LEVEL] [AREA] [ACT] - Level can be either a numeric value or a shorthand name");
djui_chat_message_create("/lua [LUA] - Execute Lua code from a string");
djui_chat_message_create("/luaf [FILENAME] - Execute Lua code from a file");
command_message_create("/warp [LEVEL] [AREA] [ACT] - Level can be either a numeric value or a shorthand name");
command_message_create("/lua [LUA] - Execute Lua code from a string");
command_message_create("/luaf [FILENAME] - Execute Lua code from a file");
}
#endif

View file

@ -46,7 +46,7 @@ void discord_fatal(int rc) {
}
static void get_oauth2_token_callback(UNUSED void* data, enum EDiscordResult result, struct DiscordOAuth2Token* token) {
LOG_INFO("> get_oauth2_token_callback returned %d", result);
LOG_INFO("get_oauth2_token_callback returned %d", result);
if (result != DiscordResult_Ok) { return; }
LOG_INFO("OAuth2 token: %s", token->access_token);
}
@ -72,7 +72,7 @@ static void register_launch_command(void) {
}
static void on_current_user_update(UNUSED void* data) {
LOG_INFO("> on_current_user_update");
LOG_INFO("on_current_user_update");
struct DiscordUser user = { 0 };
app.users->get_current_user(app.users, &user);
@ -95,7 +95,7 @@ static void on_current_user_update(UNUSED void* data) {
}
struct IDiscordUserEvents* discord_user_initialize(void) {
LOG_INFO("> discord_user_intitialize");
LOG_INFO("discord_user_intitialize");
static struct IDiscordUserEvents events = { 0 };
events.on_current_user_update = on_current_user_update;
return &events;

View file

@ -16,12 +16,12 @@ static uint64_t sQueuedLobbyId = 0;
static char sQueuedLobbyPassword[64] = "";
static void on_activity_update_callback(UNUSED void* data, enum EDiscordResult result) {
LOG_INFO("> on_activity_update_callback returned %d", result);
LOG_INFO("on_activity_update_callback returned %d", result);
DISCORD_REQUIRE(result);
}
static void on_activity_join(UNUSED void* data, const char* secret) {
LOG_INFO("> on_activity_join, secret: %s", secret);
LOG_INFO("on_activity_join, secret: %s", secret);
char *token;
// extract lobby type
@ -52,12 +52,12 @@ static void on_activity_join(UNUSED void* data, const char* secret) {
}
static void on_activity_join_request_callback(UNUSED void* data, enum EDiscordResult result) {
LOG_INFO("> on_activity_join_request_callback returned %d", (int)result);
LOG_INFO("on_activity_join_request_callback returned %d", (int)result);
DISCORD_REQUIRE(result);
}
static void on_activity_join_request(UNUSED void* data, struct DiscordUser* user) {
LOG_INFO("> on_activity_join_request from " DISCORD_ID_FORMAT, user->id);
LOG_INFO("on_activity_join_request from " DISCORD_ID_FORMAT, user->id);
}
static void strncat_len(char* destination, char* source, size_t destinationLength, size_t sourceLength) {

View file

@ -3,7 +3,7 @@
#include <string.h>
#include "pc/network/network.h"
#include "pc/lua/smlua_hooks.h"
#include "pc/chat_commands.h"
#include "pc/commands.h"
#include "pc/configfile.h"
#include "djui.h"
#include "engine/math_util.h"
@ -139,13 +139,7 @@ static void djui_chat_box_input_enter(struct DjuiInputbox* chatInput) {
if (strlen(chatInput->buffer) != 0) {
sent_history_add_message(&sentHistory, chatInput->buffer);
if (chatInput->buffer[0] == '/') {
if (strcmp(chatInput->buffer, "/help") == 0 || strcmp(chatInput->buffer, "/?") == 0 || strcmp(chatInput->buffer, "/") == 0) {
display_chat_commands();
} else if (!exec_chat_command(chatInput->buffer)) {
char extendedUnknownCommandMessage[MAX_CHAT_MSG_LENGTH];
snprintf(extendedUnknownCommandMessage, sizeof(extendedUnknownCommandMessage), "%s (/help)", DLANG(CHAT, UNRECOGNIZED));
djui_chat_message_create(extendedUnknownCommandMessage);
}
run_command(chatInput->buffer + 1);
} else {
djui_chat_message_create_from(gNetworkPlayerLocal->globalIndex, chatInput->buffer);
network_send_chat(chatInput->buffer, gNetworkPlayerLocal->globalIndex);
@ -417,7 +411,7 @@ static bool djui_chat_box_input_on_key_down(UNUSED struct DjuiBase* base, int sc
sent_history_init(&sentHistory);
if (gDjuiChatBox == NULL) { return false; }
f32 pageAmount = gDjuiChatBox->chatContainer->base.elem.height * 3.0f / 4.0f;
char previousText[MAX_CHAT_MSG_LENGTH];

View file

@ -3,6 +3,7 @@
#include "djui.h"
#include "djui_console.h"
#include "pc/pc_main.h"
#include "pc/commands.h"
#include "engine/math_util.h"
#define MAX_CONSOLE_MESSAGES 500
@ -12,6 +13,7 @@ bool gDjuiConsoleFocus = false;
char gDjuiConsoleTmpBuffer[CONSOLE_MAX_TMP_BUFFER] = "";
u32 sDjuiConsoleMessages = 0;
bool sDjuiConsoleQueueMessages = true;
bool sIgnoreNextTextInput = false;
struct ConsoleQueuedMessage {
char* message;
@ -52,12 +54,13 @@ void djui_console_message_dequeue(void) {
bool djui_console_render(struct DjuiBase* base) {
struct DjuiConsole* console = (struct DjuiConsole*)base;
djui_base_set_size(base, gDjuiRoot->base.width.value, gDjuiRoot->base.height.value * 0.5f);
djui_base_set_size(&console->base, gDjuiRoot->base.width.value, gDjuiRoot->base.height.value * 0.5f);
djui_base_set_size(&console->rectContainer->base, gDjuiRoot->base.width.value, gDjuiRoot->base.height.value * 0.5f - 32);
if (console->scrolling) {
f32 yMax = console->base.comp.height - console->flow->base.height.value;
f32 yMax = console->base.comp.height - console->flow->base.height.value - 32;
f32 target = console->flow->base.y.value + (console->scrollY - console->flow->base.y.value) * (configSmoothScrolling ? 0.5f : 1.f);
console->flow->base.y.value = clamp(target, yMax, 0.f);
console->flow->base.y.value = clamp(target, yMax, 0.0f);
if (target < yMax || 0.f < target) {
console->scrollY = clamp(target, yMax, 0.f);
if (target > 0.f) { gDjuiConsole->scrolling = false; }
@ -75,12 +78,13 @@ static void djui_console_destroy(struct DjuiBase* base) {
void djui_console_toggle(void) {
if (gDjuiConsole == NULL) { return; }
sIgnoreNextTextInput = true;
gDjuiConsoleFocus = !gDjuiConsoleFocus;
djui_base_set_visible(&gDjuiConsole->base, gDjuiConsoleFocus);
if (gDjuiConsoleFocus) {
if (gDjuiChatBoxFocus) { djui_chat_box_toggle(); }
djui_interactable_set_input_focus(&gDjuiConsole->base);
djui_interactable_set_input_focus(&gDjuiConsole->inputbox->base);
} else {
djui_interactable_set_input_focus(NULL);
}
@ -96,18 +100,34 @@ static void djui_console_on_scroll(UNUSED struct DjuiBase *base, UNUSED float x,
if (gDjuiInputHeldShift) { y *= 3; }
gDjuiConsole->scrollY -= y;
if (!gDjuiConsole->scrolling) {
gDjuiConsole->scrolling = y > 0 && gDjuiConsole->scrollY > yMax;
}
}
static bool djui_console_on_key_down(UNUSED struct DjuiBase* base, int scancode) {
static void djui_console_enter() {
char* buffer = gDjuiConsole->inputbox->buffer;
if (strcmp(buffer, "") == 0) return;
if (buffer[0] == '/') buffer++;
run_command(buffer);
djui_inputbox_set_text(gDjuiConsole->inputbox, "");
djui_inputbox_select_all(gDjuiConsole->inputbox);
}
static bool djui_console_on_key_down(struct DjuiBase* base, int scancode) {
if (gDjuiConsole == NULL) { return false; }
f32 yMax = gDjuiConsole->base.comp.height - gDjuiConsole->flow->base.height.value;
f32 pageAmount = gDjuiConsole->base.comp.height * 3.0f / 4.0f;
for (int i = 0; i < MAX_BINDS; i++) {
if (scancode == (int)configKeyConsole[i]) {
djui_console_toggle();
return true;
}
}
switch (scancode) {
case SCANCODE_UP:
gDjuiConsole->scrollY -= 15;
@ -121,8 +141,14 @@ static bool djui_console_on_key_down(UNUSED struct DjuiBase* base, int scancode)
case SCANCODE_PAGE_DOWN:
gDjuiConsole->scrollY += pageAmount;
break;
case SCANCODE_ESCAPE: djui_console_toggle(); break;
default: break;
case SCANCODE_ENTER:
djui_console_enter();
break;
case SCANCODE_ESCAPE:
djui_console_toggle();
break;
default:
return djui_inputbox_on_key_down(base, scancode);
}
if (!gDjuiConsole->scrolling) {
@ -131,6 +157,27 @@ static bool djui_console_on_key_down(UNUSED struct DjuiBase* base, int scancode)
return true;
}
static void djui_console_on_text_input(struct DjuiBase* base, char* text) {
if (sIgnoreNextTextInput) {
sIgnoreNextTextInput = false;
return;
}
djui_inputbox_on_text_input(base, text);
}
void djui_console_clear() {
if (gDjuiConsole == NULL) { return; }
struct DjuiBase* cfBase = &gDjuiConsole->flow->base;
djui_base_destroy_children(cfBase);
cfBase->height.value = 0;
cfBase->y.value = 0;
gDjuiConsole->scrollY = 0;
gDjuiConsole->scrolling = false;
sDjuiConsoleMessages = 0;
}
void djui_console_message_create(const char* message, enum ConsoleMessageLevel level) {
if (sDjuiConsoleQueueMessages || !gDjuiConsole) {
djui_console_message_queue(message, level);
@ -205,11 +252,13 @@ struct DjuiConsole* djui_console_create(void) {
djui_base_set_padding(base, 0, 8, 8, 8);
djui_base_set_visible(base, false);
djui_interactable_create(base, NULL);
djui_interactable_hook_key(base, djui_console_on_key_down, NULL);
djui_interactable_hook_scroll(base, djui_console_on_scroll);
struct DjuiRect* rectContainer = djui_rect_container_create(base, 0);
djui_base_set_alignment(&rectContainer->base, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP);
djui_base_set_size_type(&rectContainer->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
djui_base_set_size(&rectContainer->base, 1.0f, gDjuiRoot->base.height.value * 0.5f);
console->rectContainer = rectContainer;
struct DjuiFlowLayout* flow = djui_flow_layout_create(base);
struct DjuiFlowLayout* flow = djui_flow_layout_create(&rectContainer->base);
struct DjuiBase* cfBase = &flow->base;
djui_base_set_alignment(cfBase, DJUI_HALIGN_LEFT, DJUI_VALIGN_BOTTOM);
djui_base_set_location(cfBase, 0, 0);
@ -223,6 +272,21 @@ struct DjuiConsole* djui_console_create(void) {
cfBase->abandonAfterChildRenderFail = true;
console->flow = flow;
struct DjuiInputbox* inputbox = djui_inputbox_create(base, MAX_CONSOLE_INPUT_LENGTH);
inputbox->base.interactable->update_style = NULL;
djui_base_set_border_color(&inputbox->base, 0, 0, 0, 0);
djui_base_set_color(&inputbox->base, 0, 0, 0, 0);
djui_base_set_size_type(&inputbox->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
djui_base_set_size(&inputbox->base, 1.0f, 32);
djui_base_set_alignment(&inputbox->base, DJUI_HALIGN_CENTER, DJUI_VALIGN_BOTTOM);
djui_interactable_hook_key(&inputbox->base, djui_console_on_key_down, djui_inputbox_on_key_up);
djui_interactable_hook_text_input(&inputbox->base, djui_console_on_text_input);
djui_interactable_hook_text_editing(&inputbox->base, djui_inputbox_on_text_editing);
djui_interactable_hook_scroll(&inputbox->base, djui_console_on_scroll);
djui_inputbox_set_text_color(inputbox, 255, 255, 255, 255);
inputbox->yOffset = 6;
console->inputbox = inputbox;
gDjuiConsole = console;
return console;

View file

@ -1,6 +1,8 @@
#pragma once
#include "djui.h"
#define MAX_CONSOLE_INPUT_LENGTH 500
enum ConsoleMessageLevel {
CONSOLE_MESSAGE_INFO,
CONSOLE_MESSAGE_WARNING,
@ -9,7 +11,9 @@ enum ConsoleMessageLevel {
struct DjuiConsole {
struct DjuiBase base;
struct DjuiRect* rectContainer;
struct DjuiFlowLayout* flow;
struct DjuiInputbox* inputbox;
bool scrolling;
f32 scrollY;
};
@ -20,6 +24,7 @@ extern bool gDjuiConsoleFocus;
extern char gDjuiConsoleTmpBuffer[];
void djui_console_message_dequeue(void);
void djui_console_clear();
void djui_console_message_create(const char* message, enum ConsoleMessageLevel level);
/* |description|Toggles the visibility of the DJUI console|descriptionEnd| */
void djui_console_toggle(void);

View file

@ -397,7 +397,7 @@ void djui_inputbox_on_text_input(struct DjuiBase *base, char* text) {
inputbox->selection[1] = inputbox->selection[0];
sCursorBlink = 0;
djui_inputbox_on_change(inputbox);
inputbox->imePos = 0;
if (inputbox->imeBuffer != NULL) {
free(inputbox->imeBuffer);
@ -408,9 +408,9 @@ void djui_inputbox_on_text_input(struct DjuiBase *base, char* text) {
void djui_inputbox_on_text_editing(struct DjuiBase *base, char* text, int cursorPos) {
struct DjuiInputbox *inputbox = (struct DjuiInputbox *) base;
inputbox->imePos = (u16)cursorPos;
if (inputbox->imeBuffer != NULL) free(inputbox->imeBuffer);
if (*text == '\0') {
inputbox->imeBuffer = NULL;
}
@ -420,7 +420,7 @@ void djui_inputbox_on_text_editing(struct DjuiBase *base, char* text, int cursor
strcpy(copy,text);
inputbox->imeBuffer = copy;
}
djui_inputbox_on_change(inputbox);
}
@ -469,9 +469,9 @@ static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) {
}
sCursorBlink = (sCursorBlink + 1) % DJUI_INPUTBOX_MAX_BLINK;
f32 renderX = x;
u16 imePos = inputbox->imePos;
if (imePos != 0) {
char* ime = inputbox->imeBuffer;
@ -480,13 +480,13 @@ static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) {
ime = djui_unicode_next_char(ime);
}
}
// render only cursor when there is no selection width
if (selection[0] == selection[1]) {
if (sCursorBlink < DJUI_INPUTBOX_MID_BLINK && djui_interactable_is_input_focus(&inputbox->base)) {
create_dl_translation_matrix(DJUI_MTX_PUSH, renderX - DJUI_INPUTBOX_CURSOR_WIDTH / 2.0f, -0.1f, 0);
create_dl_scale_matrix(DJUI_MTX_NOPUSH, DJUI_INPUTBOX_CURSOR_WIDTH, 0.8f, 1.0f);
gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255);
gDPSetEnvColor(gDisplayListHead++, inputbox->textColor.r, inputbox->textColor.g, inputbox->textColor.b, inputbox->textColor.a);
gSPDisplayList(gDisplayListHead++, dl_djui_simple_rect);
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
}
@ -559,7 +559,7 @@ static bool djui_inputbox_render(struct DjuiBase* base) {
// translate position
f32 translatedX = comp->x + inputbox->viewX;
f32 translatedY = comp->y + DJUI_INPUTBOX_YOFF;
f32 translatedY = comp->y + inputbox->yOffset;
djui_gfx_position_translate(&translatedX, &translatedY);
create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, 0);
@ -583,7 +583,7 @@ static bool djui_inputbox_render(struct DjuiBase* base) {
u16 selection[2] = { 0 };
selection[0] = fmin(inputbox->selection[0], inputbox->selection[1]);
selection[1] = fmax(inputbox->selection[0], inputbox->selection[1]);
// render text
char* c = inputbox->buffer;
f32 drawX = inputbox->viewX;
@ -592,7 +592,7 @@ static bool djui_inputbox_render(struct DjuiBase* base) {
font->render_begin();
for (u16 i = 0; i < inputbox->bufferSize; i++) {
//render composition text
if (selection[0] == i && inputbox->imeBuffer != NULL) {
char *ime = inputbox->imeBuffer;
@ -601,7 +601,7 @@ static bool djui_inputbox_render(struct DjuiBase* base) {
ime = djui_unicode_next_char(ime);
}
}
if (*c == '\0') { break; }
// deal with seleciton color
@ -637,6 +637,7 @@ struct DjuiInputbox* djui_inputbox_create(struct DjuiBase* parent, u16 bufferSiz
struct DjuiBase* base = &inputbox->base;
inputbox->bufferSize = bufferSize;
inputbox->buffer = calloc(bufferSize, sizeof(char));
inputbox->yOffset = DJUI_INPUTBOX_YOFF;
djui_base_init(parent, base, djui_inputbox_render, djui_inputbox_destroy);
djui_base_set_size(base, 200, 32);

View file

@ -8,6 +8,7 @@ struct DjuiInputbox {
u16 bufferSize;
u16 selection[2];
f32 viewX;
f32 yOffset;
struct DjuiColor textColor;
void (*on_enter_press)(struct DjuiInputbox*);
void (*on_escape_press)(struct DjuiInputbox*);

View file

@ -180,7 +180,7 @@ void djui_interactable_set_binding(struct DjuiBase* base) {
}
void djui_interactable_set_input_focus(struct DjuiBase* base) {
if (gDjuiConsoleFocus && base != &gDjuiConsole->base) {
if (gDjuiConsoleFocus && base != &gDjuiConsole->inputbox->base) {
return;
}
@ -199,8 +199,8 @@ bool djui_interactable_on_key_down(int scancode) {
}
bool keyFocused = (gInteractableFocus != NULL)
&& (gInteractableFocus->interactable != NULL)
&& (gInteractableFocus->interactable->on_key_down != NULL);
&& (gInteractableFocus->interactable != NULL)
&& (gInteractableFocus->interactable->on_key_down != NULL);
if (keyFocused) {
bool consume = gInteractableFocus->interactable->on_key_down(gInteractableFocus, scancode);
@ -217,10 +217,22 @@ bool djui_interactable_on_key_down(int scancode) {
return true;
}
if (!gDjuiChatBoxFocus) {
for (int i = 0; i < MAX_BINDS; i++) {
if (scancode == (int)configKeyConsole[i]) {
djui_console_toggle();
return true;
}
}
}
if (gDjuiChatBox != NULL && !gDjuiChatBoxFocus) {
bool pressChat = false;
for (int i = 0; i < MAX_BINDS; i++) {
if (scancode == (int)configKeyChat[i]) { pressChat = true; }
if (scancode == (int)configKeyChat[i]) {
pressChat = true;
break;
}
}
if (pressChat && !gDjuiConsoleFocus) {
@ -264,7 +276,7 @@ bool djui_interactable_on_key_down(int scancode) {
}
}
if (gDjuiChatBoxFocus || djui_panel_is_active()) {
if (gDjuiConsoleFocus || gDjuiChatBoxFocus || djui_panel_is_active()) {
switch (scancode) {
case SCANCODE_UP: sKeyboardHoldDirection = PAD_HOLD_DIR_UP; return true;
case SCANCODE_DOWN: sKeyboardHoldDirection = PAD_HOLD_DIR_DOWN; return true;
@ -278,13 +290,6 @@ bool djui_interactable_on_key_down(int scancode) {
}
void djui_interactable_on_key_up(int scancode) {
if (!gDjuiChatBoxFocus) {
for (int i = 0; i < MAX_BINDS; i++) {
if (scancode == (int)configKeyConsole[i]) { djui_console_toggle(); break; }
}
}
if (gDjuiPlayerList != NULL || gDjuiModList != NULL) {
for (int i = 0; i < MAX_BINDS; i++) {
if (scancode == (int)configKeyPlayerList[i]) {
@ -424,12 +429,12 @@ void djui_interactable_update(void) {
u16 mainButtons = PAD_BUTTON_A | PAD_BUTTON_B;
if ((mouseButtons & MOUSE_BUTTON_1) && !(sLastMouseButtons & MOUSE_BUTTON_1) && !djui_cursor_inside_base(gInteractableFocus)) {
// clicked outside of focus
if (!gDjuiChatBoxFocus) {
if (!gDjuiChatBoxFocus && !gDjuiConsoleFocus) {
djui_interactable_set_input_focus(NULL);
}
} else if ((padButtons & mainButtons) && !(sLastInteractablePad.button & mainButtons)) {
// pressed main face button
if (!gDjuiChatBoxFocus) {
if (!gDjuiChatBoxFocus && !gDjuiConsoleFocus) {
djui_interactable_set_input_focus(NULL);
}
} else {

View file

@ -287,6 +287,11 @@ static void djui_text_read_line(struct DjuiText* text, char** message, f32* line
break;
}
// check for tab
if (*c == '\t') {
charWidth = 4 * text->font->char_width(" ");
}
// check to see if this character would exceed size
if (*lineWidth + charWidth >= maxLineWidth) {
break;
@ -388,7 +393,9 @@ static void djui_text_render_line(struct DjuiText* text, char* c1, char* c2, f32
}
f32 charWidth = text->font->char_width(c);
if (*c != '\n' && *c != ' ') {
if (*c == '\t') {
charWidth = 4 * text->font->char_width(" ");
} else if (*c != '\n' && *c != ' ') {
djui_text_render_char(text, c);
}

1762
src/pc/linenoise/linenoise.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,114 @@
/* linenoise.h -- VERSION 1.0
*
* Guerrilla line editing library against the idea that a line editing lib
* needs to be 20,000 lines of C code.
*
* See linenoise.c for more information.
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __LINENOISE_H
#define __LINENOISE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h> /* For size_t. */
extern char *linenoiseEditMore;
/* The linenoiseState structure represents the state during line editing.
* We pass this state to functions implementing specific editing
* functionalities. */
struct linenoiseState {
int in_completion; /* The user pressed TAB and we are now in completion
* mode, so input is handled by completeLine(). */
size_t completion_idx; /* Index of next completion to propose. */
int ifd; /* Terminal stdin file descriptor. */
int ofd; /* Terminal stdout file descriptor. */
char *buf; /* Edited line buffer. */
size_t buflen; /* Edited line buffer size. */
const char *prompt; /* Prompt to display. */
size_t plen; /* Prompt length. */
size_t pos; /* Current cursor position. */
size_t oldpos; /* Previous refresh cursor position. */
size_t len; /* Current edited line length. */
size_t cols; /* Number of columns in terminal. */
size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */
int oldrpos; /* Cursor row from last refresh (for multiline clearing). */
int history_index; /* The history index we are currently editing. */
};
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
} linenoiseCompletions;
/* Non blocking API. */
int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt);
char *linenoiseEditFeed(struct linenoiseState *l);
void linenoiseEditStop(struct linenoiseState *l);
void linenoiseHide(struct linenoiseState *l);
void linenoiseShow(struct linenoiseState *l);
/* Blocking API. */
char *linenoise(const char *prompt);
void linenoiseFree(void *ptr);
/* Completion API. */
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
typedef void(linenoiseFreeHintsCallback)(void *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
/* History API. */
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);
/* Other utilities. */
void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void);
void linenoiseMaskModeEnable(void);
void linenoiseMaskModeDisable(void);
#ifdef __cplusplus
}
#endif
#endif /* __LINENOISE_H */

View file

@ -1485,6 +1485,7 @@ char gSmluaConstants[] = ""
"DIALOG_168=168\n"
"DIALOG_169=169\n"
"DIALOG_COUNT=170\n"
"MAX_CONSOLE_INPUT_LENGTH=500\n"
"CONSOLE_MESSAGE_INFO=0\n"
"CONSOLE_MESSAGE_WARNING=1\n"
"CONSOLE_MESSAGE_ERROR=2\n"

View file

@ -40,6 +40,52 @@ bool smlua_functions_valid_param_range(lua_State* L, int min, int max) {
return true;
}
///////////
// print //
///////////
int smlua_func_print(lua_State *L) {
int top = lua_gettop(L);
// calculate total length first
size_t totalLen = 0;
for (int i = 1; i <= top; i++) {
size_t len;
luaL_tolstring(L, i, &len);
totalLen += len;
if (i > 1) totalLen += 1;
}
// allocate string
char* completeString = malloc(totalLen + 1);
if (!completeString) return 0;
size_t pos = 0;
// copy string
for (int i = 1; i <= top; i++) {
size_t len;
const char* str = luaL_tolstring(L, i, &len);
if (i > 1) {
completeString[pos] = '\t';
pos += 1;
}
memcpy(completeString + pos, str, len);
pos += len;
}
completeString[pos] = '\0';
// print to terminal and console
log_to_terminal("%s", completeString);
djui_console_message_create(completeString, CONSOLE_MESSAGE_INFO);
free(completeString);
return 1;
}
///////////
// table //
///////////
@ -806,6 +852,13 @@ int smlua_func_log_to_console(lua_State* L) {
}
djui_console_message_create(message, level);
char* colorCode;
switch (level) {
case CONSOLE_MESSAGE_WARNING: colorCode = "\x1b[33m"; break;
case CONSOLE_MESSAGE_ERROR: colorCode = "\x1b[31m"; break;
default: colorCode = "\x1b[0m"; break;
}
log_to_terminal("%s%s\x1b[0m\n", colorCode, message);
return 1;
}
@ -1013,6 +1066,7 @@ void smlua_bind_functions(void) {
lua_State* L = gLuaState;
// misc
smlua_bind_function(L, "print", smlua_func_print);
smlua_bind_function(L, "table_copy", smlua_func_table_copy);
smlua_bind_function(L, "table_deepcopy", smlua_func_table_deepcopy);
smlua_bind_function(L, "init_mario_after_warp", smlua_func_init_mario_after_warp);

View file

@ -12,6 +12,7 @@
#include "src/game/mario_step.h"
#include "src/game/mario.h"
#include "src/game/rumble_init.h"
#include "src/pc/commands.h"
#include "src/pc/djui/djui_popup.h"
#include "src/pc/network/network_utils.h"
#include "src/pc/djui/djui_console.h"
@ -12125,6 +12126,32 @@ int smlua_func_update_character_anim_offset(lua_State* L) {
return 1;
}
////////////////
// commands.h //
////////////////
int smlua_func_command_message_create(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top < 1 || top > 2) {
LOG_LUA_LINE("Improper param count for '%s': Expected between %u and %u, Received %u", "command_message_create", 1, 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, "command_message_create"); return 0; }
int level = (int) 0;
if (top >= 2) {
level = smlua_to_integer(L, 2);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "command_message_create"); return 0; }
}
command_message_create(message, level);
return 1;
}
/////////////////////////
// djui_chat_message.h //
/////////////////////////
@ -37478,6 +37505,9 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "get_character_anim", smlua_func_get_character_anim);
smlua_bind_function(L, "update_character_anim_offset", smlua_func_update_character_anim_offset);
// commands.h
smlua_bind_function(L, "command_message_create", smlua_func_command_message_create);
// djui_chat_message.h
smlua_bind_function(L, "djui_chat_message_create", smlua_func_djui_chat_message_create);

View file

@ -14,7 +14,7 @@
#include "pc/network/network.h"
#include "pc/network/network_player.h"
#include "pc/network/socket/socket.h"
#include "pc/chat_commands.h"
#include "pc/commands.h"
#include "pc/pc_main.h"
#include "pc/djui/djui_lua_profiler.h"
#include "pc/djui/djui_panel.h"
@ -1009,62 +1009,72 @@ bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* ob
// hooked chat command //
/////////////////////////
struct LuaHookedChatCommand {
struct LuaHookedCommand {
char* command;
char* description;
int reference;
struct Mod* mod;
struct ModFile* modFile;
bool isConsoleCommand;
};
#define MAX_HOOKED_CHAT_COMMANDS 512
static struct LuaHookedChatCommand sHookedChatCommands[MAX_HOOKED_CHAT_COMMANDS] = { 0 };
static struct LuaHookedCommand sHookedChatCommands[MAX_HOOKED_CHAT_COMMANDS] = { 0 };
static int sHookedChatCommandsCount = 0;
int smlua_hook_chat_command(lua_State* L) {
int smlua_hook_command_internal(lua_State* L, bool isConsoleCommand) {
if (L == NULL) { return 0; }
if (!smlua_functions_valid_param_count(L, 3)) { return 0; }
if (gLuaLoadingMod == NULL) {
LOG_LUA_LINE("hook_chat_command() can only be called on load.");
LOG_LUA_LINE("%s can only be called on load.", isConsoleCommand ? "hook_console_command()" : "hook_chat_command()");
return 0;
}
if (sHookedChatCommandsCount >= MAX_HOOKED_CHAT_COMMANDS) {
LOG_LUA_LINE("Hooked chat command exceeded maximum references!");
LOG_LUA_LINE("Hooked command exceeded maximum references!");
return 0;
}
const char* command = smlua_to_string(L, 1);
if (command == NULL || strlen(command) == 0 || !gSmLuaConvertSuccess) {
LOG_LUA_LINE("Hook chat command: tried to hook invalid command");
LOG_LUA_LINE("Hook command: tried to hook invalid command");
return 0;
}
const char* description = smlua_to_string(L, 2);
if (description == NULL || strlen(description) == 0 || !gSmLuaConvertSuccess) {
LOG_LUA_LINE("Hook chat command: tried to hook invalid description");
LOG_LUA_LINE("Hook command: tried to hook invalid description");
return 0;
}
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
if (ref == -1) {
LOG_LUA_LINE("Hook chat command: tried to hook undefined function '%s'", command);
LOG_LUA_LINE("Hook command: tried to hook undefined function '%s'", command);
return 0;
}
struct LuaHookedChatCommand* hooked = &sHookedChatCommands[sHookedChatCommandsCount];
struct LuaHookedCommand* hooked = &sHookedChatCommands[sHookedChatCommandsCount];
hooked->command = strdup(command);
hooked->description = strdup(description);
hooked->reference = ref;
hooked->mod = gLuaActiveMod;
hooked->modFile = gLuaActiveModFile;
hooked->isConsoleCommand = isConsoleCommand;
sHookedChatCommandsCount++;
return 1;
}
int smlua_hook_chat_command(lua_State* L) {
return smlua_hook_command_internal(L, false);
}
int smlua_hook_console_command(lua_State* L) {
return smlua_hook_command_internal(L, true);
}
int smlua_update_chat_command_description(lua_State* L) {
if (L == NULL) { return 0; }
if (!smlua_functions_valid_param_count(L, 2)) { return 0; }
@ -1082,8 +1092,8 @@ int smlua_update_chat_command_description(lua_State* L) {
}
for (int i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedChatCommand* hook = &sHookedChatCommands[i];
if (!strcmp(hook->command, command)) {
struct LuaHookedCommand* hook = &sHookedChatCommands[i];
if (!hook->isConsoleCommand && !strcmp(hook->command, command)) {
if (hook->description) {
free(hook->description);
}
@ -1096,19 +1106,50 @@ int smlua_update_chat_command_description(lua_State* L) {
return 0;
}
int smlua_update_console_command_description(lua_State* L) {
if (L == NULL) { return 0; }
if (!smlua_functions_valid_param_count(L, 2)) { return 0; }
const char* command = smlua_to_string(L, 1);
if (command == NULL || strlen(command) == 0 || !gSmLuaConvertSuccess) {
LOG_LUA_LINE("Update console command: tried to update invalid command");
return 0;
}
const char* description = smlua_to_string(L, 2);
if (description == NULL || strlen(description) == 0 || !gSmLuaConvertSuccess) {
LOG_LUA_LINE("Update console command: tried to update invalid description");
return 0;
}
for (int i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedCommand* hook = &sHookedChatCommands[i];
if (hook->isConsoleCommand && !strcmp(hook->command, command)) {
if (hook->description) {
free(hook->description);
}
hook->description = strdup(description);
return 1;
}
}
LOG_LUA_LINE("Update console command: could not find command to update");
return 0;
}
bool smlua_call_chat_command_hook(char* command) {
lua_State* L = gLuaState;
if (L == NULL) { return false; }
for (int i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedChatCommand* hook = &sHookedChatCommands[i];
struct LuaHookedCommand* hook = &sHookedChatCommands[i];
// compare strings
size_t commandLength = strlen(hook->command);
for (size_t j = 0; j < commandLength; j++) {
if (hook->command[j] != command[j + 1]) {
goto NEXT_HOOK;
}
}
if (strncmp(hook->command, command, commandLength) != 0) goto NEXT_HOOK;
char* params = &command[commandLength + 1];
// make sure we aren't running a console command
if (hook->isConsoleCommand && !gDjuiConsoleFocus) goto NEXT_HOOK;
char* params = &command[commandLength];
if (*params != '\0' && *params != ' ') {
goto NEXT_HOOK;
}
@ -1145,12 +1186,13 @@ NEXT_HOOK:;
return false;
}
void smlua_display_chat_commands(void) {
void smlua_display_chat_commands(bool isConsole) {
for (int i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedChatCommand* hook = &sHookedChatCommands[i];
struct LuaHookedCommand* hook = &sHookedChatCommands[i];
if (!isConsole && hook->isConsoleCommand) continue;
char msg[256] = { 0 };
snprintf(msg, 256, "/%s %s", hook->command, hook->description);
djui_chat_message_create(msg);
command_message_create(msg, CONSOLE_MESSAGE_INFO);
}
}
@ -1207,6 +1249,7 @@ char** smlua_get_chat_player_list(void) {
return sortedPlayers;
}
// this needs a rewrite, actually all these funcs needs a rewrite, actually, the whole autocomplete system needs a rewrite
char** smlua_get_chat_maincommands_list(void) {
#if defined(DEVELOPMENT)
s32 defaultCmdsCount = 11;
@ -1227,7 +1270,8 @@ char** smlua_get_chat_maincommands_list(void) {
}
char** commands = (char**) malloc((sHookedChatCommandsCount + defaultCmdsCountNew + 1) * sizeof(char*));
for (s32 i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedChatCommand* hook = &sHookedChatCommands[i];
struct LuaHookedCommand* hook = &sHookedChatCommands[i];
if (hook->isConsoleCommand) continue;
commands[i] = strdup(hook->command);
}
for (s32 i = 0; i < defaultCmdsCount; i++) {
@ -1251,7 +1295,7 @@ char** smlua_get_chat_subcommands_list(const char* maincommand) {
}
for (s32 i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedChatCommand* hook = &sHookedChatCommands[i];
struct LuaHookedCommand* hook = &sHookedChatCommands[i];
if (strcmp(hook->command, maincommand) == 0) {
char* noColorsDesc = djui_text_get_uncolored_string(NULL, strlen(hook->description) + 1, hook->description);
char* startSubcommands = strstr(noColorsDesc, "[");
@ -1793,7 +1837,7 @@ void smlua_hook_replace_function_references(lua_State* L, int oldReference, int
}
for (int i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedChatCommand* hooked = &sHookedChatCommands[i];
struct LuaHookedCommand* hooked = &sHookedChatCommands[i];
smlua_hook_replace_function_reference(L, &hooked->reference, oldReference, newReference);
}
@ -1829,7 +1873,7 @@ void smlua_clear_hooks(void) {
sHookedMarioActionsCount = 0;
for (int i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedChatCommand* hooked = &sHookedChatCommands[i];
struct LuaHookedCommand* hooked = &sHookedChatCommands[i];
if (hooked->command != NULL) { free(hooked->command); }
hooked->command = NULL;
@ -1896,6 +1940,7 @@ void smlua_bind_hooks(void) {
smlua_bind_function(L, "hook_event", smlua_hook_event);
smlua_bind_function(L, "hook_mario_action", smlua_hook_mario_action);
smlua_bind_function(L, "hook_chat_command", smlua_hook_chat_command);
smlua_bind_function(L, "hook_console_command", smlua_hook_console_command);
smlua_bind_function(L, "hook_on_sync_table_change", smlua_hook_on_sync_table_change);
smlua_bind_function(L, "hook_behavior", smlua_hook_behavior);
smlua_bind_function(L, "hook_mod_menu_text", smlua_hook_mod_menu_text);
@ -1904,6 +1949,7 @@ void smlua_bind_hooks(void) {
smlua_bind_function(L, "hook_mod_menu_slider", smlua_hook_mod_menu_slider);
smlua_bind_function(L, "hook_mod_menu_inputbox", smlua_hook_mod_menu_inputbox);
smlua_bind_function(L, "update_chat_command_description", smlua_update_chat_command_description);
smlua_bind_function(L, "update_console_command_description", smlua_update_console_command_description);
smlua_bind_function(L, "update_mod_menu_element_name", smlua_update_mod_menu_element_name);
smlua_bind_function(L, "update_mod_menu_element_checkbox", smlua_update_mod_menu_element_checkbox);
smlua_bind_function(L, "update_mod_menu_element_slider", smlua_update_mod_menu_element_slider);

View file

@ -177,7 +177,7 @@ bool smlua_call_action_hook(enum LuaActionHookType hookType, struct MarioState*
u32 smlua_get_action_interaction_type(struct MarioState* m);
bool smlua_call_chat_command_hook(char* command);
void smlua_display_chat_commands(void);
void smlua_display_chat_commands(bool isConsole);
char** smlua_get_chat_player_list(void);
char** smlua_get_chat_maincommands_list(void);
char** smlua_get_chat_subcommands_list(const char* maincommand);

View file

@ -66,6 +66,8 @@
#include "pc/discord/discord.h"
#endif
#include "pc/terminal.h"
#include "pc/mumble/mumble.h"
#if defined(_WIN32) || defined(_WIN64)
@ -529,7 +531,7 @@ int main(int argc, char *argv[]) {
} else
#endif
{
printf("ERROR: could not find valid vanilla us sm64 rom in game's user folder\n");
LOG_ERROR("Could not find valid vanilla us sm64 rom in game's user folder\n");
return 0;
}
}
@ -602,6 +604,10 @@ int main(int argc, char *argv[]) {
network_init(NT_NONE, false);
}
// initialize terminal
terminal_init();
//init_thread_handle(&gTerminalThread, terminal_update, NULL, NULL, 0);
// main loop
while (true) {
debug_context_reset();
@ -611,6 +617,7 @@ int main(int argc, char *argv[]) {
discord_update();
#endif
mumble_update();
terminal_update();
#ifdef DEBUG
fflush(stdout);
fflush(stderr);

86
src/pc/terminal.c Normal file
View file

@ -0,0 +1,86 @@
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
#include <termios.h>
#include "linenoise/linenoise.h"
#include "djui/djui_console.h"
#include "commands.h"
#include "pc_main.h"
#include "terminal.h"
#define TERMINAL_BUFFER_SIZE 1024
static struct linenoiseState sLinenoiseState;
static char sTerminalInput[TERMINAL_BUFFER_SIZE] = { 0 };
static bool sTerminalInitialized = false;
static bool sTerminalActive = false;
void log_to_terminal(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
if (sTerminalActive) linenoiseHide(&sLinenoiseState);
vprintf(fmt, args);
if (sTerminalActive) linenoiseShow(&sLinenoiseState);
va_end(args);
}
static void terminal_stop() {
if (!sTerminalInitialized) return;
linenoiseEditStop(&sLinenoiseState);
sTerminalActive = false;
}
void terminal_init() {
if (!isatty(STDIN_FILENO)) {
sTerminalActive = false;
sTerminalInitialized = false;
return;
}
if (tcgetpgrp(STDIN_FILENO) != getpgrp()) {
sTerminalActive = false;
sTerminalInitialized = false;
return;
}
linenoiseEditStart(&sLinenoiseState, -1, -1, sTerminalInput, sizeof(sTerminalInput), "> ");
sTerminalInitialized = true;
sTerminalActive = true;
}
void terminal_update() {
if (!sTerminalInitialized) return;
struct timeval tv = {0L, 0L};
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
if (select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv) <= 0) return;
char* input = linenoiseEditFeed(&sLinenoiseState);
if (input == NULL) {
linenoiseEditStop(&sLinenoiseState);
game_exit();
} else if (input != linenoiseEditMore) {
terminal_stop();
if (input[0] != '\0') {
run_command(input);
linenoiseHistoryAdd(input);
}
free(input);
terminal_init();
}
}
void terminal_clear() {
if (!sTerminalInitialized) return;
linenoiseClearScreen();
}

10
src/pc/terminal.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include "src/pc/thread.h"
extern struct ThreadHandle gTerminalThread;
void log_to_terminal(const char* fmt, ...);
//void* terminal_update(UNUSED void* dummy);
void terminal_init();
void terminal_update();
void terminal_clear();

View file

@ -10,6 +10,7 @@
#include "pc/djui/djui.h"
#include "pc/network/version.h"
#include "pc/loading.h"
#include "pc/debuglog.h"
#define URL "https://raw.githubusercontent.com/coop-deluxe/sm64coopdx/refs/heads/main/src/pc/network/version.h"
#define VERSION_IDENTIFIER "#define SM64COOPDX_VERSION \""
@ -73,7 +74,7 @@ void get_version_remote(void) {
// initialize WinINet
HINTERNET hInternet = InternetOpenA("sm64coopdx", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (!hInternet) {
printf("Failed to check for updates!\n");
LOG_ERROR("Failed to check for updates!");
InternetCloseHandle(hInternet);
return;
}
@ -81,7 +82,7 @@ void get_version_remote(void) {
// open the URL
HINTERNET hUrl = InternetOpenUrlA(hInternet, URL, NULL, 0, INTERNET_FLAG_RELOAD, 0);
if (!hUrl) {
printf("Failed to check for updates!\n");
LOG_ERROR("Failed to check for updates!");
InternetCloseHandle(hInternet);
InternetCloseHandle(hUrl);
return;
@ -95,7 +96,7 @@ void get_version_remote(void) {
// read data from the URL, making room in the buffer for the null-terminator
DWORD bytesRead;
if (!InternetReadFile(hUrl, buffer, sizeof(buffer) - 1, &bytesRead)) {
printf("Failed to check for updates!\n");
LOG_ERROR("Failed to check for updates!");
InternetCloseHandle(hInternet);
InternetCloseHandle(hUrl);
return;
@ -112,7 +113,7 @@ void get_version_remote(void) {
// initialize libcurl
CURL *curl = curl_easy_init();
if (!curl || curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
printf("Failed to check for updates!\n");
LOG_ERROR("Failed to check for updates!");
return;
}
@ -126,7 +127,7 @@ void get_version_remote(void) {
// perform the request
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
printf("Failed to check for updates!\n");
LOG_ERROR("Failed to check for updates!");
curl_easy_cleanup(curl);
return;
}