Torn Location Based Travel Map (MAT Responsive)

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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);
    })();