Skip to content

Split joystick logic to its own file#4524

Merged
Loobinex merged 4 commits intodkfans:masterfrom
PieterVdc:refactor_joys
Feb 5, 2026
Merged

Split joystick logic to its own file#4524
Loobinex merged 4 commits intodkfans:masterfrom
PieterVdc:refactor_joys

Conversation

@PieterVdc
Copy link
Member

No description provided.

Copilot AI review requested due to automatic review settings February 5, 2026 17:41
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request refactors the joystick/controller input handling code by extracting it from bflib_inputctrl.cpp into a new dedicated file bflib_input_joyst.cpp. The separation improves code organization and modularity.

Changes:

  • Extracted all joystick and game controller related functions, variables, and logic into a new file bflib_input_joyst.cpp
  • Cleaned up includes in bflib_inputctrl.cpp (removed unused sounds.h)
  • Added forward declarations for the joystick functions in bflib_inputctrl.cpp
  • Updated build files to include the new source file

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/bflib_input_joyst.cpp New file containing all joystick/controller input logic, including initialization, event handling, and polling functions
src/bflib_inputctrl.cpp Removed joystick-specific code, added forward declarations for joystick functions, cleaned up includes
linux.mk Added bflib_input_joyst.cpp to the source files list
Makefile Added bflib_input_joyst.o to the object files list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +158 to +530
void JEvent(const SDL_Event *ev)
{
SYNCDBG(10, "Starting");

switch (ev->type)
{
case SDL_JOYAXISMOTION:
case SDL_JOYBALLMOTION:
case SDL_JOYHATMOTION:
break;
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
{
TbKeyCode keycode = joystickbutton_to_keycode(ev->jbutton.button);
if (keycode != KC_UNASSIGNED)
{
if (ev->type == SDL_JOYBUTTONDOWN)
{
lbKeyOn[keycode] = 1;
lbInkey = keycode;
}
else
{
lbKeyOn[keycode] = 0;
}
}
}
break;
case SDL_CONTROLLERAXISMOTION:
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
{
// Some controllers with proper mappings send these events instead of joystick events
Uint8 button_val = ev->cbutton.button;
TbKeyCode keycode = gamecontrollerbutton_to_keycode(button_val);
if (keycode != KC_UNASSIGNED)
{
if (ev->type == SDL_CONTROLLERBUTTONDOWN)
{
lbKeyOn[keycode] = 1;
lbInkey = keycode;
}
else
{
lbKeyOn[keycode] = 0;
}
}
}
break;

case SDL_JOYDEVICEADDED:
open_controller(ev->jdevice.which);
break;
case SDL_JOYDEVICEREMOVED:
close_controller(ev->jdevice.which);
break;
default:
break;
}
}

void controller_rumble(long ms)
{
if (controller != NULL) {
SDL_GameControllerRumble(controller, 0xFFFF, 0xFFFF, ms);
}
}


#define STICK_DEADZONE 0.15f

static void poll_controller_movement(Sint16 lx, Sint16 ly)
{
float nx = lx / 32768.0f;
float ny = ly / 32768.0f;

// Handle horizontal movement - just accumulate for local camera
float move_mag_x = fabsf(nx);
if (move_mag_x > STICK_DEADZONE) {
float norm_mag = (move_mag_x - STICK_DEADZONE) / (1.0f - STICK_DEADZONE);
float curved = norm_mag * norm_mag;
float presses_this_frame = curved * input_delta_time;

movement_accum_x += (nx > 0 ? presses_this_frame : -presses_this_frame);

struct PlayerInfo* player = get_my_player();
if(player->work_state == PSt_FreeCtrlDirect || player->work_state == PSt_CtrlDirect) {
struct Packet* packet = get_packet(my_player_number);
while (movement_accum_x >= 1.0f) {
set_packet_control(packet, PCtr_MoveRight);
movement_accum_x -= 1.0f;
}
while (movement_accum_x <= -1.0f) {
set_packet_control(packet, PCtr_MoveLeft);
movement_accum_x += 1.0f;
}
}
}

// Handle vertical movement - just accumulate for local camera
float move_mag_y = fabsf(ny);
if (move_mag_y > STICK_DEADZONE) {
float norm_mag = (move_mag_y - STICK_DEADZONE) / (1.0f - STICK_DEADZONE);
float curved = norm_mag * norm_mag;
float presses_this_frame = curved * input_delta_time;

movement_accum_y += (ny > 0 ? presses_this_frame : -presses_this_frame);

struct PlayerInfo* player = get_my_player();
if(player->work_state == PSt_FreeCtrlDirect || player->work_state == PSt_CtrlDirect) {
struct Packet* packet = get_packet(my_player_number);
while (movement_accum_y >= 1.0f) {
set_packet_control(packet, PCtr_MoveDown);
movement_accum_y -= 1.0f;
}
while (movement_accum_y <= -1.0f) {
set_packet_control(packet, PCtr_MoveUp);
movement_accum_y += 1.0f;
}
}
}
// Packets will be sent by send_camera_catchup_packets() based on position difference
}

