From 3354a1a35239cb01f1512aaf2677430c7a367725 Mon Sep 17 00:00:00 2001 From: Dav999-v Date: Sat, 18 Mar 2023 22:30:16 +0100 Subject: [PATCH] Add support for button glyph display This adds a function that converts an action (such as interacting in-game) to the corresponding button text ("ENTER", "E") or button glyph (PlayStation triangle, Steam Deck Y, etc). This function currently only gives the existing ENTERs or Es, because I don't know how best to detect controller usage, or whether the game is running on a Steam Deck, or what buttons need to be displayed there. Still, it should now be really easy to adapt the rendering of keyboard keys to consoles, controllers, or rebound keys. To identify the actions that currently need to be displayed, this commit also adds the initial enums for action sets as described by Ethan in a comment in #834 (Jan 18, 2022). --- desktop_version/CMakeLists.txt | 1 + desktop_version/lang/en/strings.xml | 10 +- desktop_version/src/ActionSets.h | 80 ++++++++++++++++ desktop_version/src/ButtonGlyphs.cpp | 131 +++++++++++++++++++++++++++ desktop_version/src/ButtonGlyphs.h | 24 +++++ desktop_version/src/Game.cpp | 31 +++++-- desktop_version/src/Render.cpp | 37 +++++--- desktop_version/src/VFormat.c | 37 ++++++-- desktop_version/src/VFormat.h | 7 +- desktop_version/src/main.cpp | 13 ++- 10 files changed, 332 insertions(+), 39 deletions(-) create mode 100644 desktop_version/src/ActionSets.h create mode 100644 desktop_version/src/ButtonGlyphs.cpp create mode 100644 desktop_version/src/ButtonGlyphs.h diff --git a/desktop_version/CMakeLists.txt b/desktop_version/CMakeLists.txt index ce3fe902..7c2beec4 100644 --- a/desktop_version/CMakeLists.txt +++ b/desktop_version/CMakeLists.txt @@ -71,6 +71,7 @@ endif() set(VVV_SRC src/BinaryBlob.cpp src/BlockV.cpp + src/ButtonGlyphs.cpp src/CWrappers.cpp src/Ent.cpp src/Entity.cpp diff --git a/desktop_version/lang/en/strings.xml b/desktop_version/lang/en/strings.xml index 642551e9..dac6ce01 100644 --- a/desktop_version/lang/en/strings.xml +++ b/desktop_version/lang/en/strings.xml @@ -420,7 +420,8 @@ - + + @@ -434,7 +435,8 @@ - + + @@ -652,8 +654,10 @@ + - + + diff --git a/desktop_version/src/ActionSets.h b/desktop_version/src/ActionSets.h new file mode 100644 index 00000000..b522c1fa --- /dev/null +++ b/desktop_version/src/ActionSets.h @@ -0,0 +1,80 @@ +/* For now, this isn't really a foundation for action sets yet; button glyphs + * just need to be able to identify actions that are printed in text like + * "Press ENTER to teleport". Thus, this currently ONLY contains identifiers + * for the actions that button glyphs are needed for. + * + * Based on this comment: + * https://github.com/TerryCavanagh/VVVVVV/issues/834#issuecomment-1015692161 + */ + + +#ifndef ACTIONSETS_H +#define ACTIONSETS_H + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/*----------------------------------------* + * List of all action sets (all "states") * + *----------------------------------------*/ +typedef enum +{ + //ActionSet_Global, + //ActionSet_Menu, + ActionSet_InGame + //ActionSet_Editor +} +ActionSet; + + +/*----------------------------------------------------------* + * An enum for each actionset, with the actions in that set * + *----------------------------------------------------------*/ +/* +typedef enum +{ + Action_Global_Mute, + Action_Global_MuteMusic +} +Action_Global; +*/ + +typedef enum +{ + Action_InGame_Interact, + Action_InGame_Map +} +Action_InGame; + +/* +typedef enum +{ + //Action_Editor_PrevTool, + //Action_Editor_NextTool +} +Action_Editor; +*/ + + +/*-----------------------------------------* + * A union to represent any actionset enum * + *-----------------------------------------*/ +typedef union +{ + int intval; + //Action_Global Global; + Action_InGame InGame; + //Action_Editor Editor; +} +Action; + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ACTIONSETS_H diff --git a/desktop_version/src/ButtonGlyphs.cpp b/desktop_version/src/ButtonGlyphs.cpp new file mode 100644 index 00000000..b032c74c --- /dev/null +++ b/desktop_version/src/ButtonGlyphs.cpp @@ -0,0 +1,131 @@ +#include "ButtonGlyphs.h" + +#include + +#include "Game.h" +#include "Localization.h" +#include "UTF8.h" + +extern "C" +{ + +typedef enum +{ + GLYPH_NINTENDO_DECK_A, // Note that for the Deck, the icons are same as Nintendo but the layout is the same as Xbox + GLYPH_NINTENDO_DECK_B, + GLYPH_NINTENDO_DECK_X, + GLYPH_NINTENDO_DECK_Y, + GLYPH_NINTENDO_PLUS, + GLYPH_NINTENDO_MINUS, + GLYPH_NINTENDO_L, + GLYPH_NINTENDO_R, + GLYPH_NINTENDO_ZL, + GLYPH_NINTENDO_ZR, + GLYPH_NINTENDO_XBOX_LSTICK, + GLYPH_NINTENDO_XBOX_RSTICK, + GLYPH_NINTENDO_SL, + GLYPH_NINTENDO_SR, + GLYPH_GENERIC_L, + GLYPH_GENERIC_R, + + GLYPH_PLAYSTATION_CIRCLE, + GLYPH_PLAYSTATION_CROSS, + GLYPH_PLAYSTATION_TRIANGLE, + GLYPH_PLAYSTATION_SQUARE, + GLYPH_PLAYSTATION_START, + GLYPH_PLAYSTATION_OPTIONS, + GLYPH_PLAYSTATION_DECK_L1, + GLYPH_PLAYSTATION_DECK_R1, + GLYPH_PLAYSTATION_DECK_L2, + GLYPH_PLAYSTATION_DECK_R2, + GLYPH_PLAYSTATION_DECK_L3, + GLYPH_PLAYSTATION_DECK_R3, + GLYPH_DECK_L4, + GLYPH_DECK_R4, + GLYPH_DECK_L5, + GLYPH_DECK_R5, + + GLYPH_XBOX_B, + GLYPH_XBOX_A, + GLYPH_XBOX_Y, + GLYPH_XBOX_X, + GLYPH_XBOX_DECK_VIEW, + GLYPH_XBOX_DECK_MENU, + GLYPH_XBOX_LB, + GLYPH_XBOX_RB, + GLYPH_XBOX_LT, + GLYPH_XBOX_RT, + GLYPH_NINTENDO_GENERIC_ACTIONRIGHT, + GLYPH_NINTENDO_GENERIC_ACTIONDOWN, + GLYPH_NINTENDO_GENERIC_ACTIONUP, + GLYPH_NINTENDO_GENERIC_ACTIONLEFT, + GLYPH_NINTENDO_GENERIC_STICK, + GLYPH_UNKNOWN, + + GLYPH_TOTAL +} +ButtonGlyphKey; + +static char glyph[GLYPH_TOTAL][5]; + +void BUTTONGLYPHS_init(void) +{ + /* Set glyph array to strings for all the button glyph codepoints (U+EBxx) */ + for (int i = 0; i < GLYPH_TOTAL; i++) + { + SDL_strlcpy(glyph[i], UTF8_encode(0xEB00+i).bytes, sizeof(glyph[i])); + } +} + +bool BUTTONGLYPHS_keyboard_is_available(void) +{ + /* Returns true if it makes sense to show button hints that are only available + * on keyboards (like press M to mute), false if we're on a console. */ + return true; +} + +bool BUTTONGLYPHS_keyboard_is_active(void) +{ + /* Returns true if, not only do we have a keyboard available, but it's also the + * active input method. (So, show keyboard keys, if false, show controller glyphs) */ + return true; +} + +const char* BUTTONGLYPHS_get_wasd_text(void) +{ + /* Returns the string to use in Welcome Aboard */ + if (BUTTONGLYPHS_keyboard_is_active()) + { + return loc::gettext("Press arrow keys or WASD to move"); + } + return loc::gettext("Press left/right to move"); +} + +const char* BUTTONGLYPHS_get_button(const ActionSet actionset, const Action action) +{ + /* Given a specific action (like INTERACT in-game), + * return either a (localized) keyboard key string like "ENTER" or "E", + * or a controller button glyph from the table above like glyph[GLYPH_XBOX_Y], + * to fill into strings like "Press {button} to activate terminal". */ + switch (actionset) + { + case ActionSet_InGame: + switch (action.InGame) + { + case Action_InGame_Interact: + if (game.separate_interact) + { + return "E"; + } + return loc::gettext("ENTER"); + case Action_InGame_Map: + return loc::gettext("ENTER"); + } + break; + } + + SDL_assert(0 && "Trying to get label/glyph for unknown action!"); + return glyph[GLYPH_UNKNOWN]; +} + +} // extern "C" diff --git a/desktop_version/src/ButtonGlyphs.h b/desktop_version/src/ButtonGlyphs.h new file mode 100644 index 00000000..fa31a04d --- /dev/null +++ b/desktop_version/src/ButtonGlyphs.h @@ -0,0 +1,24 @@ +#ifndef BUTTONGLYPHS_H +#define BUTTONGLYPHS_H + +#include + +#include "ActionSets.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +void BUTTONGLYPHS_init(void); + +bool BUTTONGLYPHS_keyboard_is_available(void); +bool BUTTONGLYPHS_keyboard_is_active(void); +const char* BUTTONGLYPHS_get_wasd_text(void); +const char* BUTTONGLYPHS_get_button(ActionSet actionset, Action action); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // BUTTONGLYPHS_H diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index b8876a88..9a85483f 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -6,6 +6,7 @@ #include #include +#include "ButtonGlyphs.h" #include "Constants.h" #include "CustomLevels.h" #include "DeferCallbacks.h" @@ -832,7 +833,7 @@ void Game::updatestate(void) break; case 4: //End of opening cutscene for now - graphics.createtextbox(loc::gettext("Press arrow keys or WASD to move"), -1, 195, 174, 174, 174); + graphics.createtextbox(BUTTONGLYPHS_get_wasd_text(), -1, 195, 174, 174, 174); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxwrap(4); graphics.textboxcentertext(); @@ -864,7 +865,16 @@ void Game::updatestate(void) if (!obj.flags[13]) { obj.flags[13] = true; - graphics.createtextbox(loc::gettext("Press ENTER to view map and quicksave"), -1, 155, 174, 174, 174); + + char buffer[SCREEN_WIDTH_CHARS*3 + 1]; + vformat_buf( + buffer, sizeof(buffer), + loc::gettext("Press {button} to view map and quicksave"), + "button:but", + vformat_button(ActionSet_InGame, Action_InGame_Map) + ); + + graphics.createtextbox(buffer, -1, 155, 174, 174, 174); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxwrap(4); graphics.textboxcentertext(); @@ -1067,13 +1077,16 @@ void Game::updatestate(void) case 17: //Arrow key tutorial obj.removetrigger(17); - graphics.createtextbox(loc::gettext("If you prefer, you can press UP or DOWN instead of ACTION to flip."), -1, 187, 174, 174, 174); - graphics.textboxprintflags(PR_FONT_INTERFACE); - graphics.textboxwrap(2); - graphics.textboxcentertext(); - graphics.textboxpad(1, 1); - graphics.textboxcenterx(); - graphics.textboxtimer(100); + if (BUTTONGLYPHS_keyboard_is_active()) + { + graphics.createtextbox(loc::gettext("If you prefer, you can press UP or DOWN instead of ACTION to flip."), -1, 187, 174, 174, 174); + graphics.textboxprintflags(PR_FONT_INTERFACE); + graphics.textboxwrap(2); + graphics.textboxcentertext(); + graphics.textboxpad(1, 1); + graphics.textboxcenterx(); + graphics.textboxtimer(100); + } setstate(0); break; diff --git a/desktop_version/src/Render.cpp b/desktop_version/src/Render.cpp index 251d490a..8391d024 100644 --- a/desktop_version/src/Render.cpp +++ b/desktop_version/src/Render.cpp @@ -1,5 +1,6 @@ #include +#include "ActionSets.h" #include "Constants.h" #include "Credits.h" #include "CustomLevels.h" @@ -1841,18 +1842,12 @@ static const char* interact_prompt( const size_t buffer_size, const char* raw ) { - const char* button; - - if (game.separate_interact) - { - button = loc::gettext("E"); - } - else - { - button = loc::gettext("ENTER"); - } - - vformat_buf(buffer, buffer_size, raw, "button:str", button); + vformat_buf( + buffer, buffer_size, + raw, + "button:but", + vformat_button(ActionSet_InGame, Action_InGame_Interact) + ); return buffer; } @@ -1958,7 +1953,14 @@ void gamerender(void) if (alpha > 100) { - font::print(PR_BRIGHTNESS(alpha) | PR_BOR, 5, 5, loc::gettext("[Press ENTER to return to editor]"), 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2)); + char buffer[SCREEN_WIDTH_CHARS + 1]; + vformat_buf( + buffer, sizeof(buffer), + loc::gettext("[Press {button} to return to editor]"), + "button:but", + vformat_button(ActionSet_InGame, Action_InGame_Map) + ); + font::print(PR_BRIGHTNESS(alpha) | PR_BOR, 5, 5, buffer, 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2)); } } #endif @@ -2077,7 +2079,14 @@ void gamerender(void) } } - font::print(PR_BOR | PR_CEN, -1, 228, loc::gettext("[Press ENTER to stop]"), 160 - (help.glow/2), 160 - (help.glow/2), 160 - (help.glow/2)); + char buffer[SCREEN_WIDTH_CHARS + 1]; + vformat_buf( + buffer, sizeof(buffer), + loc::gettext("[Press {button} to stop]"), + "button:but", + vformat_button(ActionSet_InGame, Action_InGame_Map) + ); + font::print(PR_BOR | PR_CEN, -1, 228, buffer, 160 - (help.glow/2), 160 - (help.glow/2), 160 - (help.glow/2)); } else if(game.swngame==2) { diff --git a/desktop_version/src/VFormat.c b/desktop_version/src/VFormat.c index ba5eedd6..a485954a 100644 --- a/desktop_version/src/VFormat.c +++ b/desktop_version/src/VFormat.c @@ -4,6 +4,7 @@ #include #include "Alloc.h" +#include "ButtonGlyphs.h" #include "CWrappers.h" #include "UTF8.h" @@ -28,18 +29,36 @@ static inline void trim_whitespace(const char** string, size_t* bytes) } } -static inline void call_with_button(format_callback callback, void* userdata, int button_value) +int vformat_button(ActionSet actionset, int action) +{ + /* Pack an ActionSet and Action into a single vararg int. + * action is an Action */ + return (((int) actionset) << 16) | action; +} + +static void vformat_unbutton(ActionSet* actionset, Action* action, const int vararg_value) +{ + // Unpack the ActionSet and Action from a packed vararg value. + *actionset = vararg_value >> 16; + action->intval = vararg_value & 0xFFFF; +} + +static inline void call_with_button(format_callback callback, void* userdata, int vararg_value) { /* Call the given callback with the specified button character (from * Unicode Private Use Area) so the text renderer can display it. */ - if (button_value < 0 || button_value > 0xFF) - { - button_value = 0xFF; - } + ActionSet actionset; + Action action; + vformat_unbutton(&actionset, &action, vararg_value); - UTF8_encoding utf8 = UTF8_encode(0xE000 + button_value); - callback(userdata, utf8.bytes, utf8.nbytes); + const char* button_text = BUTTONGLYPHS_get_button(actionset, action); + if (button_text == NULL) + { + callback(userdata, "[null]", 6); + return; + } + callback(userdata, button_text, SDL_strlen(button_text)); } static inline void call_with_upper(format_callback callback, void* userdata, const char* string, size_t bytes) @@ -274,11 +293,11 @@ void vformat_cb_valist( } else if (arg_type_len == 3 && SDL_memcmp(arg_type, "but", 3) == 0) { - int button_value = va_arg(args_copy, int); + int vararg_value = va_arg(args_copy, int); if (match) { - call_with_button(callback, userdata, button_value); + call_with_button(callback, userdata, vararg_value); } } else diff --git a/desktop_version/src/VFormat.h b/desktop_version/src/VFormat.h index c2a826f1..94b92e2c 100644 --- a/desktop_version/src/VFormat.h +++ b/desktop_version/src/VFormat.h @@ -43,7 +43,7 @@ * The valid types are: * - int Signed integer * - str const char* - * - but Controller button icon + * - but Controller button icon: vformat_button(actionset, action) * * Special case: if an argument name is a single underscore (_), it matches * any name not found earlier in the list. This should normally not be needed. @@ -75,6 +75,8 @@ #include #include +#include "ActionSets.h" + #ifdef __cplusplus extern "C" { @@ -83,6 +85,9 @@ extern "C" typedef void (*format_callback)(void* userdata, const char* string, size_t bytes); +int vformat_button(ActionSet actionset, int action); + + void vformat_cb_valist( format_callback callback, void* userdata, diff --git a/desktop_version/src/main.cpp b/desktop_version/src/main.cpp index 5ddd6281..80323d4b 100644 --- a/desktop_version/src/main.cpp +++ b/desktop_version/src/main.cpp @@ -4,6 +4,7 @@ #include #endif +#include "ButtonGlyphs.h" #include "CustomLevels.h" #include "DeferCallbacks.h" #include "Editor.h" @@ -649,6 +650,7 @@ int main(int argc, char *argv[]) gameScreen.init(&screen_settings); } + BUTTONGLYPHS_init(); font::load_main(); // This loads music too... @@ -915,9 +917,14 @@ static void unfocused_run(void) * a language changes the used language metadata but not the loaded strings... */ uint32_t flags = PR_CEN | PR_BOR | PR_FONT_IDX(loc::langmeta.font_idx); font::print(flags | PR_CJK_HIGH, -1, FLIP(110), loc::gettext("Game paused"), 196 - help.glow, 255 - help.glow, 196 - help.glow); - font::print(flags | PR_CJK_LOW, -1, FLIP(120), loc::gettext("[click to resume]"), 196 - help.glow, 255 - help.glow, 196 - help.glow); - font::print(flags | PR_CJK_HIGH, -1, FLIP(220), loc::gettext("Press M to mute in game"), 164 - help.glow, 196 - help.glow, 164 - help.glow); - font::print(flags, -1, FLIP(230), loc::gettext("Press N to mute music only"), 164 - help.glow, 196 - help.glow, 164 - help.glow); + + if (BUTTONGLYPHS_keyboard_is_available()) + { + font::print(flags | PR_CJK_LOW, -1, FLIP(120), loc::gettext("[click to resume]"), 196 - help.glow, 255 - help.glow, 196 - help.glow); + + font::print(flags | PR_CJK_HIGH, -1, FLIP(220), loc::gettext("Press M to mute in game"), 164 - help.glow, 196 - help.glow, 164 - help.glow); + font::print(flags, -1, FLIP(230), loc::gettext("Press N to mute music only"), 164 - help.glow, 196 - help.glow, 164 - help.glow); + } #undef FLIP } graphics.render();