diff --git a/docs/tr1/CHANGELOG.md b/docs/tr1/CHANGELOG.md index 2306a3bea..4468169d6 100644 --- a/docs/tr1/CHANGELOG.md +++ b/docs/tr1/CHANGELOG.md @@ -4,6 +4,7 @@ - added `/nextlevel` alias to `/endlevel` console command - added `/quit` alias to `/exit` console command - added an option to toggle the in-game UI, such as healthbars and ammo text (#1656) +- added the ability to cycle through console prompt history (#1571) - changed the easter egg console command to pack more punch - changed `/set` console command to do fuzzy matching (LostArtefacts/libtrx#38) - fixed console caret position off by a couple of pixels (regression from 3.0) diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index d707e0552..7c1e1d809 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -2,6 +2,7 @@ - added `/sfx` command - added `/nextlevel` alias to `/endlevel` console command - added `/quit` alias to `/exit` console command +- added the ability to cycle through console prompt history (#1571) - changed `/set` console command to do fuzzy matching (LostArtefacts/libtrx#38) - fixed crash in the `/set` console command (regression from 0.3) - fixed using console in cutscenes immediately exiting the game (regression from 0.3) diff --git a/src/libtrx/config/file.c b/src/libtrx/config/file.c index 096b3be83..53db90f69 100644 --- a/src/libtrx/config/file.c +++ b/src/libtrx/config/file.c @@ -1,6 +1,7 @@ #include "config/file.h" #include "filesystem.h" +#include "game/console/history.h" #include "log.h" #include "memory.h" diff --git a/src/libtrx/game/console/common.c b/src/libtrx/game/console/common.c index 81cf5f80a..dbebbbafd 100644 --- a/src/libtrx/game/console/common.c +++ b/src/libtrx/game/console/common.c @@ -1,5 +1,6 @@ #include "game/console/common.h" +#include "./internal.h" #include "game/console/extern.h" #include "game/game_string.h" #include "game/ui/widgets/console.h" @@ -18,6 +19,7 @@ static UI_WIDGET *m_Console; void Console_Init(void) { m_Console = UI_Console_Create(); + Console_History_Init(); } void Console_Shutdown(void) @@ -27,6 +29,8 @@ void Console_Shutdown(void) m_Console = NULL; } + Console_History_Shutdown(); + m_IsOpened = false; } diff --git a/src/libtrx/game/console/history.c b/src/libtrx/game/console/history.c new file mode 100644 index 000000000..17cf15d5a --- /dev/null +++ b/src/libtrx/game/console/history.c @@ -0,0 +1,91 @@ +#include "game/console/history.h" + +#include "config/file.h" +#include "memory.h" +#include "utils.h" +#include "vector.h" + +#define MAX_HISTORY_ENTRIES 30 + +VECTOR *m_History = NULL; +static const char *m_Path = "cfg/" PROJECT_NAME "_console_history.json5"; + +void M_LoadFromJSON(JSON_OBJECT *const root_obj) +{ + JSON_ARRAY *const arr = JSON_ObjectGetArray(root_obj, "entries"); + if (arr == NULL) { + return; + } + + Console_History_Clear(); + for (size_t i = 0; i < arr->length; i++) { + const char *const line = JSON_ArrayGetString(arr, i, NULL); + if (line != NULL) { + Console_History_Append(line); + } + } +} + +void M_DumpToJSON(JSON_OBJECT *const root_obj) +{ + JSON_ARRAY *const arr = JSON_ArrayNew(); + + bool has_elements = false; + for (int32_t i = 0; i < Console_History_GetLength(); i++) { + JSON_ArrayAppendString(arr, Console_History_Get(i)); + has_elements = true; + } + + if (has_elements) { + JSON_ObjectAppendArray(root_obj, "entries", arr); + } else { + JSON_ArrayFree(arr); + } +} + +void Console_History_Init(void) +{ + m_History = Vector_Create(sizeof(char *)); + ConfigFile_Read(m_Path, &M_LoadFromJSON); +} + +void Console_History_Shutdown(void) +{ + if (m_History != NULL) { + ConfigFile_Write(m_Path, &M_DumpToJSON); + for (int32_t i = m_History->count - 1; i >= 0; i--) { + char *const prompt = *(char **)Vector_Get(m_History, i); + Memory_Free(prompt); + } + Vector_Free(m_History); + m_History = NULL; + } +} + +int32_t Console_History_GetLength(void) +{ + return m_History->count; +} + +void Console_History_Clear(void) +{ + Vector_Clear(m_History); +} + +void Console_History_Append(const char *const prompt) +{ + if (m_History->count == MAX_HISTORY_ENTRIES) { + Vector_RemoveAt(m_History, 0); + } + char *prompt_copy = Memory_DupStr(prompt); + Vector_Add(m_History, &prompt_copy); +} + +const char *Console_History_Get(const int32_t idx) +{ + if (idx < 0 || idx >= m_History->count) { + return NULL; + } + const char *const prompt = *(char **)Vector_Get(m_History, idx); + return prompt; +} diff --git a/src/libtrx/game/console/internal.h b/src/libtrx/game/console/internal.h new file mode 100644 index 000000000..b7c7b93ac --- /dev/null +++ b/src/libtrx/game/console/internal.h @@ -0,0 +1,4 @@ +#pragma once + +void Console_History_Init(void); +void Console_History_Shutdown(void); diff --git a/src/libtrx/game/ui/widgets/console.c b/src/libtrx/game/ui/widgets/console.c index be8abf3b8..6ebcc81f9 100644 --- a/src/libtrx/game/ui/widgets/console.c +++ b/src/libtrx/game/ui/widgets/console.c @@ -2,6 +2,7 @@ #include "game/clock.h" #include "game/console/common.h" +#include "game/console/history.h" #include "game/text.h" #include "game/ui/common.h" #include "game/ui/events.h" @@ -16,8 +17,9 @@ #include #define WINDOW_MARGIN 5 -#define LOG_MARGIN 10 #define MAX_LOG_LINES 20 +#define MAX_LISTENERS 4 +#define LOG_MARGIN 10 #define LOG_SCALE 0.8 #define DELAY_PER_CHAR 0.2 @@ -28,17 +30,17 @@ typedef struct { UI_WIDGET *spacer; char *log_lines; int32_t logs_on_screen; + int32_t history_idx; - int32_t listener1; - int32_t listener2; - int32_t listener3; - + int32_t listeners[MAX_LISTENERS]; struct { double expire_at; UI_WIDGET *label; } logs[MAX_LOG_LINES]; } UI_CONSOLE; +static void M_MoveHistoryUp(UI_CONSOLE *self); +static void M_MoveHistoryDown(UI_CONSOLE *self); static void M_HandlePromptCancel(const EVENT *event, void *data); static void M_HandlePromptConfirm(const EVENT *event, void *data); static void M_HandleCanvasResize(const EVENT *event, void *data); @@ -51,6 +53,30 @@ static void M_Control(UI_CONSOLE *self); static void M_Draw(UI_CONSOLE *self); static void M_Free(UI_CONSOLE *self); +static void M_MoveHistoryUp(UI_CONSOLE *const self) +{ + self->history_idx--; + CLAMP(self->history_idx, 0, Console_History_GetLength()); + const char *const new_prompt = Console_History_Get(self->history_idx); + if (new_prompt == NULL) { + UI_Prompt_ChangeText(self->prompt, ""); + } else { + UI_Prompt_ChangeText(self->prompt, new_prompt); + } +} + +static void M_MoveHistoryDown(UI_CONSOLE *const self) +{ + self->history_idx++; + CLAMP(self->history_idx, 0, Console_History_GetLength()); + const char *const new_prompt = Console_History_Get(self->history_idx); + if (new_prompt == NULL) { + UI_Prompt_ChangeText(self->prompt, ""); + } else { + UI_Prompt_ChangeText(self->prompt, new_prompt); + } +} + static void M_HandlePromptCancel(const EVENT *const event, void *const data) { Console_Close(); @@ -58,9 +84,13 @@ static void M_HandlePromptCancel(const EVENT *const event, void *const data) static void M_HandlePromptConfirm(const EVENT *const event, void *const data) { + UI_CONSOLE *const self = (UI_CONSOLE *)data; const char *text = event->data; + Console_History_Append(text); Console_Eval(text); Console_Close(); + + self->history_idx = Console_History_GetLength(); } static void M_HandleCanvasResize(const EVENT *event, void *data) @@ -69,6 +99,24 @@ static void M_HandleCanvasResize(const EVENT *event, void *data) UI_Stack_SetSize(self->container, M_GetWidth(self), M_GetHeight(self)); } +static void M_HandleKeyDown(const EVENT *const event, void *const user_data) +{ + if (!Console_IsOpened()) { + return; + } + + UI_CONSOLE *const self = user_data; + const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data; + + // clang-format off + switch (key) { + case UI_KEY_UP: M_MoveHistoryUp(self); break; + case UI_KEY_DOWN: M_MoveHistoryDown(self); break; + default: break; + } + // clang-format on +} + static void M_UpdateLogCount(UI_CONSOLE *const self) { self->logs_on_screen = 0; @@ -114,9 +162,9 @@ static void M_Free(UI_CONSOLE *const self) self->spacer->free(self->spacer); self->prompt->free(self->prompt); self->container->free(self->container); - UI_Events_Unsubscribe(self->listener1); - UI_Events_Unsubscribe(self->listener2); - UI_Events_Unsubscribe(self->listener3); + for (int32_t i = 0; i < MAX_LISTENERS; i++) { + UI_Events_Unsubscribe(self->listeners[i]); + } Memory_Free(self); } @@ -151,12 +199,15 @@ UI_WIDGET *UI_Console_Create(void) M_SetPosition(self, WINDOW_MARGIN, WINDOW_MARGIN); - self->listener1 = UI_Events_Subscribe( - "confirm", self->prompt, M_HandlePromptConfirm, NULL); - self->listener2 = + int32_t i = 0; + self->listeners[i++] = UI_Events_Subscribe( + "confirm", self->prompt, M_HandlePromptConfirm, self); + self->listeners[i++] = UI_Events_Subscribe("cancel", self->prompt, M_HandlePromptCancel, NULL); - self->listener3 = + self->listeners[i++] = UI_Events_Subscribe("canvas_resize", NULL, M_HandleCanvasResize, self); + self->listeners[i++] = + UI_Events_Subscribe("key_down", NULL, M_HandleKeyDown, self); return (UI_WIDGET *)self; } @@ -165,6 +216,7 @@ void UI_Console_HandleOpen(UI_WIDGET *const widget) { UI_CONSOLE *const self = (UI_CONSOLE *)widget; UI_Prompt_SetFocus(self->prompt, true); + self->history_idx = Console_History_GetLength(); } void UI_Console_HandleClose(UI_WIDGET *const widget) diff --git a/src/libtrx/game/ui/widgets/prompt.c b/src/libtrx/game/ui/widgets/prompt.c index 225924753..f99aaebe5 100644 --- a/src/libtrx/game/ui/widgets/prompt.c +++ b/src/libtrx/game/ui/widgets/prompt.c @@ -213,6 +213,7 @@ static void M_HandleKeyDown(const EVENT *const event, void *const user_data) case UI_KEY_BACK: M_DeleteCharBack(self); break; case UI_KEY_RETURN: M_Confirm(self); break; case UI_KEY_ESCAPE: M_Cancel(self); break; + default: break; } // clang-format on } @@ -305,3 +306,12 @@ void UI_Prompt_Clear(UI_WIDGET *const widget) UI_PROMPT *const self = (UI_PROMPT *)widget; M_Clear(self); } + +void UI_Prompt_ChangeText(UI_WIDGET *widget, const char *new_text) +{ + UI_PROMPT *const self = (UI_PROMPT *)widget; + Memory_FreePointer(&self->current_text); + self->current_text = Memory_DupStr(new_text); + self->caret_pos = strlen(new_text); + M_UpdateCaretLabel(self); +} diff --git a/src/libtrx/include/libtrx/game/console/history.h b/src/libtrx/include/libtrx/game/console/history.h new file mode 100644 index 000000000..1e851ed03 --- /dev/null +++ b/src/libtrx/include/libtrx/game/console/history.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +int32_t Console_History_GetLength(void); +void Console_History_Clear(void); +void Console_History_Append(const char *prompt); +const char *Console_History_Get(int32_t idx); diff --git a/src/libtrx/include/libtrx/game/ui/common.h b/src/libtrx/include/libtrx/game/ui/common.h index d64a23ad5..bf6735690 100644 --- a/src/libtrx/include/libtrx/game/ui/common.h +++ b/src/libtrx/include/libtrx/game/ui/common.h @@ -3,6 +3,8 @@ #include "./events.h" typedef enum { + UI_KEY_UP, + UI_KEY_DOWN, UI_KEY_LEFT, UI_KEY_RIGHT, UI_KEY_HOME, diff --git a/src/libtrx/include/libtrx/game/ui/widgets/prompt.h b/src/libtrx/include/libtrx/game/ui/widgets/prompt.h index fbf387d4a..972b8d128 100644 --- a/src/libtrx/include/libtrx/game/ui/widgets/prompt.h +++ b/src/libtrx/include/libtrx/game/ui/widgets/prompt.h @@ -7,6 +7,7 @@ void UI_Prompt_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); void UI_Prompt_SetFocus(UI_WIDGET *widget, bool is_focused); void UI_Prompt_Clear(UI_WIDGET *widget); +void UI_Prompt_ChangeText(UI_WIDGET *widget, const char *new_text); extern const char *UI_Prompt_GetPromptChar(void); extern int32_t UI_Prompt_GetCaretFlashRate(void); diff --git a/src/libtrx/include/libtrx/utils.h b/src/libtrx/include/libtrx/utils.h index c9ac66b0a..3a942261f 100644 --- a/src/libtrx/include/libtrx/utils.h +++ b/src/libtrx/include/libtrx/utils.h @@ -36,3 +36,9 @@ #define MKTAG(a, b, c, d) \ ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24)) + +#if TR_VERSION == 1 + #define PROJECT_NAME "TR1X" +#elif TR_VERSION == 2 + #define PROJECT_NAME "TR2X" +#endif diff --git a/src/libtrx/meson.build b/src/libtrx/meson.build index 872d9eaca..0b9ad6d39 100644 --- a/src/libtrx/meson.build +++ b/src/libtrx/meson.build @@ -88,6 +88,7 @@ sources = [ 'game/console/cmd/sfx.c', 'game/console/cmd/teleport.c', 'game/console/common.c', + 'game/console/history.c', 'game/game_string.c', 'game/items.c', 'game/objects/names.c', diff --git a/src/tr1/game/ui/common.c b/src/tr1/game/ui/common.c index 0495b0e3c..8cdc15cbc 100644 --- a/src/tr1/game/ui/common.c +++ b/src/tr1/game/ui/common.c @@ -18,6 +18,8 @@ UI_INPUT UI_TranslateInput(uint32_t system_keycode) { // clang-format off switch (system_keycode) { + case SDLK_UP: return UI_KEY_UP; + case SDLK_DOWN: return UI_KEY_DOWN; case SDLK_LEFT: return UI_KEY_LEFT; case SDLK_RIGHT: return UI_KEY_RIGHT; case SDLK_HOME: return UI_KEY_HOME; diff --git a/src/tr2/game/ui/common.c b/src/tr2/game/ui/common.c index 3833a20f7..f9cf6c892 100644 --- a/src/tr2/game/ui/common.c +++ b/src/tr2/game/ui/common.c @@ -16,6 +16,8 @@ UI_INPUT UI_TranslateInput(uint32_t system_keycode) { // clang-format off switch (system_keycode) { + case VK_UP: return UI_KEY_UP; + case VK_DOWN: return UI_KEY_DOWN; case VK_LEFT: return UI_KEY_LEFT; case VK_RIGHT: return UI_KEY_RIGHT; case VK_HOME: return UI_KEY_HOME;