diff --git a/README.md b/README.md index ba5d185..f98fb79 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,11 @@ Some features that are a part of the SDL3 build: fullscreen. - **Screenshot capture** - F10 writes the framebuffer to disk. - **Gamepad support** - cross-platform SDL_Gamepad mapped to the IIgs joystick. + A connected gamepad is used automatically (with hotplug); without one, the + numeric keypad emulates the joystick. The stick's circular travel is remapped + to the square range Apple joysticks have, so games can reach the corners. + Override either behavior in the config menu (F4) under Joystick + Configuration. - **Terminal debugger** - a REPL for the built-in 65816 debugger from the controlling terminal. - **Drag-and-drop disk loading** - drop a disk image on the window and GSplus diff --git a/gsplus/src/CMakeLists.txt b/gsplus/src/CMakeLists.txt index ebb0cea..d7ff1ea 100644 --- a/gsplus/src/CMakeLists.txt +++ b/gsplus/src/CMakeLists.txt @@ -266,6 +266,11 @@ add_executable(gsplus-sdl ) target_link_libraries(gsplus-sdl PRIVATE gsplus_core_sdl SDL3::SDL3) +if(UNIX AND NOT APPLE) + # paddles.c uses sqrt(); glibc keeps it in libm + target_link_libraries(gsplus-sdl PRIVATE m) +endif() + if(APPLE) # macOS-only: retune SDL's default menu bar so ⌘ combos pass through to the # emulated IIgs and GSplus quits on ⌥⌘Q. The Objective-C source needs Cocoa. diff --git a/gsplus/src/adb.c b/gsplus/src/adb.c index a43a5b3..fb5407e 100644 --- a/gsplus/src/adb.c +++ b/gsplus/src/adb.c @@ -2126,7 +2126,7 @@ adb_physical_key_update(Kimage *kimage_ptr, int raw_a2code, word32 unicode_c, if(ascii > 0x30 && ascii <= 0x39) { g_keypad_key_is_down[ascii - 0x30] = !is_up; } - if(g_joystick_type == 0) { + if(paddle_effective_joystick_type() == 0) { /* If Joystick type is keypad, then do not let these */ /* keypress pass on further, except for cmd/opt */ if(ascii == 0x30) { diff --git a/gsplus/src/config.c b/gsplus/src/config.c index 3d0ab74..dfe1943 100644 --- a/gsplus/src/config.c +++ b/gsplus/src/config.c @@ -57,6 +57,7 @@ extern word32 g_mem_size_exp; extern int g_video_line_update_interval; extern int g_user_halt_bad; extern int g_joystick_type; +extern int g_joystick_square_range; extern int g_joystick_scale_factor_x; extern int g_joystick_scale_factor_y; extern int g_joystick_trim_amount_x; @@ -250,8 +251,11 @@ Cfg_menu g_cfg_newslot7_menu[] = { Cfg_menu g_cfg_joystick_menu[] = { { "Joystick Configuration", g_cfg_joystick_menu, 0, 0, CFGTYPE_MENU }, -{ "Joystick Emulation,0,Keypad Joystick,1,Mouse Joystick,2,Native Joystick 1," - "3,Native Joystick 2", KNMP(g_joystick_type), CFGTYPE_INT }, +{ "Joystick Emulation,4,Auto (Gamepad or Keypad),0,Keypad Joystick," + "1,Mouse Joystick,2,Native Joystick 1,3,Native Joystick 2", + KNMP(g_joystick_type), CFGTYPE_INT }, +{ "Gamepad Range,1,Square (Full Corners),0,Circular (Raw)", + KNMP(g_joystick_square_range), CFGTYPE_INT }, { "Joystick Scale X,0x100,Standard,0x119,+10%,0x133,+20%," "0x150,+30%,0xb0,-30%,0xcd,-20%,0xe7,-10%", KNMP(g_joystick_scale_factor_x), CFGTYPE_INT }, diff --git a/gsplus/src/paddles.c b/gsplus/src/paddles.c index 66c90f0..0f52989 100644 --- a/gsplus/src/paddles.c +++ b/gsplus/src/paddles.c @@ -9,6 +9,7 @@ /**********************************************************************/ #include "defc.h" +#include extern int g_mouse_raw_x; /* from adb.c */ extern int g_mouse_raw_y; @@ -21,7 +22,8 @@ int g_joystick_scale_factor_y = 0x100; int g_joystick_trim_amount_x = 0; int g_joystick_trim_amount_y = 0; -int g_joystick_type = 0; /* 0 = Keypad Joystick */ +int g_joystick_type = 4; /* 4 = Auto (gamepad if present, else keypad) */ +int g_joystick_square_range = 1; /* remap circular stick gate to square */ int g_joystick_native_type1 = -1; int g_joystick_native_type2 = -1; int g_joystick_native_type = -1; @@ -35,17 +37,72 @@ dword64 g_paddle_dfcyc[4] = { 0, 0, 0, 0 }; /* g_paddle_dfcyc are the dfcyc the paddle goes to 0 */ +#define STICK_DEADZONE 0.08 /* radial, as a fraction of full deflection */ + +void +paddle_map_circle_to_square(int *xptr, int *yptr) +{ + double x, y, r, m, scale; + + /* A modern gamepad stick travels in a circle, so a full diagonal + * only reaches ~71% on each axis and games expecting the square + * range of Apple joysticks never see the corners. Stretch each + * sample along its own direction -- identity on the axes, up to + * sqrt(2) at the diagonals -- so the circular rim lands exactly on + * the square's perimeter. */ + x = *xptr / 32767.0; + y = *yptr / 32767.0; + r = sqrt((x * x) + (y * y)); + if(r < STICK_DEADZONE) { + /* Radial deadzone: sticks do not rest at exactly 0, and the + * stretch below would amplify the drift */ + *xptr = 0; + *yptr = 0; + return; + } + /* Rescale magnitude so travel is continuous at the deadzone edge */ + scale = (r - STICK_DEADZONE) / (1.0 - STICK_DEADZONE); + x = (x / r) * scale; + y = (y / r) * scale; + m = (fabs(x) > fabs(y)) ? fabs(x) : fabs(y); + if(m > 0.0) { + x = x * (scale / m); + y = y * (scale / m); + } + /* Clamp: some pads overshoot the unit circle near the diagonals */ + x = (x > 1.0) ? 1.0 : ((x < -1.0) ? -1.0 : x); + y = (y > 1.0) ? 1.0 : ((y < -1.0) ? -1.0 : y); + *xptr = (int)(x * 32767.0); + *yptr = (int)(y * 32767.0); +} + +int +paddle_effective_joystick_type() +{ + /* Auto (type 4) is resolved every time it's sampled, never stored: + * use the native joystick/gamepad if one is connected, else the + * keypad. g_joystick_native_type1 tracks hotplug, so connecting + * or removing a gamepad mid-game switches modes automatically. */ + if(g_joystick_type == 4) { + return (g_joystick_native_type1 >= 0) ? 2 : 0; + } + return g_joystick_type; +} + void paddle_fixup_joystick_type() { + int type; + /* If g_joystick_type points to an illegal value, change it */ - if(g_joystick_type == 2) { + type = paddle_effective_joystick_type(); + if(type == 2) { g_joystick_native_type = g_joystick_native_type1; if(g_joystick_native_type1 < 0) { g_joystick_type = 0; } } - if(g_joystick_type == 3) { + if(type == 3) { g_joystick_native_type = g_joystick_native_type2; if(g_joystick_native_type2 < 0) { g_joystick_type = 0; @@ -62,7 +119,7 @@ paddle_trigger(dword64 dfcyc) /* Determine what all the paddle values are right now */ paddle_fixup_joystick_type(); - switch(g_joystick_type) { + switch(paddle_effective_joystick_type()) { case 0: /* Keypad Joystick */ paddle_trigger_keypad(dfcyc); break; @@ -121,15 +178,27 @@ void paddle_update_trigger_dcycs(dword64 dfcyc) { dword64 trig_dfcyc; + int xy_val[2]; int val, paddle_num, scale, trim; int i; + xy_val[0] = g_paddle_val[0]; + xy_val[1] = g_paddle_val[1]; + if(g_joystick_square_range && + (paddle_effective_joystick_type() >= 2)) { + /* Work on a copy: some backends keep g_paddle_val[] between + * samples, so remapping in place would compound */ + paddle_map_circle_to_square(&xy_val[0], &xy_val[1]); + } for(i = 0; i < 4; i++) { paddle_num = i; if(g_swap_paddles) { paddle_num = i ^ 1; } val = g_paddle_val[paddle_num]; + if(paddle_num < 2) { + val = xy_val[paddle_num]; + } if(g_invert_paddles) { val = -val; } @@ -183,5 +252,12 @@ void paddle_update_buttons() { paddle_fixup_joystick_type(); - joystick_update_buttons(); + if(paddle_effective_joystick_type() >= 2) { + joystick_update_buttons(); + } else { + /* In keypad/mouse modes a connected gamepad must not drive + * the buttons either (half-working input is confusing); + * also clears bits latched before a mode switch */ + g_paddle_buttons &= ~3; + } } diff --git a/gsplus/src/protos_base.h b/gsplus/src/protos_base.h index d5e589c..306a813 100644 --- a/gsplus/src/protos_base.h +++ b/gsplus/src/protos_base.h @@ -483,6 +483,8 @@ int read_vid_counters(int loc, dword64 dfcyc); /* paddles.c */ +void paddle_map_circle_to_square(int *xptr, int *yptr); +int paddle_effective_joystick_type(void); void paddle_fixup_joystick_type(void); void paddle_trigger(dword64 dfcyc); void paddle_trigger_mouse(dword64 dfcyc); diff --git a/gsplus/src/vars_x86linux b/gsplus/src/vars_x86linux index ac985a5..e66c85c 100644 --- a/gsplus/src/vars_x86linux +++ b/gsplus/src/vars_x86linux @@ -3,7 +3,7 @@ OBJECTS1 = pulseaudio_driver.o xdriver.o CCOPTS = -O2 -Wall -fomit-frame-pointer -DPULSE_AUDIO NAME = gsplus LD = $(CC) -EXTRA_LIBS = -lXext -lpulse +EXTRA_LIBS = -lXext -lpulse -lm EXTRA_SPECIALS = XOPTS = -I/usr/X11R6/include