GeoFS Mod Menu -cool-

Mod Menu for GeoFS flight model variables using console-modifiable input fields

// ==UserScript==
// @name         GeoFS Mod Menu -cool-
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Mod Menu for GeoFS flight model variables using console-modifiable input fields
// @author       Jasp
// @match        https://www.geo-fs.com/*
// @grant        none
// @license      MIT
// ==/UserScript==


(function () {
    'use strict';

    const presets = {
        "Default": {
            maxRPM: 2500,
            enginePower: 100000,
            fuelFlow: 0.3,
            dragFactor: 0.05,
            liftFactor: 1.0
        },
        "High Power": {
            maxRPM: 4000,
            enginePower: 200000,
            fuelFlow: 0.8,
            dragFactor: 0.03,
            liftFactor: 1.2
        },
        "Glider": {
            maxRPM: 0,
            enginePower: 0,
            fuelFlow: 0,
            dragFactor: 0.01,
            liftFactor: 1.5
        },
        "Heavy Jet": {
            maxRPM: 3200,
            enginePower: 150000,
            fuelFlow: 0.6,
            dragFactor: 0.07,
            liftFactor: 0.9
        },
        "Light Sport": {
            maxRPM: 2800,
            enginePower: 75000,
            fuelFlow: 0.4,
            dragFactor: 0.04,
            liftFactor: 1.3
        }
    };

    const variableSettings = {
        maxRPM: { min: 1000, max: 4000 },
        minRPM: { min: 500, max: 1500 },
        starterRPM: { min: 300, max: 1000 },
        idleThrottle: { min: 0.01, max: 0.2 },
        fuelFlow: { min: 0.1, max: 1.0 },
        enginePower: { min: 50000, max: 200000 },
        brakeRPM: { min: 100, max: 1000 },
        wingArea: { min: 5, max: 150 },
        dragFactor: { min: 0.01, max: 0.2 },
        liftFactor: { min: 0.5, max: 2.0 },
        CD0: { min: 0.01, max: 0.1 },
        CLmax: { min: 0.5, max: 2.0 },
        elevatorFactor: { min: 0.1, max: 2.0 },
        rudderFactor: { min: 0.1, max: 2.0 },
        aileronFactor: { min: 0.1, max: 2.0 },
        mass: { min: 500, max: 50000 },
        emptyWeight: { min: 500, max: 30000 },
        maxWeight: { min: 1000, max: 100000 },
        inertia: { min: 100, max: 50000 },
        pitchMoment: { min: 10, max: 5000 },
        yawMoment: { min: 10, max: 5000 },
        rollMoment: { min: 10, max: 5000 },
        gearDrag: { min: 0.01, max: 0.5 },
        gearCompression: { min: 0.01, max: 1.0 },
        gearLength: { min: 0.5, max: 5.0 }
    };

    const explanations = {
        maxRPM: "Maximum revolutions per minute of the engine. Higher = more top power, but more stress. Lower = less max thrust.",
        minRPM: "Minimum engine RPM. Too low and the engine might stall. Too high and you lose idle control.",
        starterRPM: "RPM during engine startup. Higher starts faster. Too high = unrealistic.",
        idleThrottle: "Throttle at idle. Higher means more thrust at 0% throttle.",
        fuelFlow: "How fast fuel is consumed. Higher = more realistic consumption, lower = longer flights.",
        enginePower: "The base power of the engine. Higher = more thrust and acceleration.",
        brakeRPM: "RPM at which braking effect is active. Higher = faster deceleration.",
        wingArea: "Total wing surface. More = more lift. Too much = unrealistic flight.",
        dragFactor: "How much drag affects the aircraft. Higher = slower top speed.",
        liftFactor: "How efficiently the wings lift. Higher = easier takeoff & climb.",
        CD0: "Parasitic drag coefficient. Higher = more drag.",
        CLmax: "Max lift coefficient. Higher = more lift before stalling.",
        elevatorFactor: "Effectiveness of pitch control. Higher = more sensitive.",
        rudderFactor: "Effectiveness of yaw control. Higher = tighter turns.",
        aileronFactor: "Effectiveness of roll control. Higher = faster rolls.",
        mass: "Total aircraft mass. Higher = slower response, more stability.",
        emptyWeight: "Weight with no payload. Affects takeoff performance.",
        maxWeight: "Max takeoff weight. Higher allows more payload.",
        inertia: "Aircraft rotational inertia. Higher = more sluggish pitch/yaw/roll.",
        pitchMoment: "How much pitch resists change. Higher = smoother pitch.",
        yawMoment: "Yaw stability. Higher = less yaw wiggle.",
        rollMoment: "Roll stability. Higher = steadier rolls.",
        gearDrag: "Drag caused by landing gear. Higher = slower when deployed.",
        gearCompression: "Suspension compression. Higher = softer landings.",
        gearLength: "Length of gear strut. Higher = taller aircraft stance."
    };

    const categories = {
        "Engine": ["maxRPM", "minRPM", "starterRPM", "idleThrottle", "fuelFlow", "enginePower", "brakeRPM"],
        "Aerodynamics": ["wingArea", "dragFactor", "liftFactor", "CD0", "CLmax", "elevatorFactor", "rudderFactor", "aileronFactor"],
        "Flight Model": ["mass", "emptyWeight", "maxWeight", "inertia", "pitchMoment", "yawMoment", "rollMoment"],
        "Landing Gear": ["gearDrag", "gearCompression", "gearLength"]
    };

    const menuStyle = `
        #geofsModMenu {
            position: fixed;
            top: 50px;
            right: 20px;
            background: rgba(0,0,0,0.85);
            color: white;
            padding: 15px;
            border-radius: 10px;
            z-index: 9999;
            max-height: 90vh;
            overflow-y: auto;
            font-family: sans-serif;
            font-size: 14px;
            display: none;
            width: 300px;
        }
        #geofsModMenu h2 {
            font-size: 16px;
            margin-top: 10px;
            border-bottom: 1px solid #ccc;
        }
        #geofsModMenu input[type="number"] {
            width: 80px;
            margin: 2px 0;
            background: #222;
            color: white;
            border: 1px solid #555;
            padding: 3px;
        }
        #geofsModMenu input[type="range"] {
            width: 100%;
            margin-bottom: 10px;
        }
        .preset-button {
            background: #444;
            color: white;
            padding: 4px 10px;
            margin: 2px;
            border: 1px solid #777;
            border-radius: 6px;
            cursor: pointer;
            font-size: 13px;
        }
        .preset-button:hover {
            background: #666;
        }
        #helpOverlay {
            position: fixed;
            top: 0; left: 0;
            width: 100%; height: 100%;
            background: rgba(0,0,0,0.95);
            color: white;
            z-index: 10000;
            overflow-y: scroll;
            padding: 30px;
            display: none;
            font-size: 15px;
        }
        #helpOverlay h1 {
            font-size: 24px;
            margin-bottom: 10px;
        }
        #closeHelp {
            position: absolute;
            top: 10px;
            right: 20px;
            font-size: 24px;
            cursor: pointer;
            color: white;
        }
        #settingsIcon {
            position: absolute;
            top: 10px;
            right: 10px;
            font-size: 18px;
            cursor: pointer;
            background: none;
            border: none;
            color: white;
        }
    `;

    const styleTag = document.createElement("style");
    styleTag.innerHTML = menuStyle;
    document.head.appendChild(styleTag);

    const menu = document.createElement("div");
    menu.id = "geofsModMenu";

    const helpOverlay = document.createElement("div");
    helpOverlay.id = "helpOverlay";

    const closeBtn = document.createElement("div");
    closeBtn.id = "closeHelp";
    closeBtn.innerHTML = "✕";
    closeBtn.onclick = () => {
        helpOverlay.style.display = "none";
        menu.style.display = "block";
    };

    helpOverlay.appendChild(closeBtn);
    const helpTitle = document.createElement("h1");
    helpTitle.textContent = "GeoFS Mod Menu – Variable Help";
    helpOverlay.appendChild(helpTitle);

    for (const [key, description] of Object.entries(explanations)) {
        const para = document.createElement("p");
        para.innerHTML = `<strong>${key}</strong>: ${description}`;
        helpOverlay.appendChild(para);
    }
    document.body.appendChild(helpOverlay);

    const settingsBtn = document.createElement("button");
    settingsBtn.id = "settingsIcon";
    settingsBtn.textContent = "⚙️";
    settingsBtn.onclick = () => {
        menu.style.display = "none";
        helpOverlay.style.display = "block";
    };
    menu.appendChild(settingsBtn);

    const inputsMap = {};

    const presetHeader = document.createElement("h2");
    presetHeader.textContent = "Presets";
    menu.appendChild(presetHeader);

    for (const [name, values] of Object.entries(presets)) {
        const btn = document.createElement("button");
        btn.textContent = name;
        btn.className = "preset-button";
        btn.onclick = () => {
            for (const [key, val] of Object.entries(values)) {
                const input = inputsMap[key]?.input;
                const slider = inputsMap[key]?.slider;
                if (input && slider) {
                    input.value = val;
                    slider.value = val;
                    updateModel(key, val);
                }
            }
        };
        menu.appendChild(btn);
    }

    for (const [category, vars] of Object.entries(categories)) {
        const header = document.createElement("h2");
        header.textContent = category;
        menu.appendChild(header);

        vars.forEach(variable => {
            const setting = variableSettings[variable] || { min: 0, max: 100 };
            const label = document.createElement("label");
            label.textContent = variable + ": ";
            const input = document.createElement("input");
            input.type = "number";
            input.step = "any";
            input.value = setting.min;

            const slider = document.createElement("input");
            slider.type = "range";
            slider.min = setting.min;
            slider.max = setting.max;
            slider.step = (setting.max - setting.min) / 100;
            slider.value = setting.min;

            input.oninput = () => {
                slider.value = input.value;
                updateModel(variable, input.value);
            };
            slider.oninput = () => {
                input.value = slider.value;
                updateModel(variable, slider.value);
            };

            inputsMap[variable] = { input, slider };
            label.appendChild(input);
            menu.appendChild(label);
            menu.appendChild(slider);
            menu.appendChild(document.createElement("br"));
        });
    }

    document.body.appendChild(menu);

    function updateModel(variable, value) {
        try {
            if (geofs?.aircraft?.instance?.flightModel) {
                const model = geofs.aircraft.instance.flightModel;
                if (variable in model) {
                    model[variable] = parseFloat(value);
                    console.log(`[MOD MENU] Set ${variable} to ${value}`);
                }
            }
        } catch (err) {
            console.error("GeoFS mod menu error:", err);
        }
    }

    document.addEventListener("keydown", function (e) {
        if (e.key === "#") {
            const visible = menu.style.display === "block";
            menu.style.display = visible ? "none" : "block";
            helpOverlay.style.display = "none";
        }
    });

    console.log("[GeoFS Mod Menu] Loaded. Press '#' to toggle.");
})();