#define SECONDS_TO_CROSS 20.0f
static void poll_controller_mouse(Sint16 rx, Sint16 ry)
{
float nx = rx / 32768.0f;
float ny = ry / 32768.0f;
float mag = sqrtf(nx * nx + ny * ny);

if (mag < STICK_DEADZONE)
return;

nx /= mag;
ny /= mag;

float norm_mag = (mag - STICK_DEADZONE) / (1.0f - STICK_DEADZONE);
float curved = norm_mag * norm_mag;
float pixels_per_second = lbDisplay.GraphicsWindowWidth / SECONDS_TO_CROSS;
float pixels_this_frame = pixels_per_second * input_delta_time;

mouse_accum_x += nx * curved * pixels_this_frame;
mouse_accum_y += ny * curved * pixels_this_frame;

int dx = (int)mouse_accum_x;
int dy = (int)mouse_accum_y;

mouse_accum_x -= dx;
mouse_accum_y -= dy;

if (dx != 0 || dy != 0) {
struct TbPoint mouseDelta = { dx, dy };
mouseControl(MActn_MOUSEMOVE, &mouseDelta);
}
}

static float get_input_delta_time()
{
long double frame_time_in_nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(TimeNow - delta_time_previous_timepoint).count();
delta_time_previous_timepoint = TimeNow;
float calculated_delta_time = (frame_time_in_nanoseconds/1000000000.0) * game_num_fps;
return min(calculated_delta_time, 1.0f);
}

void poll_controller()
{
input_delta_time = get_input_delta_time();
if (controller != NULL) {

TbBool has_right_stick =
SDL_GameControllerHasAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX) &&
SDL_GameControllerHasAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
TbBool has_left_stick =
SDL_GameControllerHasAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) &&
SDL_GameControllerHasAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);

//analog sticks and dpad, layout based on what's available, with mouse being most important, then movement, then cam rotation/zoom

if (has_right_stick && has_left_stick) {
lbKeyOn[KC_HOME] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP);
lbKeyOn[KC_END] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
lbKeyOn[KC_PGDOWN] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
lbKeyOn[KC_DELETE] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);


Sint16 leftX = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
Sint16 leftY = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
poll_controller_movement(leftX, leftY);

Sint16 rx = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
Sint16 ry = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
poll_controller_mouse(rx, ry);

} else if (has_left_stick) {

lbKeyOn[KC_UP] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP);
lbKeyOn[KC_DOWN] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
lbKeyOn[KC_LEFT] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
lbKeyOn[KC_RIGHT] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);

Sint16 rx = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
Sint16 ry = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
poll_controller_mouse(rx, ry);
} else {
TbBool up = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP);
TbBool down = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
TbBool left = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
TbBool right = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);

struct TbPoint mouseDelta;
mouseDelta.x = (right - left) * lbDisplay.GraphicsWindowHeight * input_delta_time / 0.02f;
mouseDelta.y = (down - up) * lbDisplay.GraphicsWindowHeight * input_delta_time / 0.02f;
mouseControl(MActn_MOUSEMOVE, &mouseDelta);
}

TbBool has_triggers =
SDL_GameControllerHasAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) &&
SDL_GameControllerHasAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);

if(has_triggers)
{
Uint8 current_leftshoulder = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
if (current_leftshoulder && !prev_leftshoulder) {
go_to_adjacent_menu_tab(-1);
}
prev_leftshoulder = current_leftshoulder;

Uint8 current_rightshoulder = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
if (current_rightshoulder && !prev_rightshoulder) {
go_to_adjacent_menu_tab(1);
}
prev_rightshoulder = current_rightshoulder;


// Handle triggers for mouse buttons
Sint16 lt = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
Sint16 rt = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
struct TbPoint delta = {0, 0};
if (lt > 10000 && !lt_pressed) {
lt_pressed = true;
mouseControl(MActn_RBUTTONDOWN, &delta);
} else if (lt <= 10000 && lt_pressed) {
lt_pressed = false;
mouseControl(MActn_RBUTTONUP, &delta);
}
if (rt > 10000 && !rt_pressed) {
rt_pressed = true;
mouseControl(MActn_LBUTTONDOWN, &delta);
} else if (rt <= 10000 && rt_pressed) {
rt_pressed = false;
mouseControl(MActn_LBUTTONUP, &delta);
}
}
else {
struct TbPoint delta = {0, 0};

Uint8 current_leftshoulder = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
if (current_leftshoulder && !prev_leftshoulder) {
mouseControl(MActn_RBUTTONDOWN, &delta);
}
else if (!current_leftshoulder && prev_leftshoulder) {
mouseControl(MActn_RBUTTONUP, &delta);
}
prev_leftshoulder = current_leftshoulder;

Uint8 current_rightshoulder = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
if (current_rightshoulder && !prev_rightshoulder) {
mouseControl(MActn_LBUTTONDOWN, &delta);
}
else if (!current_rightshoulder && prev_rightshoulder) {
mouseControl(MActn_LBUTTONUP, &delta);
}
prev_rightshoulder = current_rightshoulder;
}

