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