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