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).
This commit is contained in:
Dav999-v 2023-03-18 22:30:16 +01:00 committed by Misa Elizabeth Kai
parent 5180e430a2
commit 3354a1a352
10 changed files with 332 additions and 39 deletions

View File

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

View File

@ -420,7 +420,8 @@
<string english="The levels path is:" translation="" explanation="" max="40"/>
<string english="[ Press ACTION to Start ]" translation="" explanation="title screen" max="38*2"/>
<string english="ACTION = Space, Z, or V" translation="" explanation="title screen" max="38*3"/>
<string english="[Press ENTER to return to editor]" translation="" explanation="`to editor` is sorta redundant" max="40"/>
<string english="[Press ENTER to return to editor]" translation="" explanation="***OUTDATED***" max="40"/>
<string english="[Press {button} to return to editor]" translation="" explanation="`to editor` is sorta redundant" max="40"/>
<string english="- Press ACTION to advance text -" translation="" explanation="to dismiss a textbox" max="40"/>
<string english="Press ACTION to continue" translation="" explanation="" max="34"/>
<string english="Current Time" translation="" explanation="super gravitron, stopwatch time" max="20"/>
@ -434,7 +435,8 @@
<string english="All Trophies collected!" translation="" explanation="" max="38*2"/>
<string english="New Record!" translation="" explanation="" max="20"/>
<string english="New Trophy!" translation="" explanation="" max="20"/>
<string english="[Press ENTER to stop]" translation="" explanation="stop super gravitron" max="40"/>
<string english="[Press ENTER to stop]" translation="" explanation="***OUTDATED***" max="40"/>
<string english="[Press {button} to stop]" translation="" explanation="stop super gravitron" max="40"/>
<string english="SUPER GRAVITRON" translation="" explanation="" max="20"/>
<string english="SUPER GRAVITRON HIGHSCORE" translation="" explanation="" max="38*4"/>
<string english="MAP" translation="" explanation="in-game menu" max="8"/>
@ -652,8 +654,10 @@
<string english="All crewmates rescued!" translation="" explanation="" max="32"/>
<string english="Game Saved" translation="" explanation="" max="30"/>
<string english="Press arrow keys or WASD to move" translation="" explanation="" max="32*2"/>
<string english="Press left/right to move" translation="" explanation="" max="32*2"/>
<string english="Press ACTION to flip" translation="" explanation="" max="32*3"/>
<string english="Press ENTER to view map and quicksave" translation="" explanation="" max="32*3"/>
<string english="Press ENTER to view map and quicksave" translation="" explanation="***OUTDATED***" max="32*3"/>
<string english="Press {button} to view map and quicksave" translation="" explanation="" max="32*3"/>
<string english="If you prefer, you can press UP or DOWN instead of ACTION to flip." translation="" explanation="" max="34*3"/>
<string english="Help! Can anyone hear this message?" translation="" explanation="Violet speaking via Comms Relay" max="25*4"/>
<string english="Verdigris? Are you out there? Are you ok?" translation="" explanation="Violet speaking via Comms Relay" max="25*4"/>

View File

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

View File

@ -0,0 +1,131 @@
#include "ButtonGlyphs.h"
#include <SDL.h>
#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"

View File

@ -0,0 +1,24 @@
#ifndef BUTTONGLYPHS_H
#define BUTTONGLYPHS_H
#include <stdbool.h>
#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

View File

@ -6,6 +6,7 @@
#include <string.h>
#include <tinyxml2.h>
#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;

View File

@ -1,5 +1,6 @@
#include <SDL.h>
#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)
{

View File

@ -4,6 +4,7 @@
#include <stdbool.h>
#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

View File

@ -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 <stdarg.h>
#include <stddef.h>
#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,

View File

@ -4,6 +4,7 @@
#include <emscripten/html5.h>
#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();