import pygame import math import time import DataTypes as dt def normalize(x, y, deadzone=0.15): if abs(x) < deadzone: x = 0.0 if abs(y) < deadzone: y = 0.0 mag = math.hypot(x, y) if mag > 1.0: x /= mag y /= mag return x, y def controller_loop(shared_intent: dt.ControlIntent, lock): pygame.init() pygame.joystick.init() joysticks = {} prev_buttons = {} clock = pygame.time.Clock() while True: # 🔑 REQUIRED: keeps joystick state updating pygame.event.pump() for event in pygame.event.get(): if event.type == pygame.JOYDEVICEADDED: joy = pygame.joystick.Joystick(event.device_index) joysticks[joy.get_instance_id()] = joy prev_buttons[joy.get_instance_id()] = [0] * joy.get_numbuttons() print(f"[Controller] Joystick connected: {joy.get_name()}") elif event.type == pygame.JOYDEVICEREMOVED: joysticks.pop(event.instance_id, None) prev_buttons.pop(event.instance_id, None) print("[Controller] Joystick disconnected") if not joysticks: time.sleep(0.1) continue # Use first joystick joy = next(iter(joysticks.values())) jid = joy.get_instance_id() # ---- AXES ---- forward = -joy.get_axis(1) # usually inverted sideways = joy.get_axis(0) forward, sideways = normalize(forward, sideways) walk = abs(forward) > 0 or abs(sideways) > 0 # ---- BUTTONS ---- buttons = joy.get_numbuttons() current_buttons = [joy.get_button(i) for i in range(buttons)] emote = None quit_flag = False for i in range(buttons): if current_buttons[i] and not prev_buttons[jid][i]: # Button DOWN (edge) if i == 0: emote = "HELLO" elif i == 1: emote = "SAD" elif i == 9: quit_flag = True prev_buttons[jid] = current_buttons # ---- UPDATE INTENT (atomic) ---- with lock: shared_intent.move_x = forward shared_intent.move_y = sideways shared_intent.walk = walk if emote: shared_intent.emote = emote if quit_flag: shared_intent.quit = True clock.tick(60)