This commit is contained in:
EmeraldLockdown 2026-04-05 00:03:50 +00:00 committed by GitHub
commit f7bc3bfb7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 3116 additions and 567 deletions

View file

@ -517,6 +517,10 @@ ifeq ($(DISCORD_SDK),1)
SRC_DIRS += src/pc/discord
endif
ifeq ($(WINDOWS_BUILD),0)
SRC_DIRS += src/pc/linenoise
endif
SRC_DIRS += src/pc/mumble
ULTRA_SRC_DIRS := lib/src lib/src/math lib/asm lib/data

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

@ -126,6 +126,19 @@ function update_chat_command_description(command, description)
-- ...
end
--- @param command string The command to run. Should be easy to type
--- @param description string Should describe what the command does and how to use it
--- @param func fun(msg:string): boolean Run upon activating the command. Return `true` to confirm the command has succeeded
function hook_console_command(command, description, func)
-- ...
end
--- @param command string The command to change the description of
--- @param description string The description to change to
function update_console_command_description(command, description)
-- ...
end
--- @param hookEventType LuaHookedEventType When a function should run
--- @param func fun(...: any): any?, any? The function to run
--- Different hooks can pass in different parameters and have different return values. Be sure to read the hooks guide for more information.
@ -407,13 +420,13 @@ end
--- @param command string
--- @vararg integer | string | Gfx | Texture | Vtx Parameters for the command
--- Sets a display list command on the display list given.
---
---
--- If `command` includes parameter specifiers (subsequences beginning with `%`), the additional arguments
--- following `command` are converted and inserted in `command` replacing their respective specifiers.
---
---
--- The number of provided parameters must be equal to the number of specifiers in `command`,
--- and the order of parameters must be the same as the specifiers.
---
---
--- The following specifiers are allowed:
--- - `%i` for an `integer` parameter
--- - `%s` for a `string` parameter

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

@ -9,36 +9,36 @@ function on_stream_play(msg)
if(msg == "load") then
audioStream = audio_stream_load("music.mp3")
audio_stream_set_looping(audioStream, true)
djui_chat_message_create("audio audioStream:" .. tostring(audioStream));
command_message_create("audio audioStream:" .. tostring(audioStream));
end
if(msg == "play") then
audio_stream_play(audioStream, true, 1);
djui_chat_message_create("playing audio");
command_message_create("playing audio");
end
if(msg == "resume") then
audio_stream_play(audioStream, false, 1);
djui_chat_message_create("resuming audio");
command_message_create("resuming audio");
end
if(msg == "pause") then
audio_stream_pause(audioStream);
djui_chat_message_create("pausing audio");
command_message_create("pausing audio");
end
if(msg == "stop") then
audio_stream_stop(audioStream);
djui_chat_message_create("stopping audio");
command_message_create("stopping audio");
end
if(msg == "destroy") then
audio_stream_destroy(audioStream);
djui_chat_message_create("destroyed audio");
command_message_create("destroyed audio");
end
if(msg == "getpos") then
djui_chat_message_create("pos: " .. tostring(audio_stream_get_position(audioStream)));
command_message_create("pos: " .. tostring(audio_stream_get_position(audioStream)));
end
return true;
@ -48,7 +48,7 @@ function on_sample_play(msg)
if(msg == "load") then
audioSample = audio_sample_load("sample.mp3");
djui_chat_message_create("audio audioStream:" .. tostring(audioSample));
command_message_create("audio audioStream:" .. tostring(audioSample));
return true;
end

View file

