Greasy Fork is available in English.

Planets.nu - Meteor's Library

Library for Planets.nu with diverse globally available basic functions to serve other plugins.

// ==UserScript==
// @name          Planets.nu - Meteor's Library
// @description   Library for Planets.nu with diverse globally available basic functions to serve other plugins.
// @namespace     Planets.nu
// @version       2.1
// @grant         none
// @date          2022-01-02
// @author        meteor
// @include       http://planets.nu/*
// @include       http://*.planets.nu/*
// @include       https://planets.nu/*
// @include       https://*.planets.nu/*
// @exclude       http://help.planets.nu/*
// @exclude       https://help.planets.nu/*
// @exclude       http://profile*.planets.nu/*
// @exclude       https://profile*.planets.nu/*
// @exclude       http://planets.nu/_library/*
// @exclude       http://api.planets.nu/*

// ==/UserScript==

/* -----------------------------------------------------------------------
 Change log:
 ----------------------------------------------------------------------- */

"use strict";

function wrapper(plugin_version)
{
    xLibrary = new XPlugin("Meteor's Library", "xLibrary", plugin_version, -20180626);

    xLibrary.setLogEnabled(false);

    xMapUtils.initialize();

    if (!xUtils.isMobileClient())
    {
        var css = "<style type='text/css'>";

        css += ".mapbutton {border-radius: 50%; position: absolute; right: 10px; font-size: 13px; color: #fff; width: 30px; height: 30px; cursor: pointer; text-align: center; vertical-align: middle; background-color: #333;}";
        css += ".mapbutton.toolactive {box-shadow: 0px 0px 0px 2px #00ffff;}";
        css += ".mapbutton:hover {opacity: 1;}";
        css += ".mapbutton::before {content: ''; font-family: 'Font Awesome 5 Free'; display: block; position: absolute; left:0; top:0; width: 30px; line-height: 30px; font-size: 14px; font-style: normal; font-variant: normal; text-rendering: auto; font-weight: 900; color: #00ffff; -webkit-font-smoothing: antialiased; text-align: center;}";
        css += ".mapbutton::after {content: ''; display:block; position:absolute; left: -10px; top:-10px; width: 50px; height: 50px;}";
        css += "#MapControls .mapbutton {position: relative; margin-bottom: 10px;}";

        css += "</style>";
        $("head:first").append(css);
    }
}

