Torn Location Based Travel Map (MAT Responsive)

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

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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);
    })();