lbKeyOn[KC_LSHIFT] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_A);
lbKeyOn[KC_LCONTROL] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_B);
lbKeyOn[settings.kbkeys[Gkey_ZoomToFight].code] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_X);
lbKeyOn[settings.kbkeys[Gkey_ZoomRoomHeart].code] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_Y);

// Handle Start and Back buttons with edge detection to simulate key presses
lbKeyOn[KC_SPACE] = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_BACK);


Uint8 current_back = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_START);
if (current_back && !prev_back) {
keyboardControl(KActn_KEYDOWN, KC_ESCAPE, KMod_NONE, 0);
keyboardControl(KActn_KEYDOWN, KC_P, KMod_NONE, 0);
} else if (!current_back && prev_back) {
keyboardControl(KActn_KEYUP, KC_ESCAPE, KMod_NONE, 0);
keyboardControl(KActn_KEYUP, KC_P, KMod_NONE, 0);
}
prev_back = current_back;

Uint8 current_joy_left = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSTICK);
if (current_joy_left && !prev_joy_left) {
keyboardControl(KActn_KEYDOWN, settings.kbkeys[Gkey_SwitchToMap].code, KMod_NONE, 0);
} else if (!current_joy_left && prev_joy_left) {
keyboardControl(KActn_KEYUP, settings.kbkeys[Gkey_SwitchToMap].code, KMod_NONE, 0);
}
prev_joy_left = current_joy_left;

//Uint8 current_joy_right = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK);
//if (current_joy_right && !prev_joy_right) {
// keyboardControl(KActn_KEYDOWN, KC_P, KMod_NONE, 0);
//} else if (!current_joy_right && prev_joy_right) {
// keyboardControl(KActn_KEYUP, KC_P, KMod_NONE, 0);
//}
//prev_joy_right = current_joy_right;


} else if (joystick != NULL) {
// Map joystick buttons to keyboard keys (assuming standard layout)
lbKeyOn[KC_HOME] = SDL_JoystickGetButton(joystick, 10); // D-pad up
lbKeyOn[KC_END] = SDL_JoystickGetButton(joystick, 12); // D-pad down
lbKeyOn[KC_PGDOWN] = SDL_JoystickGetButton(joystick, 13); // D-pad left
lbKeyOn[KC_DELETE] = SDL_JoystickGetButton(joystick, 11); // D-pad right

//lbKeyOn[KC_SPACE] = SDL_JoystickGetButton(joystick, 0); // Button 0 (A)
//lbKeyOn[KC_LCONTROL] = SDL_JoystickGetButton(joystick, 1); // Button 1 (B)
lbKeyOn[KC_LSHIFT] = SDL_JoystickGetButton(joystick, 2); // Button 2 (X)
lbKeyOn[KC_LCONTROL] = SDL_JoystickGetButton(joystick, 3); // Button 3 (Y)
lbKeyOn[settings.kbkeys[Gkey_ZoomToFight].code] = SDL_JoystickGetButton(joystick, 4);
lbKeyOn[settings.kbkeys[Gkey_ZoomRoomHeart].code] = SDL_JoystickGetButton(joystick, 5);
lbKeyOn[settings.kbkeys[Gkey_SwitchToMap].code] = SDL_JoystickGetButton(joystick, 6);
lbKeyOn[settings.kbkeys[Gkey_ToggleMessage].code] = SDL_JoystickGetButton(joystick, 7);

// Handle analog sticks for movement (axes 0 and 1)
Sint16 leftX = SDL_JoystickGetAxis(joystick, 0);
Sint16 leftY = SDL_JoystickGetAxis(joystick, 1);
poll_controller_movement(leftX, leftY);

// Handle right stick for mouse movement (axes 2 and 3)
Sint16 rightX = SDL_JoystickGetAxis(joystick, 3);
Sint16 rightY = SDL_JoystickGetAxis(joystick, 4);
poll_controller_mouse(rightX, rightY);

// Handle triggers for mouse buttons (axes 4 and 5)
struct TbPoint delta = { 0, 0 };
if (SDL_JoystickGetButton(joystick, 0))
{
lt_pressed = true;
mouseControl(MActn_LBUTTONDOWN, &delta);
}
else
{
if (lt_pressed)
{
mouseControl(MActn_LBUTTONUP, &delta);
}
lt_pressed = false;
}
if (SDL_JoystickGetButton(joystick, 1))
{
rt_pressed = true;
mouseControl(MActn_RBUTTONDOWN, &delta);
}
else
{
if (rt_pressed)
{
mouseControl(MActn_RBUTTONUP, &delta);
}
rt_pressed = false;
}
}

}


void init_controller_input()
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions init_controller_input, JEvent, and poll_controller defined in this file are called from bflib_inputctrl.cpp but are not declared in a header file. These functions should be declared in bflib_inputctrl.h or a separate header file to provide proper interface declarations. Currently, they rely on forward declarations in the calling file, which is not a clean separation of concerns and violates proper modularity practices.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@PieterVdc PieterVdc changed the title Split joystick logic to it's own file Split joystick logic to its own file Feb 5, 2026
@Loobinex Loobinex merged commit db7bd42 into dkfans:master Feb 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants