Torn Location Based Travel Map (MAT Responsive)

Replaces the plane in Torn travel page with a live, responsive location map (MAT version)

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Torn Location Based Travel Map (MAT Responsive)
// @namespace    http://tampermonkey.net/
// @version      2025-12-17
// @description  Replaces the plane in Torn travel page with a live, responsive location map (MAT version)
// @author       justlucdewit
// @match        https://www.torn.com/page.php?sid=travel
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant        none
// @license      MAT
// ==/UserScript==


(function() {
    'use strict';

    const render_frame = (canvas, ctx) => {
        const canvas_width = canvas.getBoundingClientRect().width;
        const canvas_height = canvas.getBoundingClientRect().height;
        canvas.width = canvas_width;
        canvas.height = canvas_height;

        ctx.clearRect(0, 0, canvas_width, canvas_height);

        const locations = {
            'torn': { 'x': 51, 'y': 47 },
            'mexico': { 'x': 48, 'y': 49 },
            'cayman-islands': { 'x': 54, 'y': 52 },
            'canada': { 'x': 54, 'y': 38 },
            'hawaii': { 'x': 34, 'y': 53 },
            'uk': { 'x': 77, 'y': 31 },
            'argentina': { 'x': 60, 'y': 83 },
            'switzerland': { 'x': 79, 'y': 36 },
            'japan': { 'x': 16, 'y': 42 },
            'uae': { 'x': 92, 'y': 49 },
            'china': { 'x': 9, 'y': 39 },
            'sout-africa': { 'x': 85, 'y': 78 },
        }

        // Draw location dots
        ctx.fillStyle = '#FF0000AA';
        Object.entries(locations).forEach(([name, loc]) => {
            const real_x = canvas.width / 100 * loc.x;
            const real_y = canvas.height / 100 * loc.y;

            // Draw the dot
            ctx.beginPath();
            ctx.arc(real_x, real_y, 5, 0, 2 * Math.PI);
            ctx.closePath();
            ctx.fill();
        });

        // Calculate flight percentage
        const flight_progress_bar = document.querySelector('div[class^="flightProgressBar__"]');
        let flight_percentage = flight_progress_bar.querySelector('div[class^="fill__"]').style.width;
        flight_percentage = Number(flight_percentage.slice(0, flight_percentage.length - 1))

        // Calculate destination and departure country
        const country_wrapper = document.querySelector('div[class^="nodesAndProgress___"]');
        let countries = [...country_wrapper.querySelectorAll('img[class^="circularFlag___"]')];
        const fillHead = country_wrapper.querySelector('img[class^="fillHead___"]');

        // If fillHead has value of left, we are going back
        if (fillHead.style.left) {
            countries = countries.reverse();
        }

        const destination = countries[0].src.split('/').at(-1).slice(3, -4);
        const departure = countries[1].src.split('/').at(-1).slice(3, -4);

        const dest_loc = locations[destination];
        const dep_loc = locations[departure];

        if (!dest_loc) {
            console.warn(`Destination ${destination} not found`);
            return
        }

        if (!dep_loc) {
            console.warn(`Departure ${departure} not found`);
            return
        }

        // Calculate real coordinates
        const dep_real_x = canvas.width / 100 * dep_loc.x;
        const dep_real_y = canvas.height / 100 * dep_loc.y;
        const dest_real_x = canvas.width / 100 * dest_loc.x;
        const dest_real_y = canvas.height / 100 * dest_loc.y;

        // Draw line from dep to dest
        ctx.strokeStyle = '#FF0000AA';
        ctx.lineWidth = 1; // Added line width for visibility
        ctx.beginPath();
        ctx.moveTo(dep_real_x, dep_real_y);
        ctx.lineTo(dest_real_x, dest_real_y);
        ctx.stroke();

        // Position plane at correct location (Linear Interpolation)
        const plane_x = dep_real_x + ((dest_real_x - dep_real_x) * flight_percentage / 100);
        const plane_y = dep_real_y + ((dest_real_y - dep_real_y) * flight_percentage / 100);

        // **********************************
        // NEW CODE FOR ROTATION
        // **********************************

        // 1. Calculate the angle in radians (y-axis is inverted in canvas)
        const angle_rad = Math.atan2(dest_real_y - dep_real_y, dest_real_x - dep_real_x);

        // 2. Convert radians to degrees
        const angle_deg = angle_rad * (180 / Math.PI);

        // 3. Adjust for the plane icon's default orientation (assumes plane points right (0 deg))
        //    The plane character '✈︎' points 90 degrees right by default. We correct this.
        const rotation_offset = 0;
        const final_rotation = angle_deg + rotation_offset;

        // 4. Update CSS transformation
        const plane = document.getElementById("plane-indicator");
        if (plane) {
            // Position the center of the plane icon (32px wide/high)
            plane.style.left = `${plane_x - 16}px`;
            plane.style.top = `${plane_y - 16}px`;

            // Apply the rotation
            plane.style.transform = `rotate(${final_rotation}deg)`;
        }
    }

    const create_live_location_map = () => {
        const root = document.createElement("div");
        root.id = "travel-location-map"

        // Set map background image
        root.style.height = '400px';
        root.style.position = 'relative';
        root.style.background = 'url("https://github.com/justlucdewit/tampermonkey/blob/main/torn/live-location-travel-map/assets/map.png?raw=true")';
        root.style.backgroundSize = 'cover';

        // Draw the UI with the current flying location
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext('2d');
        canvas.style.width = '100%';
        canvas.style.height = '100%';

        // Indicator of where you are currently flying
        const map_location_indicator_plane = document.createElement("div");
        map_location_indicator_plane.style.width = "32px";
        map_location_indicator_plane.style.height = "32px";
        map_location_indicator_plane.style.position = "absolute";
        map_location_indicator_plane.style.left = "0px";
        map_location_indicator_plane.style.top = "0px";
        map_location_indicator_plane.innerText = "✈︎"
        map_location_indicator_plane.style.color = "#F00";
        map_location_indicator_plane.style.display = "flex";
        map_location_indicator_plane.style.alignItems = "center";
        map_location_indicator_plane.style.justifyContent = "center";
        map_location_indicator_plane.style.fontSize = "32px";
        map_location_indicator_plane.id = "plane-indicator";

        // Set transform-origin to center so rotation happens correctly
        map_location_indicator_plane.style.transformOrigin = "center center";


        setInterval(() => {
            render_frame(canvas, ctx);
        }, 1000);
        render_frame(canvas, ctx);

        root.appendChild(canvas);
        root.appendChild(map_location_indicator_plane);

        return root;
    }

    const initalize = () => {
        const travel_root = document.getElementById('travel-root');
        const random_fact_box = travel_root.querySelector('div[class^="randomFactWrapper"]');
        const original_flight_animation = travel_root.querySelector("figure");

        if (!(random_fact_box && original_flight_animation)) {
            return false;
        }

        const location_map = create_live_location_map();
        random_fact_box.remove();

        original_flight_animation.replaceWith(location_map);

        return true;
    }

    const attempt_initialization = () => {
        const result = initalize();

        if (!result) {
            requestAnimationFrame(attempt_initialization);
        }
    }

    requestAnimationFrame(attempt_initialization);
    })();