Polytrack Controller Mapper

Maps controller inputs (Triggers for W/S, Stick for WASD) to keyboard keys for Polytrack on iPad.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Polytrack Controller Mapper
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Maps controller inputs (Triggers for W/S, Stick for WASD) to keyboard keys for Polytrack on iPad.
// @author       You
// @match        https://*kodub.com/apps/polytrack/*
// @match        https://*.polytrack.com/*
// @match        https://eelekweb.github.io/polytrack/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=polytrack.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log("Polytrack Controller Loaded");

    // Key state tracker to prevent spamming the game with events
    const keys = {
        'w': false,
        'a': false,
        's': false,
        'd': false
    };

    // Helper to send KeyDown
    function triggerDown(key) {
        if (keys[key]) return; // Already pressed
        keys[key] = true;
        let event = new KeyboardEvent('keydown', {
            key: key,
            code: 'Key' + key.toUpperCase(),
            keyCode: key.toUpperCase().charCodeAt(0),
            which: key.toUpperCase().charCodeAt(0),
            bubbles: true,
            cancelable: true,
            view: window
        });
        window.dispatchEvent(event);
        document.dispatchEvent(event);
        // Sometimes games listen to the canvas specifically
        let canvas = document.querySelector('canvas');
        if (canvas) canvas.dispatchEvent(event);
    }

    // Helper to send KeyUp
    function triggerUp(key) {
        if (!keys[key]) return; // Already released
        keys[key] = false;
        let event = new KeyboardEvent('keyup', {
            key: key,
            code: 'Key' + key.toUpperCase(),
            keyCode: key.toUpperCase().charCodeAt(0),
            which: key.toUpperCase().charCodeAt(0),
            bubbles: true,
            cancelable: true,
            view: window
        });
        window.dispatchEvent(event);
        document.dispatchEvent(event);
        let canvas = document.querySelector('canvas');
        if (canvas) canvas.dispatchEvent(event);
    }

    // The Main Loop checks the controller 60 times a second
    function gameLoop() {
        const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
        if (!gamepads) {
            requestAnimationFrame(gameLoop);
            return;
        }

        // We grab the first controller (Index 0)
        const gp = gamepads[0];

        if (gp) {
            // --- INPUT MAPPING ---

            // 1. Get Values
            // Left Stick: usually axis 0 (left/right) and 1 (up/down)
            let stickX = gp.axes[0]; 
            let stickY = gp.axes[1]; 
            
            // Triggers: typically buttons 6 (LT) and 7 (RT) on iOS/Xbox/PS
            // If these don't work, try changing 6/7 to 4/5.
            let lt = gp.buttons[6] ? gp.buttons[6].pressed : false;
            let rt = gp.buttons[7] ? gp.buttons[7].pressed : false;

            // Deadzone (ignore small stick drifts)
            let dz = 0.2;

            // 2. Logic: "RT for W" OR "Stick Up for W"
            if (rt || stickY < -dz) {
                triggerDown('w');
            } else {
                triggerUp('w');
            }

            // 3. Logic: "LT for S" OR "Stick Down for S"
            if (lt || stickY > dz) {
                triggerDown('s');
            } else {
                triggerUp('s');
            }

            // 4. Logic: Stick Left for A
            if (stickX < -dz) {
                triggerDown('a');
            } else {
                triggerUp('a');
            }

            // 5. Logic: Stick Right for D
            if (stickX > dz) {
                triggerDown('d');
            } else {
                triggerUp('d');
            }
        }
        
        // Keep the loop running
        requestAnimationFrame(gameLoop);
    }

    // Start the loop
    gameLoop();

})();