function global()
{
    var xLibrary;

    // -----------------------------------------------------------------------------------------------------------------

    function XPlugin(fullName, registerName, version, notetype, minLibraryVersion)
    {
        this.name = fullName;
        this.registerName = registerName;
        this.version = version;
        this.notetype = notetype;

        if ((typeof vgap == 'undefined') || (vgap == null))
        {
            window.alert("Cannot start " + fullName + "!\n\nThe plugin requires vgap.");
            throw "Cannot start " + fullName + ". vgap not found. Plugin disabled.";
        }

        if (vgap.version < 3.0)
        {
            throw "Cannot start " + fullName + ". Plugin requires at least NU version 3.0. Plugin disabled.";
        }

        if ((typeof minLibraryVersion != 'undefined') && (minLibraryVersion != null) && (xLibrary.getVersion() < minLibraryVersion))
        {
            window.alert("Cannot start " + fullName + "!\n\nThe plugin requires at least " + xLibrary.getName() + " version " + minLibraryVersion + ".\nVersion found: " + xLibrary.getVersion() + "\n\nPlease update the library!");
            throw "Cannot start " + fullName + ". Required " + xLibrary.getName() + " version " + minLibraryVersion + " not found. Plugin disabled.";
        }

        this.setLogEnabled(false);

        vgap.registerPlugin(this, registerName);

        console.log("Registered " + fullName + " (" + registerName + ") version: " + version);
    }

    XPlugin.prototype.getName = function()
    {
        return this.name;
    };

    XPlugin.prototype.getVersion = function()
    {
        return this.version;
    };

    XPlugin.prototype.saveObjectAsNote = function(id, obj)
    {
        let note = vgap.getNote(id, this.notetype);
        if (note == null)
        {
            note = vgap.addNote(id, this.notetype);
        }

        note.changed = 1;
        note.body = JSON.stringify(obj);

        vgap.save();
    };

    XPlugin.prototype.getObjectFromNote = function(id)
    {
        let note = vgap.getNote(id, this.notetype);
        if ((note == null) || (note.body == ""))
        {
            return null;
        }

        return JSON.parse(note.body);
    };

    XPlugin.prototype.log = function(message)
    {
        if (this.isLogEnabled())
        {
            console.log(this.getName() + "> " + message);
        }
    };

    XPlugin.prototype.logWarning = function(warning)
    {
        console.log(this.getName() + "> Warning: " + warning);
    };

    XPlugin.prototype.throwIllegalArgumentException = function(message)
    {
        throw this.getName() + "> IllegalArgumentException: " + message;
    };

    XPlugin.prototype.throwIllegalStateException = function(message)
    {
        throw this.getName() + "> IllegalStateException: " + message;
    };

    XPlugin.prototype.setLogEnabled = function(logEnabled)
    {
        this.logEnabled = logEnabled;
    };

    XPlugin.prototype.isLogEnabled = function()
    {
        return this.logEnabled;
    };

    XPlugin.prototype.processload = function()
    {
    };

    XPlugin.prototype.loadmap = function()
    {
    };

    XPlugin.prototype.draw = function()
    {
    };

    XPlugin.prototype.loaddashboard = function()
    {
    };

    XPlugin.prototype.showdashboard = function()
    {
    };

    XPlugin.prototype.showsummary = function()
    {
    };

    XPlugin.prototype.showmap = function()
    {
    };

    XPlugin.prototype.loadplanet = function()
    {
    };

    XPlugin.prototype.loadstarbase = function()
    {
    };

    XPlugin.prototype.loadship = function()
    {
    };

    // -----------------------------------------------------------------------------------------------------------------

    var xConst =
        {
            colRaceId:
                {
                    UNKNOWN: 0,
                    FEDERATION: 1,
                    LIZARDS: 2,
                    BIRDS: 3,
                    FASCISTS: 4,
                    PRIVATEERS: 5,
                    CYBORG: 6,
                    CRYSTALLINE: 7,
                    EMPIRE: 8,
                    ROBOTS: 9,
                    REBELS: 10,
                    COLONIES: 11,
                    HORWASP: 12
                },
            natRaceId:
                {
                    NONE: 0,
                    HUMANOID: 1,
                    BOVINOID: 2,
                    REPTILIAN: 3,
                    AVIAN: 4,
                    AMORPHOUS: 5,
                    INSECTOID: 6,
                    AMPHIBIAN: 7,
                    GHIPSOLDAL: 8,
                    SILICONOID: 9,
                    OTHER_PLAYER: 10
                },
            natGovernmentId:
                {
                    UNKNOWN: 0,
                    ANARCHY: 1,
                    PRE_TRIBAL: 2,
                    EARLY_TRIBAL: 3,
                    TRIBAL: 4,
                    FEUDAL: 5,
                    MONARCHY: 6,
                    REPRESENTATIVE: 7,
                    PARTICIPATORY: 8,
                    UNITY: 9,
                },
            sphereDuplication:
                {
                    NONE: 1,
                    FULL: 2,
                    ALPHA: 3
                },
            buildingThreshold:
                {
                    MINE: 200,
                    FACTORY: 100,
                    DEFENSE_POST: 50
                },
            shipFeature:
                {
                    CLOAK: 1,
                    DECLOAK: 2,
                    ADVANCED_CLOAK: 3,
                    RADIATION_SHIELDING: 4,
                    GLORY_DEVICE: 5,
                    GRAVITONIC: 6,
                    HYPERJUMP: 7,
                    RAMSCOOP: 8,
                    BIOSCAN: 9,
                    ADVANCED_BIOSCAN: 10,
                    NEBULA_SCANNER: 11,
                    TERRAFORMER: 12,
                    GAMBLING: 13,
                    ALCHEMY: 14,
                    CHUNNEL_INITIATOR: 15,
                    CHUNNEL_TARGET: 16,
                    IMPERIAL_ASSAULT: 17,
                    PLANET_IMMUNITY: 18,
                    SEND_FIGHTERS: 19,
                    RECEIVE_FIGHTERS: 20,
                    CHAMELEON_DEVICE: 21,
                    EMORKS_SPIRIT_BONUS: 22,
                    TIDAL_FORCE_SHIELD: 23,
                    CLOAKED_FIGHTER_BAYS: 24,
                    EDUCATOR: 25,
                    ORE_CONDENSOR: 26,
                    RECLOAK_INTERCEPT: 27,
                    SHIELD_GENERATOR: 28,
                    STEALTH_ARMOR: 29,
                    CHUNNEL_SELF: 30,
                    TEMPORAL_LANCE: 31,
                    CHUNNEL_STABILIZER: 32,
                    STARGATE: 33,
                    UNIVERSAL_CHUNNEL_TARGET: 34,
                    ELUSIVE: 35,
                    SQUADRON: 36,
                    WEBMINE_IMMUNITY: 37,
                    SUNBURST_DEVICE: 38,
                    MOVE_MINEFIELDS: 39,
                    REPAIR_SHIP: 40,
                    COMMAND_SHIP: 41,
                    TANTRUM_DEVICE: 42
                }

        };

    // -----------------------------------------------------------------------------------------------------------------

    var xUtils =
        {
            cloneShallow: function(object)
            {
                if (object == null)
                {
                    return null;
                }

                if (typeof object == "function")
                {
                    // expectation unclear: exception or return the specified object?
                    xLibrary.throwIllegalArgumentException("The specified object is a function. object = " + getLogString(object, [], 1));
                }

                if (typeof object != "object")
                {
                    return object;
                }

                let result = Object.create(object.constructor.prototype);

                Object.getOwnPropertyNames(object).forEach(function(propertyName)
                {
                    result[propertyName] = object[propertyName];
                });

                return result;
            },

            cloneDeep: function(object)
            {
                if (object == null)
                {
                    return null;
                }

                if (typeof object == "function")
                {
                    // expectation unclear: shallow clone, deep clone (feasible at all?), exception (rather not)?
                    return object;
                }

                if (typeof object != "object")
                {
                    return object;
                }

                let result = Object.create(object.constructor.prototype);

                Object.getOwnPropertyNames(object).forEach(function(propertyName)
                {
                    result[propertyName] = xUtils.cloneDeep(object[propertyName]);
                });

                return result;
            },

            assume: function(object, properties)
            {
                let result = Object.create(object);

                Object.getOwnPropertyNames(properties).forEach(function(propertyName)
                {
                    result[propertyName] = properties[propertyName];
                });

                return result;
            },

            update: function(object, properties)
            {
                Object.getOwnPropertyNames(properties).forEach(function(propertyName)
                {
                    object[propertyName] = properties[propertyName];
                });
            },

            getLogString: function(object, arExcludedPropertyNames, maxRecursionDepth)
            {
                let getLogStringImpl = function(object, arObjectsSeen, arExcludedPropertyNames, maxRecursionDepth)
                {
                    let str = object.constructor.name + "[";
                    let isFirst = true;

                    for ( let propertyName in object)
                    {
                        let propertyValue = object[propertyName];

                        if (!(propertyValue instanceof Function))
                        {
                            let propertyValueStr = null;

                            if (propertyValue instanceof Object)
                            {
                                if (arObjectsSeen.includes(propertyValue))
                                {
                                    propertyValueStr = "(cycle)";
                                }
                                else
                                {
                                    arObjectsSeen.push(propertyValue);

                                    if ((maxRecursionDepth > 0) && (!arExcludedPropertyNames.includes(propertyName)))
                                    {
                                        propertyValueStr = getLogStringImpl(propertyValue, arObjectsSeen, arExcludedPropertyNames, maxRecursionDepth - 1);
                                    }
                                    else
                                    {
                                        propertyValueStr = object.constructor.name + "[...]";
                                    }
                                }
                            }
                            else
                            {
                                propertyValueStr = arExcludedPropertyNames.includes(propertyName) ? "..." : propertyValue;
                            }

                            if (isFirst)
                            {
                                isFirst = false;
                            }
                            else
                            {
                                str += ", ";
                            }

                            str += propertyName + " = " + propertyValueStr;
                        }
                    }

                    str += "]";

                    return str;
                };

                let arObjectsSeen = [];
                arObjectsSeen.push(object);

                if (!arExcludedPropertyNames)
                {
                    arExcludedPropertyNames = [];
                }

                if (maxRecursionDepth == undefined || maxRecursionDepth == null || maxRecursionDepth < 0)
                {
                    maxRecursionDepth = Infinity;
                }

                return getLogStringImpl(object, arObjectsSeen, arExcludedPropertyNames, maxRecursionDepth);
            },

            isMobileClient: function()
            {
                return (vgap.version >= 4.0);
            },

            hasNatives: function(planet)
            {
                return planet.nativetype > 0;
            },

            getMaxColPop: function(planet)
            {
                let oldPlanet = vgap.planetScreen.planet;
                vgap.planetScreen.planet = planet;

                let maxCol = vgap.planetScreen.maxPop(false);

                vgap.planetScreen.planet = oldPlanet;

                return maxCol;
            },

            getMaxNatPop: function(planet)
            {
                if (!this.hasNatives(planet))
                {
                    return 0;
                }

                if (planet.nativetype == xConst.natRaceId.SILICONOID)
                {
                    return planet.temp * 1000;
                }

                return Math.round(Math.sin(3.14 * (100 - planet.temp) / 100) * 150000);
            },

            getNativeTaxRatio: function(planet)
            {
                let oldPlanet = vgap.planetScreen.planet;
                vgap.planetScreen.planet = this.assume(planet,
                    {
                        clans: 1,
                        nativetaxrate: 100
                    });

                let nativeTaxRatio = vgap.planetScreen.nativeTaxAmount();

                vgap.planetScreen.planet = oldPlanet;

                return nativeTaxRatio;
            },

            getMinColsForBuildings: function(countBuildings, buildingType)
            {
                if (countBuildings <= buildingType)
                {
                    return countBuildings;
                }

                return buildingType + ((countBuildings - buildingType) * (countBuildings - buildingType));
            },

            getMaxBuildings: function(planet, buildingThreshold)
            {
                if (planet.clans <= buildingThreshold)
                {
                    return planet.clans;
                }

                return Math.floor(buildingThreshold + Math.sqrt(planet.clans - buildingThreshold));
            },

            createColTaxTable: function(planet)
            {
                let oldPlanet = vgap.planetScreen.planet;
                vgap.planetScreen.planet = planet;

                let oldColTaxRate = planet.colonisttaxrate;

                let table = [];

                for (let taxRate = 0; taxRate < 101; taxRate++)
                {
                    planet.colonisttaxrate = taxRate;
                    let taxAmount = vgap.planetScreen.colonistTaxAmount();
                    let possibleTaxAmount = taxAmount;
                    let happinessChange = vgap.colonistTaxChange(planet);
                    let happiness = planet.colonisthappypoints + happinessChange;
                    if (happiness > 100)
                        happiness = 100;

                    let absoluteGrowth = vgap.planetScreen.colPopGrowth() * 100;
                    let relativeGrowth = absoluteGrowth / planet.clans;

                    table[taxRate] =
                        {
                            taxRate: taxRate,
                            taxAmount: taxAmount,
                            possibleTaxAmount: possibleTaxAmount,
                            happiness: happiness,
                            happinessChange: happinessChange,
                            absoluteGrowth: absoluteGrowth,
                            relativeGrowth: relativeGrowth
                        };
                }

                planet.colonisttaxrate = oldColTaxRate;
                vgap.planetScreen.planet = oldPlanet;

                return table;
            },

            createNatTaxTable: function(planet)
            {
                if (!this.hasNatives(planet))
                {
                    return null;
                }

                let oldPlanet = vgap.planetScreen.planet;
                vgap.planetScreen.planet = planet;

                let oldNatTaxRate = planet.nativetaxrate;

                let table = [];

                for (let taxRate = 0; taxRate < 101; taxRate++)
                {
                    planet.nativetaxrate = taxRate;
                    let taxAmount = vgap.planetScreen.nativeTaxAmount();
                    let possibleTaxAmount = vgap.planetScreen.nativeTaxAmount(true);
                    let happinessChange = vgap.nativeTaxChange(planet);
                    let happiness = planet.nativehappypoints + happinessChange;
                    if (happiness > 100)
                        happiness = 100;

                    let absoluteGrowth = vgap.planetScreen.nativePopGrowth() * 100;
                    let relativeGrowth = absoluteGrowth / planet.nativeclans;

                    table[taxRate] =
                        {
                            taxRate: taxRate,
                            taxAmount: taxAmount,
                            possibleTaxAmount: possibleTaxAmount,
                            happiness: happiness,
                            happinessChange: happinessChange,
                            absoluteGrowth: absoluteGrowth,
                            relativeGrowth: relativeGrowth
                        };
                }

                planet.nativetaxrate = oldNatTaxRate;
                vgap.planetScreen.planet = oldPlanet;

                return table;
            },

            getMaxFullCollectableTaxEntry: function(taxTable)
            {
                for (let i = taxTable.length - 1; i >= 0; i--)
                {
                    let entry = taxTable[i];
                    if (entry.taxAmount == entry.possibleTaxAmount)
                    {
                        return entry;
                    }
                }

                return taxTable[0];
            },

            getMaxTaxEntryByHappinessChange: function(taxTable, happinessChange)
            {
                for (let i = taxTable.length - 1; i >= 0; i--)
                {
                    let entry = taxTable[i];
                    if (entry.happinessChange >= happinessChange)
                    {
                        return entry;
                    }
                }

                return taxTable[0];
            },

            isSharingIntel: function(playerToId)
            {
                let relation = vgap.getRelation(playerToId);
                if (relation == null)
                {
                    return false;
                }

                return relation.relationto >= 3;
            },

            hasFeature: function(ship, feature)
            {
                switch (feature)
                {
                    case xConst.shipFeature.CLOAK:
                    {
                        let hull = vgap.getHull(ship.hullid);
                        return hull.cancloak;
                    }
                    case xConst.shipFeature.DECLOAK:
                    {
                        return (ship.hullid == 7);
                    }
                    case xConst.shipFeature.ADVANCED_CLOAK:
                    {
                        return ((ship.hullid == 29) || (ship.hullid == 31) || (ship.hullid == 1047) || (ship.hullid == 3033));
                    }
                    case xConst.shipFeature.RADIATION_SHIELDING:
                    {
                        // TODO: untested: 107 ore condensor from client code (docu doesn't say it)
                        return ((ship.hullid == 6) || (ship.hullid == 33) || (ship.hullid == 34) || (ship.hullid == 35) || (ship.hullid == 36) || (ship.hullid == 37) || (ship.hullid == 38) || (ship.hullid == 39) || (ship.hullid == 40) || (ship.hullid == 41) || (ship.hullid == 68) || (ship.hullid == 93) || (ship.hullid == 107) || (ship.hullid == 120) || (ship.hullid == 1006) || (ship.hullid == 1033) || (ship.hullid == 1034) || (ship.hullid == 1038) || (ship.hullid == 1039) || (ship.hullid == 1041) || (ship.hullid == 1068) || (ship.hullid == 1093) || (ship.hullid == 2006) || (ship.hullid == 2033) || (ship.hullid == 2038) || (ship.hullid == 3033));
                    }
                    case xConst.shipFeature.GLORY_DEVICE:
                    {
                        return ((ship.hullid == 39) || (ship.hullid == 41) || (ship.hullid == 1034) || (ship.hullid == 1039) || (ship.hullid == 1041));
                    }
                    case xConst.shipFeature.GRAVITONIC:
                    {
                        return ((ship.hullid == 44) || (ship.hullid == 45) || (ship.hullid == 46));
                    }
                    case xConst.shipFeature.HYPERJUMP:
                    {
                        return ((ship.hullid == 51) || (ship.hullid == 77) || (ship.hullid == 87) || (ship.hullid == 110));
                    }
                    case xConst.shipFeature.RAMSCOOP:
                    {
                        return (ship.hullid == 96);
                    }
                    case xConst.shipFeature.BIOSCAN:
                    {
                        return ((ship.hullid == 9) || (ship.hullid == 84) || (ship.hullid == 96) || (ship.hullid == 1084));
                    }
                    case xConst.shipFeature.ADVANCED_BIOSCAN:
                    {
                        return ((ship.hullid == 84) || (ship.hullid == 1084));
                    }
                    case xConst.shipFeature.NEBULA_SCANNER:
                    {
                        return ((ship.hullid == 27) || (ship.hullid == 54) || (ship.hullid == 1054));
                    }
                    case xConst.shipFeature.TERRAFORMER:
                    {
                        return ((ship.hullid == 3) || (ship.hullid == 8) || (ship.hullid == 64));
                    }
                    case xConst.shipFeature.GAMBLING:
                    {
                        return (ship.hullid == 42);
                    }
                    case xConst.shipFeature.ALCHEMY:
                    {
                        return ((ship.hullid == 97) || (ship.hullid == 104) || (ship.hullid == 105));
                    }
                    case xConst.shipFeature.CHUNNEL_INITIATOR:
                    {
                        return ((ship.hullid == 56) || (ship.hullid == 1055));
                    }
                    case xConst.shipFeature.CHUNNEL_TARGET:
                    {
                        return ((ship.hullid == 56) || (ship.hullid == 108) || (ship.hullid == 114) || (ship.hullid == 1054)); // || (ship.hullid == 51) // B200 can be a target of a B222b, but not general. Add it here or not???
                    }
                    case xConst.shipFeature.IMPERIAL_ASSAULT:
                    {
                        return (ship.hullid == 69);
                    }
                    case xConst.shipFeature.PLANET_IMMUNITY:
                    {
                        return ((ship.hullid == 69) || (ship.hullid == 1065) || (ship.hullid == 1071) || (ship.hullid == 2065) || (ship.hullid == 2071));
                    }
                    case xConst.shipFeature.SEND_FIGHTERS:
                    {
                        return (ship.hullid == 70);
                    }
                    case xConst.shipFeature.RECEIVE_FIGHTERS:
                    {
                        return (ship.hullid == 70);
                    }
                    case xConst.shipFeature.CHAMELEON_DEVICE:
                    {
                        return ((ship.hullid == 109) || (ship.hullid == 1023) || (ship.hullid == 1049));
                    }
                    case xConst.shipFeature.EMORKS_SPIRIT_BONUS:
                    {
                        return (ship.hullid == 112);
                    }
                    case xConst.shipFeature.TIDAL_FORCE_SHIELD:
                    {
                        return (ship.hullid == 112);
                    }
                    case xConst.shipFeature.CLOAKED_FIGHTER_BAYS:
                    {
                        return (ship.hullid == 1047);
                    }
                    case xConst.shipFeature.EDUCATOR:
                    {
                        return (ship.hullid == 106);
                    }
                    case xConst.shipFeature.ORE_CONDENSER:
                    {
                        return (ship.hullid == 107);
                    }
                    case xConst.shipFeature.RECLOAK_INTERCEPT:
                    {
                        return (ship.hullid == 2033);
                    }
                    case xConst.shipFeature.SHIELD_GENERATOR:
                    {
                        return (ship.hullid == 1041);
                    }
                    case xConst.shipFeature.STEALTH_ARMOR:
                    {
                        return ((ship.hullid == 120) || (ship.hullid == 1050));
                    }
                    case xConst.shipFeature.CHUNNEL_SELF:
                    {
                        return (ship.hullid == 1055);
                    }
                    case xConst.shipFeature.TEMPORAL_LANCE:
                    {
                        return (ship.hullid == 114);
                    }
                    case xConst.shipFeature.CHUNNEL_STABILIZER:
                    {
                        return (ship.hullid == 108);
                    }
                    case xConst.shipFeature.STARGATE:
                    {
                        return (ship.hullid == 108);
                    }
                    case xConst.shipFeature.UNIVERSAL_CHUNNEL_TARGET:
                    {
                        return (ship.hullid == 108);
                    }
                    case xConst.shipFeature.ELUSIVE:
                    {
                        return ((ship.hullid == 1065) || (ship.hullid == 1071) || (ship.hullid == 2065) || (ship.hullid == 2071));
                    }
                    case xConst.shipFeature.SQUADRON:
                    {
                        return ((ship.hullid == 1065) || (ship.hullid == 1071) || (ship.hullid == 2065) || (ship.hullid == 2071));
                    }
                    case xConst.shipFeature.WEBMINE_IMMUNITY:
                    {
                        return (ship.hullid == 110);
                    }
                    case xConst.shipFeature.SUNBURST_DEVICE:
                    {
                        return (ship.hullid == 1064);
                    }
                    case xConst.shipFeature.MOVE_MINEFIELDS:
                    {
                        return (ship.hullid == 113);
                    }
                    case xConst.shipFeature.REPAIR_SHIP:
                    {
                        return (ship.hullid == 1090);
                    }
                    case xConst.shipFeature.COMMAND_SHIP:
                    {
                        return (ship.hullid == 1089);
                    }
                    case xConst.shipFeature.TANTRUM_DEVICE:
                    {
                        return (ship.hullid == 111);
                    }
                }

                return false;
            }
        };

    // #################################################################################################################

    var xMapUtils =
        {
            getMapCenterCoordinates: function()
            {
                return this._mapCenterCoordinates;
            },

            getSphereBorderAddition: function()
            {
                return this._sphereBorderAddition;
            },

            getMapWidth: function()
            {
                return vgap.settings.mapwidth + (vgap.settings.sphere ? 2 * this.getSphereBorderAddition().getWidth() : 0);
            },

            getMapHeight: function()
            {
                return vgap.settings.mapheight + (vgap.settings.sphere ? 2 * this.getSphereBorderAddition().getHeight() : 0);
            },

            getMaxCoordinatesRect: function()
            {
                let mapWidth = this.getMapWidth() - (vgap.settings.sphere ? 1 : 0);
                let mapHeight = this.getMapHeight() - (vgap.settings.sphere ? 1 : 0);
                let mapCenter = this.getMapCenterCoordinates();

                let x1 = Math.floor(mapCenter.x - mapWidth / 2);
                let y1 = Math.floor(mapCenter.y - mapHeight / 2);
                let x2 = Math.floor(mapCenter.x + mapWidth / 2);
                let y2 = Math.floor(mapCenter.y + mapHeight / 2);

                return new XRect(x1, y1, x2, y2);
            },

            getMapBoundingRect: function()
            {
                return this.getMaxCoordinatesRect().enlargeXY(0.5, 0.5);
            },

            getSphereDuplicationBoundingRect: function()
            {
                return this.getMapBoundingRect().enlargeXY(vgap.accountsettings.sphereduplicate, vgap.accountsettings.sphereduplicate);
            },

            getHeadingXY: function(x1, y1, x2, y2)
            {
                return vgap.getHeading(x1, y1, x2, y2);
            },

            getHeading: function(point1, point2)
            {
                return vgap.getHeading(point1.x, point1.y, point2.x, point2.y);
            },

            getOneDimensionalSphereDistance: function(c1, c2, sphereSize)
            {
                let dist = Math.abs(c1 - c2);

                if (vgap.settings.sphere)
                {
                    while (dist > (sphereSize / 2))
                    {
                        dist = Math.abs(dist - sphereSize);
                    }
                }

                return dist;
            },

            getSphereDistanceXY: function(x1, y1, x2, y2)
            {
                let xDiff = this.getOneDimensionalSphereDistance(x1, x2, this.getMapWidth());
                let yDiff = this.getOneDimensionalSphereDistance(y1, y2, this.getMapHeight());

                return Math.sqrt((xDiff * xDiff) + (yDiff * yDiff));
            },

            getSphereDistance: function(pointlikeObject1, pointlikeObject2)
            {
                return this.getSphereDistanceXY(pointlikeObject1.x, pointlikeObject1.y, pointlikeObject2.x, pointlikeObject2.y);
            },

            screenX: function(x)
            {
                return Math.round((x - vgap.map.canvas.x) * vgap.map.zoom) + 0.5;
            },

            screenY: function(y)
            {
                return vgap.map.canvas.height - (Math.round((y - vgap.map.canvas.y) * vgap.map.zoom) + 0.5);
            },

            hasNebulas: function()
            {
                return (vgap.nebulas && (vgap.nebulas.length > 0));
            },

            getNebulaVisibilityFromIntensity: function(nebulaIntensity)
            {
                return Math.round(4000 / (nebulaIntensity + 1));
            },

            getNebulaVisibility: function(point)
            {
                let intensity = vgap.getNebulaIntensity(point.x, point.y);

                return this.getNebulaVisibilityFromIntensity(intensity);
            },

            getNebulaVisibilityXY: function(x, y)
            {
                let intensity = this.getNebulaIntensityXY(x, y);

                return this.getNebulaVisibilityFromIntensity(intensity);
            },

            getNebulaIntensityXY: function(x, y)
            {
                let intensity = 0;

                if (this.hasNebulas())
                {
                    for (let i = 0; i < vgap.nebulas.length; i++)
                    {
                        let nebula = vgap.nebulas[i];

                        if (nebula.id < 0) // sphere duplicated nebula
                        {
                            continue;
                        }

                        let dist = this.getSphereDistanceXY(nebula.x, nebula.y, x, y);

                        if (dist <= nebula.radius)
                        {
                            intensity += Math.ceil(nebula.intensity * (1 - (dist / nebula.radius)));
                        }
                    }
                }

                return intensity;
            },

            drawLineSection: function(section, drawParams)
            {
                if (section == null)
                {
                    return;
                }

                let ctx = vgap.map.ctx;

                ctx.strokeStyle = drawParams.strokeStyle;
                ctx.lineWidth = drawParams.lineWidth;

                if (!vgap.settings.sphere)
                {
                    ctx.beginPath();
                    ctx.moveTo(xMapUtils.screenX(section.x1), xMapUtils.screenY(section.y1));
                    ctx.lineTo(xMapUtils.screenX(section.x2), xMapUtils.screenY(section.y2));
                    ctx.closePath();
                    ctx.stroke();

                    return;
                }

                let mapBoundingRect = this.getMapBoundingRect();
                let sphereDuplicationBoundingRect = this.getSphereDuplicationBoundingRect();

                let mapWidth = mapBoundingRect.getWidth();
                let mapHeight = mapBoundingRect.getHeight();

                let sphereClipRects = [];
                let delta = XPoint.minDistance();
                sphereClipRects.push(new XRect(sphereDuplicationBoundingRect.left, sphereDuplicationBoundingRect.bottom, mapBoundingRect.left - delta, sphereDuplicationBoundingRect.top));
                sphereClipRects.push(new XRect(mapBoundingRect.right + delta, sphereDuplicationBoundingRect.bottom, sphereDuplicationBoundingRect.right, sphereDuplicationBoundingRect.top));
                sphereClipRects.push(new XRect(mapBoundingRect.left, mapBoundingRect.top + delta, mapBoundingRect.right, sphereDuplicationBoundingRect.top));
                sphereClipRects.push(new XRect(mapBoundingRect.left, sphereDuplicationBoundingRect.bottom, mapBoundingRect.right, mapBoundingRect.bottom - delta));

                let collectSections = function(section, normalSections, sphereSections)
                {
                    let clippedSection = section.clip(mapBoundingRect);
                    if (clippedSection)
                    {
                        normalSections.push(clippedSection);
                    }

                    if (vgap.settings.sphere)
                    {
                        for (let i = 0; i < sphereClipRects.length; i++)
                        {
                            clippedSection = section.clip(sphereClipRects[i]);
                            if (clippedSection)
                            {
                                sphereSections.push(clippedSection);
                            }
                        }
                    }
                };

                let normalSections = [];
                let sphereSections = [];

                collectSections(section, normalSections, sphereSections);

                if (vgap.settings.sphere)
                {
                    collectSections(section.offsetXY(-mapWidth, -mapHeight), normalSections, sphereSections);
                    collectSections(section.offsetXY(-mapWidth, 0), normalSections, sphereSections);
                    collectSections(section.offsetXY(-mapWidth, +mapHeight), normalSections, sphereSections);
                    collectSections(section.offsetXY(0, -mapHeight), normalSections, sphereSections);
                    collectSections(section.offsetXY(0, +mapHeight), normalSections, sphereSections);
                    collectSections(section.offsetXY(+mapWidth, -mapHeight), normalSections, sphereSections);
                    collectSections(section.offsetXY(+mapWidth, 0), normalSections, sphereSections);
                    collectSections(section.offsetXY(+mapWidth, +mapHeight), normalSections, sphereSections);
                }

                for (let i = 0; i < normalSections.length; i++)
                {
                    let curSection = normalSections[i];

                    ctx.beginPath();
                    ctx.moveTo(xMapUtils.screenX(curSection.x1), xMapUtils.screenY(curSection.y1));
                    ctx.lineTo(xMapUtils.screenX(curSection.x2), xMapUtils.screenY(curSection.y2));
                    ctx.closePath();
                    ctx.stroke();
                }

                if ((drawParams.sphereDuplication == xConst.sphereDuplication.FULL) || (drawParams.sphereDuplication == xConst.sphereDuplication.ALPHA))
                {
                    if (drawParams.sphereDuplication == xConst.sphereDuplication.ALPHA)
                    {
                        ctx.strokeStyle = colorToRGBA(drawParams.strokeStyle, 0.5);
                    }

                    for (i = 0; i < sphereSections.length; i++)
                    {
                        curSection = sphereSections[i];

                        ctx.beginPath();
                        ctx.moveTo(xMapUtils.screenX(curSection.x1), xMapUtils.screenY(curSection.y1));
                        ctx.lineTo(xMapUtils.screenX(curSection.x2), xMapUtils.screenY(curSection.y2));
                        ctx.closePath();
                        ctx.stroke();
                    }
                }
            },

            drawRect: function(rect, drawParams)
            {
                if (rect == null)
                {
                    return;
                }

                this.drawLineSection(rect.getLeftSection(), drawParams);
                this.drawLineSection(rect.getRightSection(), drawParams);
                this.drawLineSection(rect.getTopSection(), drawParams);
                this.drawLineSection(rect.getBottomSection(), drawParams);
            },

            addMapTool: function(text, cls, onclick, target)
            {
                if (xUtils.isMobileClient())
                {
                    vgap.map.addMapTool(text, cls, onclick, target);
                    return;
                }

                if (!target || (target == "#MapControls"))
                {
                    $("<li class='" + cls + "'>" + text + "</li>").tclick(onclick).appendTo("#MapTools");
                    return;
                }

                $("<div class='mapbutton " + cls + "' title='" + text + "'></div>").tclick(function(e)
                {
                    if (onclick)
                        onclick(e);
                }).appendTo(target);
            },

            activateToggleMapTools: function(isToggleMapToolsActive)
            {
                this._isToggleMapToolsActive = isToggleMapToolsActive;

                if (isToggleMapToolsActive)
                {
                    $("#MapToolsMenu").show();
                }
                else
                {
                    $("#MapToolsMenu").hide();
                }
            },

            initialize: function()
            {
                this._mapCenterCoordinates = new XPoint(2000, 2000);
                this._sphereBorderAddition = new XDimension(10, 10);
                this._isToggleMapToolsActive = true;
            }
        };

    xMapUtils.oldToggleTools = vgapMap.prototype.toggleTools;
    vgapMap.prototype.toggleTools = function()
    {
        if (!xMapUtils._isToggleMapToolsActive || (typeof xMapUtils.oldToggleTools == 'undefined') || (xMapUtils.oldToggleTools == null))
        {
            return;
        }

        xMapUtils.oldToggleTools.apply(this, arguments);
    };

    // -----------------------------------------------------------------------------------------------------------------

    const X_POINTS_PER_LIGHTYEAR = 4096;

    function XPoint(x, y)
    {
        this.x = XPoint.roundCoordinate(x);
        this.y = XPoint.roundCoordinate(y);
    }

    XPoint.fromPoint = function(pointlikeObject)
    {
        return new XPoint(pointlikeObject.x, pointlikeObject.y);
    };

    XPoint.roundCoordinate = function(c)
    {
        return Math.round(c * X_POINTS_PER_LIGHTYEAR) / X_POINTS_PER_LIGHTYEAR;
    };

    XPoint.minDistance = function()
    {
        return 1 / X_POINTS_PER_LIGHTYEAR;
    };

    XPoint.prototype.equalsXY = function(x, y)
    {
        return ((this.x == XPoint.roundCoordinate(x)) && (this.y == XPoint.roundCoordinate(y)));
    };

    XPoint.prototype.equals = function(pointlikeObject)
    {
        if (pointlikeObject == null)
        {
            return false;
        }

        if (pointlikeObject instanceof XPoint)
        {
            return ((this.x == pointlikeObject.x) && (this.y == pointlikeObject.y));
        }

        return this.equalsXY(pointlikeObject.x, pointlikeObject.y);
    };

    XPoint.prototype.getLogString = function()
    {
        return "XPoint(" + this.x + ", " + this.y + ")";
    };

    XPoint.prototype.offsetXY = function(dx, dy)
    {
        return new XPoint(this.x + dx, this.y + dy);
    };

    // -----------------------------------------------------------------------------------------------------------------

    function XDimension(width, height)
    {
        this.width = XPoint.roundCoordinate(width);
        this.height = XPoint.roundCoordinate(height);
    }

    XDimension.prototype.equals = function(dimension)
    {
        if (dimension == null)
        {
            return false;
        }

        return (this.width == dimension.width) && (this.height == dimension.height);
    };

    XDimension.prototype.getLogString = function()
    {
        return "XDimension(" + this.width + ", " + this.height + ")";
    };

    XDimension.prototype.getWidth = function()
    {
        return this.width;
    };

    XDimension.prototype.getHeight = function()
    {
        return this.height;
    };

    // -----------------------------------------------------------------------------------------------------------------

    function XRect(x1, y1, x2, y2)
    {
        this.left = XPoint.roundCoordinate((x1 < x2) ? x1 : x2);
        this.bottom = XPoint.roundCoordinate((y1 < y2) ? y1 : y2);
        this.right = XPoint.roundCoordinate((x1 < x2) ? x2 : x1);
        this.top = XPoint.roundCoordinate((y1 < y2) ? y2 : y1);
    }

    XRect.fromPoints = function(pointlikeObject1, pointlikeObject2)
    {
        return new XRect(pointlikeObject1.x, pointlikeObject1.y, pointlikeObject2.x, pointlikeObject2.y);
    };

    XRect.prototype.equals = function(rect)
    {
        if (rect == null)
        {
            return false;
        }

        return ((this.left == rect.left) && (this.bottom == rect.bottom) && (this.right == rect.right) && (this.top == rect.top));
    };

    XRect.prototype.getLogString = function()
    {
        return "XRect(" + this.left + ", " + this.bottom + ", " + this.right + ", " + this.top + ")";
    };

    XRect.prototype.getWidth = function()
    {
        return this.right - this.left;
    };

    XRect.prototype.getHeight = function()
    {
        return this.top - this.bottom;
    };

    XRect.prototype.getDimension = function()
    {
        return new XDimension(this.getWidth(), this.getHeight());
    };

    XRect.prototype.getLeftSection = function()
    {
        return new XLineSection(this.left, this.bottom, this.left, this.top);
    };

    XRect.prototype.getBottomSection = function()
    {
        return new XLineSection(this.left, this.bottom, this.right, this.bottom);
    };

    XRect.prototype.getRightSection = function()
    {
        return new XLineSection(this.right, this.bottom, this.right, this.top);
    };

    XRect.prototype.getTopSection = function()
    {
        return new XLineSection(this.left, this.top, this.right, this.top);
    };

    XRect.prototype.getLeftBottomPoint = function()
    {
        return new XPoint(this.left, this.bottom);
    };

    XRect.prototype.getRightBottomPoint = function()
    {
        return new XPoint(this.right, this.bottom);
    };

    XRect.prototype.getLeftTopPoint = function()
    {
        return new XPoint(this.left, this.top);
    };

    XRect.prototype.getRightTopPoint = function()
    {
        return new XPoint(this.right, this.top);
    };

    XRect.prototype.getCenterPoint = function()
    {
        return new XPoint((this.left + this.right) * 0.5, (this.bottom + this.top) * 0.5);
    };

    XRect.prototype.enlargeXY = function(x, y)
    {
        return new XRect(this.left - x, this.bottom - y, this.right + x, this.top + y);
    };

    XRect.prototype.enlarge = function(dimension)
    {
        return this.enlarge(dimension.width, dimension.height);
    };

    XRect.prototype.containsXY = function(x, y)
    {
        return ((x >= this.left) && (x <= this.right) && (y >= this.bottom) && (y <= this.top));
    };

    XRect.prototype.containsPoint = function(pointlikeObject)
    {
        if (pointlikeObject == null)
        {
            return false;
        }

        return this.containsXY(pointlikeObject.x, pointlikeObject.y);
    };

    // -----------------------------------------------------------------------------------------------------------------

    // a*x + b*y = c
    function XLine(a, b, c)
    {
        let a1 = a;
        let b1 = b;
        let c1 = c;

        if (b != 0)
        {
            if (b != 1)
            {
                a1 = a / b;
                b1 = 1;
                c1 = c / b;
            }
        }
        else if (a != 0)
        {
            if (a != 1)
            {
                a1 = 1;
                b1 = 0;
                c1 = c / a;
            }
        }
        else
        {
            xLibrary.throwIllegalArgumentException("XLine: Cannot create line. The specified parameters a and b both are 0.");
        }

        this.a = a1;
        this.b = b1;
        this.c = c1;
    }

    XLine.prototype.getLogString = function()
    {
        return "XLine(" + this.a + ", " + this.b + ", " + this.c + ")";
    };

    XLine.fromXY = function(x1, y1, x2, y2)
    {
        if ((x1 == x2) && (y1 == y2))
        {
            xLibrary.throwIllegalArgumentException("XLine.fromXY(): Cannot create line. The specified points are identical. points = (" + x1 + ", " + y1 + ")");
        }

        return new XLine(y1 - y2, x2 - x1, x2 * y1 - x1 * y2);
    };

    XLine.fromPoints = function(pointlikeObject1, pointlikeObject2)
    {
        return XLine.fromXY(pointlikeObject1.x, pointlikeObject1.y, pointlikeObject2.x, pointlikeObject2.y);
    };

    XLine.getPerpendicularBisector = function(pointlikeObject1, pointlikeObject2)
    {
        let x1 = pointlikeObject1.x;
        let y1 = pointlikeObject1.y;
        let x2 = pointlikeObject2.x;
        let y2 = pointlikeObject2.y;

        if (y1 == y2)
        {
            if (x1 == x2)
            {
                xLibrary.throwIllegalArgumentException("XLine.getPerpendicularBisector(): Cannot calculate the perpendicular bisector. The specified points are identical. points = (" + x1 + ", " + y1 + ")");
            }

            return new XLine(1, 0, (x1 + x2) / 2);
        }

        let a = (x1 - x2) / (y1 - y2);
        let c = (x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2) / (2 * (y1 - y2));

        return new XLine(a, 1, c);
    };

    XLine.prototype.getIntersectionPoint = function(lineOrLineSection)
    {
        if (lineOrLineSection instanceof (XLineSection))
        {
            return lineOrLineSection.getIntersectionPoint(this);
        }

        let line2 = lineOrLineSection;

        let a1 = this.a;
        let b1 = this.b;
        let c1 = this.c;

        let a2 = line2.a;
        let b2 = line2.b;
        let c2 = line2.c;

        let d = a1 * b2 - a2 * b1;

        if (d == 0) // parallel (incl. identical)
        {
            return null;
        }

        let x = (b2 * c1 - b1 * c2) / d;
        let y = (a1 * c2 - a2 * c1) / d;

        return new XPoint(x, y);
    };

    XLine.prototype.isVertical = function()
    {
        return (this.b == 0);
    };

    XLine.prototype.isHorizontal = function()
    {
        return (this.a == 0);
    };

    XLine.prototype.getPointFromX = function(x)
    {
        if (this.b == 0)
        {
            return null;
        }

        let xNormalized = XPoint.roundCoordinate(x);

        return new XPoint(xNormalized, (this.c - this.a * xNormalized) / this.b);
    };

    XLine.prototype.getPointFromY = function(y)
    {
        if (this.a == 0)
        {
            return null;
        }

        let yNormalized = XPoint.roundCoordinate(y);

        return new XPoint((this.c - this.b * yNormalized) / this.a, yNormalized);
    };

    XLine.prototype.getHeading = function()
    {
        let heading = xMapUtils.getHeadingXY(0, 0, this.a, this.b) + 90;
        while (heading >= 180)
        {
            heading -= 180;
        }

        return heading;
    };

    XLine.prototype.clip = function(rect)
    {
        let ip = [];
        ip.push(rect.getLeftSection().getIntersectionPoint(this));
        ip.push(rect.getRightSection().getIntersectionPoint(this));
        ip.push(rect.getBottomSection().getIntersectionPoint(this));
        ip.push(rect.getTopSection().getIntersectionPoint(this));

        let p1 = null;
        let p2 = null;

        for (var i = 0; (p1 == null) && (i < ip.length); i++)
        {
            p1 = ip[i];
        }

        for (; (p2 == null) && (i < ip.length); i++)
        {
            p2 = ip[i];
        }

        if ((p1 == null) || (p2 == null) || p1.equals(p2))
        {
            return null;
        }

        return XLineSection.fromPoints(p1, p2);
    };

    // -----------------------------------------------------------------------------------------------------------------

    function XLineSection(x1, y1, x2, y2)
    {
        this.x1 = XPoint.roundCoordinate(x1);
        this.y1 = XPoint.roundCoordinate(y1);
        this.x2 = XPoint.roundCoordinate(x2);
        this.y2 = XPoint.roundCoordinate(y2);

        if ((this.x1 == this.x2) && (this.y1 == this.y2))
        {
            xLibrary.throwIllegalArgumentException("XLineSection: Cannot create line section. The specified points are identical. points = (" + this.x1 + ", " + this.y1 + ")");
        }
    }

    XLineSection.fromPoints = function(pointlikeObject1, pointlikeObject2)
    {
        return new XLineSection(pointlikeObject1.x, pointlikeObject1.y, pointlikeObject2.x, pointlikeObject2.y);
    };

    XLineSection.prototype.equals = function(lineSection)
    {
        if (lineSection == null)
        {
            return false;
        }

        return ((this.x1 == lineSection.x1) && (this.y1 == lineSection.y1) && (this.x2 == lineSection.x2) && (this.y2 == lineSection.y2));
    };

    XLineSection.prototype.getLogString = function()
    {
        return "XLineSection(" + this.x1 + ", " + this.y1 + ", " + this.x2 + ", " + this.y2 + ")";
    };

    XLineSection.prototype.getLine = function()
    {
        return XLine.fromXY(this.x1, this.y1, this.x2, this.y2);
    };

    XLineSection.prototype.getBoundingRect = function()
    {
        return new XRect(this.x1, this.y1, this.x2, this.y2);
    };

    XLineSection.prototype.isVertical = function()
    {
        return (this.x1 == this.x2);
    };

    XLineSection.prototype.isHorizontal = function()
    {
        return (this.y1 == this.y2);
    };

    XLineSection.prototype.offsetXY = function(dx, dy)
    {
        return new XLineSection(this.x1 + dx, this.y1 + dy, this.x2 + dx, this.y2 + dy);
    };

    XLineSection.prototype.offsetDimension = function(dimension)
    {
        return this.offsetXY(dimension.getWidth(), dimension.getHeight());
    };

    XLineSection.prototype.getIntersectionPoint = function(lineOrLineSection)
    {
        let isParamLineSection = (lineOrLineSection instanceof (XLineSection));

        let line1 = this.getLine();
        let line2 = (isParamLineSection ? lineOrLineSection.getLine() : lineOrLineSection);

        let point = line1.getIntersectionPoint(line2);

        if ((point != null) && this.getBoundingRect().containsPoint(point) && (!isParamLineSection || lineOrLineSection.getBoundingRect().containsPoint(point)))
        {
            return point;
        }

        return null;
    };

    XLineSection.prototype.getCenterPoint = function()
    {
        return new XPoint((this.x1 + this.x2) * 0.5, (this.y1 + this.y2) * 0.5);
    };

    XLineSection.prototype.getPointFromX = function(x)
    {
        let point = this.getLine().getPointFromX(x);
        if ((point != null) && this.getBoundingRect().containsPoint(p))
        {
            return point;
        }

        return null;
    };

    XLineSection.prototype.getPointFromY = function(y)
    {
        let point = this.getLine().getPointFromY(y);
        if ((point != null) && this.getBoundingRect().containsPoint(p))
        {
            return point;
        }

        return null;
    };

    XLineSection.prototype.getHeading = function()
    {
        return xMapUtils.getHeadingXY(this.x1, this.y1, this.x2, this.y2);
    };

    XLineSection.prototype.clip = function(rect)
    {
        let containsP1 = rect.containsXY(this.x1, this.y1);
        let containsP2 = rect.containsXY(this.x2, this.y2);

        if (containsP1 && containsP2)
        {
            return this;
        }

        let ips = [];
        ips.push(rect.getLeftSection().getIntersectionPoint(this));
        ips.push(rect.getRightSection().getIntersectionPoint(this));
        ips.push(rect.getBottomSection().getIntersectionPoint(this));
        ips.push(rect.getTopSection().getIntersectionPoint(this));

        let ip1 = null;
        let ip2 = null;

        for (var i = 0; (ip1 == null) && (i < ips.length); i++)
        {
            ip1 = ips[i];
        }

        for (; (ip2 == null) && (i < ips.length); i++)
        {
            ip2 = ips[i];
        }

        if (containsP1)
        {
            if (!ip1.equalsXY(this.x1, this.y1))
            {
                return new XLineSection(this.x1, this.y1, ip1.x, ip1.y);
            }

            if ((ip2 != null) && (!ip2.equalsXY(this.x1, this.y1)))
            {
                return new XLineSection(this.x1, this.y1, ip2.x, ip2.y);
            }

            return null;
        }

        if (containsP2)
        {
            if (!ip1.equalsXY(this.x2, this.y2))
            {
                return new XLineSection(ip1.x, ip1.y, this.x2, this.y2);
            }

            if ((ip2 != null) && (!ip2.equalsXY(this.x2, this.y2)))
            {
                return new XLineSection(ip2.x, ip2.y, this.x2, this.y2);
            }

            return null;
        }

        if ((ip1 == null) || (ip2 == null) || ip1.equals(ip2))
        {
            return null;
        }

        let section = XLineSection.fromPoints(ip1, ip2);
        if (section.getHeading() == this.getHeading())
        {
            return section;
        }

        return XLineSection.fromPoints(ip2, ip1);
    };

    // -----------------------------------------------------------------------------------------------------------------

    function XDrawParams()
    {
        this.strokeStyle = "#FFFFFF";
        this.lineWidth = 1;
        this.sphereDuplication = xConst.sphereDuplication.ALPHA;
    }

    XDrawParams.prototype.getLogString = function()
    {
        return "XDrawParams(strokeStyle: " + this.strokeStyle + ", lineWidth: " + this.lineWidth + ", sphereDuplication: " + this.sphereDuplication + ")";
    };

    XDrawParams.prototype.setStrokeStyle = function(strokeStyle)
    {
        this.strokeStyle = strokeStyle;

        return this;
    };

    XDrawParams.prototype.setLineWidth = function(lineWidth)
    {
        this.lineWidth = lineWidth;

        return this;
    };

    XDrawParams.prototype.setSphereDuplication = function(sphereDuplication)
    {
        this.sphereDuplication = sphereDuplication;

        return this;
    };

    // -----------------------------------------------------------------------------------------------------------------
}

let script = document.createElement("script");
script.type = "application/javascript";
let globalText = "" + global;
globalText = globalText.substring(globalText.indexOf('{') + 1, globalText.lastIndexOf('}'));
script.textContent = globalText + ";(" + wrapper + ")(\"" + GM_info.script.version + "\");";
document.body.appendChild(script);
document.body.removeChild(script);