Back to Index Page generated: Nov 12, 2024, 11:02:04 PM

Expansion Lave

Content

Warnings

  1. Required Expansions mismatch between OXP Manifest and Expansion Manager at character position 0065 (DIGIT ZERO vs LATIN SMALL LETTER N)
  2. No version in dependency reference to oolite.oxp.Thargoid.LaveAcademy:null
  3. No version in dependency reference to oolite.oxp.stranger.FPO_Lave:null
  4. Conflict Expansions mismatch between OXP Manifest and Expansion Manager at character position 0062 (DIGIT ZERO vs LATIN SMALL LETTER N)

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Adds some eye candy and background story to the Ooniverse by recreating elements from the Holdstock novella 'The Dark Wheel' in the Lave system. Adds some eye candy and background story to the Ooniverse by recreating elements from the Holdstock novella 'The Dark Wheel' in the Lave system.
Identifier oolite.oxp.murgh.Lave oolite.oxp.murgh.Lave
Title Lave Lave
Category Ambience Ambience
Author Murgh & phkb Murgh & phkb
Version 2.2 2.2
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
  • oolite.oxp.Thargoid.LaveAcademy:0
  • oolite.oxp.Thargoid.LaveAcademy:
  • Optional Expansions
    Conflict Expansions
  • oolite.oxp.stranger.FPO_Lave:0
  • oolite.oxp.stranger.FPO_Lave:
  • Information URL https://wiki.alioth.net/index.php/Lave_OXP n/a
    Download URL https://wiki.alioth.net/img_auth.php/9/9a/Lave_2.2.oxz n/a
    License CC-BY-SA-NC 4.0 CC-BY-SA-NC 4.0
    File Size n/a
    Upload date 1716769315

    Documentation

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

    lave_readme.txt

    Lave.oxp
    by murgh
    
    Brief
    =====
    The scene that unfolds upon exiting Lave station, with elements from The Dark Wheel. An Ironassed reboot of my 2006 Lave.OXP with some improvements all-round. 
    
    In essence
    - So far the elements that appear are;
    - The Lave flight school pupils and instructor in carefree whimsy
    - A gabble of ADdroids around the buoy
    - Two restaurant junks soliciting
    - A lurking, somewhat sinister Cobra Mk.III
    - Nearby passenger ships Coral. Anaconda SpaceWays and a Moray transport variant
    
    Notes on the update
    ===================
    I've updated all the ship models to use a Griff version of some sort. And I've added a requirement to have the Lave Academy installed (so the Instructors and Learners can have an obvious point of origin). 
    
    For FPO Lave, I've made it a conflict OXP, because I think the city lights on this OXP are an improvement, and there is no other way to work around the conflict. I've copied over any missing data to this OXP.
    
    Acknowledgements
    ================
    Gratitude to Sir Giles who made the toy, 
    Mr.Holdstock who wrote The Dark Wheel,
    Cmdr.Kaks and one Cmdr.Anon who made improvements to the original Lave.OXP in my absence.
    
    License
    =======
    This work is available for reuse under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 Generic, CC BY-NC-SA 4.0
    
    To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. 
    
    You are free to:
    • Share — copy and redistribute the material in any medium or format
    • Adapt — remix, transform, and build upon the material
    
    Under the following terms: 
    
    • Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 
    • Non-commercial — You may not use this work for commercial purposes. 
    • Share Alike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
    • No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
    
    Changelog
    =========
    V2.2 - 28 Apr 2024
    - Added PlanetFall 2.0 integration.
    v2.1 - 24 Feb 2024
    - Made sure the new Lave planet texture is used, no matter what other OXP's are installed.
    - Tweaked cloud layer to be more obvious.
    v2.0 - 23 Feb 2024
    - Converted all plist files to OpenStep format.
    - Renamed ship data keys to make them more unique and less likely to run into conflicts with other OXPs.
    - Switched to Griff's models for all ships
        Used a Hognose pulling a billboard to replace the Add Droids
        Used a Boa Class Cruiser to replace the Coral Liner.
        Used a Sidewinder Scout Ship to replace the Learner Trinket.
        Used a Asp Mark II to replace the Instructor Trinket.
        Used a Worm to replace the Food trucks.
        Used a Ophidian for the passenger transport (instead of an Anaconda).
        Used a Bug for the two transporters (instead of Morays).
    - Change spawning functions to script.
    - Added scriptInfo for Random Ship Names for most ships, so they will get an appropriate name from that OXP.
    - Added new moon texture.
    - Added new main planet texture with city lights.
    - Made the Lave Academy OXP a requirement for this one.
    - Make the FPO Lave OXP a conflicting OXP, so the textures from this OXP can be used instead.
    - Added three mini-missions that can trigger when you dock at the Academy.
    
    b1 - 04/22	
    Complete makeover. WIP publish.
    
    v.1.71 - 31 Dec 2009
    - Fixed a C&P error in junkAI.plist.
    - Fixed an error in the krait definition.
    - Fixed instructorAI and traineeAI. (added missing "WAYPOINT_SET" instructions)
    - Added a pilot to the billboards so they can broadcast both with 1.65 Oolite as with current versions.
    
    v. 1.70
    Kaks - 28 Jan 2008
    - resolved all conflicts with transports.oxp.
    - added one extra texture for the Moray Transport.
    - added specific Coral escorts to the Liner, as per transports.oxp.
    - updated demoships.plist to show all ship models.
    - removed views & max_missiles from NPC ships.
    - doesn't require any other OXP.
    
    
    

    Equipment

    This expansion declares no equipment. This may be related to warnings.

    Ships

    Name
    Pilot Examiner
    Lave Billboard
    lave_billboard_01
    lave_billboard_02
    lave_billboard_03
    lave_billboard_04
    Bug Transport
    lave_bug_transporterL
    lave_cobra3_customer
    Herald Ad-Droid
    lave_hog_add1
    lave_hog_add2
    lave_hog_add3
    lave_hog_add4
    Boa Class StarLiner
    lave_mission1_instructor
    lave_mission1_trainee
    lave_mission2_trainee1
    lave_mission2_trainee2
    lave_mission3_trainee
    Ophidian Passenger Transport
    Paddy Restaurant Junk
    Paddy Restaurant Junk
    Sidewinder Schoolship
    Sidewinder Liner Escort
    Tug Line

    Models

    This expansion declares no models. This may be related to warnings.

    Scripts

    Path
    Scripts/lave_mission_1.js
    "use strict";
    this.name = "lave_mission_1";
    this.author = "phkb";
    this.copyright = "2024 phkb";
    this.description = "Mission type 1 for Lave system";
    this.license = "CC-BY-NC-SA 4.0";
    
    this._missionShips = [];
    this._pirates = [];
    this._progress = null;
    this._sentLastOneComms = false;
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function() {
        var status = worldScripts.lave_missions._missionStatus["mission_1"];
        // check if the player has reloaded the game while it was in progress
        if (status == "STARTED") {
            this.$setupMission();
            return;
        }
        if (status == "FAILED" || status == "PARTIAL_COMPLETE" || status == "COMPLETE") {
            this.missionScreenOpportunity = this.missionScreenOpportunity_hold;
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$setupMission = function() {
        mission.setInstructionsKey(this.name + "_instructions", this.name);
    
        this.shipLaunchedFromStation = this.shipLaunchedFromStation_hold;
        this.missionScreenOpportunity = this.missionScreenOpportunity_hold;
        this.shipWillDockWithStation = this.shipWillDockWithStation_hold;
        this.shipWillEnterWitchspace = this.shipWillEnterWitchspace_hold;
        this.playerEnteredNewGalaxy = this.playerEnteredNewGalaxy_hold;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$cleanUp = function() {
        delete this.shipLaunchedFromStation;
        delete this.missionScreenOpportunity;
        delete this.shipWillDockWithStation;
        delete this.shipWillEnterWitchspace;
        delete this.playerEnteredNewGalaxy;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipLaunchedFromStation_hold = function(station) {
        var pos = Vector3D.interpolate(system.sun.position, station.position, 0.66);
    
        // create the instructor flight and put them all in a group
        this._missionShips = system.addShips("instructor_mission", 1, pos, 1000);
        // make sure all the escorts are in the missionShips array
        for (var i = 0; i < this._missionShips[0].escorts.length; i++) {
            this._missionShips.push(this._missionShips[0].escorts[i]);
        }
    
        // create our pirates
        this._pirates = system.addShips("pirate", 4, pos, 10000);
        var prGroup = new ShipGroup("pirate_flight", this._pirates[0]);
        var i = this._pirates.length;
        while (i--) {
            // we're removing fuel injectors, because this mission gets messy if everything spreads out too much
            // and the player has to chase the bad guys too much
            this._pirates[i].removeEquipment("EQ_FUEL_INJECTION");
            prGroup.addShip(this._pirates[i]);
            this._pirates[i].group = prGroup;
            this._pirates[i].target = this._missionShips[parseInt(Math.random() * this._missionShips.length)];
            this._pirates[i].performAttack();
        }
    
        this._progress = new Timer(this, this.$checkProgress, 5, 5);
        delete this.shipLaunchedFromStation;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipWillEnterWitchspace_hold = function() {
        if (worldScripts.lave_missions._missionStatus["mission_1"] == "STARTED") {
            worldScripts.lave_missions._missionStatus["mission_1"] = "FAILED";
            mission.setInstructionsKey(this.name + "_instructions_complete", this.name);
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerEnteredNewGalaxy_hold = function() {
        if (worldScripts.lave_missions._missionStatus["mission_1"] == "STARTED") {
            worldScripts.lave_missions._missionStatus["mission_1"] = "FINISHED";
            mission.setInstructionsKey(null, this.name);
            this.$cleanUp();
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipWillDockWithStation_hold = function(station) {
        if (this._progress.isRunning) this._progress.stop();
        var status = worldScripts.lave_missions._missionStatus["mission_1"];
        if (status == "STARTED") {
            // have we finished but wasn't close enough to any of the mission ships to get the final message?
            var pr = this._pirates;
            var i = pr.length;
            while (i--) {
                // if there's at least one valid pirate left, it's still on
                // todo: check if they ran away - despawn them if they get too far from the mission ships
                if (pr[i] && pr[i].isValid) return;
            }
            var ms = this._missionShips;
            var i = ms.length;
            var deaths = 0;
            while (i--) {
                if (!ms[i] || !ms[i].isValid) deaths += 1;
            }
            if (deaths == ms.length) {
                worldScripts.lave_missions._missionStatus["mission_1"] = "FAILED";
            } else {
                worldScripts.lave_missions._missionStatus["mission_1"] = (deaths > 0 ? "PARTIAL_COMPLETE" : "COMPLETE");                
            }
            mission.setInstructionsKey(this.name + "_instructions_complete", this.name);
            this.$sendFlightHome();
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.missionScreenOpportunity_hold = function() {
        if (player.ship.dockedStation.dataKey == "laveAcademy_academy") {
            var status = worldScripts.lave_missions._missionStatus["mission_1"];
            if (status == "NOT_STARTED" || status == "FINISHED" || status == "STARTED") return; // player redocked for some reason
            // otherwise, we're finished
            worldScripts.lave_missions._missionStatus["mission_1"] = "FINISHED";
            switch (status) {
                case "FAILED":
                    mission.runScreen({ title: "No survivors", messageKey: "lave_mission_1_failed", screenID:"lave_missions" });
                    break;
                case "PARTIAL_COMPLETE":
                    mission.runScreen({ title: "A hard battle", messageKey: "lave_mission_1_partial", screenID:"lave_missions" });
                    player.credits += 200;
                    break;
                case "COMPLETE":
                    mission.runScreen({ title: "A resounding success", messageKey: "lave_mission_1_complete", screenID:"lave_missions" });
                    player.credits += 1000;
                    break;
            }
            mission.setInstructionsKey(null, this.name);
            this.$cleanUp();
            worldScripts.lave_missions._checkForMission = false; // so we don't get asked about a new mission when we finish this one
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$checkProgress = function $checkProgress() {
        var pr = this._pirates;
        var i = pr.length;
        var enemies = 0;
        while (i--) {
            // if there's at least one valid pirate left, it's still on
            if (pr[i] && pr[i].isValid) {
                // todo: check if they ran away - despawn them if they get too far from the mission ships
                enemies += 1;
            }
        }
        if (enemies == 1 && this._sentLastOneComms == false) {
            this._sentLastOneComms = true;
            var sc = player.ship.checkScanner(true);
            i = sc.length;
            while (i--) {
                if (sc[i].roles.indexOf("lave_mission")) {
                    sc[i].commsMessage("There's still one out there!", player.ship);
                    break;
                }
            }
        }
        if (enemies > 0) return;
        var ms = this._missionShips;
        var i = ms.length;
        var commSource;
        var deaths = 0;
        while (i--) {
            if (!ms[i] || !ms[i].isValid) {
                deaths += 1;
            } else {
                commSource = ms[i]; // get a reference to a valid ship object
            }
        }
        if (deaths == ms.length) {
            worldScripts.lave_missions._missionStatus["mission_1"] = "FAILED";
            this._progress.stop();
            return;
        }
        // we'd only get to this point if all the pirates are dead and at least 1 of the group is still alive
        // get one of the ships to send a message once they're in range
        if (commSource && commSource.isValid) {
            if (commSource.position.distanceTo(player.ship) < player.ship.scannerRange) {
                commSource.commsMessage("Thanks for your help, commander! You arrived just in time!", player.ship);
                worldScripts.lave_missions._missionStatus["mission_1"] = (deaths > 0 ? "PARTIAL_COMPLETE" : "COMPLETE");
                mission.setInstructionsKey(this.name + "_instructions_complete", this.name);
                this._progress.stop();
            }
        }
        this.$sendFlightHome();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$sendFlightHome = function() {
        if (this._missionShips.length == 0) return;
        var stnLoc = system.shipsWithRole("laveAcademy_academy")[0];
        // check if we got an actual ship location returned
        if (stnLoc) {
            if (this._missionShips[0].isValid) {
                this._missionShips[0].switchAI("oolite-shuttleAI.js");
                // send leader back to academy - escorts will follow so no need to do this for them
    			this._missionShips[0].AIScript.oolite_priorityai.setParameter("oolite_selectedPlanet", null);
    			this._missionShips[0].AIScript.oolite_priorityai.setParameter("oolite_selectedStation", stnLoc);
                this._missionShips[0].AIScript.oolite_priorityai.reconsiderNow();
            } else {
                // unless the leader is dead, so send all trainees back to the station individually
                for (var i = 1; i < this._missionShips.length; i++) {
                    if (this._missionShips[i].isValid) {
                        this._missionShips[i].switchAI("oolite-shuttleAI.js");
                        this._missionShips[i].AIScript.oolite_priorityai.setParameter("oolite_selectedPlanet", null);
                        this._missionShips[i].AIScript.oolite_priorityai.setParameter("oolite_selectedStation", stnLoc);
                        this._missionShips[i].AIScript.oolite_priorityai.reconsiderNow();
                    }
                }
            }
        }
    }
    Scripts/lave_mission_2.js
    "use strict";
    this.name = "lave_mission_2";
    this.author = "phkb";
    this.copyright = "2024 phkb";
    this.description = "Mission type 2 for Lave system";
    this.license = "CC-BY-NC-SA 4.0";
    
    this._targetSystem = -2;
    this._sourceSystem = -1;
    this._missionShip = null;
    this._messages = [];
    this._checkForRefueling = false;
    this._dist = 0;
    this._holdShipName = "";
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function() {
        if (missionVariables.LaveMission_SourceSystemID) {
            this._sourceSystem = missionVariables.LaveMission2_SourceSystemID;
        }
        if (missionVariables.LaveMission_TargetSystemID) {
            this._targetSystem = missionVariables.LaveMission2_TargetSystemID;
        }
        if (this._sourceSystem != -1 && this._targetSystem != -2) {
            this.$setupFunctions();
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$setupMission = function() {
        // pick a system within 7 ly of current.
        var syslist = system.info.systemsInRange(7);
        var sys = syslist[parseInt(Math.random() * syslist.length)];
        this._targetSystem = sys.systemID;
        this._sourceSystem = system.ID;
    
        missionVariables.LaveMission2_SourceSystemID = system.ID;
        missionVariables.LaveMission2_SourceSystem = system.name;
        missionVariables.LaveMission2_TargetSystemID = this._targetSystem;
    
        this.$setupFunctions();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$setupFunctions = function() {
        this.systemWillPopulate = this.systemWillPopulate_hold;
        this.missionScreenOpportunity = this.missionScreenOpportunity_hold;
        this.playerEnteredNewGalaxy = this.playerEnteredNewGalaxy_hold;
        this.playerWillEnterWitchspace = this.playerWillEnterWitchspace_hold;
    
        mission.setInstructionsKey(this.name + "_instructions", this.name);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$cleanUp = function() {
        this._targetSystem = -2;
        if (this._progress && this._progress.isRunning) this._progress.stop();
        delete missionVariables.LaveMission2_SourceSystem;
        delete missionVariables.LaveMission2_TargetSystemID;
        delete missionVariables.LaveMission2_SourceSystemID;
        delete this.systemWillPopulate;
        delete this.missionScreenOpportunity;
        delete this.playerEnteredNewGalaxy;
        delete this.playerWillEnterWitchspace;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerWillEnterWitchspace = function() {
        if (this._progress && this._progress.isRunning) this._progress.stop();
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.systemWillPopulate_hold = function() {
        if (system.ID == this._targetSystem) {
            this._missionShip = system.addShips("trainee_lost1", 1, [0, 0, 0], 5000)[0];
            this._progress = new Timer(this, this.$checkProgress.bind(this), 10, 5);
        }
        if (system.ID == this._sourceSystem && worldScripts.lave_missions._missionStatus["mission_2"] == "FOUND") {
            this._missionShip = system.addShips("trainee_lost2", 1, [0, 0, 0], 5000)[0];
            this._delay = new Timer(this, this.$sendFlightHome, 3, 0);
            if (this._holdShipName != "") {
                this._missionShip.displayName = this._holdShipName;
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerEnteredNewGalaxy_hold = function() {
        if (worldScripts.lave_missions._missionStatus["mission_2"] == "STARTED") {
            worldScripts.lave_missions._missionStatus["mission_2"] = "FINISHED";
            mission.setInstructionsKey(null, this.name);
            this.$cleanUp();
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.missionScreenOpportunity_hold = function() {
        if (player.ship.dockedStation.dataKey == "laveAcademy_academy") {
            var status = worldScripts.lave_missions._missionStatus["mission_2"];
            if (status == "NOT_STARTED" || status == "FINISHED" || status == "STARTED") return; // player redocked for some reason
            // otherwise, we're finished
            switch (status) {
                case "FAILED":
                    mission.runScreen({ title: "Unfortunate Outcome", messageKey: "lave_mission_2_failed", screenID:"lave_missions" });
                    break;
                case "FOUND":
                    // trainee hasn't docked yet
                    return;
                case "COMPLETE":
                    mission.runScreen({ title: "A resounding success", messageKey: "lave_mission_2_complete", screenID:"lave_missions" });
                    player.credits += 1000;
                    break;
            }
            worldScripts.lave_missions._missionStatus["mission_2"] = "FINISHED";
            mission.setInstructionsKey(null, this.name);
            this.$cleanUp();
            worldScripts.lave_missions._checkForMission = false; // so we don't get asked about a new mission when we finish this one
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$checkProgress = function $checkProgress() {
        if (system.ID == this._targetSystem) {
            if (worldScripts.lave_missions._missionStatus["mission_2"] == "STARTED" && this._missionShip.position.distanceTo(player.ship) < player.ship.scannerRange * 0.8) {
                worldScripts.lave_missions._missionStatus["mission_2"] = "FOUND";
                mission.setInstructionsKey(this.name + "_instructions_found", this.name);
                if (this._holdShipName == "") this._holdShipName = this._missionShip.displayName;
                this._dist = system.info.distanceToSystem(System.infoForSystem(galaxyNumber, this._sourceSystem));
                if (player.ship.fuel < this._dist) {
                    // offer some fuel to player
                    this._messages = expandMissionText("lave_mission_2_offerfuel").split("|");
                    this._checkForRefueling = true;
                } else {
                    // thank player for coming
                    this._messages = expandMissionText("lave_mission_2_thanksforhelp").split("|");
                }
            }
        }
        if (this._messages.length > 0 && this._missionShip.position.distanceTo(player.ship) < player.ship.scannerRange) {
            var text = this._messages.shift();
            this._missionShip.commsMessage(text, player.ship);
        }
        if (this._checkForRefueling == true && player.ship.fuel < this._dist && this._missionShip.position.distanceTo(player.ship) <= 86) {
            player.consoleMessage("1LY of fuel transferred", 4);
            player.ship.fuel += 1;
        }
        if (this._checkForRefueling == true && player.ship.fuel >= this._dist) {
            this._checkForRefueling = false;
            this._missionShip.commsMessage("That should be enough, don't you think?", player.ship);
            this._messages = ["I'm ready to follow you as soon as you open a wormhole."];
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$updateStatus = function() {
        mission.setInstructionsKey(this.name + "_instructions_complete", this.name);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$sendFlightHome = function $sendFlightHome() {
        var stnLoc = system.shipsWithRole("laveAcademy_academy")[0];
        // check if we got an actual ship location returned
        if (stnLoc) {
            if (this._missionShip.isValid) {
                // send ship back to academy
    			this._missionShip.AIScript.oolite_priorityai.setParameter("oolite_selectedPlanet", null);
    			this._missionShip.AIScript.oolite_priorityai.setParameter("oolite_selectedStation", stnLoc);
                this._missionShip.AIScript.oolite_priorityai.reconsiderNow();
            }
        }
    }
    Scripts/lave_mission_3.js
    "use strict";
    this.name = "lave_mission_3";
    this.author = "phkb";
    this.copyright = "2024 phkb";
    this.description = "Mission type 3 for Lave system";
    this.license = "CC-BY-NC-SA 4.0";
    
    this._rhRoles = ["rockhermit", "rockhermit-chaotic", "rockhermit-pirate"];
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function() {
        if (missionVariables.LaveMission3_SourceSystemID) {
            if (system.ID == missionVariables.LaveMission3_SourceSystemID) {
                this.$setupMission();
            } else {
                this.systemWillPopulate = this.systemWillPopulate_hold;
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$setupMission = function() {
        this.missionScreenOpportunity = this.missionScreenOpportunity_hold;
        this.playerEnteredNewGalaxy =this.playerEnteredNewGalaxy_hold;
    
        var rhlist = [].concat(system.shipsWithRole("rockhermit")).concat(system.shipsWithRole("rockhermit-chaotic")).concat(system.shipsWithRole("rockhermit-pirate"));
        var idx = parseInt(Math.random() * rhlist.length);
        rhlist[idx]._laveMission3 = true;
        mission.setInstructionsKey(this.name + "_instructions", this.name);
        missionVariables.LaveMission3_SourceSystemID = system.ID;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$cleanUp = function() {
        delete missionVariables.LaveMission3_SourceSystemID;
        delete this.missionScreenOpportunity;
        delete this.playerEnteredNewGalaxy;
        delete this.systemWillPopulate;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.systemWillPopulate_hold = function() {
        if (missionVariables.LaveMission3_SourceSystemID && system.ID == missionVariables.LaveMission3_SourceSystemID) {
            this.$setupMission();
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerEnteredNewGalaxy_hold = function() {
        if (worldScripts.lave_missions._missionStatus["mission_3"] == "STARTED") {
            worldScripts.lave_missions._missionStatus["mission_3"] = "FINISHED";
            mission.setInstructionsKey(null, this.name);
            this.$cleanUp();
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.missionScreenOpportunity_hold = function() {
        var stn = player.ship.dockedStation;
        if (this._rhRoles.indexOf(stn.primaryRole) >= 0) {
            if (stn.hasOwnProperty("_laveMission3") && stn._laveMission3 == true) {
                stn._laveMission3 = false;
                worldScripts.lave_missions._missionStatus["mission_3"] = "COMPLETE";
                mission.runScreen({ title: "Another on-time delivery", messageKey: "lave_mission_3_found", screenID:"lave_missions" });
                mission.setInstructionsKey(this.name + "_instructions_complete", this.name);
                this._missionShip = stn.launchShipWithRole("trainee_repaired");
                this._delay = new Timer(this, this.$sendFlightHome, 10, 0);
                return;
            }
        }
        if (player.ship.dockedStation.dataKey == "laveAcademy_academy") {
            var status = worldScripts.lave_missions._missionStatus["mission_3"];
            if (status == "NOT_STARTED" || status == "FINISHED" || status == "STARTED") return; // player redocked for some reason
            // otherwise, we're finished
            switch (status) {
                case "COMPLETE":
                    mission.runScreen({ title: "A resounding success", messageKey: "lave_mission_3_complete", screenID:"lave_missions" });
                    player.credits += 200;
                    break;
            }
            worldScripts.lave_missions._missionStatus["mission_3"] = "FINISHED";
            mission.setInstructionsKey(null, this.name);
            this.$cleanUp();
            worldScripts.lave_missions._checkForMission = false; // so we don't get asked about a new mission when we finish this one
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$sendFlightHome = function $sendFlightHome() {
        var stnLoc = system.shipsWithRole("laveAcademy_academy")[0];
        // check if we got an actual ship location returned
        if (stnLoc) {
            if (this._missionShip.isValid) {
                // send ship back to academy
    			this._missionShip.AIScript.oolite_priorityai.setParameter("oolite_selectedPlanet", null);
    			this._missionShip.AIScript.oolite_priorityai.setParameter("oolite_selectedStation", stnLoc);
                this._missionShip.AIScript.oolite_priorityai.reconsiderNow();
            }
        }
    }
    Scripts/lave_missions.js
    "use strict";
    this.name = "lave_missions";
    this.author = "phkb";
    this.copyright = "2024 phkb";
    this.description = "Missions for Lave";
    this.license = "CC-BY-NC-SA 4.0";
    
    /*
        Mission 1: help a flight of students who have fallen under pirate attack on their way to the sun.
        Mission 2: find lost student who fell through wormhole and got stranded.
        Mission 3: rescue a learner who has broken down and docked at a Rock Hermit. Deliver parts to rock hermit so Learner can repair ship
    */
    this._checkForMission = false;
    this._missionStatus = {};
    this._forceStart = "";
    this._hold = "";
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function() {
        if (missionVariables.LaveMissions) {
            this._missionStatus = JSON.parse(missionVariables.LaveMissions);
        } else {
            this._missionStatus["mission_1"] = "NOT_STARTED";
            this._missionStatus["mission_2"] = "NOT_STARTED";
            this._missionStatus["mission_3"] = "NOT_STARTED";
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function() {
        if (player.ship.dockedStation.dataKey == "laveAcademy_academy") {
            this._checkForMission = true;
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.playerWillSaveGame = function() {
        missionVariables.LaveMissions = JSON.stringify(this._missionStatus);
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipWillDockWithStation = function(station) {
        this._checkForMission = false;
        if (station.dataKey == "laveAcademy_academy") {
            this._checkForMission = true;
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.missionScreenOpportunity = function() {
        if (this._checkForMission == true) {
            this._checkForMission = false;
            if ((player.score >= 128 && Math.random() > 0.9) || this._forceStart != "") {
                this._hold = this.$availableMission();
                if (this._forceStart != "" && this._hold != "NONE") this._hold = this._forceStart;
                if (this._hold == "NONE") return;
                if (this._hold == "mission_3") {
                    missionVariables.LaveMission3_RockHermitSummary = this.$rhSummary();
                }
                // start the mission
                mission.runScreen({ title: "Urgent Help Needed", messageKey: "lave_" + this._hold + "_briefing", choicesKey: "lave_mission_options", screenID:"lave_missions" },
                    function (choice) {
                        var lm = worldScripts.lave_missions;
                        if (!choice) choice = "02_NO";
                        if (choice == "01_YES") {
                            this._missionStatus[lm._hold] = "STARTED";
                            worldScripts["lave_" + lm._hold].$setupMission();
                            mission.runScreen({ title: "Urgent Help Needed", messageKey: "lave_" + this._hold + "_briefing_yes", screenID:"lave_missions"});
                        }
                        if (choice == "02_NO") {
                            this._missionStatus[lm._hold] = "SKIPPED";
                            mission.runScreen({ title: "Urgent Help Needed", messageKey: "lave_" + this._hold + "_briefing_no", screenID:"lave_missions"});
                        }
                    });
                delete missionVariables.LaveMission3_RockHermitSummary;
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$availableMission = function() {
        var avail = "";
        var missions = Object.keys(this._missionStatus);
        var finishedTypes = ["SKIPPED", "FINISHED", "NOT_STARTED"];
        for (var i = 0; i < missions.length; i++) {
            if (this._missionStatus[missions[i]] == "NOT_STARTED" && avail == "") avail = missions[i];
            // if we're part way through a mission, always return "NONE"
            if (finishedTypes.indexOf(this._missionStatus[missions[i]]) == -1) {avail = "NONE"; break; }
        }
        if (avail == "") avail = "NONE";
        return avail;
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.$rhSummary = function() {
        var text = "";
        var rhlist = [].concat(system.shipsWithRole("rockhermit")).concat(system.shipsWithRole("rockhermit-chaotic")).concat(system.shipsWithRole("rockhermit-pirate"));
        if (rhlist.length == 1) text = "there is at least one Rock Hermit in the system";
        if (rhlist.length == 2) text = "there is at least two Rock Hermits in the system";
        if (rhlist.length > 2) text = "there is at least three Rock Hermits in the system";
        if (rhlist.length > 3) text += ", possibly more";
        return text;
    }
    Scripts/lave_populator.js
    "use strict";
    this.name = "lave_populator";
    this.author = "phkb";
    this.copyright = "2024 phkb";
    this.description = "Makeover for the Lave System in Chart One";
    this.license = "CC-BY-NC-SA 4.0";
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUp = function() {
        this.blockSystemRedux();
    	if (worldScripts["Famous Planets Overhaul"]) {
    		// we want to keep our texture for the planet
    		var fpo = worldScripts["Famous Planets Overhaul"];
    		var idx = fpo.planetList[0].indexOf(7);
    		fpo.planetList.splice(idx, 1);
    	}
    
    	var pf = worldScripts.PlanetFall2;
    	if (!pf) return;
        pf._locationOverrides["0 7"] = {
            main: [
                {
                    roles: ["capitalCity", "capitalCity", "leisureComplex", "factory"],
                    names: ["Ashoria (Capital City)", "Port Arcadia (City)", "Daphne Project (Leisure Complex)", "Cowell & MgRath Shipyards"],
                },
            ],
            moon: [
                {
                    roles: ["colonyDome"],
                    names: ["Imperial Palace (Colony Dome)"],
                }
            ],
        };
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.startUpComplete = function() {
        // make sure our texture wins any races to be the last texture installed
        var info = System.infoForSystem(0, 7);
        info.texture = "lave_planet_diffuse.png";
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.systemWillPopulate = function() {
        if (galaxyNumber == 0 && system.ID == 7) {
            var mn = system.addMoon("murgh_moon_lave");
            if (worldScripts.PlanetFall2) {
                var pf = worldScripts.PlanetFall2;
                pf.$addExtraPlanetDocksForPlanet(mn);
            }
            var pl;
            if (system.countShipsWithRole("liner") < 1) {
                pl = Vector3D(14000, 50720, 34350).fromCoordinateSystem("pwm");
                system.addShips("lave_liner", 1, pl);
            }
            if (system.countShipsWithRole("junk") < 1) {
                pl = Vector3D(18730, 62700, 2500).fromCoordinateSystem("pwm");
                system.addShips("junk", 1, pl);
            }
            if (system.countShipsWithRole("junk1") < 1) {
                pl = Vector3D(16650, 70000, 17700).fromCoordinateSystem("pwm");
                system.addShips("junk1", 1, pl);
            }
            if (system.countShipsWithRole("eatcobra") < 1) {
                pl = Vector3D(31410, 71830, 20120).fromCoordinateSystem("pwm");
                system.addShips("eatcobra", 1, pl);
            }
            if (system.countShipsWithRole("bugtran") < 1) {
                pl = Vector3D(13935, 41730, 14230).fromCoordinateSystem("pwm");
                system.addShips("bugtran", 1, pl);
            }
            if (system.countShipsWithRole("ophidianpt") < 1) {
                pl = Vector3D(18935, 41730, 14230).fromCoordinateSystem("pwm");
                system.addShips("ophidianpt", 1, pl);
            }
            if (system.countShipsWithRole("trainee") < 3) {
                pl = Vector3D(16585, 59787, 20280).fromCoordinateSystem("pwm");
                system.addShips("trainee", 3, pl, 2000);
            }
            if (system.countShipsWithRole("instructor") < 1) {
                pl = Vector3D(16585, 59787, 20280).fromCoordinateSystem("pwm");
                system.addShips("instructor", 1, pl, 2000);
            }
            if (system.countShipsWithRole("lave_add1") < 1) {
                pl = Vector3D(18015, 66700, 22486).fromCoordinateSystem("pwm");
                system.addShips("lave_add1", 1, pl, 10000);
            }
            if (system.countShipsWithRole("lave_add2") < 1) {
                pl = Vector3D(18015, 66700, 22486).fromCoordinateSystem("pwm");
                system.addShips("lave_add2", 1, pl, 10000);
            }
            if (system.countShipsWithRole("lave_add3") < 1) {
                pl = Vector3D(18015, 66700, 22486).fromCoordinateSystem("pwm");
                system.addShips("lave_add3", 1, pl, 10000);
            }
            if (system.countShipsWithRole("lave_add4") < 1) {
                pl = Vector3D(18015, 66700, 22486).fromCoordinateSystem("pwm");
                system.addShips("lave_add4", 1, pl, 10000);
            }
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.blockSystemRedux = function () {
    	if (worldScripts["System Redux"]) {
    		var sr = worldScripts["System Redux"];
    		// make sure system redux startup script has been run before we try to add moon exclusions
    		if (!sr.excl) {
    			sr.startUp();
    		}
    		delete sr.startUp;
    		if (sr.excl[0].indexOf(7) == -1) sr.excl[0].push(7);
    	}
    }
    
    Scripts/lave_trainee.js
    "use strict";
    this.name = "lave_trainee";
    this.author = "phkb";
    this.copyright = "2024 phkb";
    this.description = "Ship script for mission type 2 trainee ship";
    this.license = "CC-BY-NC-SA 4.0";
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDockedWithStation = function(station) {
        if (system.ID == worldScripts.lave_mission_2._sourceSystem) {
            worldScripts.lave_missions._missionStatus["mission_2"] = "COMPLETE";
            worldScripts.lave_mission_2.$updateStatus();
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------
    this.shipDied = function() {
        worldScripts.lave_missions._missionStatus["mission_2"] = "FAILED";
        worldScripts.lave_mission_2.$updateStatus();
    }