Planets.nu - Meteor's Library

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

当前为 2020-04-08 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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       1.11
// @grant         none
// @date          2018-06-26
// @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/*
// ==/UserScript==

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

"use strict";

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

function global()
{
    var xLibrary;

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

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

        if (vgap.version < 3.0)
        {
            console.log(name + "requires at least NU version 3.0. Plugin disabled.");
            return;
        }

        console.log(name + " version: v" + version);
    }

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

    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.logWarning = function(warning)
    {
        console.log(this.name + "> Warning: " + warning);
    };

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

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

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

    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
                },
            building:
                {
                    MINE: 200,
                    FACTORY: 100,
                    DEFENSE_POST: 50
                }

        };

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

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

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

                return result;
            },

            getLogString: function(object, arExcludedPropertyNames, maxRecursionDepth)
            {
                let getLogStringImpl = function(object, arObjectsSeen, arExcludedPropertyNames, maxRecursionDepth)
                {
                    let str = Object.getPrototypeOf(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)
                            {
                                let cycle = arObjectsSeen.includes(propertyValue);
                                arObjectsSeen.push(propertyValue);

                                if (cycle)
                                {
                                    propertyValueStr = "(cycle)";
                                }
                                else if ((maxRecursionDepth > 0) && (!arExcludedPropertyNames.includes(propertyName)))
                                {
                                    propertyValueStr = getLogStringImpl(propertyValue, arObjectsSeen, arExcludedPropertyNames, maxRecursionDepth - 1);
                                }
                                else
                                {
                                    propertyValueStr = Object.getPrototypeOf(propertyValue).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, buildingType)
            {
                if (planet.clans <= buildingType)
                {
                    return planet.clans;
                }

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

            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];
            }

        };

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

    var xMapUtils =
        {
            getMapCenter: function()
            {
                return new XPoint(2000, 2000);
            },

            getHomeworldCenter: function()
            {
                return this.getMapCenter();
            },

            getSphereBorderAddition: function()
            {
                return new XDimension(10, 10);
            },

            getMapBoundingRect: function()
            {
                let mapWidth = vgap.settings.mapwidth + (vgap.settings.sphere ? 2 * this.getSphereBorderAddition().getWidth() : 0);
                let mapHeight = vgap.settings.mapheight + (vgap.settings.sphere ? 2 * this.getSphereBorderAddition().getHeight() : 0);
                let mapCenter = this.getMapCenter();

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

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

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

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

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

            drawLineSection: function(section, drawParams)
            {
                if (section == null)
                {
                    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);
                }

                let ctx = vgap.map.ctx;

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

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

                    ctx.beginPath();
                    ctx.moveTo(vgap.map.screenX(curSection.point1.x), vgap.map.screenY(curSection.point1.y));
                    ctx.lineTo(vgap.map.screenX(curSection.point2.x), vgap.map.screenY(curSection.point2.y));
                    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(vgap.map.screenX(curSection.point1.x), vgap.map.screenY(curSection.point1.y));
                        ctx.lineTo(vgap.map.screenX(curSection.point2.x), vgap.map.screenY(curSection.point2.y));
                        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);
            }
        };

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

    const X_POINTS_PER_LIGHTYEAR = 4096;

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

    XPoint.fromPoint = function(point)
    {
        return new XPoint(point.x, point.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.equals = function(point)
    {
        if (point == null)
        {
            return false;
        }

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

        return this.equals(XPoint.fromPoint(point));
    };

    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(point1, point2)
    {
        return new XRect(point1.x, point1.y, point2.x, point2.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.enlarge = function(dimension)
    {
        return new XRect(this.left - dimension.width, this.bottom - dimension.height, this.right + dimension.width, this.top + dimension.height);
    };

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

        if (point instanceof XPoint)
        {
            return ((point.x >= this.left) && (point.x <= this.right) && (point.y >= this.bottom) && (point.y <= this.top));
        }

        return this.containsPoint(XPoint.fromPoint(point));
    };

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

    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
        {
            throw "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))
        {
            throw "Cannot create line. The specified points are identical.";
        }

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

    XLine.fromPoints = function(point1, point2)
    {
        return XLine.fromXY(point1.x, point1.y, point2.x, point2.y);
    };

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

        if (y1 == y2)
        {
            if (x1 == x2)
            {
                throw "Cannot calculate the perpendicular bisector. The specified points are identical.";
            }

            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 == null) // 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.getPointFromX = function(x)
    {
        if (this.b == 0)
        {
            return null;
        }

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

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

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

    XLine.prototype.getHeading = function()
    {
        let heading = vgap.getHeading(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.point1 = new XPoint(x1, y1);
        this.point2 = new XPoint(x2, y2);

        if (this.point1.equals(this.point2))
        {
            throw "Cannot create line section. The specified points are identical.";
        }
    }

    XLineSection.fromPoints = function(point1, point2)
    {
        return new XLineSection(point1.x, point1.y, point2.x, point2.y);
    };

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

        return (this.point1.equals(lineSection.point1) && (this.point2.equals(lineSection.point2)));
    };

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

    XLineSection.prototype.getLine = function()
    {
        return XLine.fromPoints(this.point1, this.point2);
    };

    XLineSection.prototype.getBoundingRect = function()
    {
        return XRect.fromPoints(this.point1, this.point2);
    };

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

    XLineSection.prototype.offsetDimension = function(dimension)
    {
        return new XLineSection(this.point1.x + dimension.getWidth(), this.point1.y + dimension.getHeight(), this.point2.x + dimension.getWidth(), this.point2.y + 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.getMidPoint = function()
    {
        let x = (this.point1.x + this.point2.x) / 2;
        let y = (this.point1.y + this.point2.y) / 2;

        return new XPoint(x, y);
    };

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

        return null;
    };

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

        return null;
    };

    XLineSection.prototype.getHeading = function()
    {
        return xMapUtils.getHeading(this.point1, this.point2);
    };

    XLineSection.prototype.clip = function(rect)
    {
        let containsP1 = rect.containsPoint(this.point1);
        let containsP2 = rect.containsPoint(this.point2);

        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.equals(this.point1))
            {
                return XLineSection.fromPoints(this.point1, ip1);
            }

            if ((ip2 != null) && (!ip2.equals(this.point1)))
            {
                return XLineSection.fromPoints(this.point1, ip2);
            }

            return null;
        }

        if (containsP2)
        {
            if (!ip1.equals(this.point2))
            {
                return XLineSection.fromPoints(ip1, this.point2);
            }

            if ((ip2 != null) && (!ip2.equals(this.point2)))
            {
                return XLineSection.fromPoints(ip2, this.point2);
            }

            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);