Back to Index Page generated: Dec 20, 2024, 7:22:09 AM

Expansion Diplomacy

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description WIP: wars maps and History! GNN news concerning alliances and wars! Treasuries! Citizenships! Taxes! Systems treasury! Wars require money, and countries are defeated either by battles or by bankruptcy, sometimes the latter producing the former. Strategic maps! New F4 Interface screens: the Wars Map! The Diplomatic Map! Systems alliances, wars! Two systems within 7ly of each other may now form an alliance, break their alliance, wage war on each other, and make peace too :) ! News! Some GNN news are now displayed when alliance/break/war/peace happens, and the player is in one of those systems. Citizenships! The player may acquire, or renounce, a system citizenship when visiting this system, for the right price. They may choose which of their citizenships is displayed as their flagship. The player is considered fugitive when in systems warring with their flag. The player may buy visas in the Embassy district. WIP: wars maps and History! GNN news concerning alliances and wars! Treasuries! Citizenships! Taxes! Systems treasury! Wars require money, and countries are defeated either by battles or by bankruptcy, sometimes the latter producing the former. Strategic maps! New F4 Interface screens: the Wars Map! The Diplomatic Map! Systems alliances, wars! Two systems within 7ly of each other may now form an alliance, break their alliance, wage war on each other, and make peace too :) ! News! Some GNN news are now displayed when alliance/break/war/peace happens, and the player is in one of those systems. Citizenships! The player may acquire, or renounce, a system citizenship when visiting this system, for the right price. They may choose which of their citizenships is displayed as their flagship. The player is considered fugitive when in systems warring with their flag. The player may buy visas in the Embassy district.
Identifier oolite.oxp.Day.Diplomacy oolite.oxp.Day.Diplomacy
Title Diplomacy Diplomacy
Category Ambience Ambience
Author Day Day
Version 0.19 0.19
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
  • oolite.oxp.Svengali.GNN:0
  • oolite.oxp.Commander_McLane.Anarchies:0
  • oolite.oxp.Svengali.GNN:0
  • oolite.oxp.Commander_McLane.Anarchies:0
  • Optional Expansions
    Conflict Expansions
    Information URL http://wiki.alioth.net/index.php/Diplomacy n/a
    Download URL https://wiki.alioth.net/img_auth.php/1/19/Diplomacy.oxz n/a
    License CC BY-NC-SA 4 CC BY-NC-SA 4
    File Size n/a
    Upload date 1709378438

    Documentation

    Also read http://wiki.alioth.net/index.php/Diplomacy

    Diplomacy_EngineAPI_readme.txt

    FIXME reintegrate this to DayDiplomacy_JsDocDiplomacy.js
    An 'EventType' is a string defined by an oxp developer. As an example, the system taxation EventType is "SELFTAX".
    EventTypes are stored in an ordered array, so that "Event"s' "Response"s and "Action"s may be executed in a designed order
    (for example, "VICTORY" should follow "ATTACK" and not happen before :) ).
    For a same EventType, recurrent "Action"s are executed before "Event"s.
    
    An 'ActorType' is a string defined by an oxp developer. As an example, the system ActorType is "SYSTEM".
    ActorTypes are stored in an ordered array, so that "Event"s' "Response"s and "Action"s may be executed in a designed order
    (for example, "SYSTEM"s should act before "ALLIANCE"s as information come to alliances through their systems :) ).
    For a same ActorType, recurrent "Action"s are executed before "Event"s.
    
    An "Action" may be said init (only executed once at the creation of an Actor) or recurring (executed each turn).
    An "Action" encapsulates a function, which typically will fire Events, or act onto the Oolite world.
    It contains: an 'EventType', an 'ActorType' (whose kind of actors will execute this action?), and a function.
    
    An "Event" is something done by an "Actor", to which other Actors may react by some "Response"s.
    It contains: an 'EventType', the acting Actor id, and some args to be used by the Responses (defined by the oxp developer).
    
    An "Actor" is everything which should react to events. Systems are Actors, Alliances will be.
    It contains : an 'ActorType', the Actor id, its responses, and the observing other actors' ids.
    An Actor observer is another actor which may react onto that actor events.
    This is useful so that for example only near systems may react to an event, and not far away systems.
    It's useful to limit the cpu load too by at least a factor 100.
    
    A "Response" encapsulates a function.
    It contains: an Id, the 'EventType' to which it responds, the 'ActorType' of actors which will use this response, and a function which may use the args given in the event.
    
    When docked in station, once every 10 frames, an action (event, response, etc) is realized.
    A 'turn' of events is allowed each jump.
    Before beginning a new 'turn', all the actions of the precedent turn have been realized.

    Diplomacy_readme.txt

    Diplomacy OXP
    
    ==============================
    Summary
    1. Description
    2. What's currently implemented functionality-wise?
    3. What's currently implemented technically?
    4. Effects on game difficulty
    5. Effects on game performance
    6. Compatibility
    7. Dependencies
    8. Instructions
    9. License
    10. Known bugs
    11. Changelog
    
    ==============================
    Description
    
    Hello everybody,
    
    this OXP intended goal is to allow historical events to happen between systems (attacks, loots,
     alliances, taxes...), and to have actions depending on this (news, massed flotillas, state racket, who knows?).
    Technically, I see it as a war/diplomacy framework. It includes a good JsDoc for oxp developers.
    
    It is STILL a Work In Progress.
    
    In particular, the savefile format might change in the future; so it wouldn't do to expect a savefile from an old
     version to work with a newer version code.
    If experimenting problems, the easy way is to edit the savefile and remove the lines including "Diplomacy".
     Then next start, the oxp will begin anew.
    
    ==============================
    What's currently implemented functionality-wise?
    
    Systems treasury!
        Wars require money, and countries are defeated either by battles or by bankruptcy,
         sometimes the latter producing the former.
        The Treasury and Tax levels are displayed in the F7 system information.
        Each system treasury is increased through taxation each player jump,
         depending on the time past since the last jump.
    
    Strategic maps!
        Showing the warring systems, and the diplomatic relationships!
        Requires the advanced navigational array equipment.
    
    Systems alliances, wars!
        Two systems within 7ly of each other may now form an alliance, if they like each other enough.
        They may break their alliance, too =-o !
        They may wage war to each other, and make peace too :) !
        New F4 Interface screen: the Systems History!
    
    News!
        Some GNN news are now displayed when an alliance is formed or broken between two systems,
          or when a war starts or ends between two systems, and the player is in one of those systems.
    
    Citizenships!
        The player may acquire, or renounce, a system citizenship when visiting this system, for the right price. They may
          choose which one of their citizenships is announced as the flag of their ship.
        The player is considered fugitive when in systems warring with their flag.
        Anarchies provide no citizenship and have no embassy district.
        The player may buy days of visa in the Embassy district in a neighbouring, non-enemy from the destination, system.
        Corporate systems, dictatorships and communists refuse docking to the player when stateless and visaless.
    
    ==============================
    What's currently implemented technically?
    
    The oxp contains Engines which may be use by developers to implement interesting galaxy-spanning events:
    - (main) Engine,
    - War,
    - History,
    - Systems,
    - Economy,
    - Citizenships.
    
    ==============================
    Effects on game difficulty
    
    + the player is considered fugitive when in systems warring with their flag.
    + the player is refused docking in some stations when they have not the requisite visa or passport.
    + some new ways to spend money: passports, visas.
    
    ==============================
    Effects on game performance
    
    This oxp works only when the player is docked, so there is no impact during the flight time.
    During the docking, it works once every ten frames, the effect on player experience should be negligible.
    If it isn't negligible, tell me and I'll put in the ability to choose the number of frames.
    Even if the effect is negligible on the player, it uses lots of cpu, so it might not be negligible on the battery.
    
    ==============================
    Compatibility
    
    ==============================
    Dependencies
    
    - GNN OXP
    - Anarchies OXP
    
    ==============================
    Instructions
    
    Do not unzip the .oxz file, just move into the AddOns folder of your Oolite installation.
    
    ==============================
    License
    
    This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License version 4.0.
    If you are re-using any piece of this OXP, please let me know by sending an e-mail to david at pradier dot info
    
    ==============================
    Known bugs
    
    None.
    
    ==============================
    Changelog
    
    0.19    Bugfix: breaking alliances and making peace now works again.
    0.18    Improvement, user-friendliness: the visa system in-game explanation is clearerer.
    0.17    Improvement, user-friendliness: the visa system in-game explanation is clearer.
    0.16    Improvement, flavor: the player is considered fugitive when in systems warring with their flag.
            Improvement, flavor: no "Embassy district" in anarchies, no citizenship in an Anarchy
            Improvement, flavor: the player may buy days of visa (cost: productivity / population / 365 per day) in the embassy district in a neighbouring, non-enemy from the destination system.
            Improvement, flavor: the first time the Diplomacy OXP is used, if a visa is needed in the current system, we give the player a 1-day visa.
            Improvement, flavor: a GNN news introduces the Visa Law.
            Improvement, flavor: when stateless, docking is refused in corporates, dictatorships and communists without having a visa.
            Improvement, flavor: maps are centered and zoomed. Possibility to center on the target system, or on the whole trajectory. Possibility to display the short/quick trajectory, or no trajectory at all.
            Improvement, performance: the API are removed in favor of JsDoc.
            Improvement, code quality: the Snoopers dependency, which is deprecated, is replaced by the GNN dependency. Oolite minimal required version is now 1.88, because of this.
            Improvement, code quality: moved the GNN connection to external script.
            Improvement, code quality: tax level and treasury are now displayed through mission.addMessageText rather than through a modification of the system description.
            Improvement, tweaking: the alliance threshold between systems is lowered.
            Bugfix: the initActions wasn't set as it should be. In particular, initActionsByType wasn't set when adding an initAction, and initActions was set with an ActorType as key instead of an ActionId.
    0.15    Improvement, flavor: the player may acquire or renounce the citizenship of the system they are in.
            Improvement, flavor: the player may display one of their citizenships as the flag of their ship.
            Improvement, dev func: other scripts may subscribe to be informed of a citizenship change of the player.
            Improvement, dev func: citizenships prices are dynamic and available to other scripts.
            Improvement, dev func: other scripts may inquire if the player has a particular citizenship.
            Improvement, code quality: introduced JsDoc comments, including some allowing to document the Oolite javascript hooks!
    0.14    Improvement, flavor: War declaration! Peace! Snooper news about them!
            Improvement, flavor: Wars map!
            Improvement, dev func: the war threshold and the alliance threshold are scriptable through the WarEngineAPI.
            Improvement, dev func: new F4 interface making History happen for debug purposes.
            Improvement, code consistency: Alliances scripts become War scripts.
    0.13    Bugfix: manifest.plist for the oxz manager.
    0.12    Bugfix: manifest.plist for the oxz manager.
    0.11    Improvement, flavor: added a F4 Alliances Map, showing alliances between systems.
            Improvement, flavor: having only Snooper news for the player current system.
            Improvement, flavor: fixed Diziet Sma citation
            Improvement, code consistency: the tax script becomes the economy script.
            Improvement, code consistency: removed the TechnicalPrinciples.txt file, as its content is now mainly in the new OXP Performance thread.
            Improvement, dev func: added a beginning of Economy Engine API.
            Improvement, dev func: added a _debug flag in the Engine to start as if you just spent a turn and entered the station; ie, events are processing.
    0.10    Improvement, flavor: added a F4 System history, showing the F7-selected system events history. The displayed text depends on a formatting function definable through API per event.
            Improvement, flavor: the alliance and alliance break events are now displayed in the history.
            Improvement, dev func: the EngineAPI provides the events, and the events by actor.
            Improvement, dev func: the EngineAPI allows now to store a variable in the saved state with the other saved variables of the oxp.
            Improvement, dev func: introduced a Systems API and a History API. The systems API provides the system actors indexed by galaxyID and systemID.
    0.9     Improvement, flavor: the score given to a system by another depend on who they are allied to.
            Improvement, flavor: systems may now break their alliances.
            Bugfix: systems within 7 ly of any of both systems in the alliance are now informed of the alliance.
            Improvement, dev func: introduced an Alliance API.
    0.8     Improvement, flavor: Two systems within 7ly of each other may now form an alliance, depending
            on their relation quality. Currently, each must have a score for the other of at least +0.5.
            Improvement, flavor: some Snooper news are now displayed when an alliance is formed between two systems,
            and the player is within 7ly of one of the allied systems.
            New, oxp dev func: introduced a functional Event system.
    0.7     Improvement, flavor: added a F4 Strategic Map, showing links between systems nearer than 7. The relation between systems is currently based on their government.
    0.6     Improvement, flavor: tax amount depends on the time spent since the last taxation.
            Improvement, speed: major refactor to remove closures, dereferences, JSON (de)serialization special functions.
    0.5     Bugfix manifest.plist for the oxz manager.
    0.4     Put into the oxz manager.
    0.3     New, oxp dev func: delivered DiplomacyEngineAPI for oxp developers, provided a dedicated readme file.
            New, doc: provided a dedicated file DiplomacyRoadmap.txt
            Improvement, code consistency: the Systems and Tax js script now use the API rather than the native engine calls.
            Improvement, speed: optimized the loops in the engine
            Improvement, logic: when starting, the player is docked, so we should process the saved actions.
            Bugfix: solved a bug in the Tax script recurrent tax task after a savefile load.
            Bugfix: the init action using the api wasn't working after a restore from the savefile.
            Cleaning: log cleaning, file cleaning.
    0.2     The actions are made progressively, one every 10 frames when docked, so as not to need more execution time than allowed at the same time, and to avoid slowdowns during the game.
    0.1     First version of the Diplomacy engine. Systems are introduced as a type of "Actor". "SELFTAX" is introduced as an event for systems. Tax level and treasury are displayed on the F7 screen.
    

    Equipment

    This expansion declares no equipment.

    Ships

    This expansion declares no ships.

    Models

    This expansion declares no models.

    Scripts

    Path
    Scripts/DayDiplomacy_Citizenships.js
    "use strict";
    
    this.name = "DayDiplomacy_060_Citizenships";
    this.author = "Loic Coissard, David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.copyright = "(C) 2019 Loic Coissard, David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.licence = "CC-NC-by-SA 4.0";
    this.description = "This script is the citizenships engine.";
    
    /* ************************** Public functions ********************************************************/
    
    /**
     * This formula would put the US citizenship at 57.000.000 USD in 2016 and the french one at 37.000.000 USD.
     * Remember everything is a lot more expensive in space!
     * @param {System} aSystem
     * @returns {number} the price to acquire or renounce this citizenship in credits
     * @lends worldScripts.DayDiplomacy_060_Citizenships.$getCitizenshipPrice
     */
    this.$getCitizenshipPrice = function (aSystem) {
        // productivity is in 10^6 credits units, and population is in 10^8 people units
        // the price is 1000 years of 1-person productivity: prod*10^6 *1000 / (pop *10^8) = 10*prod/pop
        return Math.round(10 * aSystem.productivity / aSystem.population * 10) / 10;
    };
    
    /**
     * This formula would put the US 1-day visa at 15.600 USD in 2016 and the french one at 10.100 USD.
     * Remember everything is a lot more expensive in space!
     * @param {SystemInfo} aSystem
     * @returns {number} the price to acquire or renounce the visa for 1 day in credits
     * @lends worldScripts.DayDiplomacy_060_Citizenships.$getVisaPrice
     */
    this.$getVisaPrice = function (aSystem) {
        // the price is 100 days of 1-person productivity: prod*10^6 / (pop*10^8) /365 * 100 = prod/pop/365
        return Math.round(aSystem.productivity / aSystem.population / 365 * 10) / 10;
    };
    
    /**
     * @param {int} galaxyID
     * @param {int} systemID
     * @returns {boolean} true if the player has the citizenship
     * @lends worldScripts.DayDiplomacy_060_Citizenships.$hasPlayerCitizenship
     */
    this.$hasPlayerCitizenship = function (galaxyID, systemID) {
        var citizenships = this._citizenships;
        var i = citizenships.length;
        while (i--) {
            var planetarySystem = citizenships[i];
            if (planetarySystem.galaxyID === galaxyID && planetarySystem.systemID === systemID) {
                return true;
            }
        }
        return false;
    };
    
    /**
     *
     * @param systemID
     * @return {boolean}
     * @lends worldScripts.DayDiplomacy_060_Citizenships.$hasPlayerVisa
     */
    this.$hasPlayerVisa = function(systemID) {
        this._cleaningVisas();
        return this._visas.hasOwnProperty(systemID);
    };
    
    /**
     * @param {PlanetarySystem[]} citizenships
     * @returns {string} a displayable list of citizenships
     * @lends worldScripts.DayDiplomacy_060_Citizenships.$buildCitizenshipsString
     */
    this.$buildCitizenshipsString = function (citizenships) {
        var result = "";
        var i = citizenships.length;
        while (i--) {
            result += citizenships[i].name + ", ";
        }
        if (result.length) { // We delete the comma at the end of the string
            result = result.substring(0, result.length - 2);
        }
        return result;
    };
    
    this.$buildVisasString = function () {
        this._cleaningVisas(); // Cleaning obsolete visas before displaying visas
        var visas = this._visas;
        var result = "";
        var now = clock.seconds;
        for (var systemID in visas) {
            if (visas.hasOwnProperty(systemID)) {
                var systemInfo = System.infoForSystem(system.info.galaxyID, systemID);
                var remainingTime = visas[systemID] - now;
                var remainingHours = Math.floor(remainingTime / 3600);
                var remainingMinutes = Math.floor((remainingTime - remainingHours * 3600) / 60);
                result += "\n   " + systemInfo.name + ": " + remainingHours + " h " + remainingMinutes + " min" + ","
            }
        }
    
        if (result.length) { // We delete the comma at the end of the string
            result = result.substring(0, result.length - 1);
        } else {
            result = "none";
        }
        return result;
    };
    
    /**
     * Allows the script which name is given as argument to be called through the method $playerCitizenshipsUpdated
     * each time the player citizenships are updated. The script must implement that method: this.$playerCitizenshipsUpdated = function(citizenships) {}
     * @param {string} scriptName the script.name
     * @lends worldScripts.DayDiplomacy_060_Citizenships.$subscribeToPlayerCitizenshipsUpdates
     */
    this.$subscribeToPlayerCitizenshipsUpdates = function (scriptName) {
        (this._playerCitizenshipsUpdatesSubscribers || (this._playerCitizenshipsUpdatesSubscribers = [])).push(scriptName);
    };
    
    /* ************************** OXP private functions *******************************************************/
    
    /**
     * @param {number} price
     * @returns {boolean} true if there was enough money to pay
     * @private
     */
    this._payIfCapable = function(price) {
        if (player.credits >= price) {
            player.credits -= price;
            return true;
        }
        return false;
    };
    
    /**
     * Allows the player to acquire a citizenship
     * @param {int} galaxyID the galaxyID of the citizenship
     * @param {int} systemID the systemID of the citizenship
     * @returns {boolean} true if the player had the money to acquire the citizenship
     * @private
     */
    this._buyCitizenship = function (galaxyID, systemID) {
        if (this._payIfCapable(this.$getCitizenshipPrice(system))) { // FIXME incorrect price if not asking for the current system
            this._citizenships.push({
                "galaxyID": galaxyID,
                "systemID": systemID,
                "name": this._Systems.$retrieveNameFromSystem(galaxyID, systemID)
            });
            return true;
        }
        return false;
    };
    
    /**
     * Allows the player to renounce a citizenship
     * @param {int} galaxyID the galaxyID of the citizenship
     * @param {int} systemID the systemID of the citizenship
     * @returns {boolean} true if the citizenship has been renounced
     * @private
     */
    this._loseCitizenship = function (galaxyID, systemID) {
        if (this._payIfCapable(this.$getCitizenshipPrice(system))) { // FIXME incorrect price if not asking for the current system
            var citizenships = this._citizenships;
            var i = citizenships.length;
            while (i--) {
                var planetarySystem = citizenships[i];
                if (planetarySystem.galaxyID === galaxyID && planetarySystem.systemID === systemID) {
                    if (this._flag.galaxyID === galaxyID && this._flag.systemID === systemID) {
                        delete this._flag.galaxyID;
                        delete this._flag.systemID;
                        delete this._flag.name;
                    }
                    citizenships.splice(i, 1);
                    return true;
                }
            }
        }
        return false;
    };
    
    /**
     *
     * @private
     */
    this._cleaningVisas = function () {
        var now = clock.seconds;
        var visas = this._visas;
        for (var systemID in visas) {
            if (visas.hasOwnProperty(systemID)) {
                if (visas[systemID] <= now) {
                    delete visas[systemID];
                }
            }
        }
    };
    
    /**
     * Displays the citizenship's screen allowing the player to buy and lose citizenships, to display their citizenships
     * and to choose which citizenship is displayed.
     * @param {boolean} notEnoughMoney set to true if a previous command failed because the player had not enough money
     * @private
     */
    this._runCitizenship = function (notEnoughMoney) {
        player.ship.hudHidden || (player.ship.hudHidden = true);
    
        var info = system.info;
        var currentGalaxyID = info.galaxyID;
        var currentSystemID = info.systemID;
        var currentSystemName = this._Systems.$retrieveNameFromSystem(currentGalaxyID, currentSystemID);
        var currentCitizenships = this._citizenships;
        var i = currentCitizenships.length;
        var price = this.$getCitizenshipPrice(system);
        var currentFlag = this._flag;
    
        // Exit choice, and displayed information
        var opts = {
            screenID: "DiplomacyCitizenshipsScreenId",
            title: "Embassy",
            allowInterrupt: true,
            exitScreen: "GUI_SCREEN_INTERFACES",
            choices: {"6_EXIT": "Exit"},
            message: "Your credits: " + (Math.round(player.credits * 10) / 10) + " ₢\n"
                + (notEnoughMoney ? "You had not enough money to do this.\n" : "")
                + "Your flag: " + (currentFlag.name || "stateless")
                + "\nYour passports: " + (i ? this.$buildCitizenshipsString(currentCitizenships) : "none") // FIXME "none" should be in the $build
                + "\nYour visas: " + this.$buildVisasString()
        };
    
        // Choices to acquire or renounce the system citizenship
        var currentChoices = opts.choices;
        if (this.$hasPlayerCitizenship(currentGalaxyID, currentSystemID)) {
            currentChoices["2_LOSE"] = "Renounce your " + currentSystemName + "ian passport for a cost of " + price + " ₢";
        } else {
            currentChoices["1_BUY"] = "Acquire " + currentSystemName + " citizenship for a cost of " + price + " ₢";
        }
    
        // Choosing which among the owned citizenships to display as the flagship
        while (i--) {
            var planetarySystem = currentCitizenships[i];
            // We don't propose the current flagship
            if (!(currentFlag.galaxyID === planetarySystem.galaxyID && currentFlag.systemID === planetarySystem.systemID)) {
                currentChoices["3_DISPLAY_" + planetarySystem.galaxyID + "_" + planetarySystem.systemID] = "Make your ship display your " + planetarySystem.name + " flag";
            }
        }
    
        // Choice to hide the flagship
        if (currentFlag.name) {
            currentChoices["4_HIDEFLAG"] = "Hide your flag";
        }
    
        // Choices to buy a visa for the neighbouring, non-enemy, dictator, communist or corporate systems
        var theseSystems = info.systemsInRange(), j = theseSystems.length;
        if (j) {
            var systemsActorIdsByGalaxyAndSystemId = this._Systems.$getSystemsActorIdsByGalaxyAndSystemId();
            var war = worldScripts.DayDiplomacy_040_WarEngine;
            while (j--) {
                var thatSystemInfo = theseSystems[j];
                var gov = thatSystemInfo.government;
                if (gov === 3 || gov === 4 || gov === 7) { // dictator, communist, corporate
                    var isEnemy = war.$areActorsWarring(
                        // current system ActorId
                        systemsActorIdsByGalaxyAndSystemId[currentGalaxyID][currentSystemID],
                        // other system ActorId
                        systemsActorIdsByGalaxyAndSystemId[currentGalaxyID][thatSystemInfo.systemID]
                    );
                    if (!isEnemy) {
                        if (this.$hasPlayerVisa(thatSystemInfo.systemID)) {
                            currentChoices["5_BUYVISA_" + thatSystemInfo.systemID] =
                                "Extend your visa for " + thatSystemInfo.name + " by 24 hours for a cost of " + this.$getVisaPrice(thatSystemInfo) + " ₢";
                        } else {
                            currentChoices["5_BUYVISA_" + thatSystemInfo.systemID] =
                                "Buy 24 hours of visa for " + thatSystemInfo.name + " for a cost of " + this.$getVisaPrice(thatSystemInfo) + " ₢";
                        }
                    }
                }
            }
        }
    
        mission.runScreen(opts, this._F4InterfaceCallback.bind(this));
    };
    
    this._add1DayVisa = function (systemID) {
        var now = clock.seconds;
        if (this._visas[systemID] > now) {
            this._visas[systemID] += 3600 * 24;
        } else {
            this._visas[systemID] = now + 3600 * 24;
        }
    };
    
    /**
     Calls the necessary functions depending on the player's choice in the F4 interface
     @param {String} choice - predefined values: 1_BUY, 2_LOSE, 3_DISPLAY_{int}, 4_HIDEFLAG, 5_EXIT
     @private
     */
    this._F4InterfaceCallback = function (choice) {
        if (choice === "1_BUY" || choice === "2_LOSE") {
            var info = system.info;
            var success = choice === "1_BUY" ? this._buyCitizenship(info.galaxyID, info.systemID) : this._loseCitizenship(info.galaxyID, info.systemID);
            if (success) {
                this._publishNewsSubscribers();
            }
            this._runCitizenship(!success);
        } else {
            var currentFlag = this._flag;
            if (choice === "4_HIDEFLAG") {
                delete currentFlag.galaxyID;
                delete currentFlag.systemID;
                delete currentFlag.name;
                this._runCitizenship(false);
            } else if (choice !== null && choice.substring(0, 10) === "3_DISPLAY_") {
                var galaxyID = parseInt(choice.substring(10, 11)), systemID = parseInt(choice.substring(12));
                currentFlag.galaxyID = galaxyID;
                currentFlag.systemID = systemID;
                currentFlag.name = this._Systems.$retrieveNameFromSystem(galaxyID, systemID);
                this._runCitizenship(false);
            } else if (choice !== null && choice.substring(0, 10) === "5_BUYVISA_") {
                var systemID = parseInt(choice.substring(10));
                var thatSystemInfo = System.infoForSystem(system.info.galaxyID, systemID);
                var paid = this._payIfCapable(this.$getVisaPrice(thatSystemInfo));
                if (paid) {
                    this._add1DayVisa(systemID);
                }
                this._runCitizenship(!paid);
            }
        } // else EXIT
    };
    
    /**
     * Hides the HUD and displays the F4 interface
     * @private
     */
    this._displayF4Interface = function () {
        this._runCitizenship(false);
    };
    
    /**
     * Displays the citizenship line in the F4 interface
     * @private
     */
    this._initF4Interface = function () {
    
        // No Embassy district in anarchies
        if (system.government === 0) return;
    
        player.ship.dockedStation.setInterface("DiplomacyCitizenships",
            {
                title: "Embassy district",
                category: "Diplomacy",
                summary: "You may see current citizenships",
                callback: this._displayF4Interface.bind(this)
            });
    };
    
    /**
     * Calls the method $playerCitizenshipsUpdated() for each subscribed script with the current citizenships list as argument.
     * @private
     */
    this._publishNewsSubscribers = function () {
        var subscribers = this._playerCitizenshipsUpdatesSubscribers, l = subscribers.length,
            citizenships = this._citizenships;
        while (l--) {
            // noinspection JSUnresolvedFunction This method must be implemented in the subscribed scripts.
            worldScripts[subscribers[l]].$playerCitizenshipsUpdated(citizenships);
        }
    };
    
    /**
     * This function makes sure that the player is considered as a fugitive in an enemy system.
     * @private
     */
    this._checkPlayerStatusInWar = function () {
        var worldScriptsVar = worldScripts;
        var systemInfo = system.info;
        var flag = this._flag;
        var systemsActorIdsByGalaxyAndSystemId = worldScriptsVar.DayDiplomacy_010_Systems.$getSystemsActorIdsByGalaxyAndSystemId();
        var inEnemySystem = flag.systemID && worldScriptsVar.DayDiplomacy_040_WarEngine.$areActorsWarring(
            // current system ActorId
            systemsActorIdsByGalaxyAndSystemId[systemInfo.galaxyID][systemInfo.systemID],
            // current flag ActorId
            systemsActorIdsByGalaxyAndSystemId[flag.galaxyID][flag.systemID]
        );
        var comingFromEnemySystem = this._peacefulSystemsBounty.value !== null;
    
        if (inEnemySystem) {
            if (!comingFromEnemySystem) { // Entering enemy system
                this._peacefulSystemsBounty.value = player.bounty;
            }
            player.bounty = 200;
            player.commsMessage("It seems we are in an enemy system, fights are probable...");
        } else if (comingFromEnemySystem) { // Exiting enemy system
            player.bounty = this._peacefulSystemsBounty.value;
            this._peacefulSystemsBounty.value = null;
        }
    };
    
    /* ************************** Oolite events ***************************************************************/
    
    // noinspection JSUnusedLocalSymbols Called by Oolite itself
    /**
     * Displays the citizenship's line in the F4 interface when the player is docked.
     * @param {Station} station an Oolite object where the ship is docked. We don't use it.
     */
    this.shipDockedWithStation = function (station) {
        this._initF4Interface();
    };
    
    // noinspection JSUnusedGlobalSymbols Called by Oolite itself
    /**
     * We stop hiding the HUD when we exit our citizenship interface
     */
    this.missionScreenEnded = function () {
        player.ship.hudHidden = false;
    };
    
    /**
     *
     * @private
     */
    this._setStationsVisaRequirements = function () {
        var gov = system.government;
        if (gov === 3 || gov === 4 || gov === 7) {
            var checker = function (ship) {
                if (!(ship instanceof PlayerShip)) { // Only for the player ship
                    return true;
                }
                if (worldScripts.DayDiplomacy_060_Citizenships._citizenships.length) {
                    // No problem if the player has a citizenship
                    return true;
                }
                if (worldScripts.DayDiplomacy_060_Citizenships.$hasPlayerVisa(system.info.systemID)) {
                    // No problem if the player has a visa
                    return true;
                }
                this.commsMessage("WARNING - This station is accessible only to citizens and visa holders, Commander.", player.ship);
                return false;
            };
            var ss = system.stations, z = ss.length;
            while (z--) {
                var station = ss[z];
                var al = station.allegiance;
                if (al === "galcop" || al === "neutral") {
                    var ses = station.subEntities, y = ses.length;
                    while (y--) {
                        var se = ses[y];
                        if (se.isDock) {
                            se.script.acceptDockingRequestFrom = checker.bind(station);
                            break;
                        }
                    }
                }
            }
        }
    };
    
    // noinspection JSUnusedGlobalSymbols Called by Oolite itself
    this.shipExitedWitchspace = function () {
        this._checkPlayerStatusInWar();
        this._setStationsVisaRequirements();
    };
    
    // noinspection JSUnusedGlobalSymbols Called by Oolite itself
    /**
     *
     * @param {Station}station - the station from which the ship is launched
     */
    this.shipLaunchedFromStation = function (station) {
        this._checkPlayerStatusInWar();
    };
    
    /**
     * Loads the player citizenship from the save file, loads the scripts which are subscribed to the
     * playerCitizenshipsUpdates, and initialises the F4 interface.
     * @private
     */
    this._startUp = function () {
        worldScripts.XenonUI && worldScripts.XenonUI.$addMissionScreenException("DiplomacyCitizenshipsScreenId");
        worldScripts.XenonReduxUI && worldScripts.XenonReduxUI.$addMissionScreenException("DiplomacyCitizenshipsScreenId");
    
        this._Systems = worldScripts.DayDiplomacy_010_Systems;
        var engine = worldScripts.DayDiplomacy_000_Engine;
    
        // {String[]} _playerCitizenshipsUpdatesSubscribers - an array containing the names of the scripts which have subscribed to receive notifications when the player citizenships have changed.
        this._playerCitizenshipsUpdatesSubscribers || (this._playerCitizenshipsUpdatesSubscribers = []);
    
        /**
         * The flag of the player ship, saved. None by default.
         * @type {PlanetarySystem}
         * @private
         */
        this._flag = engine.$initAndReturnSavedData("flag", {});
    
        /**
         * The value is only set when the player is in an enemy system; else it is 'null'.
         * When beginning to use the Diplomacy Oxp, the player is not in an enemy system.
         * @type {Object}
         * @param {int} value
         * @private
         */
        this._peacefulSystemsBounty = engine.$initAndReturnSavedData("peacefulSystemsBounty", {value: null});
    
        /**
         * The object in which the player citizenships are saved. That object is saved into the saveGame file.
         * @type {PlanetarySystem[]}
         */
        this._citizenships = engine.$initAndReturnSavedData("citizenships", []);
    
        var visasDefaultValue = {};
        var gov = system.government;
        if (gov == 3 || gov == 4 || gov == 7) {
            visasDefaultValue[system.info.systemID] = clock.seconds + 24*3600;
        }
        /**
         * The object in which the player visas are saved. That object is saved into the saveGame file. The first int is the systemID, the second the end date of the visa in seconds.
         * The first time the Diplomacy OXP is used, if a visa is needed in the current system, we give the player a 1-day visa.
         * @type {Object<int,int>}
         */
        this._visas = engine.$initAndReturnSavedDataAndInitialize("visas", visasDefaultValue, function() {
            worldScripts.DayDiplomacy_015_GNN.$publishNews(
                 "Serious news! To ensure their security, corporate systems have agreed to require a visa for all undocumented travelers."
                +" Are you up-to-date on your citizenship papers, Commanders?\n"
                +"\nIn a shocking political twist, dictatorships and communist systems have happily adopted the same law."
                +" The President of Ceesxe, the Preeminent Corporate Planet, told us: \"We are appalled that our well-meant initiatives and technologies are copied by rogue governments.\"\n"
                +"\nTo avoid an economic freeze due to the newly introduced laws, all pilots currently in a system requiring a visa will be provided a 1-day visa free of charge."
                +" The Ceesxe President confided in us: \"The first shot is always free. That's only good business, after all.\"\n"
                +" What he meant by this, the truth is, we don't know.");
        });
    
        this._setStationsVisaRequirements();
        this._initF4Interface();
        delete this._startUp; // No need to startup twice
    };
    
    this.startUp = function () {
        worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
        delete this.startUp; // No need to startup twice
    };
    Scripts/DayDiplomacy_Economy.js
    "use strict";
    this.name = "DayDiplomacy_030_EconomyEngine";
    this.author = "David (Day) Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.copyright = "(C) 2017 David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.licence = "CC-NC-by-SA 4.0";
    this.description = "This script is the economy engine of the Diplomacy OXP. It makes systems tax themselves." +
        " The idea here is to use the system GDP, called 'productivity' in Oolite," +
        " and a 'tax level' on GDP which adds to the system's government's 'treasury'.";
    
    /* Credit monetary policy is such that the total number of credits in the Ooniverse is always the same.
       This is needed to avoid game imbalances leading to exploding monetary mass, or monetary mass converging to zero.
       This implies that the money cannot be produced, destroyed or counterfeited, which is a mystery in itself. */
    /**
     *
     * @type {{"0": number, "1": number, "2": number, "3": number, "4": number, "5": number, "6": number, "7": number}}
     * @lends worldScripts.DayDiplomacy_030_EconomyEngine.$GOVERNMENT_DEFAULT_TAX_LEVEL;
     */
    this.$GOVERNMENT_DEFAULT_TAX_LEVEL = {
        "0": 0.0, // Anarchy => no tax
        "1": 0.3, // Feudal => not everybody is taxed
        "2": 0.1, // Multi-government => tax avoiding is rampant
        "3": 0.2, // Dictator => feeble taxes
        "4": 0.5, // Communist => major taxes, but those systems are not crumbling
        "5": 0.1, // Confederacy => tax avoiding is rampant
        "6": 0.5, // Democracy => major taxes, but those systems are not crumbling
        "7": 0.1  // Corporate => tax avoiding is rampant
    };
    
    this.$moveProductivityInPercentage = function(fromSystemActor, percentage) {
        // FIXME 0.15 TODO
    };
    this.$moveProductivityInCredits = function(fromSystemActor, creditsNb) {
        // FIXME 0.15 TODO
    };
    this.$moveProductivityToNeighborsInPercentage = function(fromSystemActor, percentage) {
        // FIXME 0.15 TODO
    };
    this.$moveProductivityToNeighborsInCredits = function(fromSystemActor, creditsNb) {
        // FIXME 0.15 TODO
    };
    this.$moveProductivityToNeighborsDependingOnDistanceInPercentage = function(fromSystemActor, percentage) {
        // FIXME 0.15 TODO
    };
    this.$moveProductivityToNeighborsDependingOnDistanceInCredits = function(fromSystemActor, creditsNb) {
        // FIXME 0.15 TODO
    };
    
    /* ************************** Oolite events ***************************************************************/
    
    this._startUp = function () {
        var engine = worldScripts.DayDiplomacy_000_Engine;
    
        // Not initializing if already done.
        if (engine.$getEventTypes().indexOf("SELFTAX") !== -1) {
            return;
        }
    
        // This eventType means a system government taxes the system GDP (economic output) to fund its treasury.
        engine.$addEventType("SELFTAX", 0);
    
        /**
         * @function
         * @param {Actor}aSystem
         */
        var diplomacyTaxInitAction = function diplomacyTaxInitAction(aSystem) {
            var that = diplomacyTaxInitAction;
            var engine = that.engine || (that.engine = worldScripts.DayDiplomacy_000_Engine);
            var taxLevel = that.taxLevel || (that.taxLevel = worldScripts.DayDiplomacy_030_EconomyEngine.$GOVERNMENT_DEFAULT_TAX_LEVEL);
            var sys = that.sys || (that.sys = System);
            var cloc = that.cloc || (that.cloc = clock);
            var ourSystemInOolite = sys.infoForSystem(aSystem.galaxyNb, aSystem.systemId);
            var government = ourSystemInOolite.government;
            engine.$setField(aSystem, "government", government);
            // Necessary for alliancesAndWars. Bad location but avoids other system initialization :/
            // FIXME 0.perfectstyle fields should be inited in the systems part. Make it all fields?
            // FIXME 0.f move treasury and tax level to a F4 Diplomacy system information including the history.
            // Or use the new description system?
            // FIXME Should the Actor aSystem be typed ActorSystem? mwofff
            engine.$setField(aSystem, "name", ourSystemInOolite.name);
            engine.$setField(aSystem, "taxLevel", taxLevel[government]);
            engine.$setField(aSystem, "treasury", 0); // Everybody begins with treasury = 0.
            engine.$setField(aSystem, "lastTaxDate", cloc.seconds);
        };
        var functionId = engine.$getNewFunctionId();
        engine.$setFunction(functionId, diplomacyTaxInitAction);
        engine.$setInitAction(engine.$buildAction(engine.$getNewActionId(), "SELFTAX", "SYSTEM", functionId));
    
        // Recurrent tax.
        var diplomacyTaxRecurrentAction = function diplomacyTaxRecurrentAction(aSystem) {
            var that = diplomacyTaxRecurrentAction;
            var engine = that.engine || (that.engine = worldScripts.DayDiplomacy_000_Engine);
            var sys = that.sys || (that.sys = System);
            var cloc = that.cloc || (that.cloc = clock);
            var ourSystemInOolite = sys.infoForSystem(aSystem.galaxyNb, aSystem.systemId);
            var now = cloc.seconds;
            engine.$setField(aSystem, "treasury", aSystem.treasury + Math.floor(ourSystemInOolite.productivity * (now - parseInt(aSystem.lastTaxDate)) / 31.5576 * aSystem.taxLevel));
            engine.$setField(aSystem, "lastTaxDate", now);
        };
        var fid =  engine.$getNewFunctionId();
        engine.$setFunction(fid, diplomacyTaxRecurrentAction);
        engine.$setRecurrentAction(engine.$buildAction(engine.$getNewActionId(), "SELFTAX", "SYSTEM", fid));
        delete this._startUp; // No need to startup twice
    };
    
    this.guiScreenChanged = function(to, from) {
        if (to == "GUI_SCREEN_SYSTEM_DATA") {
            var targetSystem = worldScripts.DayDiplomacy_010_Systems.$retrieveActorFromSystem(system.info.galaxyID, player.ship.targetSystem);
            mission.addMessageText("Tax level: " + targetSystem.taxLevel*100 + "% Treasury: " + Math.floor(targetSystem.treasury/1000000) + " M€");
        }
    };
    
    this.startUp = function() {
        worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
        delete this.startUp; // No need to startup twice
    };
    Scripts/DayDiplomacy_Engine.js
    "use strict";
    this.name = "DayDiplomacy_000_Engine";
    this.author = "David (Day) Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.copyright = "(C) 2017 David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.licence = "CC-NC-by-SA 4.0";
    this.description = "This script is the engine of the Diplomacy OXP.";
    
    /* ************************** Closures ********************************************************************/
    
    this._missionVariables = missionVariables;
    this._clock = clock;
    this._JSON = JSON;
    
    /* ************************** Engine **********************************************************************/
    
    // FIXME use the debugger to set _debug to true
    this._debug = false;
    
    /**
     * Loads state from the saved game
     * @param toBeModifiedState
     * @param sourceState
     * @private
     */
    this._loadState = function (toBeModifiedState, sourceState) {
        for (var id in sourceState) {
            if (sourceState.hasOwnProperty(id)) { // Avoiding prototypes' fields
                toBeModifiedState[id] = sourceState[id];
            }
        }
    };
    
    /**
     *
     * @return {{shortStack: Array, eventsToPublish: {}, initActions: {}, eventMaxId: number, actorTypes: Array, initActionsByType: {}, recurrentActionsByType: {}, actorsEvents: {}, functionMaxId: number, eventTypes: Array, actorMaxId: number, actors: {}, recurrentActions: {}, responseMaxId: number, actionMaxId: number, responsesByType: {}, actorsByType: {}, eventsToPublishNextTurn: {}, responses: {}, currentActorType: string, currentEventType: string, events: {}}}
     * @private
     * @lends worldScripts.DayDiplomacy_000_Engine._getInitState
     */
    this._getInitState = function () {
      return {
    
          /** @type {Object.<ActorId,Actor>}*/
          actors: {},
    
          /** @type {Object.<ActionId,Action>}*/
          initActions: {},
    
          /** @type {Object.<ActionId,Action>}*/
          recurrentActions: {},
    
          /** @type {Object.<EventId,DiplomacyEvent>}*/
          events: {},
    
          /** @type {Object.<ResponseId,DiplomacyResponse>}*/
          responses: {},
    
          /** @type {Object.<ActorType,ActorId[]>}*/
          actorsByType: {},
    
          /** @type {Object.<ActorType,ActionId[]>}*/
          initActionsByType: {},
    
          /** @type {Object.<EventType,Object.<ActorType,ActionId[]>>}*/
          recurrentActionsByType: {},
    
          /** @type {Object.<EventType,Object.<ActorType,ResponseId[]>>}*/
          responsesByType: {},
    
          /** @type {int} */
          actorMaxId: 1,
    
          /** Useful to remove recurrentActions and initActions.
           *  @type {int} */
          actionMaxId: 1,
    
          /** @type {int} */
          eventMaxId: 1,
    
          /** @type {int} */
          responseMaxId: 1,
    
          /** @type {int} */
          functionMaxId: 1,
    
          /** @type {EventType[]} */
          eventTypes: [],
    
          /** @type {ActorType[]} */
          actorTypes: [],
    
          /** @type {Object.<ActorId,EventId[]>}*/
          actorsEvents: {},
    
          /** @type {Object.<EventType,EventId[]>}*/
          eventsToPublish: {},
    
          /** @type {Object.<EventType,EventId[]>}*/
          eventsToPublishNextTurn: {},
    
          /** @type EventType */
          currentEventType: "",
    
          /** @type ActorType */
          currentActorType: "",
    
          /** @type {Object[]} */
          shortStack: []
      };
    };
    
    // FIXME should _State be of a defined state? That would be what would make stable the savefile... ?
    /**
     * @type {{shortStack: Array, eventsToPublish: {}, initActions: {}, eventMaxId: number, actorTypes: Array, initActionsByType: {}, recurrentActionsByType: {}, actorsEvents: {}, functionMaxId: number, eventTypes: Array, actorMaxId: number, actors: {}, recurrentActions: {}, responseMaxId: number, actionMaxId: number, responsesByType: {}, actorsByType: {}, eventsToPublishNextTurn: {}, responses: {}, currentActorType: string, currentEventType: string, events: {}}}
     * @private
     * @lends worldScripts.DayDiplomacy_000_Engine._State
     */
    this._State = this._getInitState();
    
    /**
     * @type {Object.<FunctionId,function>}
     * @private
     */
    this._Functions = {};
    
    /**
     * @return {ActorId}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getNewActorId
     */
    this.$getNewActorId = function () {
        return "DAr_" + this._State.actorMaxId++;
    };
    
    /**
     * @return {ResponseId}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getNewResponseId
     */
    this.$getNewResponseId = function () {
        return "DR_" + this._State.responseMaxId++;
    };
    
    /**
     * @return {ActionId}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getNewActionId
     */
    this.$getNewActionId = function () {
        return "DAn_" + this._State.actionMaxId++;
    };
    
    /**
     * @return {FunctionId}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getNewFunctionId
     */
    this.$getNewFunctionId = function () {
        return "DFn_" + this._State.functionMaxId++;
    };
    
    /**
     * @return {EventId}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getNewEventId
     */
    this.$getNewEventId = function () {
        return "DEt_" + this._State.eventMaxId++;
    };
    
    /**
     * An action, whether it is init or recurrent isn't put into the History. Only Events are.
     * @param {ActionId}id -
     * @param {EventType}eventType - is used to order the actions and events execution. For a same eventType, Actions are executed before Events.
     * @param {ActorType}actorType - Only actors of the type will execute the action.
     * @param {FunctionId} actionFunctionId - the id of a function which must take one and only one argument: the actor which will "act".
     * @return {Action}
     * @lends worldScripts.DayDiplomacy_000_Engine.$buildAction
     */
    this.$buildAction = function (id, eventType, actorType, actionFunctionId) {
        /** @type {Action} */
        return {id: id, eventType: eventType, actorType: actorType, actionFunctionId: actionFunctionId};
    };
    
    /**
     * To copy before modifying
     * @return {EventType[]}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getEventTypes
     */
    this.$getEventTypes = function () {
        return this._State.eventTypes;
    };
    
    /**
     * @return {Object<FunctionId, function>}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getFunctions
     */
    this.$getFunctions = function () {
        return this._Functions;
    };
    
    /**
     * @return {Object<EventId,DiplomacyEvent>}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getEvents
     */
    this.$getEvents = function () {
        return this._State.events;
    };
    
    /**
     * @param {ActorId} actorId
     * @return {EventId[]}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getActorEvents
     */
    this.$getActorEvents = function (actorId) {
        return this._State.actorsEvents[actorId] || [];
    };
    
    /**
     * Make sure you don't modify that or its content. Copy it before if you need to modify it.
     * @return {ActorType[]} The ActorType list
     * @lends worldScripts.DayDiplomacy_000_Engine.$getActorTypes
     */
    this.$getActorTypes = function () {
        return this._State.actorTypes;
    };
    
    /**
     * @param {ActorType} actorType
     * @returns {ActorId[]} the list of actorId having the type given as parameter
     * @lends worldScripts.DayDiplomacy_000_Engine.$getActorsIdByType
     */
    this.$getActorsIdByType = function (actorType) {
        return this._State.actorsByType[actorType];
    };
    
    /**
     * @name $getActors
     * @returns {Object.<ActorId,Actor>} - an object with {@link ActorId} as keys and as value the corresponding {@link Actor}
     * @lends worldScripts.DayDiplomacy_000_Engine.$getActors
     */
    this.$getActors = function () {
        return this._State.actors;
    };
    
    /**
     * A planetary system or an alliance, or whatever you wish :)
     * An actor is {id:id, actorType:actorType, responsesIdByEventType:{eventType:[responseIds]}, observers:{actorType:[actorIds]}}
     * @param {ActorType} actorType
     * @param {ActorId} id
     * @return {Actor}
     * @lends worldScripts.DayDiplomacy_000_Engine.$buildActor
     */
    this.$buildActor = function (actorType, id) {
        /** @type {Actor} */
        return {id: id, actorType: actorType, responsesIdByEventType: {}, observers: {}};
    };
    
    /**
     * @param {EventId} id
     * @param {EventType}eventType
     * @param {ActorId} actorId
     * @param {Object[]} args  Have to be compatible with our implementation of JSON stringify/parse. Those are the information/arguments which will be given to the response function.
     * @return {DiplomacyEvent}
     */
    this.$buildEvent = function (id, eventType, actorId, args) {
        return {id: id, eventType: eventType, actorId: actorId, args: args, date:0};
    };
    
    /**
     *
     * @param {Actor} anActor
     * @param {ActorType} observersActorType
     * @returns {ActorId[]} the list of the actorId's of the observers of the given actor, which are of the given type
     * @lends worldScripts.DayDiplomacy_000_Engine.$getObservers
     */
    this.$getObservers = function (anActor, observersActorType) {
        return anActor.observers[observersActorType];
    };
    
    /**
     * @param {Actor} anActor
     * @param {Action} anAction
     */
    this.$letActorExecuteAction = function (anActor, anAction) {
        this._Functions[anAction.actionFunctionId](anActor);
    };
    
    /**
     *
     * @param {ActorId} actorId
     * @param {EventType}anEventType
     * @param {Object[]} someArgs
     * @lends worldScripts.DayDiplomacy_000_Engine.$makeActorEventKnownToUniverse
     */
    this.$makeActorEventKnownToUniverse = function (actorId, anEventType, someArgs) {
        this._record(this.$buildEvent(this.$getNewEventId(), anEventType, actorId, someArgs));
    };
    
    // this.$Actor.prototype.actNextTurn = function (anEventType, someArgs) {
    //     this.recordForNextTurn({id:eventId, eventType: anEventType, actorId: this._State.id, args: someArgs});
    // };
    
    /**
     * @param {Actor} anActor
     * @lends worldScripts.DayDiplomacy_000_Engine.$addActor
     */
    this.$addActor = function (anActor) {
        var state = this._State, responsesByType = state.responsesByType, initActions = state.initActions,
            initActionsByType = state.initActionsByType, eventTypes = state.eventTypes, actorType = anActor.actorType,
            id = anActor.id, responses = state.responses;
    
        // We add the actor to the actors maps.
        state.actorsByType[actorType].push(id);
        state.actors[id] = anActor;
    
        // We complete the existing actor responses with the engine responses.
        var y = eventTypes.length;
        while (y--) {
            var eventType = eventTypes[y];
            var eventTypeResponses = responsesByType[eventType] || (responsesByType[eventType] = {});
            var responsesIdsToAdd = eventTypeResponses[actorType] || (eventTypeResponses[actorType] = []);
            var x = responsesIdsToAdd.length;
            while (x--) {
                this.$addResponseToActor(responses[responsesIdsToAdd[x]], anActor);
            }
        }
    
        // We execute the initActions on the actor
        var initActionsToExecute = initActionsByType[actorType];
        var z = initActionsToExecute.length;
        while (z--) {
            this.$letActorExecuteAction(anActor, initActions[initActionsToExecute[z]]);
        }
    };
    
    // Consistent with history usage.
    // this.disableActor = function (anActor) {
    //     var engineState = this._State, actorState = anActor._State, arr = engineState.actorsByType[actorState.actorType];
    //     arr.splice(arr.indexOf(actorState.id), 1);
    //     delete engineState.actors[actorState.id];
    // };
    
    /**
     * @param {Actor} anActor
     * @param {ActorType} thatObserverType
     * @param {ActorId} thatObserverId
     * @lends worldScripts.DayDiplomacy_000_Engine.$addObserverToActor
     */
    this.$addObserverToActor = function (anActor, thatObserverType, thatObserverId) {
        var observers = anActor.observers;
        (observers[thatObserverType] || (observers[thatObserverType] = [])).push(thatObserverId);
    };
    
    /**
     * @param {DiplomacyResponse}aResponse
     * @param {Actor}anActor
     */
    this.$addResponseToActor = function (aResponse, anActor) {
        var responsesIdByEventType = anActor.responsesIdByEventType;
        (responsesIdByEventType[aResponse.eventType] || (responsesIdByEventType[aResponse.eventType] = [])).push(aResponse.id);
    };
    
    // this.$Actor.prototype.removeResponse = function (aResponse) {
    //     var arr = this._State.responses[aResponse.eventType];
    //     arr.splice(arr.indexOf(aResponse.id), 1);
    // };
    
    /**
     * @param {FunctionId} anId
     * @param {function} aFunction
     * @lends worldScripts.DayDiplomacy_000_Engine.$setFunction
     */
    this.$setFunction = function (anId, aFunction) {
        this._Functions[anId] = aFunction;
    };
    
    /**
     * @param {Object} anObject
     * @param {string} fieldName
     * @param {Object} fieldValue
     * @lends worldScripts.DayDiplomacy_000_Engine.$setField
     */
    this.$setField = function (anObject, fieldName, fieldValue) {
        if (anObject.hasOwnProperty("_State")) { // We put the field into _State
            anObject._State[fieldName] = fieldValue;
        } else {
            anObject[fieldName] = fieldValue;
        }
    };
    
    /**
     * @param {Action} anInitAction
     * @lends worldScripts.DayDiplomacy_000_Engine.$setInitAction
     */
    this.$setInitAction = function (anInitAction) {
        var initActions = this._State.initActions, initActionsByType = this._State.initActionsByType,
            initActionActorType = anInitAction.actorType;
    
        // We add the initAction to initActions and initActionsByType
        initActions[anInitAction.id] = anInitAction;
        (initActionsByType[initActionActorType] || (initActionsByType[initActionActorType] = [])).push(anInitAction.id);
    
        // We execute the action on the existing actors in an ordered fashion.
        this.$executeAction(anInitAction);
    };
    
    /**
     * @param {Action} anAction
     * @lends worldScripts.DayDiplomacy_000_Engine.$setRecurrentAction
     */
    this.$setRecurrentAction = function (anAction) {
        // We add the action to recurrentActions
        var recurrentActionsByType = this._State.recurrentActionsByType, recurrentActions = this._State.recurrentActions,
            actionEventType = anAction.eventType, actionActorType = anAction.actorType;
        var eventTypeActions = recurrentActionsByType[actionEventType] || (recurrentActionsByType[actionEventType] = {});
        (eventTypeActions[actionActorType] || (eventTypeActions[actionActorType] = [])).push(anAction.id);
        recurrentActions[anAction.id] = anAction;
    };
    
    /**
     * FIXME
     * @param {string} name
     * @param {*} defaultValue
     * @returns {*}
     * @lends worldScripts.DayDiplomacy_000_Engine.$initAndReturnSavedData
     */
    this.$initAndReturnSavedData = function (name, defaultValue) {
        return this._State[name] || (this._State[name] = defaultValue);
    };
    
    /**
     * FIXME
     * @param {string} name
     * @param {*} defaultValue
     * @returns {*}
     * @lends worldScripts.DayDiplomacy_000_Engine.$initAndReturnSavedDataAndInitialize
     */
    this.$initAndReturnSavedDataAndInitialize = function (name, defaultValue, initFunction) {
        if (this._State[name] === undefined) {
            initFunction();
        }
        return this._State[name] || (this._State[name] = defaultValue);
    };
    
    this.$executeAction = function (anAction) {
        var ourActorIds = this._State.actorsByType[anAction.actorType], actors = this._State.actors;
        var z = ourActorIds.length;
        while (z--) {
            this.$letActorExecuteAction(actors[ourActorIds[z]], anAction);
        }
    };
    
    /**
     * A Response contains a behaviour to be executed when a certain event happens.
     * The responseFunction must take as first argument the responding actor,
     * 2nd argument the eventActor, and may take as many additional arguments as you wish.
     * The actorType is the type of the responding actors.
     *
     * @param {ResponseId}id
     * @param {EventType}eventType
     * @param {ActorType}actorType
     * @param {FunctionId}responseFunctionId
     * @return {DiplomacyResponse}
     * @lends worldScripts.DayDiplomacy_000_Engine.$buildResponse
     */
    this.$buildResponse = function (id, eventType, actorType, responseFunctionId) {
        return {id: id, eventType: eventType, actorType: actorType, responseFunctionId: responseFunctionId};
    };
    
    /**
     * @param {DiplomacyResponse} aResponse
     * @lends worldScripts.DayDiplomacy_000_Engine.$setResponse
     */
    this.$setResponse = function (aResponse) {
        var state = this._State, actors = state.actors;
        // We add the response to responses
        state.responsesByType[aResponse.eventType][aResponse.actorType].push(aResponse.id);
        state.responses[aResponse.id] = aResponse;
    
        // We add the response to the existing actors in an ordered fashion.
        var ourActorIds = state.actorsByType[aResponse.actorType];
        var z = ourActorIds.length;
        while (z--) {
            this.$addResponseToActor(aResponse, actors[ourActorIds[z]]);
        }
    };
    
    // this.unsetInitAction = function (anInitAction) { // This doesn't impact History.
    //     delete this._State.initActions[anInitAction.actorType][anInitAction.id];
    // };
    // this.unsetRecurrentAction = function (anAction) { // This doesn't impact History.
    //     var engineState = this._State, arr = engineState.recurrentActionsByType[anAction.eventType][anAction.actorType];
    //     arr.splice(arr.indexOf(anAction.id), 1);
    //     delete engineState.recurrentActions[anAction.id];
    // };
    // this.unsetResponse = function (aResponse) { // This doesn't impact History.
    //     var state = this._State, actors = state.actors;
    //     delete state.responses[aResponse.eventType][aResponse.actorType][aResponse.id];
    //     var ourActorIds = state.actorsByType[aResponse.actorType];
    //     var z = ourActorIds.length;
    //     while (z--) {
    //         actors[ourActorIds[z]].removeResponse(aResponse);
    //     }
    // };
    
    /**
     * We don't allow to remove eventTypes as it would make the history inconsistent.
     * @param {EventType} name - name of the new eventType, it must be different from already existing names.
     * @param {int} position - the position in the ordered list of existing types
     * @lends worldScripts.DayDiplomacy_000_Engine.$addEventType
     */
    this.$addEventType = function (name, position) {
        var state = this._State;
        state.eventTypes.splice(position, 0, name);
        if (this._debug) log("DiplomacyEngine", "Added " + name + " event type in position " + position + ". Current event types: " + state.eventTypes);
        var ourResponses = (state.responsesByType[name] = {});
        var ourRecurrentActions = (state.recurrentActionsByType[name] = {});
        var actorTypes = state.actorTypes;
        var z = actorTypes.length;
        while (z--) {
            var ourActorType = actorTypes[z];
            ourResponses[ourActorType] = [];
            ourRecurrentActions[ourActorType] = [];
        }
        state.eventsToPublish[name] = [];
        state.eventsToPublishNextTurn[name] = [];
    };
    
    /**
     * @param {ActorType} name
     * @param {int} position
     * @lends worldScripts.DayDiplomacy_000_Engine.$addActorType
     */
    this.$addActorType = function (name, position) {
        var state = this._State;
        state.actorTypes.splice(position, 0, name);
    
        state.actorsByType[name] = [];
        state.initActionsByType[name] = [];
    
        var responses = state.responsesByType;
        var recurrentActions = state.recurrentActionsByType;
        var z = state.eventTypes.length;
        while (z--) {
            var eventType = state.eventTypes[z];
            responses[eventType][name] = [];
            recurrentActions[eventType][name] = [];
        }
    };
    
    /**
     * Gives the next state. Returns empty string if array is finished.
     * @param {EventType | ActorType} type
     * @param currentState FIXME document what's a state
     */
    this._nextState = function (type, currentState) {
        var arr = this._State[type];
        var newIndex = arr.indexOf(currentState) + 1;
        return newIndex === arr.length ? "" : arr[newIndex];
    };
    
    /**
     * @param {DiplomacyEvent} anEvent
     * @private
     */
    this._record = function (anEvent) {
        var eventsToPublish = this._State.eventsToPublish, eventType = anEvent.eventType,
            eventId = anEvent.id, eventActorId = anEvent.actorId, actorsEvents = this._State.actorsEvents;
    
        // Stamping the event
        anEvent.date = this._clock.seconds;
    
        // Recording the history. This is ordered by insertion, so ordered by date.
        (actorsEvents[eventActorId] || (actorsEvents[eventActorId] = [])).push(eventId);
    
        this._State.events[eventId] = anEvent;
    
        // Publishing the reality
        (eventsToPublish[eventType] || (eventsToPublish[eventType] = [])).push(anEvent);
    };
    // this.recordForNextTurn = function (anEvent) {
    //     var eventsToPublishNextTurn = this._State.eventsToPublishNextTurn, eventType = anEvent.eventType;
    //     (eventsToPublishNextTurn[eventType] || (eventsToPublishNextTurn[eventType] = [])).push(anEvent);
    // };
    
    this._gatherEventsToPublish = function () {
        var state = this._State;
        var currentEventType = state.currentEventType, eventsToPublishNextTurn = state.eventsToPublishNextTurn;
        var ourEvents = (eventsToPublishNextTurn[currentEventType] || (eventsToPublishNextTurn[currentEventType] = []));
        // FIXME 0.perfectperf, when we use Events: does the length change? check through logs
        // FIXME 0.perfectperf, when we use Events: 'while' could be cut into frames, but it would slow the history. Check the time spent through logs
        while (ourEvents.length) {
            var z = ourEvents.length;
            while (z--) {
                this._record(ourEvents.shift());
            }
        }
    
        // We go to next eventType
        var newEventType = this._nextState("eventTypes", currentEventType);
        if (this._debug && newEventType !== "") log(this.name, "Gathering events to publish for state: " + newEventType);
    
        state.currentEventType = newEventType || state.eventTypes[0];
        state.currentActorType = state.actorTypes[0];
        return !newEventType;
    };
    
    /** @return {boolean} - true when everything is finished, else false. */
    this._populateStack = function () {
        var state = this._State, currentEventType = state.currentEventType, currentActorType = state.currentActorType,
            firstActorType = state.actorTypes[0];
        if (!state.recurrentActionsIsDoneForCurrentEventType) {
            if (this._debug) log(this.name, "Putting recurrent actions onto stack for event type: " + currentEventType + " and actor type: " + state.currentActorType);
            this._putRecurrentActionsOntoStack(currentEventType, currentActorType);
    
            // We go to next actorType
            var newActorType = this._nextState("actorTypes", currentActorType);
            state.currentActorType = newActorType || firstActorType;
            state.recurrentActionsIsDoneForCurrentEventType = !newActorType;
            return false; // No need to use too much time.
        }
    
        var ourEvents = state.eventsToPublish[currentEventType];
        if (ourEvents.length) {
            this._putEventOntoStack(ourEvents[0], currentActorType);
    
            // We go to next actorType
            var newActorType2 = this._nextState("actorTypes", currentActorType);
            state.currentActorType = newActorType2 || firstActorType;
            // When the event is processed, we remove it from the array.
            newActorType2 || ourEvents.shift();
            return false; // No need to use too much time.
        }
    
        // We go to next eventType
        state.currentActorType = firstActorType;
        state.recurrentActionsIsDoneForCurrentEventType = false;
        var newEventType = this._nextState("eventTypes", currentEventType);
        if (this._debug && newEventType !== "") log(this.name, "Gathering events to publish for state: " + newEventType);
        // We may have finished: no more eventType, no more actorType, no more recurrentAction, no more event to respond to.
        state.currentEventType = newEventType || state.eventTypes[0];
        return !newEventType;
    };
    this._putRecurrentActionsOntoStack = function (currentEventType, currentActorType) {
        var state = this._State, actions = state.recurrentActionsByType[currentEventType][currentActorType],
            actorIds = state.actorsByType[currentActorType], shortStack = state.shortStack;
        var y = actions.length;
        while (y--) {
            var id = actions[y];
            var z = actorIds.length;
            while (z--) {
                shortStack.push({type: "action", actorId: actorIds[z], recurrentActionId: id});
            }
        }
    };
    this._putEventOntoStack = function (thatEvent, currentActorType) {
        // FIXME perfectstyle we should use the eventId as arg rather than the whole event
        var eventActorId = thatEvent.actorId, eventEventType = thatEvent.eventType, eventArgs = thatEvent.args,
            state = this._State, actors = state.actors, eventActor = actors[eventActorId],
            observers = eventActor.observers[currentActorType], z = observers.length, shortStack = state.shortStack,
            responses = state.responses;
        while (z--) {
            var observer = actors[observers[z]];
            // First argument: observer, 2nd arg: eventActor, other args: other args
            var someArgs = [observer, eventActor].concat(eventArgs);
            var responseIds = observer.responsesIdByEventType[eventEventType];
            if (!responseIds) {
                continue; // No responses to process for this observer
            }
            var y = responseIds.length;
            while (y--) {
                shortStack.push({
                    type: "response",
                    responseFunctionId: responses[responseIds[y]].responseFunctionId,
                    args: someArgs
                });
            }
        }
    };
    
    /** @return {boolean} true if finished (empty stack), false otherwise. */
    this._executeStack = function () {
        var s = this._State;
        var action = s.shortStack.shift();
        if (action === undefined) {
            return true;
        }
        // FIXME 0.perfectperf measure execution time?
        if (action.type == "action") {
            this._Functions[s.recurrentActions[action.recurrentActionId].actionFunctionId](s.actors[action.actorId]);
        } else { // == "response"
            this._Functions[action.responseFunctionId](action.args);
        }
        return false;
    };
    this._addFrameCallback = function () {
        this._removeFrameCallback();
        if (this._debug) log(this.name, "Adding frame callback");
        this._State.callback = addFrameCallback(this._ourFrameCallback);
    };
    this._removeFrameCallback = function () {
        if (this._State.callback) {
            removeFrameCallback(this._State.callback);
            delete this._State.callback;
            if (this._debug) log(this.name, "Removed frame callback");
        }
    };
    this._ourFrameCallback = function (delta) {
        if (this._frame = ((this._frame || 0) + 1) % 10) { // One action each 10 frames
            return; // Only one in n frames is used.
        }
    
        var state = this._State;
        if (state.isJumpTokenBeingUsed) {
            state.isJumpTokenBeingUsed = !(this._executeStack() && this._populateStack()); // Still some work to do ?
            return; // we did enough this time
        }
    
        if (state.jumpTokenNb) { // Do we have an available jump token?
            if (this._gatherEventsToPublish()) { // Finished gathering
                if (this._debug) log(this.name, "Using a jump token");
                state.jumpTokenNb--;
                state.isJumpTokenBeingUsed = true;
            }
            return; // we did enough this time
        }
    
        if (this._debug) log(this.name, "No more jump token");
        this._removeFrameCallback(); // We have finished, we remove the callback
    }.bind(this);
    
    /* ************************** Methods to save/restore *****************************************************/
    
    this._functionReplacer = function (key, value) {
        return typeof value == 'function' ? '/Function(' + value.toString() + ')/' : value;
    };
    this._functionReviver = (function () {
        var innerFn = function innerFn(key, value) {
            // All our special cases are strings // FIXME 0.perfectperf check if we get something else than string
            if (typeof value != 'string') {
                return value;
            }
    
            var that = innerFn; // Closure for recursion
            if (value.match(that._functionRegexp)) { // FIXME 0.perfectperf: benchmark using only one regexp rather than 2
                return eval(value.replace(that._functionReplaceRegexp, '($1)'));
            }
            return value;
        };
        innerFn._functionRegexp = new RegExp("^\\/Function\\([\\s\\S]*\\)\\/$");
        innerFn._functionReplaceRegexp = new RegExp("^\\/Function\\(([\\s\\S]*)\\)\\/$");
        return innerFn;
    }.bind(this))();
    
    /* ************************** Oolite events ***************************************************************/
    
    this._startUp = function () {
        var sa = this._missionVariables.DayDiplomacyEngine_EngineState;
        if (sa && sa.length) { // Loading if necessary.
            this._loadState(this._State, this._JSON.parse(sa));
            this._loadState(this._Functions, this._JSON.parse(this._missionVariables.DayDiplomacyEngine_Functions, this._functionReviver));
        }
    
        delete this._startUp; // No need to startup twice
    };
    this.playerWillSaveGame = function (message) {
        this._removeFrameCallback();
        var start = new Date();
        this._missionVariables.DayDiplomacyEngine_EngineState = this._JSON.stringify(this._State);
        this._missionVariables.DayDiplomacyEngine_Functions = this._JSON.stringify(this._Functions, this._functionReplacer);
        var end = new Date();
        log("DiplomacyEngine", "Saved in ms: " + (end.getTime() - start.getTime()));
        this._addFrameCallback();
    };
    this.shipExitedWitchspace = function () {
        var s = this._State;
        s.jumpTokenNb || (s.jumpTokenNb = 0);
        s.jumpTokenNb++;
        if (this._debug) log(this.name, "Added jump token");
    };
    this.shipDockedWithStation = function (station) {
        this._addFrameCallback();
    };
    this.shipWillLaunchFromStation = function (station) {
        this._removeFrameCallback();
    };
    
    /* ************************** Subscribing system for scripts order ****************************************/
    
    this.startUp = function () {
        worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
        delete this.startUp; // No need to startup twice
    };
    this.startUpComplete = function () {
        var s = this._subscribers.sort();
        var z = s.length, y = z - 1;
        while (z--) {
            var startDate = new Date();
            worldScripts[s[y - z]]._startUp();
            log(s[y - z], "startUp in ms: " + (new Date().getTime() - startDate.getTime()));
        }
    
        this.shipDockedWithStation(null); // When starting, the player is docked.
    
        delete this.startUpComplete; // No need to startup twice
    };
    
    // FIXME create a type ScriptName?
    /**  names of scripts
     * @type {string[]} */
    this._subscribers = [];
    
    /**
     * Allows an external script to use the Diplomacy API.
     * The external script must implement a function named _startUp() which will be called during the startUpComplete() function of the Diplomacy Engine.
     * @name $subscribe
     * @param {string} scriptName - the name property of the subscribing script object
     * @lends worldScripts.DayDiplomacy_000_Engine.$subscribe
     */
    this.$subscribe = function (scriptName) {
        this._subscribers.push(scriptName);
    };
    Scripts/DayDiplomacy_GNNConnector.js
    "use strict";
    this.name = "DayDiplomacy_015_GNN";
    this.author = "David (Day) Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.copyright = "(C) 2017 David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.licence = "CC-NC-by-SA 4.0";
    this.description = "This script connects to the GNN OXP.";
    
    /**
     *
     * @param message
     * @lends worldScripts.DayDiplomacy_015_GNN.$publishNews
     */
    this.$publishNews = function (message) {
        var news = {ID:this.name, Message:message};
        var returnCode = worldScripts.GNN._insertNews(news);
        if (returnCode > 0) { // A prerequisite is wrong
            log("DayDiplomacy_015_GNN.$publishNews", "GNN ERROR: " + returnCode);
        } // else: everything is okay.
    };
    
    /**
     *
     * @param message
     * @lends worldScripts.DayDiplomacy_015_GNN.$publishNewsNow
     */
    this.$publishNewsNow = function (message) {
        var news = {ID:this.name, Message:message};
        var returnBoolean = worldScripts.GNN._showScreen(news);
        if (!returnBoolean) { // A prerequisite is wrong
            log("DayDiplomacy_015_GNN.$publishNewsNow", "GNN ERROR");
        } // else: everything is okay.
    };
    
    /* ************************** Oolite events ***************************************************************/
    
    this._startUp = function () {
        delete this._startUp; // No need to startup twice
    };
    this.startUp = function () {
        worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
        delete this.startUp; // No need to startup twice
    };
    Scripts/DayDiplomacy_History.js
    "use strict";
    this.name = "DayDiplomacy_020_History";
    this.author = "David (Day) Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.copyright = "(C) 2017 David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.licence = "CC-NC-by-SA 4.0";
    this.description = "This script displays an Interface showing the F7 system history.";
    
    this._displayF4Interface = function () {
        player.ship.hudHidden || (player.ship.hudHidden = true);
        // for each event in history for this system, we add a line
        var ourMessage = "", f = this._F, eff = this._eff,
            ourEventsIds = this._Engine.$getActorEvents(this._selectedSystemActorId), events = this._Engine.$getEvents(),
            y = ourEventsIds.length, _clock = clock;
        while (y--) {
            // Anti-chronological order
            var thatEvent = events[ourEventsIds[y]];
            ourMessage += _clock.clockStringForTime(thatEvent.date) + ": " + f[eff[thatEvent.eventType]](thatEvent)+"\n";
        }
        var opts = {
            screenID: "DiplomacyHistoryScreenId",
            title: "System history",
            allowInterrupt: true,
            exitScreen: "GUI_SCREEN_INTERFACES",
            message: ourMessage
        };
        mission.runScreen(opts);
    };
    this._initF4Interface = function () {
        player.ship.dockedStation.setInterface("DiplomacyHistory",
            {
                title: "System history",
                category: "Diplomacy",
                summary: "All the notable events in the system history",
                callback: this._displayF4Interface.bind(this)
            });
    };
    
    /* ************************** OXP public functions ********************************************************/
    
    /**
     * @param {EventType} eventType
     * @param {function} func
     * @lends worldScripts.DayDiplomacy_020_History.$setEventFormattingFunction
     */
    this.$setEventFormattingFunction = function(eventType, func) {
        var engine = this._Engine;
        var fid = engine.$getNewFunctionId();
        engine.$setFunction(fid, func);
        this._eff[eventType] = fid;
    };
    
    /* ************************** Oolite events ***************************************************************/
    
    this.infoSystemChanged = function (currentSystemId, previousSystemId) {
        this._selectedSystemActorId = this._Systems.$getCurrentGalaxySystemsActorIdsBySystemsId()[currentSystemId];
    };
    this.shipDockedWithStation = function (station) {
        this._initF4Interface();
    };
    this.missionScreenEnded = function () {
        player.ship.hudHidden = false;
    };
    
    this._startUp = function () {
        worldScripts.XenonUI && worldScripts.XenonUI.$addMissionScreenException("DiplomacyHistoryScreenId");
        worldScripts.XenonReduxUI && worldScripts.XenonReduxUI.$addMissionScreenException("DiplomacyHistoryScreenId");
    
        var engine = this._Engine = worldScripts.DayDiplomacy_000_Engine;
        this._Systems = worldScripts.DayDiplomacy_010_Systems;
        this._F = engine.$getFunctions();
        this._selectedSystemActorId = this._Systems.$getCurrentGalaxySystemsActorIdsBySystemsId()[system.info.systemID]; // FIXME perfectperf?
        this._eff = engine.$initAndReturnSavedData("eventFormatingFunctionsIds", {}); // { eventType => functionId }
    
        this._initF4Interface();
    
        delete this._startUp; // No need to startup twice
    };
    this.startUp = function () {
        worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
        delete this.startUp; // No need to startup twice
    };
    Scripts/DayDiplomacy_JsDocDiplomacy.js
    "use strict";
    
    /**
     * An id identifying an {@link Actor}
     * @typedef ActorId
     * @alias {string}
     */
    
    /**
     * An id identifying an {@link Action}
     * @typedef ActionId
     * @alias {string}
     */
    
    /**
     * An id identifying a {@link DiplomacyEvent}
     * @typedef EventId
     * @alias {string}
     */
    
    /**
     * An id identifying a {@link DiplomacyFunction}
     * @typedef FunctionId
     * @alias {string}
     */
    
    /**
     * An id identifying a {@link DiplomacyResponse}
     * @typedef ResponseId
     * @alias {string}
     */
    
    /**
     * A type qualifying a {@link DiplomacyEvent}
     * @typedef EventType
     * @alias {string}
     */
    
    /**
     * A type of {@link Actor}
     * @typedef ActorType
     * @alias {string}
     */
    
    /**
     @typedef DiplomacyEvent
     @property {EventId} id
     @property {EventType} eventType
     @property {ActorId} actorId - actorId
     @property {Object[]} args
     @property {number} date - seconds since epoch
     */
    
    /**
     @typedef DiplomacyResponse
     @property {ResponseId} id
     @property {EventType} eventType
     @property {ActorType} actorType
     @property {FunctionId} responseFunctionId
     */
    
    /**
     @typedef Actor
     @property {ActorId} id
     @property {ActorType} actorType
     @property {Object<ActorType,ActorId[]>} observers
     @property {Object<EventType,ResponseId[]>} responsesIdByEventType
     */
    
    /**
     @typedef Action
     @property {ActionId} id
     @property {EventType} eventType - anEventType is used to order the actions and events execution. For a same eventType, Actions are executed before Events.
     @property {ActorType} actorType - Only actors of the type will execute the action.
     @property {FunctionId} actionFunctionId - the id of a function which must take one and only one argument: the actor which will "act".
    
     */
    
    /**
     * @typedef Script
     * @property {string} name FIXME
     * @property {string} author FIXME
     * @property {string} copyright FIXME
     * @property {string} description FIXME
     * @property {string} licence FIXME
     */
    
    /**
     * The Diplomacy Engine script
     * @type Script
     */
    worldScripts.DayDiplomacy_000_Engine;
    
    /**
     * The Diplomacy Planetary Systems script
     * @type Script
     */
    worldScripts.DayDiplomacy_010_Systems;
    
    /**
     * The Diplomacy-Snoopers connector script
     * @type Script
     */
    worldScripts.DayDiplomacy_015_GNN;
    
    /**
     * The Diplomacy History script
     * @type Script
     */
    worldScripts.DayDiplomacy_020_History;
    
    /**
     * The Diplomacy Economy script
     * @type Script
     */
    worldScripts.DayDiplomacy_030_EconomyEngine;
    
    /**
     * The Diplomacy War Engine script
     * @type Script
     */
    worldScripts.DayDiplomacy_040_WarEngine;
    
    /**
     * The Diplomacy War script
     * @type Script
     */
    worldScripts.DayDiplomacy_045_War;
    
    /**
     * The Diplomacy Citizenships script
     * @type Script
     */
    worldScripts.DayDiplomacy_060_Citizenships;
    
    /**
     * The XenonUI script, for compatibility
     * @type Script
     */
    worldScripts.XenonUI;
    
    /**
     * @function
     */
    worldScripts.XenonUI.$addMissionScreenException;
    
    /**
     * The XenonReduxUI script, for compatibility
     * @type Script
     */
    worldScripts.XenonReduxUI;
    
    /**
     * @function
     */
    worldScripts.XenonReduxUI.$addMissionScreenException;
    Scripts/DayDiplomacy_JsDocOolite.js
    "use strict";
    /**
     * The System class represents the current system. There is always one System object, available through the global property system.
     * @typedef System
     * @property {int} ID {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#ID}
     * @property {SystemInfo} info {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#info}
     * @property {function} infoForSystem {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#infoForSystem}
     * @property {string} name {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#name}
     * @property {int} population {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#population}
     * @property {int} productivity {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#productivity}
     * @property {int} techLevel {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#techLevel}
     * @property {int} government {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#government}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System}
     */
    
    /**
     * @function System.infoForSystem
     * @param {int} galaxyID
     * @param {int} systemID
     * @return {SystemInfo}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#infoForSystem}
     */
    
    /**
     * SystemInfo objects provide information about a specific system.
     * @typedef SystemInfo
     * @property {int} galaxyID {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_SystemInfo#galaxyID}
     * @property {int} systemID {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_SystemInfo#systemID}
     * @property {int} productivity
     * @property {int} population
     * @property {function} systemsInRange {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_SystemInfo#systemsInRange}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_SystemInfo}
     */
    
    /**
     *  Returns the SystemInfo of the systems nearer than 7 ly from the original SystemInfo
     * @function SystemInfo.systemsInRange
     * @instance
     * @return {SystemInfo[]}
     */
    
    // Note: player.ship doesn't exist in the Oolite player wiki.
    /**
     * The Player class is represents the player. There is always exactly one Player object in existence, which can be accessed through the player global property.
     * @typedef Player
     * @property {PlayerShip} ship
     * @property {Number} credits {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player#credits}
     * @property {int} bounty {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player#bounty}
     * @property {function} commsMessage  {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player#commsMessage}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player}
     */
    
    /**
     * @function Player.commsMessage
     * @param {string} message
     * @instance
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player#commsMessage}
     */
    
    /**
     * The Ship class is an Entity representing a ship, station, missile, cargo pod or other flying item – anything that can be specified in shipdata.plist.
     * @typedef Ship
     * @property {int} homeSystem {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Ship#homeSystem}
     * @property {function} awardEquipment {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Ship#awardEquipment}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Ship}
     */
    
    /**
     * The Station class is an Entity representing a station or carrier (i.e., a ship with a docking port). A Station has all the properties and methods of a Ship, and some others.
     * @typedef {Ship} Station
     * @augments {Ship}
     * @property {function} setInterface {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Station#setInterface}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Station}
     */
    
    /**
     * The PlayerShip class is an Entity representing the player’s ship. The PlayerShip has all the properties and methods of a Ship, and several others. There is always exactly one PlayerShip object in existence, which can be accessed through player.ship.
     * @typedef {Ship} PlayerShip
     * @augments {Ship}
     * @property {boolean} hudHidden {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_PlayerShip#hudHidden}
     * @property {Station} dockedStation {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_PlayerShip#dockedStation}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_PlayerShip}
     */
    
    /**
     * The mission global object is used to run mission screens, and perform other actions related to mission scripting.
     * @typedef Mission
     * @property {function} runScreen {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Mission#runScreen}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Mission}
     */
    
    /**
     * The clock global object is used to tell the time. Apart from absoluteSeconds, all its properties have to do with game clock time.
     * @typedef Clock
     * @property {int} seconds
     * @property {function} clockStringForTime {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:Clock#clockStringForTime}
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Clock}
     */
    
    /**
     * @name system
     * @type System
     * */
    var system;
    
    /**
     * @name player
     * @type Player
     * */
    var player;
    
    /**
     * @name mission
     * @type Mission
     * */
    var mission;
    
    /**
     * @name missionVariables
     * @type Object
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#missionVariables}
     * */
    var missionVariables;
    
    
    /**
     * The dictionary of all available oxp scripts.
     * @name worldScripts
     * @type Object
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#worldScripts}
     */
    var worldScripts;
    
    /**
     * @name clock
     * @type Clock
     * */
    var clock;
    
    /**
     * @function
     * @param {string} prefix prefix
     * @param {string} textToLog text to log
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#log}
     */
    var log = function(prefix, textToLog) {};
    
    /**
     * @function
     * @param {function} callback
     * @return {string} trackingID
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#addFrameCallback}
     */
    var addFrameCallback = function (callback) {};
    
    /**
     * @function
     * @param {string} trackingID
     * @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#removeFrameCallback}
     */
    var removeFrameCallback = function (trackingID) {};
    Scripts/DayDiplomacy_Systems.js
    "use strict";
    this.name = "DayDiplomacy_010_Systems";
    this.author = "David (Day) Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.copyright = "(C) 2017 David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.licence = "CC-NC-by-SA 4.0";
    this.description = "This script creates systems.";
    
    /**
     * An object identifying a planetary system
     * @typedef {Object} PlanetarySystem
     * @property {int} galaxyID - the galaxyID of the system
     * @property {int} systemID - the systemID of the system
     * @property {string} name - the name of the system
     */
    
    /* ************************** OXP public functions *******************************************************/
    
    /**
     *
     * @param {int} galaxyID - Identifies the galaxy of the wanted system
     * @param {int} systemID - Identifies the wanted system in the given galaxy
     * @return {Actor}
     * @lends worldScripts.DayDiplomacy_010_Systems.$retrieveActorFromSystem
     */
    this.$retrieveActorFromSystem = function (galaxyID, systemID) {
        return this._Engine.$getActors()[this._systemsByGalaxyAndSystemId[galaxyID][systemID]];
    };
    
    /**
     * @param {int} galaxyID - Identifies the galaxy of the wanted system
     * @param {int} systemID - Identifies the wanted system in the given galaxy
     * @returns {String} - Returns the system name of the system defined by the given galaxyId and systemId
     * @lends worldScripts.DayDiplomacy_010_Systems.$retrieveNameFromSystem
     */
    this.$retrieveNameFromSystem = function (galaxyID, systemID) {
        return this._Engine.$getActors()[this._systemsByGalaxyAndSystemId[galaxyID][systemID]].name;
    };
    
    /**
     * @returns {Object} a dictionary with {int} galaxyId key and as value: a dictionary with {int} systemId key and as value: the corresponding {@link ActorId}
     * @lends worldScripts.DayDiplomacy_010_Systems.$getSystemsActorIdsByGalaxyAndSystemId
     */
    this.$getSystemsActorIdsByGalaxyAndSystemId = function() {
        return this._systemsByGalaxyAndSystemId;
    };
    /**
     * For the current galaxy only
     * @returns {Object} a dictionary with {int} systemId key and as value: the corresponding {@link ActorId}
     * @lends worldScripts.DayDiplomacy_010_Systems.$getCurrentGalaxySystemsActorIdsBySystemsId
     */
    this.$getCurrentGalaxySystemsActorIdsBySystemsId = function() {
        return this._systemsByGalaxyAndSystemId[system.info.galaxyID];
    };
    
    /* ************************** OXP private functions *******************************************************/
    
    /**
     * @param {int} aGalaxyNb
     * @private
     */
    this._setObservers = function (aGalaxyNb) {
        // We set the observers. No need to use an initAction as there won't be any more system.
        var engine = this._Engine;
        var actorsIdByType = engine.$getActorsIdByType("SYSTEM");
        var actors = engine.$getActors();
    
        var galaxyFirstSystem = actors[actorsIdByType[255 + 256 * (7 - aGalaxyNb)]];
        var galaxyLastSystem = actors[actorsIdByType[256 * (7 - aGalaxyNb)]];
        var firstSystemKnownObservers = engine.$getObservers(galaxyFirstSystem, "SYSTEM");
        var lastSystemKnownObservers = engine.$getObservers(galaxyLastSystem, "SYSTEM");
        if (firstSystemKnownObservers && firstSystemKnownObservers.length && lastSystemKnownObservers.length) {
            return; // Already initialized
        }
    
        var infoForSystem = System.infoForSystem, sys = this._systemsByGalaxyAndSystemId, z = actorsIdByType.length;
        while (z--) {
            var thisActor = actors[actorsIdByType[z]];
            if (thisActor.galaxyNb === aGalaxyNb && !thisActor.observers.length) {
                var observers = infoForSystem(aGalaxyNb, thisActor.systemId).systemsInRange();
                var y = observers.length;
                while (y--) {
                    var observer = observers[y];
                    engine.$addObserverToActor(thisActor, "SYSTEM", sys[observer.galaxyID][observer.systemID]);
                }
            }
        }
    };
    
    this._startUp = function () {
        var engine = this._Engine = worldScripts.DayDiplomacy_000_Engine;
        var sys = this._systemsByGalaxyAndSystemId = engine.$initAndReturnSavedData("systemsByGalaxyAndSystemId", {});
    
        // Not initializing if already done.
        if (engine.$getActorTypes().indexOf("SYSTEM") !== -1) {
            return;
        }
        /** @type {ActorType} */
        var actorType = "SYSTEM";
        engine.$addActorType(actorType, 0);
    
        // We initiate the systems
        var i = 8;
        while (i--) {
            var j = 256;
            while (j--) {
                var id = engine.$getNewActorId();
                var aSystem = engine.$buildActor("SYSTEM", id);
                // FIXME why do I use setField here rather than directly 'aSystem.galaxyNb = '?
                engine.$setField(aSystem, "galaxyNb", i);
                engine.$setField(aSystem, "systemId", j);
                engine.$addActor(aSystem);
                (sys[i] || (sys[i] = {}))[j] = id; // Needed for quick access in the next part.
            }
        }
    
        // We init the observers for the current galaxy
        this._setObservers(system.info.galaxyID);
        delete this._startUp; // No need to startup twice
    };
    
    /* ************************** Oolite events ***************************************************************/
    
    // noinspection JSUnusedGlobalSymbols - Called by Oolite itself
    this.playerEnteredNewGalaxy = function (galaxyNumber) {
        // This function is necessary as we can't calculate distances in other galaxies.
        this._setObservers(galaxyNumber);
    };
    this.startUp = function () {
        worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
        delete this.startUp; // No need to startup twice
    };
    Scripts/DayDiplomacy_War.js
    "use strict";
    this.name = "DayDiplomacy_045_War";
    this.author = "David (Day) Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.copyright = "(C) 2017 David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.licence = "CC-NC-by-SA 4.0";
    this.description = "This script makes systems ally to each other," +
        " break their alliances," +
        " make peace and war," +
        " publish the related news," +
        " draw the war and the diplomatic maps.";
    
    /* ************************** OXP private functions *******************************************************/
    
    // FIXME 0.14 make that as long as we are not in 1.0, the diplomacy save data is erased when there is a new version?
    this._initSystemsScores = function (aGalaxyNb) {
        // Initializing static scores
        // For a given galaxy, for each system in the galaxy, for each system it observes,
        // it must assign a score to some properties, then recalculate the final score.
        // FIXME perfectstyle shouldn't this script be actorType-agnostic?
        var engine = this._s;
        var actorsIdByType = engine.$getActorsIdByType("SYSTEM");
        var actors = engine.$getActors();
        var z = actorsIdByType.length;
        var we = this._we;
        while (z--) {
            var thisActor = actors[actorsIdByType[z]];
            if (thisActor.galaxyNb != aGalaxyNb) {
                continue;
            }
            var observersId = thisActor.observers["SYSTEM"];
            var y = observersId.length;
            while (y--) {
                we.$recalculateScores(actors[observersId[y]], thisActor);
            }
        }
    };
    this._drawDiplomaticMap = function () {
        var scores = this._we.$getScores();
        var actors = this._s.$getActors();
        var links = [];
    
        for (var observedId in scores) {
            if (scores.hasOwnProperty(observedId)) {
                var observed = actors[observedId];
                var observedNb = observed.systemId;
                var galaxyNb = observed.galaxyNb;
                var observedScores = scores[observedId];
                for (var observerId in observedScores) {
                    if (observedScores.hasOwnProperty(observerId)) {
                        var observerNb = actors[observerId].systemId;
                        // Doc: "When setting link_color, the lower system ID must be placed first,
                        // because of how the chart is drawn."
                        if (observerNb < observedNb) {
                            var scoreFromTo = observedScores[observerId].SCORE;
                            var scoreToFrom = scores[observerId][observedId].SCORE;
                            if (scoreFromTo || scoreToFrom) {
                                var color = null;
                                if (scoreFromTo > 0 && scoreToFrom > 0) {
                                    color = "greenColor";
                                } else if (scoreFromTo < 0 && scoreToFrom < 0) {
                                    color = "redColor";
                                } else if (scoreFromTo * scoreToFrom < 0) {
                                    color = "yellowColor";
                                } else if (scoreFromTo + scoreToFrom > 0) {
                                    color = "blueColor";
                                } else {
                                    color = "orangeColor";
                                }
                                links.push({galaxyNb: galaxyNb, from: observerNb, to: observedNb, color: color});
                            }
                        }
                    }
                }
            }
        }
    
        this._drawMap(links);
    };
    this._drawWarMap = function () {
        var alliancesAndWars = this._we.$getAlliancesAndWars();
        var actors = this._s.$getActors();
        var links = [];
    
        for (var actorId in alliancesAndWars) {
            if (alliancesAndWars.hasOwnProperty(actorId)) {
                var actorAlliancesAndWars = alliancesAndWars[actorId];
                var actor = actors[actorId];
                var systemNb = actor.systemId;
                var galaxyNb = actor.galaxyNb;
                for (var targetId in actorAlliancesAndWars) {
                    if (actorAlliancesAndWars.hasOwnProperty(targetId)) {
                        var targetSystemNb = actors[targetId].systemId;
                        // Doc: "When setting link_color, the lower system ID must be placed first,
                        // because of how the chart is drawn."
                        if (systemNb < targetSystemNb) {
                            links.push({
                                galaxyNb: galaxyNb,
                                from: systemNb,
                                to: targetSystemNb,
                                color: actorAlliancesAndWars[targetId] === 1 ? "greenColor" : "redColor"
                            });
                        }
                    }
                }
            }
        }
    
        this._drawMap(links);
    };
    this._drawWarringMap = function () {
        var alliancesAndWars = this._we.$getAlliancesAndWars();
        var actors = this._s.$getActors();
        var warringSystems = [];
        var m = mission;
    
        mainloop:
            for (var actorId in alliancesAndWars) {
                if (alliancesAndWars.hasOwnProperty(actorId)) {
                    var actorAlliancesAndWars = alliancesAndWars[actorId];
                    var systemNb = actors[actorId].systemId;
                    for (var targetId in actorAlliancesAndWars) {
                        if (actorAlliancesAndWars.hasOwnProperty(targetId)) {
                            if (actorAlliancesAndWars[targetId] === -1) {
                                m.markSystem({system: systemNb, name: "DayDiplomacyWarringMap"});
                                continue mainloop;
                            }
                        }
                    }
                }
            }
    };
    this._drawMap = function (links) {
        var systemInfo = SystemInfo;
        var z = links.length;
        while (z--) {
            var link = links[z];
            // Hmm... We calculate and then set the links for all the galaxies...
            // This is useless, but at the same time simpler and maybe useful for the future.
            systemInfo.setInterstellarProperty(link.galaxyNb, link.from, link.to, 2, "link_color", link.color);
        }
        this._links = links;
    };
    this._resetLinks = function () {
        var m = mission;
        var i = 256;
        while(i--) {
            m.unmarkSystem({system:i, name:"DayDiplomacyWarringMap"});
        }
    
        var links = this._links;
        if (!links) return;
        var systemInfo = SystemInfo;
        var z = links.length;
        while (z--) {
            var link = links[z];
            systemInfo.setInterstellarProperty(link.galaxyNb, link.from, link.to, 2, "link_color", null);
        }
        this._links = null;
    };
    
    /**
     * If 2 vectors are given, center is the center of the vectors, and zoom the customChartCentreInLY value
     * necessary to display the 2 vectors and 20% more space around.
     * If only one vector is given, the center is that vector, and the zoom is 1.5.
     * @param {Vector3D} v1
     * @param {Vector3D} v2
     * @return {{center: Vector3D, zoom: number}}
     * @private
     */
    this._getCustomZoom = function (v1, v2) {
        var z = 1.5;
        if (v2) {
            var v = v1.subtract(v2).multiply(1.2);
            z = Math.min(4, Math.max(1, Math.abs(v.x) / 100 * 4, Math.abs(v.y) / 50 * 4));
        }
        return {
            center: v2 ? v1.add(v2).multiply(.5) : v1,
            zoom: z
        };
    };
    
    /**
     * Save the target system and make as if there was no target system, so that no trajectory appears.
     * @private
     */
    this._saveTarget = function () {
        if (!this._savedTarget) {
            this._savedTarget = player.ship.targetSystem;
            player.ship.targetSystem = system.info.systemID;
        }
    };
    /**
     * Reloads the target system.
     * @private
     */
    this._loadTarget = function () {
        if (this._savedTarget) {
            player.ship.targetSystem = this._savedTarget;
            this._savedTarget = undefined;
        }
    };
    
    this._F4InterfaceCallback = function (choice) {
        this._loadTarget();
    
        // Exit
        if (choice === "5_EXIT") {
            return;
        }
    
        // Default choice
        choice = choice === "DiplomacyWars" ? "0_WARRING_NO_TRAVEL" : choice;
    
        // Choice
        var choices = choice.split("_");
        var no = choices[0], wd = choices[1], qs = choices[2], td = choices[3];
    
        // Options
        var wdp = ["WARRING", "WARS", "DIPLOMACY"];
        var qsp = ["QUICK", "SHORT", "NO"];
        var tdp = ["TARGET", "TRAVEL"];
    
        // Next proposed options
        var nextwd = wdp[(wdp.indexOf(wd) + 1) % wdp.length];
        var nextqs = qsp[(qsp.indexOf(qs) + 1) % qsp.length];
        var nexttd = tdp[(tdp.indexOf(td) + 1) % tdp.length];
    
        var bgs = {QUICK: "CUSTOM_CHART_QUICKEST", SHORT: "CUSTOM_CHART_SHORTEST", NO: "CUSTOM_CHART"};
    
        var texts = {
            WARRING: "Display warring systems",
            WARS: "Display wars map",
            DIPLOMACY: "Display diplomacy map",
            QUICK: "Display quickest travel",
            SHORT: "Display shortest travel",
            NO: "Display no travel",
            TARGET: "Display the target surroundings",
            TRAVEL: "Display the travel surroundings"
        };
    
        // Init
        this._resetLinks();
        var playerShip = player.ship;
        playerShip.hudHidden || (playerShip.hudHidden = true);
    
        var opts = {
            screenID: "DiplomacyWarScreenId",
            title: "Star wars",
            allowInterrupt: true,
            exitScreen: "GUI_SCREEN_INTERFACES",
            choices: {"5_EXIT": "Exit"}
        };
    
        if (no === '4'/*Help*/) {
            opts.message = "Warring systems map: red crosses\n\nDiplomacy map:\nGreen: Love\nBlue: Love+Neutrality\nGray: Neutrality\nYellow: Love+Hate\nOrange: Neutrality+Hate\nRed: Hate\n"
                + "\n\nWars map:\nRed: war\nGreen: alliance";
        } else {
            // Screen
            var info = system.info;
    
            var currentSystemCoordinates = info.coordinates;
            var targetCoordinates = System.infoForSystem(info.galaxyID, playerShip.targetSystem).coordinates;
            var customZoom = td === "TARGET"
                ? this._getCustomZoom(targetCoordinates, undefined)
                : this._getCustomZoom(currentSystemCoordinates, targetCoordinates);
    
            if (qs === "NO") this._saveTarget();
    
            opts.backgroundSpecial = bgs[qs];
    
            opts.customChartZoom = customZoom.zoom;
            opts.customChartCentreInLY = customZoom.center;
    
            switch (wd) {
                case 'DIPLOMACY':
                    this._drawDiplomaticMap();
                    break;
                case 'WARS':
                    this._drawWarMap();
                    break;
                case 'WARRING':
                    this._drawWarringMap();
                    break;
            }
        }
    
        opts.choices[[1, nextwd, qs, td].join('_')] = texts[nextwd];
        opts.choices[[2, wd, nextqs, td].join('_')] = texts[nextqs];
        opts.choices[[3, wd, qs, nexttd].join('_')] = texts[nexttd];
        opts.choices[[4, wd, qs, td].join('_')] = "Help";
    
        mission.runScreen(opts, this._F4InterfaceCallback.bind(this));
    };
    
    this._initF4Interface = function () {
        if (player.ship.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
            player.ship.dockedStation.setInterface("DiplomacyWars",
                {
                    title: "Star wars",
                    category: "Diplomacy",
                    summary: "Wars and diplomacy",
                    callback: this._F4InterfaceCallback.bind(this)
                });
        }
    };
    this._startUp = function () {
        // FIXME 0.perfectstyle hmff, this might have to be into its own function.
        // Nope, it would be contrary to perfectperf. Explain that in TechnicalPrinciples.txt.
        // XenonUI would overlay over our mission screens without these exception.
        // FIXME 0.perfectstyle i should have a list of screens, rather than copying here their names, to avoid forgetting
        // to update here when I add or change a screen.
        worldScripts.XenonUI && worldScripts.XenonUI.$addMissionScreenException("DiplomacyWarScreenId");
        worldScripts.XenonReduxUI && worldScripts.XenonReduxUI.$addMissionScreenException("DiplomacyWarScreenId");
    
        this._storedNews = []; // No real need to save it
        var engine = this._s;
        var we = this._we = worldScripts.DayDiplomacy_040_WarEngine;
        var sf = we.$getScoringFunctions();
    
        // Economy comparison
        if (sf.indexOf("EconomyComparison") === -1) {
            we.$addScoringFunction("EconomyComparison", function (observer, observed) {
                var map = {
                    0: {0: +0.5, 1: -1.0, 2: -0.5, 3: -1.0, 4: -1.0, 5: -0.5, 6: -0.5, 7: -0.5}, // Anarchy
                    1: {0: +0.0, 1: +0.5, 2: -0.5, 3: -0.5, 4: -1.0, 5: -0.5, 6: -1.0, 7: -0.5}, // Feudal
                    2: {0: +0.0, 1: +0.0, 2: +0.5, 3: -0.5, 4: -0.5, 5: +0.5, 6: +0.0, 7: +0.0}, // Multi-government
                    3: {0: +0.0, 1: +0.0, 2: +0.0, 3: +0.5, 4: +0.0, 5: +0.0, 6: -0.5, 7: +0.0}, // Dictator
                    4: {0: -0.5, 1: -0.5, 2: +0.0, 3: +0.0, 4: +0.5, 5: +0.0, 6: -0.5, 7: -0.5}, // Communist
                    5: {0: +0.0, 1: +0.0, 2: +0.5, 3: -0.5, 4: +0.0, 5: +0.5, 6: +0.0, 7: +0.0}, // Confederacy
                    6: {0: +0.0, 1: -0.5, 2: +0.0, 3: -0.5, 4: -0.5, 5: +0.0, 6: +0.5, 7: +0.0}, // Democracy
                    7: {0: +0.0, 1: +0.0, 2: +0.0, 3: +0.0, 4: -1.0, 5: +0.0, 6: +0.0, 7: +0.5}  // Corporate
                };
                return map[observer.government][observed.government];
            }, 0);
        }
    
        // Alliances influence on score, this function is and should be last executed.
        if (sf.indexOf("alliancesAndWarsInfluence") === -1) {
            we.$addScoringFunction("alliancesAndWarsInfluence", function alliancesAndWarsInfluence(observer, observed) {
    
                /* This function calculates the relation bonus given by observer to observed, depending on observed allies and foes.
                 * If their allies are considered nice by observer, they get a bonus.
                 * If their foes are considered baddies by observer, they get a bonus.
                 * And vice-versa. */
                var that = alliancesAndWarsInfluence;
                var we = that.we || (that.we = worldScripts.DayDiplomacy_040_WarEngine);
                var observedAlliesAndFoes = we.$getAlliancesAndWars()[observed.id];
                var allScores = we.$getScores();
                var observerId = observer.id;
    
                var result = 0;
                for (var alliedId in observedAlliesAndFoes) {
                    if (observedAlliesAndFoes.hasOwnProperty(alliedId)) {
                        var scores = allScores[alliedId][observerId];
                        scores && (result += observedAlliesAndFoes[alliedId] * scores.SCORE);
                    }
                }
                return result > 0 ? .25 : result < 0 ? -.25 : 0;
    
            }, 1);
        }
    
        this._initSystemsScores(system.info.galaxyID);
    
        // We set the response to the ALLY event.
        var allyResponseFunctionId = "diplomacyAlliancesOnSystemAllyFunction";
        if (!engine.$getFunctions()[allyResponseFunctionId]) {
            // We use a recurrent action to recalculate the scores,
            // as doing it on every event would generate LOTS of calculus.
            // Currently, we only generate the news.
            var diplomacyAlliancesOnSystemAllyFunction = function diplomacyAlliancesOnSystemAllyFunction(argsArray) {
    
                /** @type {Actor} */
                var respondingActor = argsArray[0];
    
                /** @type {Actor} */
                var eventActor = argsArray[1];
    
                /** @type {ActorId} */
                var alliedActorId = argsArray[2];
    
                // On ALLY event, if the player is in a responder system, a news is generated.
                // This could be optimized, but the role of this function should be to manage all responses.
                if (system.info.name === respondingActor.name) {
                    var allyName = worldScripts.DayDiplomacy_000_Engine.$getActors()[alliedActorId].name;
                    if (respondingActor.name === allyName) {
                        worldScripts.DayDiplomacy_015_GNN.$publishNews("YOU might be interested in knowing that " + eventActor.name + " just allied with " + allyName
                            + ".\n\nAs Commander Diziet Sma, currently aboard the \"Blackwidow\" Pitviper S.E., famously said, 'the neatest definition of diplomacy I've seen is \"The art of saying 'nice doggy' while you reach behind you for a rock to throw.\"'.\n\nSo with that in mind, Who will gain? Who will lose?\n\nTruth is, we don't know!");
                    }
                }
    
            };
            engine.$setFunction(allyResponseFunctionId, diplomacyAlliancesOnSystemAllyFunction);
            engine.$setResponse(engine.$buildResponse(engine.$getNewResponseId(), "ALLY", "SYSTEM", allyResponseFunctionId));
        }
    
        // We set the response to the BREAK event.
        var breakResponseFunctionId = "diplomacyAlliancesOnSystemBreakFunction";
        if (!engine.$getFunctions()[breakResponseFunctionId]) {
            // We use a recurrent action to recalculate the scores,
            // as doing it on every event would generate LOTS of calculus.
            // Currently, we only generate the news.
            var diplomacyAlliancesOnSystemBreakFunction = function diplomacyAlliancesOnSystemBreakFunction(argsArray) {
    
                /** @type {Actor} */
                var respondingActor = argsArray[0];
    
                /** @type {Actor} */
                var eventActor = argsArray[1];
    
                /** @type {ActorId} */
                var alliedActorId = argsArray[2];
    
                // On BREAK event, if the player is in a responder system, a news is generated.
                if (system.info.name === respondingActor.name) {
                    var allyName = worldScripts.DayDiplomacy_000_Engine.$getActors()[alliedActorId].name;
                    if (respondingActor.name === allyName) {
                        worldScripts.DayDiplomacy_015_GNN.$publishNews("YOU might be interested in knowing that " + eventActor.name + " just broke their alliance with " + allyName
                            + ".\n\nAs Commander Diziet Sma, currently aboard the \"Blackwidow\" Pitviper S.E., famously said, 'the neatest definition of diplomacy I've seen is \"The art of saying 'nice doggy' while you reach behind you for a rock to throw.\"'.\n\nSo with that in mind, Who will gain? Who will lose?\n\nTruth is, we don't know!");
                    }
                }
    
            };
            engine.$setFunction(breakResponseFunctionId, diplomacyAlliancesOnSystemBreakFunction);
            engine.$setResponse(engine.$buildResponse(engine.$getNewResponseId(), "BREAK", "SYSTEM", breakResponseFunctionId));
        }
    
        // We set the response to the WAR event.
        var warResponseFunctionId = "diplomacyAlliancesOnSystemWarFunction";
        if (!engine.$getFunctions()[warResponseFunctionId]) {
            // We use a recurrent action to recalculate the scores,
            // as doing it on every event would generate LOTS of calculus.
            // Currently, we only generate the news.
            var diplomacyAlliancesOnSystemWarFunction = function diplomacyAlliancesOnSystemWarFunction(argsArray) {
    
                /** @type {Actor} */
                var respondingActor = argsArray[0];
    
                /** @type {Actor} */
                var eventActor = argsArray[1];
    
                /** @type {ActorId} */
                var foeActorId = argsArray[2];
    
                // On WAR event, if the player is in a responder system, a news is generated.
                if (system.info.name === respondingActor.name) {
                    var foeName = worldScripts.DayDiplomacy_000_Engine.$getActors()[foeActorId].name;
                    if (respondingActor.name === foeName) {
                        // FIXME 0.14 make different citation for war and peace
                        worldScripts.DayDiplomacy_015_GNN.$publishNews("YOU might be interested in knowing that " + eventActor.name + " just declared war with " + foeName
                            + ".\n\nAs Commander Diziet Sma, currently aboard the \"Blackwidow\" Pitviper S.E., famously said, 'the neatest definition of diplomacy I've seen is \"The art of saying 'nice doggy' while you reach behind you for a rock to throw.\"'.\n\nSo with that in mind, Who will gain? Who will lose?\n\nTruth is, we don't know!");
                    }
                }
    
            };
            engine.$setFunction(warResponseFunctionId, diplomacyAlliancesOnSystemWarFunction);
            engine.$setResponse(engine.$buildResponse(engine.$getNewResponseId(), "WAR", "SYSTEM", warResponseFunctionId));
        }
    
        // We set the response to the PEACE event.
        var peaceResponseFunctionId = "diplomacyAlliancesOnSystemPeaceFunction";
        if (!engine.$getFunctions()[peaceResponseFunctionId]) {
            // We use a recurrent action to recalculate the scores,
            // as doing it on every event would generate LOTS of calculus.
            // Currently, we only generate the news.
            var diplomacyAlliancesOnSystemPeaceFunction = function diplomacyAlliancesOnSystemPeaceFunction(argsArray) {
    
                /** @type {Actor} */
                var respondingActor = argsArray[0];
    
                /** @type {Actor} */
                var eventActor = argsArray[1];
    
                /** @type {ActorId} */
                var foeActorId = argsArray[2];
    
                // On PEACE event, if the player is in a responder system, a news is generated.
                if (system.info.name === respondingActor.name) {
                    var foeName = worldScripts.DayDiplomacy_000_Engine.$getActors()[foeActorId].name;
                    if (respondingActor.name === foeName) {
                        // FIXME 0.14 make different citation for war and peace
                        worldScripts.DayDiplomacy_015_GNN.$publishNews("YOU might be interested in knowing that " + eventActor.name + " just made peace with " + foeName
                            + ".\n\nAs Commander Diziet Sma, currently aboard the \"Blackwidow\" Pitviper S.E., famously said, 'the neatest definition of diplomacy I've seen is \"The art of saying 'nice doggy' while you reach behind you for a rock to throw.\"'.\n\nSo with that in mind, Who will gain? Who will lose?\n\nTruth is, we don't know!");
                    }
                }
    
            };
            engine.$setFunction(peaceResponseFunctionId, diplomacyAlliancesOnSystemPeaceFunction);
            engine.$setResponse(engine.$buildResponse(engine.$getNewResponseId(), "PEACE", "SYSTEM", peaceResponseFunctionId));
        }
    
        this._initF4Interface();
    
        delete this._startUp; // No need to startup twice
    };
    
    /* ************************** Oolite events ***************************************************************/
    
    this.startUp = function () {
        this._s = worldScripts.DayDiplomacy_000_Engine;
        this._s.$subscribe(this.name);
        delete this.startUp; // No need to startup twice
    };
    this.shipDockedWithStation = function (station) {
        this._initF4Interface();
    };
    this.equipmentAdded = function (equipmentKey) {
        if (equipmentKey === "EQ_ADVANCED_NAVIGATIONAL_ARRAY") {
            this._initF4Interface();
        }
    };
    this.playerEnteredNewGalaxy = function (galaxyNumber) {
        this._initSystemsScores(galaxyNumber);
    };
    this.missionScreenEnded = function () {
        player.ship.hudHidden = false;
        this._resetLinks();
        this._loadTarget();
    };
    Scripts/DayDiplomacy_WarEngine.js
    "use strict";
    this.name = "DayDiplomacy_040_WarEngine";
    this.author = "David (Day) Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.copyright = "(C) 2017 David Pradier";
    // noinspection JSUnusedGlobalSymbols Used by Oolite itself
    this.licence = "CC-NC-by-SA 4.0";
    this.description = "This script is the war engine of the Diplomacy OXP.";
    
    /* ************************** OXP public functions ********************************************************/
    
    /**
     * @return {Object.<string,FunctionId>}
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$getScoringFunctions
     */
    this.$getScoringFunctions = function () {
        return this._asf;
    };
    
    /**
     * @param {FunctionId}keyword the keyword is used as a FunctionId
     * @param {function}f
     * @param {int}position
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$addScoringFunction
     */
    this.$addScoringFunction = function (keyword, f, position) {
        this._s.$setFunction(keyword, f);
        this._asf.splice(position, 0, keyword);
    };
    
    /**
     * @param {Actor}observedActor
     * @param {Actor}observerActor
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$recalculateScores
     */
    this.$recalculateScores = function (observedActor, observerActor) {
        var asf = this._asf, funcs = this._F, as = this._as;
        var observedId = observedActor.id, observerId = observerActor.id;
        var observedAs = as[observedId] || (as[observedId] = {});
        var score = observedAs[observerId] || (observedAs[observerId] = {});
        var finalScore = 0, z = asf.length, z0 = z - 1;
        while (z--) {
            var keyword = asf[z0 - z];
            var thatScore = funcs[keyword](observerActor, observedActor);
            score[keyword] = thatScore;
            finalScore += thatScore;
        }
        score.SCORE = finalScore;
    };
    
    /**
     *
     * @param {number}threshold
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$setAllianceThreshold
     */
    this.$setAllianceThreshold = function (threshold) {
        // warCouncilRecurrentAction is a function defined at the beginning of this WarEngine.
        this._F.warCouncilRecurrentAction.allianceThreshold = threshold;
        this._s._State.allianceThreshold = threshold;
    };
    
    /**
     *
     * @param {number}threshold
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$setWarThreshold
     */
    this.$setWarThreshold = function (threshold) {
        // warCouncilRecurrentAction is a function defined at the beginning of this WarEngine.
        this._F.warCouncilRecurrentAction.warThreshold = threshold;
        this._s._State.warThreshold = threshold;
    };
    
    /**
     *
     * @return {number}
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$getAllianceThreshold
     */
    this.$getAllianceThreshold = function () {
        return this._s._State.allianceThreshold;
    };
    
    /**
     *
     * @return {number}
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$getWarThreshold
     */
    this.$getWarThreshold = function () {
        return this._s._State.warThreshold;
    };
    
    /**
     * Returns a dictionary with an {@link ActorId} as key, and as value: a dictionary with another {@link ActorId} as key,
     * and as value: -1 is there's a war between those 2 actors, 1 if there's an alliance.
     * @return {Object.<ActorId,Object.<ActorId,number>>}
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$getAlliancesAndWars
     */
    this.$getAlliancesAndWars = function () {
        return this._a;
    };
    
    /**
     *
     * @return {Object.<ActorId,Object.<ActorId,Object.<string,number>>>}
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$getScores
     */
    this.$getScores = function () {
        return this._as;
    };
    
    /**
     *  true if those 2 actors are at war
     * @param {ActorId} actorIdA  an actorId
     * @param {ActorId} actorIdB  another actorId
     * @return {boolean}
     * @lends worldScripts.DayDiplomacy_040_WarEngine.$areActorsWarring
     */
    this.$areActorsWarring = function (actorIdA, actorIdB) {
        // FIXME use hasOwnProperty ?
        var tmp = this._a[actorIdA];
        return tmp && tmp[actorIdB] === -1
    };
    
    /* ************************** OXP private functions *******************************************************/
    
    /**
     *
     * @param {ActorId}aSystemId
     * @param {ActorId}anotherSystemId
     * @private
     */
    this._ally = function (aSystemId, anotherSystemId) {
        var a = this._a; // alliances and wars
        a[aSystemId] = a[aSystemId] || {};
        a[aSystemId][anotherSystemId] = 1; // Alliance
        a[anotherSystemId] = a[anotherSystemId] || {};
        a[anotherSystemId][aSystemId] = 1; // Alliance
        this._s.$makeActorEventKnownToUniverse(aSystemId, "ALLY", [anotherSystemId]);
        this._s.$makeActorEventKnownToUniverse(anotherSystemId, "ALLY", [aSystemId]);
        // Commented out because closure
        // log("DiplomacyWarEngine", "Alliance between " + aSystemId + " and " + anotherSystemId);
    };
    
    /**
     *
     * @param {ActorId}aSystemId
     * @param {ActorId}anotherSystemId
     * @private
     */
    this._breakAlliance = function (aSystemId, anotherSystemId) {
        var a = this._a; // Alliances and wars
        a[aSystemId] && a[aSystemId][anotherSystemId]  === 1 && (delete a[aSystemId][anotherSystemId]); // Breaking alliance
        a[anotherSystemId] && a[anotherSystemId][aSystemId] === 1 && (delete a[anotherSystemId][aSystemId]); // Breaking alliance
        this._s.$makeActorEventKnownToUniverse(aSystemId, "BREAK", [anotherSystemId]);
        this._s.$makeActorEventKnownToUniverse(anotherSystemId, "BREAK", [aSystemId]);
        // Commented out because closure
        // log("DiplomacyWarEngine", "Alliance broken between " + aSystemId + " and " + anotherSystemId);
    };
    
    /**
     *
     * @param {ActorId}aSystemId
     * @param {ActorId}anotherSystemId
     * @private
     */
    this._declareWar = function (aSystemId, anotherSystemId) {
        var a = this._a; // Alliances and wars
        a[aSystemId] = a[aSystemId] || {};
        a[aSystemId][anotherSystemId] = -1; // War
        a[anotherSystemId] = a[anotherSystemId] || {};
        a[anotherSystemId][aSystemId] = -1; // War
        this._s.$makeActorEventKnownToUniverse(aSystemId, "WAR", [anotherSystemId]);
        this._s.$makeActorEventKnownToUniverse(anotherSystemId, "WAR", [aSystemId]);
        // Commented out because closure
        // log("DiplomacyWarEngine", "War between " + aSystemId + " and " + anotherSystemId);
    };
    
    /**
     *
     * @param {ActorId} aSystemId
     * @param {ActorId} anotherSystemId
     * @private
     */
    this._makePeace = function (aSystemId, anotherSystemId) {
        var a = this._a; // Alliances and wars
        a[aSystemId] && a[aSystemId][anotherSystemId] === -1 && (delete a[aSystemId][anotherSystemId]); // Making peace
        a[anotherSystemId] && a[anotherSystemId][aSystemId] === -1 && (delete a[anotherSystemId][aSystemId]); // Making peace
        this._s.$makeActorEventKnownToUniverse(aSystemId, "PEACE", [anotherSystemId]);
        this._s.$makeActorEventKnownToUniverse(anotherSystemId, "PEACE", [aSystemId]);
        // Commented out because closure
        // log("DiplomacyWarEngine", "Peace between " + aSystemId + " and " + anotherSystemId);
    };
    
    /**
     *
     * @private
     */
    this._initAllyScore = function () {
        var engine = this._s;
    
        if (engine.$getEventTypes().indexOf("ALLYSCORE") === -1) {
            engine.$addEventType("ALLYSCORE", 1);
            // Function to calculate scores, here is the system for which scores are calculated
            var diplomacyAlliancesScoringRecurrentAction = function diplomacyAlliancesScoringRecurrentAction(aSystem) {
                // FIXME perfectfunc should be actor-agnostic
                var observersId = aSystem.observers["SYSTEM"];
                if (!observersId) {
                    return; // There may be no observer yet.
                }
                var that = diplomacyAlliancesScoringRecurrentAction;
                var we = that.warEngine || (that.warEngine = worldScripts.DayDiplomacy_040_WarEngine);
                var engine = that._engine || (that._engine = worldScripts.DayDiplomacy_000_Engine);
                var actors = engine.$getActors();
                var y = observersId.length;
                while (y--) {
                    we.$recalculateScores(actors[observersId[y]], aSystem);
                }
            };
            var fid = "diplomacyAlliancesScoringRecurrentAction";
            engine.$setFunction(fid, diplomacyAlliancesScoringRecurrentAction);
            engine.$setRecurrentAction(engine.$buildAction(engine.$getNewActionId(), "ALLYSCORE", "SYSTEM", fid));
        }
    };
    
    /**
     * @private
     */
    this._init = function () {
        var engine = this._s;
        var history = worldScripts.DayDiplomacy_020_History;
    
        if (engine.$getEventTypes().indexOf("BREAK") !== -1) {
            return; // Already initialized
        }
    
        // Creating events
        engine.$addEventType("WARCOUNCIL", 2);
        engine.$addEventType("BREAK", 3);
        engine.$addEventType("ALLY", 4);
        engine.$addEventType("WAR", 5);
        engine.$addEventType("PEACE", 6);
    
        // Managing history sentences
        history.$setEventFormattingFunction("BREAK",
            /**
             * @param {DiplomacyEvent} breakEvent
             * @return {string} the formatted message
             */
            function breakEventFormattingFunction(breakEvent) {
                var f = breakEventFormattingFunction;
                var engine = f._engine || (f._engine = worldScripts.DayDiplomacy_000_Engine);
                var actors = engine.$getActors();
                return actors[breakEvent.actorId].name + " broke their alliance with " + actors[breakEvent.args[0]].name + ".";
            });
        history.$setEventFormattingFunction("ALLY",
            /**
             * FIXME create a type AllyEvent?
             * @param  {DiplomacyEvent} allyEvent
             * @return {string}
             */
            function allyEventFormattingFunction(allyEvent) {
                var f = allyEventFormattingFunction;
                var engine = f._engine || (f._engine = worldScripts.DayDiplomacy_000_Engine);
                var actors = engine.$getActors();
                return actors[allyEvent.actorId].name + " allied with " + actors[allyEvent.args[0]].name + ".";
        });
        history.$setEventFormattingFunction("WAR",
            /**
             * @param  {DiplomacyEvent} warEvent
             * @return {string}
             */
            function warEventFormattingFunction(warEvent) {
                var f = warEventFormattingFunction;
                var engine = f._engine || (f._engine = worldScripts.DayDiplomacy_000_Engine);
                var actors = engine.$getActors();
                return actors[warEvent.actorId].name + " declared war with " + actors[warEvent.args[0]].name + ".";
        });
        history.$setEventFormattingFunction("PEACE",
            /**
             * @param  {DiplomacyEvent} peaceEvent
             * @return {string}
             */
            function peaceEventFormattingFunction(peaceEvent) {
                var f = peaceEventFormattingFunction;
                var engine = f._engine || (f._engine = worldScripts.DayDiplomacy_000_Engine);
                var actors = engine.$getActors();
                return actors[peaceEvent.actorId].name + " made peace with " + actors[peaceEvent.args[0]].name + ".";
        });
    
        // Function to ally, break alliance, declare war or peace: here, aSystem is the system to which the action might be directed.
        var warCouncilRecurrentAction = function warCouncilRecurrentAction(aSystem) {
            var that = warCouncilRecurrentAction;
            var alliancesScores = that.alliancesScores || (that.alliancesScores = worldScripts.DayDiplomacy_000_Engine._State.alliancesScores);
            var a = that.alliancesAndWars || (that.alliancesAndWars = worldScripts.DayDiplomacy_000_Engine._State.alliancesAndWars);
            var allianceThreshold = that.allianceThreshold || (that.allianceThreshold = worldScripts.DayDiplomacy_000_Engine._State.allianceThreshold);
            var warThreshold = that.warThreshold || (that.warThreshold = worldScripts.DayDiplomacy_000_Engine._State.warThreshold);
            var aSystemId = aSystem.id;
            var aSystemScores = alliancesScores[aSystemId];
            var warEngine = that.warEngine || (that.warEngine = worldScripts.DayDiplomacy_040_WarEngine);
    
            for (var targetId in aSystemScores) {
                if (aSystemScores.hasOwnProperty(targetId)) {
                    // Alliance
                    if ((!a.hasOwnProperty(targetId) || !a[targetId].hasOwnProperty(aSystemId) || a[targetId][aSystemId] !== 1) // Not yet allied
                        && aSystemScores[targetId].SCORE >= allianceThreshold
                        && alliancesScores[targetId][aSystemId].SCORE >= allianceThreshold) { // Both are willing
                        warEngine._ally(aSystemId, targetId);
                    }
    
                    // Break
                    if ((a.hasOwnProperty(targetId) && a[targetId][aSystemId] === 1) // Allied
                        && (aSystemScores[targetId].SCORE < allianceThreshold
                            || alliancesScores[targetId][aSystemId].SCORE < allianceThreshold)) { // One is willing to break
                        warEngine._breakAlliance(aSystemId, targetId);
                    }
    
                    // War
                    if ((!a.hasOwnProperty(targetId) || !a[targetId].hasOwnProperty(aSystemId) || a[targetId][aSystemId] !== -1) // Not yet warring
                        && (aSystemScores[targetId].SCORE <= warThreshold || alliancesScores[targetId][aSystemId].SCORE <= warThreshold)) { // One is willing
                        warEngine._declareWar(aSystemId, targetId);
                    }
    
                    // Peace
                    if ((a.hasOwnProperty(targetId) && a[targetId][aSystemId] === -1) // Warring
                        && aSystemScores[targetId].SCORE > warThreshold && alliancesScores[targetId][aSystemId].SCORE > warThreshold) { // Both are willing
                        warEngine._makePeace(aSystemId, targetId);
                    }
                }
            }
        };
        var fid = "warCouncilRecurrentAction";
        engine.$setFunction(fid, warCouncilRecurrentAction);
        engine.$setRecurrentAction(engine.$buildAction(engine.$getNewActionId(), "WARCOUNCIL", "SYSTEM", fid));
    
        this.$setAllianceThreshold(.5); // Default value for the very first initialization
        this.$setWarThreshold(-1); // Default value for the very first initialization
    };
    
    this._startUp = function () {
        var engine = this._s;
        this._F = engine.$getFunctions();
    
        // Alliances Scoring _Functions: { keyword => fid }
        this._asf = engine.$initAndReturnSavedData("alliancesScoringFunctions", []);
        // Alliances Scores: { observedId => { observerId => { keyword => score } } }
        this._as = engine.$initAndReturnSavedData("alliancesScores", {});
        this._a = engine.$initAndReturnSavedData("alliancesAndWars", {});
    
        this._initAllyScore();
        this._init(); // ALLY/BREAK/WAR/PEACE
    
        this.$setAllianceThreshold(this._s._State.allianceThreshold); // Startup init using saved value
        this.$setWarThreshold(this._s._State.warThreshold); // Startup init using saved value
    
        delete this._startUp; // No need to startup twice
    };
    
    /* ************************** Oolite events ***************************************************************/
    
    this.startUp = function () {
        this._s = worldScripts.DayDiplomacy_000_Engine;
        this._s.$subscribe(this.name);
        delete this.startUp; // No need to startup twice
    };