From: Holger Schemel Date: Tue, 25 Jul 2017 19:07:16 +0000 (+0200) Subject: replaced obsolete joystick calibration with game controller configuration X-Git-Tag: 4.0.1.0~21 X-Git-Url: https://git.artsoft.org/?p=rocksndiamonds.git;a=commitdiff_plain;h=835e565490c1502920280c77eed9bb65fa932164;hp=819b52d40a92b8b83b13cf31185744d433b69e14 replaced obsolete joystick calibration with game controller configuration --- diff --git a/graphics/gfx_classic/joystick/axis_x.png b/graphics/gfx_classic/joystick/axis_x.png new file mode 100644 index 00000000..ff3fa943 Binary files /dev/null and b/graphics/gfx_classic/joystick/axis_x.png differ diff --git a/graphics/gfx_classic/joystick/axis_y.png b/graphics/gfx_classic/joystick/axis_y.png new file mode 100644 index 00000000..6942de5f Binary files /dev/null and b/graphics/gfx_classic/joystick/axis_y.png differ diff --git a/graphics/gfx_classic/joystick/button.png b/graphics/gfx_classic/joystick/button.png new file mode 100644 index 00000000..490e8bcc Binary files /dev/null and b/graphics/gfx_classic/joystick/button.png differ diff --git a/graphics/gfx_classic/joystick/controller.png b/graphics/gfx_classic/joystick/controller.png new file mode 100644 index 00000000..d1d7d4b2 Binary files /dev/null and b/graphics/gfx_classic/joystick/controller.png differ diff --git a/src/files.c b/src/files.c index 4370cba4..b3d18ba2 100644 --- a/src/files.c +++ b/src/files.c @@ -8690,6 +8690,55 @@ void LoadSetup_EditorCascade() free(filename); } +static void addGameControllerMappingToHash(SetupFileHash *mappings_hash, + char *mapping_line) +{ + char mapping_guid[MAX_LINE_LEN]; + char *mapping_start, *mapping_end; + + // get GUID from game controller mapping line: copy complete line + strncpy(mapping_guid, mapping_line, MAX_LINE_LEN - 1); + mapping_guid[MAX_LINE_LEN - 1] = '\0'; + + // get GUID from game controller mapping line: cut after GUID part + mapping_start = strchr(mapping_guid, ','); + if (mapping_start != NULL) + *mapping_start = '\0'; + + // cut newline from game controller mapping line + mapping_end = strchr(mapping_line, '\n'); + if (mapping_end != NULL) + *mapping_end = '\0'; + + // add mapping entry to game controller mappings hash + setHashEntry(mappings_hash, mapping_guid, mapping_line); +} + +static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash, + char *filename) +{ + FILE *file; + + if (!(file = fopen(filename, MODE_READ))) + { + Error(ERR_WARN, "cannot read game controller mappings file '%s'", filename); + + return; + } + + while (!feof(file)) + { + char line[MAX_LINE_LEN]; + + if (!fgets(line, MAX_LINE_LEN, file)) + break; + + addGameControllerMappingToHash(mappings_hash, line); + } + + fclose(file); +} + void SaveSetup() { char *filename = getSetupFilename(); @@ -8799,6 +8848,47 @@ void SaveSetup_EditorCascade() free(filename); } +static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash, + char *filename) +{ + FILE *file; + + if (!(file = fopen(filename, MODE_WRITE))) + { + Error(ERR_WARN, "cannot write game controller mappings file '%s'",filename); + + return; + } + + BEGIN_HASH_ITERATION(mappings_hash, itr) + { + fprintf(file, "%s\n", HASH_ITERATION_VALUE(itr)); + } + END_HASH_ITERATION(mappings_hash, itr) + + fclose(file); +} + +void SaveSetup_AddGameControllerMapping(char *mapping) +{ + char *filename = getPath2(getSetupDir(), GAMECONTROLLER_BASENAME); + SetupFileHash *mappings_hash = newSetupFileHash(); + + InitUserDataDirectory(); + + // load existing personal game controller mappings + LoadSetup_ReadGameControllerMappings(mappings_hash, filename); + + // add new mapping to personal game controller mappings + addGameControllerMappingToHash(mappings_hash, mapping); + + // save updated personal game controller mappings + SaveSetup_WriteGameControllerMappings(mappings_hash, filename); + + freeSetupFileHash(mappings_hash); + free(filename); +} + void LoadCustomElementDescriptions() { char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS); diff --git a/src/files.h b/src/files.h index 46dcae9c..1db49886 100644 --- a/src/files.h +++ b/src/files.h @@ -63,6 +63,8 @@ void SaveSetup(); void LoadSetup_EditorCascade(); void SaveSetup_EditorCascade(); +void SaveSetup_AddGameControllerMapping(char *); + void LoadCustomElementDescriptions(); void InitMenuDesignSettings_Static(); void LoadMenuDesignSettings(); diff --git a/src/libgame/joystick.h b/src/libgame/joystick.h index f3be2954..74dbad76 100644 --- a/src/libgame/joystick.h +++ b/src/libgame/joystick.h @@ -17,6 +17,8 @@ #define JOYSTICK_NOT_AVAILABLE 0 #define JOYSTICK_AVAILABLE (1 << 0) #define JOYSTICK_ACTIVE (1 << 1) +#define JOYSTICK_CONFIGURED (1 << 2) +#define JOYSTICK_NOT_CONFIGURED (1 << 3) #define JOYSTICK_ACTIVATED (JOYSTICK_AVAILABLE | JOYSTICK_ACTIVE) #if defined(PLATFORM_FREEBSD) diff --git a/src/libgame/sdl.c b/src/libgame/sdl.c index f9419312..c473b265 100644 --- a/src/libgame/sdl.c +++ b/src/libgame/sdl.c @@ -2616,7 +2616,7 @@ void SDLClearJoystickState() } } -static boolean SDLOpenJoystick(int nr) +boolean SDLOpenJoystick(int nr) { if (nr < 0 || nr >= MAX_PLAYERS) return FALSE; @@ -2644,7 +2644,7 @@ static boolean SDLOpenJoystick(int nr) return (sdl_joystick[nr] != NULL); } -static void SDLCloseJoystick(int nr) +void SDLCloseJoystick(int nr) { if (nr < 0 || nr >= MAX_PLAYERS) return; diff --git a/src/libgame/sdl.h b/src/libgame/sdl.h index 5391b683..14872400 100644 --- a/src/libgame/sdl.h +++ b/src/libgame/sdl.h @@ -492,6 +492,8 @@ void SDLInitJoysticks(void); boolean SDLReadJoystick(int, int *, int *, boolean *, boolean *); boolean SDLCheckJoystickOpened(int); void SDLClearJoystickState(); +boolean SDLOpenJoystick(int); +void SDLCloseJoystick(int); void PrepareFadeBitmap(int); diff --git a/src/screens.c b/src/screens.c index b3d9111d..265c9e2f 100644 --- a/src/screens.c +++ b/src/screens.c @@ -163,7 +163,7 @@ static void HandleScreenGadgets(struct GadgetInfo *); static void HandleSetupScreen_Generic(int, int, int, int, int); static void HandleSetupScreen_Input(int, int, int, int, int); static void CustomizeKeyboard(int); -static void CalibrateJoystick(int); +static void ConfigureJoystick(int); static void execSetupGame(void); static void execSetupGraphics(void); static void execSetupSound(void); @@ -6007,8 +6007,8 @@ static void drawPlayerSetupInputInfo(int player_nr, boolean active) { &custom_key.right, "Joystick Right" }, { &custom_key.up, "Joystick Up" }, { &custom_key.down, "Joystick Down" }, - { &custom_key.snap, "Button 1" }, - { &custom_key.drop, "Button 2" } + { &custom_key.snap, "Button 1/A/X" }, + { &custom_key.drop, "Button 2/B/Y" } }; static char *joystick_name[MAX_PLAYERS] = { @@ -6038,7 +6038,7 @@ static void drawPlayerSetupInputInfo(int player_nr, boolean active) int font_nr = (joystick_active ? FONT_VALUE_1 : FONT_VALUE_OLD); DrawText(mSX + 8 * 32, mSY + 3 * 32, text, font_nr); - DrawText(mSX + 32, mSY + 4 * 32, "Calibrate", text_font_nr); + DrawText(mSX + 32, mSY + 4 * 32, "Configure", text_font_nr); } else { @@ -6189,7 +6189,7 @@ void HandleSetupScreen_Input(int mx, int my, int dx, int dy, int button) else if (y == 2) { if (setup.input[input_player_nr].use_joystick) - CalibrateJoystick(input_player_nr); + ConfigureJoystick(input_player_nr); else CustomizeKeyboard(input_player_nr); } @@ -6336,7 +6336,8 @@ void CustomizeKeyboard(int player_nr) DrawSetupScreen_Input(); } -static boolean CalibrateJoystickMain(int player_nr) +#if 0 +static boolean OLD_CalibrateJoystickMain(int player_nr) { int new_joystick_xleft = JOYSTICK_XMIDDLE; int new_joystick_xright = JOYSTICK_XMIDDLE; @@ -6507,23 +6508,398 @@ static boolean CalibrateJoystickMain(int player_nr) return TRUE; } +#endif + +/* game controller mapping generator by Gabriel Jacobo */ + +#define MARKER_BUTTON 1 +#define MARKER_AXIS_X 2 +#define MARKER_AXIS_Y 3 + +static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) +{ +#if defined(TARGET_SDL2) + static Bitmap *controller, *button, *axis_x, *axis_y; + Bitmap *marker; + boolean bitmaps_initialized = FALSE; + boolean screen_initialized = FALSE; + const char *name = NULL; + boolean success = TRUE; + boolean done = FALSE, next = FALSE; + Event event; + int alpha = 200, alpha_step = -1; + int alpha_ticks = 0; + char mapping[4096], temp[4096]; + int i, j; + + struct + { + int x, y; + int marker; + char *field; + int axis, button, hat, hat_value; + char mapping[4096]; + } + *step, *prev_step, steps[] = + { + { 370, 175, MARKER_BUTTON, "a", }, + { 410, 142, MARKER_BUTTON, "b", }, + { 334, 145, MARKER_BUTTON, "x", }, + { 372, 115, MARKER_BUTTON, "y", }, + { 176, 145, MARKER_BUTTON, "back", }, + { 230, 145, MARKER_BUTTON, "guide", }, + { 285, 145, MARKER_BUTTON, "start", }, + { 124, 220, MARKER_BUTTON, "dpleft", }, + { 160, 248, MARKER_BUTTON, "dpdown", }, + { 192, 220, MARKER_BUTTON, "dpright", }, + { 160, 192, MARKER_BUTTON, "dpup", }, + { 64, 60, MARKER_BUTTON, "leftshoulder", }, + { 102, 10, MARKER_AXIS_Y, "lefttrigger", }, + { 396, 60, MARKER_BUTTON, "rightshoulder", }, + { 360, 10, MARKER_AXIS_Y, "righttrigger", }, + { 87, 161, MARKER_BUTTON, "leftstick", }, + { 296, 230, MARKER_BUTTON, "rightstick", }, + { 87, 161, MARKER_AXIS_X, "leftx", }, + { 87, 161, MARKER_AXIS_Y, "lefty", }, + { 296, 230, MARKER_AXIS_X, "rightx", }, + { 296, 230, MARKER_AXIS_Y, "righty", }, + }; + + unsigned int event_frame_delay = 0; + unsigned int event_frame_delay_value = GAME_FRAME_DELAY; + + ResetDelayCounter(&event_frame_delay); + + if (!bitmaps_initialized) + { + controller = LoadCustomImage("joystick/controller.png"); + button = LoadCustomImage("joystick/button.png"); + axis_x = LoadCustomImage("joystick/axis_x.png"); + axis_y = LoadCustomImage("joystick/axis_y.png"); + + bitmaps_initialized = TRUE; + } + + /* print info about the joystick we are watching */ + name = SDL_JoystickName(joystick); + Error(ERR_DEBUG, "Watching joystick %d: (%s)\n", + SDL_JoystickInstanceID(joystick), + (name ? name : "(unknown joystick)")); + Error(ERR_DEBUG, "Joystick has %d axes, %d hats, %d balls, and %d buttons\n", + SDL_JoystickNumAxes(joystick), SDL_JoystickNumHats(joystick), + SDL_JoystickNumBalls(joystick), SDL_JoystickNumButtons(joystick)); + + /* initialize mapping with GUID and name */ + SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joystick), temp, sizeof(temp)); + + snprintf(mapping, sizeof(mapping), "%s,%s,platform:%s,", + temp, name ? name : "Unknown Joystick", SDL_GetPlatform()); + + /* loop through all steps (buttons and axes), getting joystick events */ + for (i = 0; i < SDL_arraysize(steps) && !done;) + { + step = &steps[i]; + strcpy(step->mapping, mapping); + step->axis = -1; + step->button = -1; + step->hat = -1; + step->hat_value = -1; + + marker = (step->marker == MARKER_BUTTON ? button : + step->marker == MARKER_AXIS_X ? axis_x : + step->marker == MARKER_AXIS_Y ? axis_y : marker); + + next = FALSE; + + while (!done && !next) + { + alpha += alpha_step * (int)(SDL_GetTicks() - alpha_ticks) / 5; + alpha_ticks = SDL_GetTicks(); + + if (alpha >= 255) + { + alpha = 255; + alpha_step = -1; + } + else if (alpha < 128) + { + alpha = 127; + alpha_step = 1; + } + + ClearField(); + + DrawTextSCentered(mSY - SY + 22 * 16, FONT_REQUEST, + "Press buttons and move axes on"); + DrawTextSCentered(mSY - SY + 23 * 16, FONT_REQUEST, + "your controller when indicated."); + DrawTextSCentered(mSY - SY + 24 * 16, FONT_REQUEST, + "(Your controller may look different.)"); + +#if defined(PLATFORM_ANDROID) + DrawTextSCentered(mSY - SY + 26 * 16, FONT_REQUEST, + "To correct a mistake,"); + DrawTextSCentered(mSY - SY + 27 * 16, FONT_REQUEST, + "press the 'back' button."); + DrawTextSCentered(mSY - SY + 28 * 16, FONT_REQUEST, + "To skip a button or axis,"); + DrawTextSCentered(mSY - SY + 29 * 16, FONT_REQUEST, + "press the 'menu' button."); +#else + DrawTextSCentered(mSY - SY + 26 * 16, FONT_REQUEST, + "To correct a mistake,"); + DrawTextSCentered(mSY - SY + 27 * 16, FONT_REQUEST, + "press the 'backspace' key."); + DrawTextSCentered(mSY - SY + 28 * 16, FONT_REQUEST, + "To skip a button or axis,"); + DrawTextSCentered(mSY - SY + 29 * 16, FONT_REQUEST, + "press the 'return' key."); + DrawTextSCentered(mSY - SY + 30 * 16, FONT_REQUEST, + "To exit, press the 'escape' key."); +#endif + + int controller_x = gfx.sx + (gfx.sxsize - controller->width) / 2; + int controller_y = gfx.sy; + + int marker_x = controller_x + step->x; + int marker_y = controller_y + step->y; + + BlitBitmapMasked(controller, drawto, 0, 0, + controller->width, controller->height, + controller_x, controller_y); + + SDL_SetSurfaceAlphaMod(marker->surface_masked, alpha); + + BlitBitmapMasked(marker, drawto, 0, 0, + marker->width, marker->height, + marker_x, marker_y); + + if (!screen_initialized) + FadeIn(REDRAW_FIELD); + else + BackToFront(); + + screen_initialized = TRUE; + + while (NextValidEvent(&event)) + { + switch (event.type) + { + case SDL_JOYAXISMOTION: + if (event.jaxis.value > 20000 || + event.jaxis.value < -20000) + { + for (j = 0; j < i; j++) + if (steps[j].axis == event.jaxis.axis) + break; + + if (j == i) + { + if (step->marker != MARKER_AXIS_X && + step->marker != MARKER_AXIS_Y) + break; + + step->axis = event.jaxis.axis; + strcat(mapping, step->field); + snprintf(temp, sizeof(temp), ":a%u,", event.jaxis.axis); + strcat(mapping, temp); + i++; + next = TRUE; + } + } + + break; + + case SDL_JOYHATMOTION: + /* ignore centering; we're probably just coming back + to the center from the previous item we set */ + if (event.jhat.value == SDL_HAT_CENTERED) + break; + + for (j = 0; j < i; j++) + if (steps[j].hat == event.jhat.hat && + steps[j].hat_value == event.jhat.value) + break; + + if (j == i) + { + step->hat = event.jhat.hat; + step->hat_value = event.jhat.value; + strcat(mapping, step->field); + snprintf(temp, sizeof(temp), ":h%u.%u,", + event.jhat.hat, event.jhat.value ); + strcat(mapping, temp); + i++; + next = TRUE; + } + + break; + + case SDL_JOYBALLMOTION: + break; + + case SDL_JOYBUTTONUP: + for (j = 0; j < i; j++) + if (steps[j].button == event.jbutton.button) + break; + + if (j == i) + { + step->button = event.jbutton.button; + strcat(mapping, step->field); + snprintf(temp, sizeof(temp), ":b%u,", event.jbutton.button); + strcat(mapping, temp); + i++; + next = TRUE; + } + + break; + + case SDL_FINGERDOWN: + case SDL_MOUSEBUTTONDOWN: + /* skip this step */ + i++; + next = TRUE; + + break; + + case SDL_KEYDOWN: + if (event.key.keysym.sym == KSYM_BackSpace || + event.key.keysym.sym == KSYM_Back) + { + /* undo this step */ + if (i > 0) + { + prev_step = &steps[--i]; + strcpy(mapping, prev_step->mapping); + next = TRUE; + } + + break; + } + + if (event.key.keysym.sym == KSYM_space || + event.key.keysym.sym == KSYM_Return || + event.key.keysym.sym == KSYM_Menu) + { + /* skip this step */ + i++; + next = TRUE; + + break; + } + + if (event.key.keysym.sym == KSYM_Escape) + { + success = FALSE; + done = TRUE; + } + + break; + + case SDL_QUIT: + program.exit_function(0); + break; + + default: + break; + } + + // do not handle events for longer than standard frame delay period + if (DelayReached(&event_frame_delay, event_frame_delay_value)) + break; + } + } + } + + if (success) + { + Error(ERR_DEBUG, "New game controller mapping:\n\n%s\n\n", mapping); + + // activate mapping for this game + SDL_GameControllerAddMapping(mapping); + + // save mapping to personal mappings + SaveSetup_AddGameControllerMapping(mapping); + } + + /* wait until the last pending event was removed from event queue */ + while (NextValidEvent(&event)); + + return success; +#else + return TRUE; +#endif +} -void CalibrateJoystick(int player_nr) +static int ConfigureJoystickMain(int player_nr) { - if (!CalibrateJoystickMain(player_nr)) + char *device_name = setup.input[player_nr].joy.device_name; + int joystick_nr = getJoystickNrFromDeviceName(device_name); + boolean joystick_active = CheckJoystickOpened(joystick_nr); + int success = FALSE; + int i; + + if (joystick.status == JOYSTICK_NOT_AVAILABLE) + return JOYSTICK_NOT_AVAILABLE; + + if (!joystick_active || !setup.input[player_nr].use_joystick) + return JOYSTICK_NOT_AVAILABLE; + + FadeSetEnterMenu(); + FadeOut(REDRAW_FIELD); + + // close all joystick devices (potentially opened as game controllers) + for (i = 0; i < SDL_NumJoysticks(); i++) + SDLCloseJoystick(i); + + // open joystick device as plain joystick to configure as game controller + SDL_Joystick *joystick = SDL_JoystickOpen(joystick_nr); + + // as the joystick was successfully opened before, this should not happen + if (joystick == NULL) + return FALSE; + + // create new game controller mapping (buttons and axes) for joystick device + success = ConfigureJoystickMapButtonsAndAxes(joystick); + + // close joystick (and maybe re-open as configured game controller later) + SDL_JoystickClose(joystick); + + // re-open all joystick devices (potentially as game controllers) + for (i = 0; i < SDL_NumJoysticks(); i++) + SDLOpenJoystick(i); + + // clear all joystick input actions for all joystick devices + SDLClearJoystickState(); + + return (success ? JOYSTICK_CONFIGURED : JOYSTICK_NOT_CONFIGURED); +} + +void ConfigureJoystick(int player_nr) +{ + boolean state = ConfigureJoystickMain(player_nr); + + if (state != JOYSTICK_NOT_CONFIGURED) { + boolean success = (state == JOYSTICK_CONFIGURED); + char *message = (success ? " IS CONFIGURED! " : " NOT AVAILABLE! "); char *device_name = setup.input[player_nr].joy.device_name; int nr = getJoystickNrFromDeviceName(device_name) + 1; int xpos = mSX - SX; int ypos = mSY - SY; + unsigned int wait_frame_delay = 0; + unsigned int wait_frame_delay_value = 2000; + + ResetDelayCounter(&wait_frame_delay); ClearField(); DrawTextF(xpos + 16, ypos + 6 * 32, FONT_TITLE_1, " JOYSTICK %d ", nr); - DrawTextF(xpos + 16, ypos + 7 * 32, FONT_TITLE_1, " NOT AVAILABLE! "); - BackToFront(); + DrawTextF(xpos + 16, ypos + 7 * 32, FONT_TITLE_1, message); - Delay(2000); /* show error message for a short time */ + while (!DelayReached(&wait_frame_delay, wait_frame_delay_value)) + BackToFront(); ClearEventQueue(); } diff --git a/src/tools.c b/src/tools.c index 1ea116ba..31ae317a 100644 --- a/src/tools.c +++ b/src/tools.c @@ -3759,6 +3759,10 @@ void WaitForEventToContinue() { case EVENT_BUTTONPRESS: case EVENT_KEYPRESS: +#if defined(TARGET_SDL2) + case SDL_CONTROLLERBUTTONDOWN: +#endif + case SDL_JOYBUTTONDOWN: still_wait = FALSE; break;