console: remember prompt history

Resolves #1571.
This commit is contained in:
Marcin Kurczewski 2024-10-07 19:40:33 +02:00
parent 53101ab9a5
commit 8bab311d79
No known key found for this signature in database
GPG key ID: CC65E6FD28CAE42A
15 changed files with 198 additions and 12 deletions

View file

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

View file

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

View file

@ -1,6 +1,7 @@
#include "config/file.h"
#include "filesystem.h"
#include "game/console/history.h"
#include "log.h"
#include "memory.h"

View file

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

View file

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

View file

@ -0,0 +1,4 @@
#pragma once
void Console_History_Init(void);
void Console_History_Shutdown(void);

View file

@ -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 <string.h>
#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)

View file

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

View file

@ -0,0 +1,8 @@
#pragma once
#include <stdint.h>
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);

View file

@ -3,6 +3,8 @@
#include "./events.h"
typedef enum {
UI_KEY_UP,
UI_KEY_DOWN,
UI_KEY_LEFT,
UI_KEY_RIGHT,
UI_KEY_HOME,

View file

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

View file

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

View file

@ -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',

View file

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

View file

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