@ -27,12 +27,12 @@ function send_example_1(byte_param, short_param, long_param, float_param, double
network_send_bytestring(true, bytestring)
djui_chat_message_create('Sent bytestring packet example 1:')
djui_chat_message_create(' byte_param: ' .. byte_param)
djui_chat_message_create(' short_param: ' .. short_param)
djui_chat_message_create(' long_param: ' .. long_param)
djui_chat_message_create(' float_param: ' .. float_param)
djui_chat_message_create(' double_param: ' .. double_param)
command_message_create('Sent bytestring packet example 1:')
command_message_create(' byte_param: ' .. byte_param)
command_message_create(' short_param: ' .. short_param)
command_message_create(' long_param: ' .. long_param)
command_message_create(' float_param: ' .. float_param)
command_message_create(' double_param: ' .. double_param)
end
function on_packet_bytestring_receive_example_1(bytestring)
@ -55,11 +55,17 @@ function on_packet_bytestring_receive_example_1(bytestring)
---------------------------------------
djui_chat_message_create('Received bytestring packet example 1:')
log_to_console('Received bytestring packet example 1:')
djui_chat_message_create(' byte_param: ' .. byte_param)
log_to_console(' byte_param: ' .. byte_param)
djui_chat_message_create(' short_param: ' .. short_param)
log_to_console(' short_param: ' .. short_param)
djui_chat_message_create(' long_param: ' .. long_param)
log_to_console(' long_param: ' .. long_param)
djui_chat_message_create(' float_param: ' .. float_param)
log_to_console(' float_param: ' .. float_param)
djui_chat_message_create(' double_param: ' .. double_param)
log_to_console(' double_param: ' .. double_param)
end
---------------------------------------------------------------------------------------------------
@ -76,9 +82,9 @@ function send_example_2(long_param, string_param)
network_send_bytestring(true, bytestring)
djui_chat_message_create('Sent bytestring packet example 2:')
djui_chat_message_create(' byte_param: ' .. long_param)
djui_chat_message_create(' string_param: ' .. string_param)
command_message_create('Sent bytestring packet example 2:')
command_message_create(' byte_param: ' .. long_param)
command_message_create(' string_param: ' .. string_param)
end
function on_packet_bytestring_receive_example_2(bytestring)
@ -98,8 +104,11 @@ function on_packet_bytestring_receive_example_2(bytestring)
---------------------------------------
djui_chat_message_create('Received bytestring packet example 2:')
log_to_console('Received bytestring packet example 2:')
djui_chat_message_create(' long_param: ' .. long_param)
log_to_console(' long_param: ' .. long_param)
djui_chat_message_create(' string_param: ' .. string_param)
log_to_console(' string_param: ' .. string_param)
end
---------------------------------------------------------------------------------------------------

View file

@ -2,18 +2,6 @@
-- description: Run /matrix and a builtin texture name to replace with the digital rain
-- deluxe: true
if SM64COOPDX_VERSION == nil then
local first = false
hook_event(HOOK_ON_LEVEL_INIT, function()
if not first then
first = true
play_sound(SOUND_MENU_CAMERA_BUZZ, gMarioStates[0].marioObj.header.gfx.cameraToObject)
djui_chat_message_create("\\#ff7f7f\\Matrix Code is not supported with sm64ex-coop\nas it uses sm64coopdx exclusive Lua functionality.\n\\#dcdcdc\\To use this mod, try out sm64coopdx at\n\\#7f7fff\\https://sm64coopdx.com")
end
end)
return
end
local sMatrixFrames = {}
for i = 0, 10 do
sMatrixFrames[i] = get_texture_info("matrix_" .. i)

View file

@ -3,24 +3,24 @@
local function on_get_command(msg)
if not network_is_server() then
djui_chat_message_create("You need to be the host!")
command_message_create("You need to be the host!", CONSOLE_MESSAGE_ERROR)
return true
end
djui_chat_message_create(tostring(get_water_level(0)))
djui_chat_message_create(tostring(get_water_level(1)))
command_message_create(tostring(get_water_level(0)))
command_message_create(tostring(get_water_level(1)))
return true
end
local function on_set_command(msg)
if not network_is_server() then
djui_chat_message_create("You need to be the host!")
command_message_create("You need to be the host!", CONSOLE_MESSAGE_ERROR)
return true
end
local num = tonumber(msg)
if not num then
djui_chat_message_create("Not a number!")
command_message_create("Not a number!", CONSOLE_MESSAGE_ERROR)
return true
end

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

@ -6,6 +6,7 @@ Hooks are a way for SM64 to trigger Lua code, whereas the functions listed in [f
# Supported Hooks
- [hook_behavior](#hook_behavior)
- [hook_chat_command](#hook_chat_command)
- [hook_console_command](#hook_console_command)
- [hook_event](#hook_event)
- [hook_mario_action](#hook_mario_action)
- [hook_on_sync_table_change](#hook_on_sync_table_change)
@ -54,7 +55,7 @@ id_bhvExample = hook_behavior(nil, OBJ_LIST_DEFAULT, true, bhv_example_init, bhv
<br />
## [hook_chat_command](#hook_chat_command)
`hook_chat_command()` allows Lua mods to react and respond to chat commands. Chat commands start with the `/` character. The function the mod passes to the hook should return `true` when the command was valid and `false` otherwise.
`hook_chat_command()` allows Lua mods to react and respond to chat commands. Chat commands start with the `/` character. The function the mod passes to the hook should return `true` when the command was valid and `false` otherwise. Use `command_message_create` to show any message to the user. Chat commands appear in the chat, console, and terminal.
### Parameters
@ -69,10 +70,10 @@ id_bhvExample = hook_behavior(nil, OBJ_LIST_DEFAULT, true, bhv_example_init, bhv
```lua
function on_test_command(msg)
if msg == "on" then
djui_chat_message_create("Test: enabled")
command_message_create("Test: enabled")
return true
elseif msg == "off" then
djui_chat_message_create("Test: disabled")
command_message_create("Test: disabled")
return true
end
return false
@ -85,6 +86,38 @@ hook_chat_command("test", "[on|off] turn test on or off", on_hide_and_seek_comma
<br />
## [hook_console_command](#hook_console_command)
`hook_console_command()` allows Lua mods to react and respond to console commands. The function the mod passes to the hook should return `true` when the command was valid and `false` otherwise. You should use `command_message_create` to show any messages to the user. Console messages only appear in the console and terminal.
### Parameters
| Field | Type |
| ----- | ---- |
| command | `string` |
| description | `string` |
| func | `Lua Function` (`string` message) -> `bool` |
### Lua Example
```lua
function on_test_command(msg)
if msg == "on" then
command_message_create("Test: enabled")
return true
elseif msg == "off" then
command_message_create("Test: disabled")
return true
end
return false
end
hook_console_command("test", "[on|off] turn test on or off", on_test_command)
```
[:arrow_up_small:](#)
<br />
## [hook_event](#hook_event)
The lua functions sent to `hook_event()` will be automatically called by SM64 when certain events occur.
@ -143,7 +176,7 @@ The lua functions sent to `hook_event()` will be automatically called by SM64 wh
| HOOK_ON_GEO_PROCESS | Called when a GeoLayout is processed **Note:** You must set the `hookProcess` field of the graph node to a non-zero value | [GraphNode](../structs.md#GraphNode) graphNode, `integer` matStackIndex |
| HOOK_BEFORE_GEO_PROCESS | Called before a GeoLayout is processed **Note:** You must set the `hookProcess` field of the graph node to a non-zero value | [GraphNode](../structs.md#GraphNode) graphNode, `integer` matStackIndex |
| HOOK_ON_GEO_PROCESS_CHILDREN | Called when the children of a GeoLayout node is processed **Note:** You must set the `hookProcess` field of the parent graph node to a non-zero value | [GraphNode](../structs.md#GraphNode) graphNode, `integer` matStackIndex |
| HOOK_MARIO_OVERRIDE_GEOMETRY_INPUTS | Called before running Mario's geometry input logic, return `false` to not run it. | [MarioState](../structs.md) m |
| HOOK_MARIO_OVERRIDE_GEOMETRY_INPUTS | Called before running Mario's geometry input logic, return `false` to not run it. | [MarioState](../structs.md) m |
| HOOK_ON_INTERACTIONS | Called when the Mario interactions are processed | [MarioState](../structs.md#MarioState) mario |
| HOOK_ALLOW_FORCE_WATER_ACTION | Called when executing a non-water action while under the water's surface, or vice versa. Return `false` to prevent the player from being forced out of the action at the water's surface | [MarioState](../structs.md#MarioState) mario, `boolean` isInWaterAction |
| HOOK_BEFORE_WARP | Called before the local player warps. Return a table with `destLevel`, `destArea`, `destWarpNode`, to override the warp | `integer` destLevel, `integer` destArea, `integer` destWarpNode, `integer` arg |

View file

@ -17,9 +17,9 @@ Save file locations:
<br />
## Tips
- When developing Lua mods, run the game from a console. Lua errors and logs will appear there, but only if the game is launched with the `--console` launch parameter.
- When developing Lua mods, open the console with `~` or `F1` to see Lua errors and warnings.
- When a function requests a time parameter, it is almost if not always in frames.
- You can use the `print()` command when debugging. Your logs will show up in the console.
- You can use the `print()` command when debugging. Your logs will show up in the console and terminal.
- You can create a folder within the mods folder containing multiple lua scripts as long as one script is called `main.lua`. Dynos actors can be placed inside this mod folder under `<your mod folder>/actors/`.
<br />
@ -30,7 +30,7 @@ Save file locations:
- [Structs](structs.md)
### Guides
- [Setting up Visual Studio Code](guides/vs-code-setup.md)
- [Setting up Visual Studio Code](guides/vs-code-setup.md)
- [Hooks](guides/hooks.md)
- [gMarioStates](guides/mario-state.md)
- [Behavior Object Lists](guides/object-lists.md)

View file

@ -378,7 +378,7 @@ function on_arena_player_death(victimGlobalId, attackerGlobalId)
if sAttacker.team ~= 0 then
local teamScore = calculate_team_score(sAttacker.team)
if teamScore >= gGameModes[gGlobalSyncTable.gameMode].scoreCap then
round_end()
round_end()
end
end
end
@ -472,7 +472,7 @@ function on_gamemode_command(msg)
end
if msg == 'random' then
djui_chat_message_create("[Arena] Setting to random gamemode.")
command_message_create("[Arena] Setting to random gamemode.")
sRandomizeMode = true
round_end()
sWaitTimer = 1
@ -481,7 +481,7 @@ function on_gamemode_command(msg)
end
if setMode ~= nil then
djui_chat_message_create("[Arena] Setting game mode.")
command_message_create("[Arena] Setting game mode.")
gGlobalSyncTable.gameMode = setMode
sRandomizeMode = false
round_end()
@ -490,7 +490,7 @@ function on_gamemode_command(msg)
return true
end
djui_chat_message_create("/arena \\#00ffff\\gamemode\\#ffff00\\ " .. string.format("[%s|random]\\#dcdcdc\\ sets gamemode", sGameModeShortTimes))
command_message_create("/arena \\#00ffff\\gamemode\\#ffff00\\ " .. string.format("[%s|random]\\#dcdcdc\\ sets gamemode", sGameModeShortTimes))
return true
end
@ -512,21 +512,21 @@ function on_level_command(msg)
return true
end
djui_chat_message_create("/arena \\#00ffff\\level\\#ffff00\\ " .. string.format("[%s]\\#dcdcdc\\ sets level", get_level_choices()))
command_message_create("/arena \\#00ffff\\level\\#ffff00\\ " .. string.format("[%s]\\#dcdcdc\\ sets level", get_level_choices()))
return true
end
function on_jump_leniency_command(msg)
local num = tonumber(msg)
if not network_is_server and not network_is_moderator() then
djui_chat_message_create("\\#ffa0a0\\[Arena] You need to be a moderator to use this command.")
command_message_create("\\#ffa0a0\\[Arena] You need to be a moderator to use this command.")
return true
elseif num == nil then
djui_chat_message_create("\\#ffa0a0\\[Arena] Invalid number!")
command_message_create("\\#ffa0a0\\[Arena] Invalid number!")
return true
else
gGlobalSyncTable.jumpLeniency = num
djui_chat_message_create("[Arena] The number of jump leniency frames has been set to " .. num)
command_message_create("[Arena] The number of jump leniency frames has been set to " .. num)
return true
end
end
@ -545,7 +545,7 @@ local function on_arena_command(msg)
return on_jump_leniency_command(args[2] or "")
end
djui_chat_message_create("/arena \\#00ffff\\[gamemode|level|jump-leniency]")
command_message_create("/arena \\#00ffff\\[gamemode|level|jump-leniency]")
return true
end

View file

@ -528,7 +528,7 @@ local function update_character_render_table()
end
end
end
if #characterTableRender > 0 then
-- Get icons for category based on name similarity
if category.icon1 == nil or category.icon2 == nil then
@ -902,7 +902,7 @@ local worldColor = {
ambient = {r = 255, g = 255, b = 255}
}
local menuOffsetX = 0
local menuOffsetY = 0
local menuOffsetY = 0
local camScale = 1
local prevMusicToggle = 1
local prevVisualToggle = 1
@ -919,7 +919,7 @@ local function mario_update(m)
set_all_models()
queueStorageFailsafe = false
end
local np = gNetworkPlayers[m.playerIndex]
local p = gCSPlayers[m.playerIndex]
@ -952,7 +952,7 @@ local function mario_update(m)
end
end
if djui_hud_is_pause_menu_created() then
if djui_hud_is_pause_menu_created() then
if prevBaseCharFrame ~= np.modelIndex then
force_set_character(np.modelIndex)
p.presetPalette = 0
@ -978,7 +978,7 @@ local function mario_update(m)
local charTable = characterTable[currChar]
p.saveName = charTable.saveName
p.currAlt = charTable.currAlt
p.modelId = charTable[charTable.currAlt].model
if charTable[charTable.currAlt].baseChar ~= nil then
p.baseChar = charTable[charTable.currAlt].baseChar
@ -1316,9 +1316,9 @@ function set_model(o, model)
if o.oOriginalModel == 0 then
o.oOriginalModel = obj_get_model_id_extended(o)
end
local model = run_func_or_get_var(currReplace, o, o.oOriginalModel)
if model ~= nil and visualToggle then
o.oModelHasBeenReplaced = 1
if obj_has_model_extended(o, model) == 0 then
@ -1601,7 +1601,7 @@ local function on_hud_render()
djui_hud_set_color(charColor.r*0.5 + 127, charColor.g*0.5 + 127, charColor.b*0.5 + 127, math.min(paletteTrans, 255))
djui_hud_print_text(paletteName, x, y, 0.5)
end
-- Render Background Wall
local wallWidth = TEX_WALL_LEFT.width
local wallHeight = TEX_WALL_LEFT.height
@ -1615,11 +1615,11 @@ local function on_hud_render()
djui_hud_render_texture_auto_interpolated("wall-l", TEX_WALL_LEFT, x, y, wallScale, wallScale)
djui_hud_set_color(playerPants.r, playerPants.g, playerPants.b, 255)
djui_hud_render_texture_auto_interpolated("wall-r", TEX_WALL_RIGHT, x, y, wallScale, wallScale)
-- Render Graffiti
local graffiti = characterGraffiti[currChar] or TEX_GRAFFITI_DEFAULT
local graffitiWidthScale = 120/graffiti.width
local graffitiHeightScale = 120/graffiti.width
local graffitiWidthScale = 120/graffiti.width
local graffitiHeightScale = 120/graffiti.width
djui_hud_set_color(255, 255, 255, 150)
djui_hud_render_texture_auto_interpolated("graffiti", graffiti, wallMiddle - graffiti.width*0.5*graffitiWidthScale - menuOffsetX, height*0.5 - graffiti.height*0.5*graffitiHeightScale - menuOffsetY, graffitiWidthScale, graffitiHeightScale)
@ -1638,7 +1638,7 @@ local function on_hud_render()
local scale = 0.35
local textScale = scale*1.5
local buttonSpacing = 32
if not gridMenu then
-- Render Character List
gridYOffset = lerp(gridYOffset, currCharRender*buttonSpacing, 0.1)
@ -1666,7 +1666,7 @@ local function on_hud_render()
-- Name Screen
djui_hud_set_color(charColor.r*0.5, charColor.g*0.5, charColor.b*0.5, 255)
djui_hud_print_text(charName, x + 112*scale + segments*16*scale*0.5 - charNameLength*textScale*0.5, y + 32*scale, textScale)
-- Bottom Info
djui_hud_render_rect(x + 112*scale, y + 84*scale, segments*16*scale, scale)
djui_hud_print_text(channel, x + 112*scale, y + 85*scale, 0.3*scale)
@ -1907,7 +1907,7 @@ local function on_hud_render()
djui_hud_print_text(TEXT_VERSION, 2, height - 7, 0.4)
local currMenu = gridMenu and MENU_BINDS_GRID or MENU_BINDS_DEFAULT
if options == OPTIONS_MAIN then
currMenu = MENU_BINDS_OPTIONS
currMenu = MENU_BINDS_OPTIONS
elseif options == OPTIONS_CREDITS then
currMenu = MENU_BINDS_GRID
end
@ -2091,7 +2091,7 @@ local function before_mario_update(m)
until update_character_render_table()
gearRotationTarget = gearRotationTarget + 0x10000/#characterCategories
categoryOpenTimer = 150
play_sound(SOUND_MENU_CAMERA_TURN, cameraToObject)
end
)
@ -2108,7 +2108,7 @@ local function before_mario_update(m)
play_sound(SOUND_MENU_CAMERA_TURN, cameraToObject)
end
)
if not gridMenu then
-- List Controls
run_func_with_condition_and_cooldown(FUNC_INDEX_VERTICAL,
@ -2150,7 +2150,7 @@ local function before_mario_update(m)
end
)
end
else
-- Grid Controls
run_func_with_condition_and_cooldown(FUNC_INDEX_VERTICAL,
@ -2186,7 +2186,7 @@ local function before_mario_update(m)
play_sound(SOUND_MENU_MESSAGE_NEXT_PAGE, cameraToObject)
end
)
-- Alt switcher
if #characterTable[currChar] > 1 then
run_func_with_condition_and_cooldown(FUNC_INDEX_ALT,
@ -2392,14 +2392,14 @@ local function chat_command(msg)
menu = not menu
return true
else
djui_chat_message_create(TEXT_PAUSE_UNAVAILABLE)
command_message_create(TEXT_PAUSE_UNAVAILABLE)
return true
end
end
-- Help Prompt Check
if msg == "?" or msg == "help" then
djui_chat_message_create("Character Select's Avalible Commands:" ..
command_message_create("Character Select's Avalible Commands:" ..
"\n\\#ffff33\\/char-select help\\#ffffff\\ - Returns Avalible Commands" ..
"\n\\#ffff33\\/char-select menu\\#ffffff\\ - Opens the Menu" ..
"\n\\#ffff33\\/char-select [name/num]\\#ffffff\\ - Switches to Character" ..
@ -2413,9 +2413,9 @@ local function chat_command(msg)
return true
end
-- Stop Character checks if API disallows it
-- Stop Character checks if API disallows it
if not menu_is_allowed() or charBeingSet then
djui_chat_message_create("Character Cannot be Changed")
command_message_create("Character Cannot be Changed")
return true
end
@ -2426,7 +2426,7 @@ local function chat_command(msg)
for a = 1, #characterTable[i] do
if msg == string.lower(characterTable[i][a].name) or msg == saveName then
force_set_character(i, msg ~= saveName and a or 1)
djui_chat_message_create('Character set to "' .. characterTable[i][characterTable[i].currAlt].name .. '" Successfully!')
command_message_create('Character set to "' .. characterTable[i][characterTable[i].currAlt].name .. '" Successfully!')
return true
end
end
@ -2441,12 +2441,12 @@ local function chat_command(msg)
altNum = altNum and altNum or 1
if charNum > 0 and charNum <= #characterTable and characterTable[charNum].locked ~= LOCKED_TRUE then
force_set_character(charNum, altNum)
djui_chat_message_create('Character set to "' .. characterTable[charNum][altNum].name .. '" Successfully!')
command_message_create('Character set to "' .. characterTable[charNum][altNum].name .. '" Successfully!')
return true
end
end
djui_chat_message_create("Character Not Found")
command_message_create("Character Not Found")
return true
end

View file

@ -333,12 +333,12 @@ end
local function on_set_command(msg)
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
if msg == "" then
djui_chat_message_create("/time \\#00ffff\\set\\#ffff00\\ [TIME]\\#dcdcdc\\ to set the time")
command_message_create("/time \\#00ffff\\set\\#ffff00\\ [TIME]\\#dcdcdc\\ to set the time")
return
end
@ -359,9 +359,9 @@ local function on_set_command(msg)
local amount = tonumber(msg)
if amount ~= nil then
gGlobalSyncTable.time = amount * SECOND
djui_chat_message_create("[Day Night Cycle] Time set to " .. math_floor(gGlobalSyncTable.time / SECOND))
command_message_create("[Day Night Cycle] Time set to " .. math_floor(gGlobalSyncTable.time / SECOND))
else
djui_chat_message_create(string.format("\\#ffa0a0\\[Day Night Cycle] Could not set time to '%s'", msg))
command_message_create(string.format("\\#ffa0a0\\[Day Night Cycle] Could not set time to '%s'", msg), CONSOLE_MESSAGE_ERROR)
end
end
@ -373,13 +373,13 @@ end
local function on_add_command(msg)
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
local amount = tonumber(msg)
if amount == nil then
djui_chat_message_create("/time \\#00ffff\\add\\#ffff00\\ [AMOUNT]\\#dcdcdc\\ to add to the time")
command_message_create("/time \\#00ffff\\add\\#ffff00\\ [AMOUNT]\\#dcdcdc\\ to add to the time")
return
end
local oldTime = gGlobalSyncTable.time
@ -388,7 +388,7 @@ local function on_add_command(msg)
update_mod_menu_element_inputbox(modMenuTimeModifier, msg)
djui_chat_message_create("[Day Night Cycle] Time set to " .. math_floor(gGlobalSyncTable.time / SECOND))
command_message_create("[Day Night Cycle] Time set to " .. math_floor(gGlobalSyncTable.time / SECOND))
save_time()
end
@ -397,13 +397,13 @@ end
local function on_scale_command(msg)
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
local scale = tonumber(msg)
if scale == nil then
djui_chat_message_create("/time \\#00ffff\\scale\\#ffff00\\ [SCALE]\\#dcdcdc\\ to scale the rate at which time passes")
command_message_create("/time \\#00ffff\\scale\\#ffff00\\ [SCALE]\\#dcdcdc\\ to scale the rate at which time passes")
return
end
gGlobalSyncTable.timeScale = scale
@ -411,13 +411,13 @@ local function on_scale_command(msg)
update_mod_menu_element_slider(modMenuTimeScale, scale)
djui_chat_message_create("[Day Night Cycle] Time scale set to " .. scale)
command_message_create("[Day Night Cycle] Time scale set to " .. scale)
save_time()
end
local function on_query_command()
djui_chat_message_create(string.format("[Day Night Cycle] Time is %d (%s), day %d", math_floor(gGlobalSyncTable.time / SECOND), get_time_string(gGlobalSyncTable.time), get_day_count()))
command_message_create(string.format("[Day Night Cycle] Time is %d (%s), day %d", math_floor(gGlobalSyncTable.time / SECOND), get_time_string(gGlobalSyncTable.time), get_day_count()))
end
local function on_24h_command()
@ -430,11 +430,11 @@ end
local function on_sync_command()
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
djui_chat_message_create("[Day Night Cycle] Attempting to sync in-game time with real life time...")
command_message_create("[Day Night Cycle] Attempting to sync in-game time with real life time...")
local dateTime = get_date_and_time()
gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * dateTime.hour) + (SECOND * dateTime.minute)
@ -446,25 +446,25 @@ end
local function on_sync_sun_command()
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
if dayNightCycleApi.lockSunHours then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] Changing sun hours has been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] Changing sun hours has been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
syncSun = not syncSun
mod_storage_save_bool("sync_sun", syncSun)
if syncSun then
djui_chat_message_create("[Day Night Cycle] Syncing sunrise and sunset times to real life...")
command_message_create("[Day Night Cycle] Syncing sunrise and sunset times to real life...")
local month = get_date_and_time().month + 1
set_sun_hours(gSunriseTimes[month], gSunsetTimes[month])
else
djui_chat_message_create("[Day Night Cycle] Resetting sunrise and sunset times...")
command_message_create("[Day Night Cycle] Resetting sunrise and sunset times...")
set_sun_hours(HOUR_SUNRISE_START_BASE, HOUR_SUNSET_START_BASE)
end
@ -475,13 +475,13 @@ end
local function on_music_command()
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
playNightMusic = not playNightMusic
mod_storage_save_bool("night_music", playNightMusic)
djui_chat_message_create("[Day Night Cycle] Night music status: " .. on_or_off(playNightMusic))
command_message_create("[Day Night Cycle] Night music status: " .. on_or_off(playNightMusic))
update_mod_menu_element_checkbox(modMenuMusic, playNightMusic)
end
@ -489,13 +489,13 @@ end
local function on_display_time_command()
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
displayTime = not displayTime
mod_storage_save_bool("display_time", displayTime)
djui_chat_message_create("[Day Night Cycle] Display time status: " .. on_or_off(displayTime))
command_message_create("[Day Night Cycle] Display time status: " .. on_or_off(displayTime))
update_mod_menu_element_checkbox(modMenuDisplayTime, displayTime)
end
@ -506,19 +506,19 @@ local function on_time_command(msg)
if args[1] == "set" then
if not network_is_server() then
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time set")
command_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time set", CONSOLE_MESSAGE_ERROR)
else
on_set_command(args[2] or "")
end
elseif args[1] == "add" then
if not network_is_server() then
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time add")
command_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time add", CONSOLE_MESSAGE_ERROR)
else
on_add_command(args[2] or "")
end
elseif args[1] == "scale" then
if not network_is_server() then
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time scale")
command_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time scale", CONSOLE_MESSAGE_ERROR)
else
on_scale_command(args[2] or "")
end
@ -528,7 +528,7 @@ local function on_time_command(msg)
on_24h_command()
elseif args[1] == "sync" then
if not network_is_server() then
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time sync")
command_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time sync", CONSOLE_MESSAGE_ERROR)
else
on_sync_command()
end
@ -539,13 +539,13 @@ local function on_time_command(msg)
elseif args[1] == "display-time" then
on_display_time_command()
elseif args[1] ~= nil then
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] Unrecognized command '" .. args[1] .. "'")
command_message_create("\\#ffa0a0\\[Day Night Cycle] Unrecognized command '" .. args[1] .. "'", CONSOLE_MESSAGE_ERROR)
else
if not network_is_server() then
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to enable or disable Day Night Cycle")
command_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to enable or disable Day Night Cycle", CONSOLE_MESSAGE_ERROR)
else
gGlobalSyncTable.dncEnabled = not gGlobalSyncTable.dncEnabled
djui_chat_message_create("[Day Night Cycle] Status: " .. on_or_off(gGlobalSyncTable.dncEnabled))
command_message_create("[Day Night Cycle] Status: " .. on_or_off(gGlobalSyncTable.dncEnabled))
end
end
@ -586,7 +586,7 @@ end
local function on_set_dnc_enabled(_, value)
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
@ -597,7 +597,7 @@ end
local function on_set_time_scale(index, value)
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
@ -610,7 +610,7 @@ end
local function on_set_time_modifier(_, value)
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end
@ -621,7 +621,7 @@ end
local function on_add_hour()
if dayNightCycleApi.lockTime then
play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource)
djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.")
command_message_create("\\#ffa0a0\\[Day Night Cycle] The Day Night Cycle settings have been locked by another mod.", CONSOLE_MESSAGE_ERROR)
return
end

View file

@ -389,31 +389,31 @@ end
local function on_touch_tag_command()
gGlobalSyncTable.touchTag = not gGlobalSyncTable.touchTag
djui_chat_message_create("Touch tag: " .. on_or_off(gGlobalSyncTable.touchTag))
command_message_create("Touch tag: " .. on_or_off(gGlobalSyncTable.touchTag))
return true
end
local function on_hider_cap_command()
gGlobalSyncTable.hiderCaps = not gGlobalSyncTable.hiderCaps
djui_chat_message_create("Hider Caps: " .. on_or_off(gGlobalSyncTable.hiderCaps))
command_message_create("Hider Caps: " .. on_or_off(gGlobalSyncTable.hiderCaps))
return true
end
local function on_seeker_cap_command()
gGlobalSyncTable.seekerCaps = not gGlobalSyncTable.seekerCaps
djui_chat_message_create("Seeker Caps: " .. on_or_off(gGlobalSyncTable.seekerCaps))
command_message_create("Seeker Caps: " .. on_or_off(gGlobalSyncTable.seekerCaps))
return true
end
local function on_koopa_shell_command()
gGlobalSyncTable.banKoopaShell = not gGlobalSyncTable.banKoopaShell
djui_chat_message_create("Koopa Shells: " .. on_or_off(not gGlobalSyncTable.banKoopaShell))
command_message_create("Koopa Shells: " .. on_or_off(not gGlobalSyncTable.banKoopaShell))
return true
end
local function on_blj_command()
gGlobalSyncTable.disableBLJ = not gGlobalSyncTable.disableBLJ
djui_chat_message_create("BLJS: " .. on_or_off(not gGlobalSyncTable.disableBLJ))
command_message_create("BLJS: " .. on_or_off(not gGlobalSyncTable.disableBLJ))
return true
end

View file

@ -23,9 +23,9 @@ end
local function on_swap_command()
local np = gNetworkPlayers[0]
if np.currAreaIndex == 1 then
djui_chat_message_create("Swapping to Extreme Edition")
command_message_create("Swapping to Extreme Edition")
else
djui_chat_message_create("Swapping to normal edition")
command_message_create("Swapping to normal edition")
end
warp_to_level(np.currLevelNum, np.currAreaIndex ~ 3, np.currActNum)
return true

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

@ -879,7 +879,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,64 @@ 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* color, 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 = 0;
len = snprintf(buffer, capacity, "%s", color);
if (len < 0 || (size_t)len >= capacity) return;
buffer += len; capacity -= 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\x1b[0m\n", log);
}
#if defined(DISABLE_MODULE_LOG)
@ -48,9 +83,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("\x1b[31m", "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 sClearConsoleInput = false;
struct ConsoleQueuedMessage {
char* message;
@ -52,18 +54,25 @@ 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; }
}
} else { console->scrollY = console->flow->base.y.value; }
if (sClearConsoleInput) {
djui_inputbox_set_text(gDjuiConsole->inputbox, "");
djui_inputbox_select_all(gDjuiConsole->inputbox);
sClearConsoleInput = false;
}
djui_rect_render(base);
return true;
}
@ -75,12 +84,13 @@ static void djui_console_destroy(struct DjuiBase* base) {
void djui_console_toggle(void) {
if (gDjuiConsole == NULL) { return; }
sClearConsoleInput = 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 +106,33 @@ 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);
sClearConsoleInput = true;
}
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 +146,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 +162,23 @@ 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) {
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 +253,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 +273,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);
@ -584,7 +584,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;
@ -593,7 +593,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;
@ -602,7 +602,7 @@ static bool djui_inputbox_render(struct DjuiBase* base) {
ime = djui_unicode_next_char(ime);
}
}
if (*c == '\0') { break; }
// deal with seleciton color
@ -638,6 +638,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;
@ -390,7 +395,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

@ -19,9 +19,9 @@
#include "pc/debuglog.h"
#include "pc/djui/djui_console.h"
#define LOG_LUA(...) { if (!gSmLuaSuppressErrors) { printf("[LUA] "), printf(__VA_ARGS__), printf("\n"), smlua_mod_error(), snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__), sys_swap_backslashes(gDjuiConsoleTmpBuffer), djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_ERROR); } }
#define LOG_LUA_LINE(...) { if (!gSmLuaSuppressErrors) { printf("[LUA] "), printf(__VA_ARGS__), printf("\n"), smlua_mod_error(); snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__), sys_swap_backslashes(gDjuiConsoleTmpBuffer), djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_ERROR), smlua_logline(); } }
#define LOG_LUA_LINE_WARNING(...) { if (!gLuaActiveMod->showedScriptWarning) { gLuaActiveMod->showedScriptWarning = true; smlua_mod_warning(); snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__), sys_swap_backslashes(gDjuiConsoleTmpBuffer), djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_WARNING); } }
#define LOG_LUA(...) { if (!gSmLuaSuppressErrors) { log_to_terminal("\x1b[31m[LUA] "); log_to_terminal(__VA_ARGS__); log_to_terminal("\x1b[0m\n"); smlua_mod_error(); snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__); sys_swap_backslashes(gDjuiConsoleTmpBuffer); djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_ERROR); } }
#define LOG_LUA_LINE(...) { if (!gSmLuaSuppressErrors) { log_to_terminal("\x1b[31m[LUA] "); log_to_terminal(__VA_ARGS__); log_to_terminal("\x1b[0m\n"); smlua_mod_error(); snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__); sys_swap_backslashes(gDjuiConsoleTmpBuffer); djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_ERROR); smlua_logline(); } }
#define LOG_LUA_LINE_WARNING(...) { if (!gLuaActiveMod->showedScriptWarning) { gLuaActiveMod->showedScriptWarning = true; log_to_terminal("\x1b[33m[LUA] "); log_to_terminal(__VA_ARGS__); log_to_terminal("\x1b[0m\n"); smlua_mod_warning(); snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__); sys_swap_backslashes(gDjuiConsoleTmpBuffer); djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_WARNING); } }
#ifdef DEVELOPMENT
#define LUA_STACK_CHECK_BEGIN_NUM(state, n) int __LUA_STACK_TOP = lua_gettop(state) + (n)

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\n", 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 //
/////////////////////////
@ -37519,6 +37546,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

@ -743,33 +743,33 @@ const char* smlua_lnt_to_str(struct LSTNetworkType* lnt) {
void smlua_dump_stack(void) {
lua_State* L = gLuaState;
int top = lua_gettop(L);
printf("--------------\n");
log_to_terminal("--------------\n");
for (int i = 1; i <= top; i++) {
printf("%d\t%s\t", i, luaL_typename(L, i));
log_to_terminal("%d\t%s\t", i, luaL_typename(L, i));
switch (lua_type(L, i)) {
case LUA_TNUMBER:
printf("%g\n", lua_tonumber(L, i));
log_to_terminal("%g\n", lua_tonumber(L, i));
break;
case LUA_TSTRING:
printf("%s\n", lua_tostring(L, i));
log_to_terminal("%s\n", lua_tostring(L, i));
break;
case LUA_TBOOLEAN:
printf("%s\n", (lua_toboolean(L, i) ? "true" : "false"));
log_to_terminal("%s\n", (lua_toboolean(L, i) ? "true" : "false"));
break;
case LUA_TNIL:
printf("%s\n", "nil");
log_to_terminal("%s\n", "nil");
break;
default:
printf("%p\n", lua_topointer(L, i));
log_to_terminal("%p\n", lua_topointer(L, i));
break;
}
}
printf("--------------\n");
log_to_terminal("--------------\n");
}
void smlua_dump_globals(void) {
lua_State* L = gLuaState;
printf("--------------\n");
log_to_terminal("--------------\n");
lua_pushglobaltable(L);
// table is in the stack at index 't'
@ -777,12 +777,12 @@ void smlua_dump_globals(void) {
while (lua_next(L, -2) != 0) {
// uses 'key' (at index -2) and 'value' (at index -1)
if (lua_type(L, -2) == LUA_TSTRING) {
printf("%s - %s\n",
log_to_terminal("%s - %s\n",
lua_tostring(L, -2),
lua_typename(L, lua_type(L, -1)));
}
else {
printf("%s - %s\n",
log_to_terminal("%s - %s\n",
lua_typename(L, lua_type(L, -2)),
lua_typename(L, lua_type(L, -1)));
}
@ -790,23 +790,23 @@ void smlua_dump_globals(void) {
lua_pop(L, 1);
}
lua_pop(L, 1); // remove global table(-1)
printf("--------------\n");
log_to_terminal("--------------\n");
}
void smlua_dump_table(int index) {
lua_State* L = gLuaState;
printf("--------------\n");
log_to_terminal("--------------\n");
if (lua_getmetatable(L, index)) {
lua_pushnil(L); // first key
while (lua_next(L, -2) != 0) {
if (lua_type(L, -2) == LUA_TSTRING) {
printf("[meta] %s - %s\n",
log_to_terminal("[meta] %s - %s\n",
lua_tostring(L, -2),
lua_typename(L, lua_type(L, -1)));
}
else {
printf("[meta] %s - %s\n",
log_to_terminal("[meta] %s - %s\n",
lua_typename(L, lua_type(L, -2)),
lua_typename(L, lua_type(L, -1)));
}
@ -820,19 +820,19 @@ void smlua_dump_table(int index) {
while (lua_next(L, index) != 0) {
// uses 'key' (at index -2) and 'value' (at index -1)
if (lua_type(L, -2) == LUA_TSTRING) {
printf("%s - %s\n",
log_to_terminal("%s - %s\n",
lua_tostring(L, -2),
lua_typename(L, lua_type(L, -1)));
}
else {
printf("%s - %s\n",
log_to_terminal("%s - %s\n",
lua_typename(L, lua_type(L, -2)),
lua_typename(L, lua_type(L, -1)));
}
// removes 'value'; keeps 'key' for next iteration
lua_pop(L, 1);
}
printf("--------------\n");
log_to_terminal("--------------\n");
}
void smlua_logline(void) {

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,9 @@ int main(int argc, char *argv[]) {
network_init(NT_NONE, false);
}
// initialize terminal
terminal_init();
// main loop
while (true) {
debug_context_reset();
@ -611,6 +616,7 @@ int main(int argc, char *argv[]) {
discord_update();
#endif
mumble_update();
terminal_update();
#ifdef DEBUG
fflush(stdout);
fflush(stderr);

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

@ -0,0 +1,102 @@
#include <stdio.h>
#if !defined(_WIN32) && !defined(_WIN64)
#include <sys/select.h>
#include <unistd.h>
#include <termios.h>
#include "linenoise/linenoise.h"
#endif
#include "djui/djui_console.h"
#include "commands.h"
#include "pc_main.h"
#include "terminal.h"
#define TERMINAL_BUFFER_SIZE 1024
#if !defined(_WIN32) && !defined(_WIN64)
static struct linenoiseState sLinenoiseState;
#endif
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 !defined(_WIN32) && !defined(_WIN64)
if (sTerminalActive) linenoiseHide(&sLinenoiseState);
#endif
vprintf(fmt, args);
#if !defined(_WIN32) && !defined(_WIN64)
if (sTerminalActive) linenoiseShow(&sLinenoiseState);
#endif
va_end(args);
}
static void terminal_stop() {
#if !defined(_WIN32) && !defined(_WIN64)
if (!sTerminalInitialized) return;
linenoiseEditStop(&sLinenoiseState);
sTerminalActive = false;
#endif
}
void terminal_init() {
#if !defined(_WIN32) && !defined(_WIN64)
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;
#endif
}
void terminal_update() {
#if !defined(_WIN32) && !defined(_WIN64)
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();
}
#endif
}
void terminal_clear() {
#if !defined(_WIN32) && !defined(_WIN64)
if (!sTerminalInitialized) return;
linenoiseClearScreen();
#endif
}

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

@ -0,0 +1,9 @@
#pragma once
#include "src/pc/thread.h"
extern struct ThreadHandle gTerminalThread;
void log_to_terminal(const char* fmt, ...);
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;
}