| Scripts/epc_passenger_contracts.js | "use strict";
this.name = "EnhancedPassengerContracts";
this.author = "phkb";
this.copyright = "2018 phkb";
this.description = "Expands the passenger contracts system";
this.licence = "CC BY-NC-SA 4.0";
/*
    todo:
        passengers who get injured during battles and need medical treatment at the next station
        passengers who steal cargo and disappear
        passengers who steal parcels and disappear
*/
//
this._flags = [1, 1, 1, 1, 1, 1, 1, 1, 1];
this._settings = 511;
this._simulator = false;
this._internal = false;
this._loading = true;
this._emailDisable = false;
this._sendEmailType = 0;
this._informPlayerOfSystem = false;
this._avoidGovTypeLoading = 1.2; // 20% increase
this._avoidSpeciesLoading = 1.3; // 30% increase
this._avoidSystemLoading = 1.05; // 5% for each system
this._smoothFlightLoading = 2.0; // 100% increase
this._bountyLoading = 1.6; // 60% increase
this._noScanLoading = 1.6; // 60% increase
this._bonusSystemLoading = 0.1; // 10%
this._changeTimer = null;
this._monitorWildMovementTimer = null;
this._chatTimer = null;
this._complimentTimer = null;
this._damageTimer = null;
this._impatientTimer = null;
this._interstellarSpaceTimer = null;
this._transmissions = [];
this._govTypeNames = expandDescription("[epc_gov_names]").split("|");
this._inhabitantTypes = expandDescription("[epc_inhabitant_names]").split("|");
this._config = {
    Name: this.name,
    Display: expandDescription("[epc_config_display]"),
    Alias: expandDescription("[epc_config_alias]"),
    Alive: "_config",
    Notify: "$onChange",
    EInt: {
        E0: {
            Name: "_settings",
            Def: 511,
            Min: 0,
            Max: 511,
            Desc: expandDescription("[epc_eint_desc]").split("|")
        },
        Info: expandDescription("[epc_eint_info]")
    },
};
//-------------------------------------------------------------------------------------------------------------
this.$onChange = function () {
    var set = 1;
    for (var i = 0; i < this._flags.length; i++) {
        if (this._settings & set) {
            this._flags[i] = 1;
        } else {
            this._flags[i] = 0;
        }
        set *= 2;
    }
}
//-------------------------------------------------------------------------------------------------------------
this.startUp = function () {
    // turn off standard passenger contract system
    // disable script and empty its dataset
    this.$disableCorePassengerContracts();
}
//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function () {
    if (worldScripts.BountySystem_Core) {
        // tell the bounty system about the new offense type
        var b = worldScripts.BountySystem_Core;
        b.$uncoverBounties_endpoint = b.$uncoverBounties;
        b.$uncoverBounties = this.$ovr_bountySystem_uncoverBounties;
        b._offenceTypes["transporting offenders"] = { description: expandDescription("[epc_offence_transport_offenders]"), severity: 1 };
    }
    if (worldScripts.AutoPrimeEquipment) {
        // sync up autoprime equip so selecting our MFD automatically primes broadcast comms
        worldScripts.AutoPrimeEquipment.$addConfig("EnhancedPassengerMFD", "EQ_BROADCASTCOMMSMFD");
    }
    if (missionVariables.EnhancedPassengers_Settings) {
        // pull out the stored value
        this._settings = parseInt(missionVariables.EnhancedPassengers_Settings);
    } else {
        // build settings from flags array (in case a user has manually changed the defaults)
        var set = 1;
        this._settings = 0;
        for (var i = 0; i < this._flags.length; i++) {
            if (this._flags[i] === 1) {
                this._settings += set;
            }
            set *= 2;
        }
    }
    // register our settings, if Lib_Config is present
    if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this._config);
}
//-------------------------------------------------------------------------------------------------------------
this.systemWillPopulate = function () {
    // add our station key to the main station, so the contracts are only offered there
    if (system.mainStation) {
        worldScripts.BulletinBoardSystem.$addStationKey(this.name, system.mainStation, "mainStation");
    }
}
//-------------------------------------------------------------------------------------------------------------
this.missionScreenOpportunity = function () {
    if (this._loading === true) {
        this._loading = false;
        this.$removeExistingContracts();
        if (this.$countAvailableContracts() === 0) this.$createPassengerContracts();
        // force the interface entry to update
        worldScripts.BulletinBoardSystem.$initInterface(player.ship.dockedStation);
        delete this.missionScreenOpportunity;
    }
}
//-------------------------------------------------------------------------------------------------------------
this.playerEnteredContract = function (type, contract) {
    if (this._internal === true) {
        if (this._sendEmailType > 0) {
            switch (this._sendEmailType) {
                case 1:
                    this.$customPassengerContractEmail(contract);
                    break;
                default:
                    this.$contractAdjustmentEmail(contract);
                    break;
            }
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.playerCompletedContract = function (type, result, fee, contract) {
    if (type === "passenger") {
        var bb = worldScripts.BulletinBoardSystem;
        for (var i = 0; i < bb._data.length; i++) {
            var itm = bb._data[i];
            if (itm != null && itm.accepted === true) {
                // is this item the one we're dealing with?
                if (itm.destination === contract.destination && itm.payment === contract.fee && itm.source === contract.start) {
                    bb.$removeBBMission(itm.ID);
                    break;
                }
            }
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$epc_shipBeingAttacked = function (whom) {
    player.ship.script._epc_attacked += 1;
}
//-------------------------------------------------------------------------------------------------------------
this.$epc_shipTargetDestroyed = function (target) {
    if (Math.random() < 0.4) {
        if (!this._complimentTimer || this._complimentTimer.isRunning === false) {
            this._complimentTimer = new Timer(this, this.$niceShotMessage, 2, 0);
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$epc_shipTakingDamage = function (amount, whom, type) {
    if (amount > 0 && (player.ship.energy - amount) > 0 && Math.random() < 0.6) {
        if (!this._damageTimer || this._damageTimer.isRunning === false) {
            this._damageTimer = new Timer(this, this.$damageMessage, 2, 0);
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.shipDied = function (whom, why) {
    if (this._simulator === true) return;
    this.$stopTimers();
}
//-------------------------------------------------------------------------------------------------------------
this.shipLaunchedFromStation = function (station) {
    if (this.$simulatorRunning() === true) {
        this._simulator = true;
        return;
    }
    this.$startTimers();
}
//-------------------------------------------------------------------------------------------------------------
this.shipWillDockWithStation = function (station) {
    if (this._simulator === true) return;
    this.$stopTimers();
    // check if we have any contracts to terminate
    var bb = worldScripts.BulletinBoardSystem;
    var data = bb._data;
    for (var i = data.length - 1; i >= 0; i--) {
        if (data[i].accepted === true && data[i].worldScript === this.name) {
            var contract = data[i].data.details;
            var terminate = false;
            if (station.allegiance === "galcop") {
                if (this._flags[0] === 1 && contract.avoidGovType >= 0 && contract.avoidGovType === system.government) {
                    player.addMessageToArrivalReport(expandDescription("[epc_avoid-government-fail]", { name: contract.name, govType: system.governmentDescription }));
                    terminate = true;
                }
                if (this._flags[1] === 1 && contract.avoidSpecies != "" && system.info.inhabitants.indexOf(contract.avoidSpecies) >= 0) {
                    player.addMessageToArrivalReport(expandDescription("[epc_avoid-species-fail]", { name: contract.name, species: contract.avoidSpecies }));
                    terminate = true;
                }
                if (this._flags[2] === 1 && contract.avoidSysList && contract.avoidSysList.length > 0) {
                    if (contract.avoidSysList.indexOf(system.ID) >= 0) {
                        player.addMessageToArrivalReport(expandDescription("[epc_avoid-system-fail]", { name: contract.name, system: system.name }));
                        terminate = true;
                    }
                }
                if (this._flags[3] === 1 && contract.bonusSysList && contract.bonusSysList.length > 0) {
                    var idx = contract.bonusSysList.indexOf(system.ID);
                    if (idx >= 0) {
                        // remove the system now, so the player can't get the bonus twice
                        contract.bonusSysList.splice(idx, 1);
                        // remove this system from the map
                        for (var j = 0; j < data[i].additionalMarkers.length; j++) {
                            if (data[i].additionalMarkers[j].destination === system.ID) {
                                data[i].additionalMarkers.splice(j, 1);
                                break;
                            }
                        }
                        // because we are still mid-mission at this point, the BB system will not automatically remove
                        // any chart markers (it will wait until the mission is finished to do this). 
                        /// So we'll do it now manually.
                        mission.unmarkSystem({ system: system.ID, name: this.name + "_" + data[i].ID });
                        // add an arrival report
                        player.addMessageToArrivalReport(expandDescription("[epc_bonus-system-success]", { name: contract.name, bonus: formatCredits(data[i].payment * this._bonusSystemLoading, true, true) }));
                        // pay the player
                        player.credits += Math.floor((data[i].payment * this._bonusSystemLoading) * 10) / 10;
                    }
                }
            }
            if (terminate === true) {
                // send an email message for our special case
                var w = worldScripts.EmailSystem;
                if (w && this._emailDisable === false) {
                    var sndr = expandDescription("[passenger-contract-sender]");
                    var msg = expandDescription("[passenger-contract-terminated]",
                        {
                            contractname: contract.name,
                            systemname: System.systemNameForID(contract.destination),
                            time: global.clock.clockStringForTime(data[i].expiry)
                        });
                    var subj = expandDescription("[passenger-contract-terminated-subject]");
                    w.$createEmail({
                        sender: sndr,
                        subject: subj,
                        date: global.clock.adjustedSeconds,
                        message: msg,
                        expiryDays: worldScripts.GalCopAdminServices._defaultExpiryDays
                    });
                }
                player.ship.removePassenger(contract.name);
                player.decreasePassengerReputation();
                bb.$removeBBMission(data[i].ID);
                break;
            }
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.shipWillExitWitchspace = function () {
    if (!system.isInterstellarSpace && !system.sun.hasGoneNova && system.mainStation) {
        // must be a regular system with a main station
        this.$createPassengerContracts();
    }
    var p = player.ship;
    p.script._epc_wildMovement = 0;
    p.script._epc_attacked = 0;
    // only try and do a change if broadcast comms is installed
    if (p.passengerCount > 0 && p.equipmentStatus("EQ_BROADCASTCOMMSMFD") === "EQUIPMENT_OK") {
        if (system.isInterstellarSpace) {
            if (this._flags[8] === 1) this._interstellarSpaceTimer = new Timer(this, this.$interstellarSpaceMessage.bind(this), 7, 0);
        } else {
            if (this._flags[8] === 1) {
                if (this._impatientTimer && this._impatientTimer.isRunning) this._impatientTimer.stop();
                this._impatientTimer = new Timer(this, this.$impatientMessage.bind(this), (60 * 10), (60 * 5)); // after 10 minutes, then every 5 mins after that
            }
            if (this._flags[7] === 1) {
                if (this._changeTimer && this._changeTimer.isRunning) this._changeTimer.stop();
                this._changeTimer = new Timer(this, this.$doContractChange.bind(this), Math.random() * 60 + 30, 0);
            }
        }
    }
    this.$resetFoundFlag();
}
//-------------------------------------------------------------------------------------------------------------
this.guiScreenChanged = function (to, from) {
    if (this._informPlayerOfSystem === true) {
        player.consoleMessage(expandDescription("[epc_new_destination_mark]"), 6);
        this._informPlayerOfSystem = false;
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$createPassengerContracts = function () {
    var bountySys = false;
    if (worldScripts.BountySystem_Core) bountySys = true;
    // no point in generating too many, but generally want 5 or more
    // some of them will be discarded later
    var numContracts = Math.floor(5 * Math.random() + 5 * Math.random() + 5 * Math.random() + (player.passengerReputationPrecise * Math.random()));
    if (player.passengerReputationPrecise >= 0 && numContracts < 5) {
        numContracts += 5;
    }
    if (numContracts > 16) {
        numContracts = 16;
    } else if (numContracts < 0) {
        numContracts = 0;
    }
    // some of these possible contracts may be discarded later on
    for (var i = 0; i < numContracts; i++) {
        // pick a random system to take the passenger to
        var destination = Math.floor(Math.random() * 256);
        // discard if chose the current system
        if (destination === system.ID) continue;
        // get the SystemInfo object for the destination
        var destinationInfo = System.infoForSystem(galaxyNumber, destination);
        // skip nova systems
        if (destinationInfo.sun_gone_nova) continue;
        // create a passenger contract record
        this.$createRecord(destinationInfo, bountySys);
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$createRecord = function (sys, bountySystem) {
    // this part is the same as core
    var passenger = {};
    var daysUntilDeparture = 1 + (Math.random() * (7 + player.passengerReputationPrecise - sys.government));
    if (daysUntilDeparture <= 0) {
        // loses some more contracts if reputation negative
        return;
    }
    var route = system.info.routeToSystem(sys);
    if (!route || route.route.length === 0) return;
    passenger.payment = 0;
    passenger.destination = sys.systemID;
    passenger.route = route;
    if (Math.random() < 0.5) {// 50% local inhabitant
        passenger.species = system.info.inhabitant;
    } else { // 50% random species (which will be 50%ish human)
        passenger.species = System.infoForSystem(galaxyNumber, Math.floor(Math.random() * 256)).inhabitant;
    }
    if (passenger.species.match(new RegExp(expandDescription("[human-word]"), "i"))) {
        passenger.name = expandDescription("%N ") + expandDescription("[nom]");
    } else {
        passenger.name = randomName() + " " + randomName();
    }
    /* Because passengers with duplicate names won't be accepted,
     * check for name duplication with either other passengers
     * here or other passengers carried by the player, and adjust
     * this passenger's name a little if there's a match */
    do {
        var okay = true;
        for (var j = 0; j < player.ship.passengers.length; j++) {
            if (player.ship.passengers[j].name == passenger.name) {
                okay = false;
                break;
            }
        }
        if (okay) {
            var bb = worldScripts.BulletinBoardSystem;
            var data = bb._data;
            for (var j = 0; j < data.length; j++) {
                var item = data[j];
                if (item.data && item.data.hasOwnProperty("type") && item.data.type === "enhanced_passenger" && item.data.details.name == passenger.name) {
                    okay = false;
                    break;
                }
            }
        }
        if (!okay) passenger.name += "a";
    } while (!okay);
    // risk
    passenger.risk = Math.floor(Math.random() * 3);
    passenger.species = expandDescription("[passenger-description-risk" + passenger.risk + "]") + " " + passenger.species;
    // time allowed for delivery is time taken by "fewest jumps"
    // route, plus timer above. Higher reputation makes longer
    // times available.
    var d_time = Math.floor(daysUntilDeparture * 86400) + (passenger.route.time * 3600);
    passenger.deadline = clock.adjustedSeconds + d_time;
    if (passenger.risk < 2 && sys.government <= 1 && Math.random() < 0.5) {
        passenger.risk++;
    }
    // ok, we have a valid target - what sort of destination is this?
    // total payment is:
    passenger.payment = Math.floor(
        // payment per hop (higher at rep > 5)
        5 * Math.pow(route.route.length - 1, (passenger.risk * 0.2) + (player.passengerReputationPrecise > 5 ? 2.45 : 2.3)) +
        // payment by route length
        route.distance * (8 + (Math.random() * 8)) +
        // premium for delivery to more dangerous systems
        (5 * (7 - sys.government) * (7 - sys.government))
    );
    passenger.payment *= (Math.random() + Math.random() + Math.random() + Math.random()) / 2;
    var prudence = (2 * Math.random()) - 1;
    var desperation = (Math.random() * (0.5 + passenger.risk)) * (1 + 1 / (Math.max(0.5, d_time - (route.time * 3600))));
    var competency = Math.max(50, (route.route.length - 1) * (0.5 + (passenger.risk * 2)));
    if (passenger.risk == 0) competency -= 30;
    passenger.payment = Math.floor(passenger.payment * (1 + (0.4 * prudence)));
    passenger.payment += (passenger.risk * 200);
    passenger.skill = Math.min(60, competency + 20 * (prudence - desperation));
    // this is the enhanced stuff
    passenger.changed = false;
    var typeCheck = Math.random() * (10 + (7 - sys.economy));
    var avoidGov = 0.2;
    var avoidSpecies = 0.2;
    var avoidSystem = 0.2;
    var bonusSystem = 0.2;
    var smooth = 0.1;
    var noScan = 0.2;
    if (typeCheck >= 9) {
        smooth = 0.5;
        noScan = 0.6;
    } else if (typeCheck >= 7) {
        smooth = 0.2;
        noScan = 0.4;
    }
    //log(this.name, "Passenger " + passenger.name);
    passenger.smooth = false;
    // only low-risk passengers want smooth flights, on shorter trips only
    if (this._flags[6] === 1 && passenger.risk === 0 && route.route.length < 10 && Math.random() < smooth) {
        // this passenger wants a smooth flight
        passenger.smooth = true;
        // make the payment a bit larger 
        //log(this.name, "pre smooth loading  = " + passenger.payment);
        passenger.payment = Math.floor(passenger.payment * this._smoothFlightLoading);
        //log(this.name, "post smooth loading = " + passenger.payment);
        //passenger.changed = true; // we'll prevent these passengers from making mid-route changes, though
    }
    passenger.avoidGovType = -1;
    if (this._flags[0] === 1 && Math.random() < avoidGov) {
        do {
            passenger.avoidGovType = Math.floor(Math.random() * 8);
        } while (passenger.avoidGovType === sys.government || passenger.avoidGovType === system.government);
        //log(this.name, "pre avoidGov loading  = " + passenger.payment);
        passenger.payment = Math.floor(passenger.payment * this._avoidGovTypeLoading);
        //log(this.name, "post avoidGov loading = " + passenger.payment);
    }
    passenger.avoidSpecies = "";
    if (this._flags[1] === 1 && passenger.avoidGovType === -1 && Math.random() < avoidSpecies) {
        do {
            passenger.avoidSpecies = this._inhabitantTypes[Math.floor(Math.random() * this._inhabitantTypes.length)];
        } while (sys.inhabitants.indexOf(passenger.avoidSpecies) >= 0 || passenger.species.indexOf(passenger.avoidSpecies) >= 0);
        //log(this.name, "pre avoidSpecies loading  = " + passenger.payment);
        passenger.payment = Math.floor(passenger.payment * this._avoidSpeciesLoading);
        //log(this.name, "post avoidSpecies loading = " + passenger.payment);
    }
    var rt = system.info.routeToSystem(sys, "OPTIMIZED_BY_TIME");
    passenger.avoidSysList = [];
    if (this._flags[2] === 1 && Math.random() < avoidSystem) {
        // how many will we have to avoid?
        var num = Math.floor(route.route.length * 0.2);
        if (num > 3) num = 3;
        if (num < 1) num = 1;
        //log(this.name, "number to avoid " + num + ", routeLen = " + route.route.length);
        for (var i = 0; i < num; i++) {
            // sometime pick a system on the quick route
            if (Math.random() > 0.6) {
                var tgt = System.infoForSystem(galaxyNumber, rt.route[Math.floor(Math.random() * (rt.route.length - 1))]);
            } else {
                var tgt = System.infoForSystem(galaxyNumber, route.route[Math.floor(Math.random() * (route.route.length - 1))]);
            }
            // make sure we avoid any conflicts with other clauses
            if ((passenger.avoidGovType === -1 || passenger.avoidGovType != tgt.government) &&
                (passenger.avoidSpecies === "" || tgt.inhabitants.indexOf(passenger.avoidSpecies) === -1) &&
                tgt.systemID != system.ID &&
                passenger.avoidSysList.indexOf(tgt.systemID) === -1) {
                passenger.avoidSysList.push(tgt.systemID);
                passenger.payment = Math.floor(passenger.payment * this._avoidSystemLoading);
            }
        }
        //log(this.name, "avoid sysList = " + passenger.avoidSysList.length);
    }
    passenger.bonusSysList = [];
    if (this._flags[3] === 1 && Math.random() < bonusSystem) {
        // how many extra will we have?
        var num = Math.floor(route.route.length * 0.2);
        if (num > 3) num = 3;
        if (num < 1) num = 1;
        // get the fast route as well
        //log(this.name, "potential bonus systems " + num + ", routeLen = " + route.route.length);
        for (var i = 0; i < num; i++) {
            // make sure the point we pick along the route is not the start or end point
            var point = Math.floor(Math.random() * (route.route.length - 2)) + 1;
            var tgtList = System.infoForSystem(galaxyNumber, route.route[point]).systemsInRange(6);
            var tgt = tgtList[Math.floor(Math.random() * tgtList.length)];
            // make sure we avoid any conflicts with other clauses
            if ((passenger.avoidGovType === -1 || passenger.avoidGovType != tgt.government) &&
                (passenger.avoidSpecies === "" || tgt.inhabitants.indexOf(passenger.avoidSpecies) === -1) &&
                route.route.indexOf(tgt.systemID) === -1 &&
                rt.route.indexOf(tgt.systemID) === -1 &&
                passenger.avoidSysList.indexOf(tgt.systemID) === -1 &&
                passenger.bonusSysList.indexOf(tgt.systemID) === -1)
                passenger.bonusSysList.push(tgt.systemID);
        }
        //log(this.name, "bonus sysList = " + passenger.bonusSysList.length);
    }
    passenger.noScan = false;
    passenger.bounty = 0;
    passenger.hiddenBounty = 0;
    passenger.found = false;
    var custMenu = null;
    if (bountySystem) {
        // warrant check only available if player is clean
        if (player.bounty === 0 && this._flags[4] === 1) {
            custMenu = [];
            custMenu.push({ text: expandDescription("[epc_warrant_info]"), worldScript: this.name, callback: "$doWarrantCheck", condition: "$warrantCheckAvailable", autoRemove: true });
        }
        if (this._flags[4] === 1 && Math.random() < ((8 - system.government) / 8)) {
            if (Math.random() > 0.8) {
                // this passenger has a bounty
                passenger.bounty += Math.floor(Math.random() * (10 - system.government) + 5);
                // make the payment a bit larger - this passenger knows it's riskier
                //log(this.name, "pre bounty loading  = " + passenger.payment);
                passenger.payment = Math.floor(passenger.payment * this._bountyLoading);
                //log(this.name, "post bounty loading = " + passenger.payment);
            } else {
                // this passenger has a hidden bounty
                passenger.hiddenBounty += Math.floor(Math.random() * (10 - system.government) + 5);
                //log(this.name, passenger.name + " - " + passenger.hiddenBounty);
            }
        }
        if (this._flags[5] === 1 && Math.random() > 0.8) {
            // but only some will care, even those who don't have a bounty
            if (passenger.smooth === false && Math.random() < noScan) {
                passenger.noScan = true;
                // make the payment a bit larger 
                //log(this.name, "pre noscan loading  = " + passenger.payment);
                passenger.payment = Math.floor(passenger.payment * this._noScanLoading);
                //log(this.name, "post noscan loading = " + passenger.payment);
            }
        }
    }
    passenger.advance = Math.min(passenger.payment * 0.9, Math.max(0, Math.floor(passenger.payment * (0.05 + (0.1 * desperation) + (0.02 * player.passengerReputationPrecise))))); // some% up front
    passenger.payment -= passenger.advance;
    this.$ovr_addPassengerToSystem(passenger, custMenu);
}
//-------------------------------------------------------------------------------------------------------------
this.$disableCorePassengerContracts = function () {
    var psngr = worldScripts["oolite-contracts-passengers"];
    // remove the event hooks
    delete psngr.shipWillExitWitchspace;
    delete psngr.playerWillSaveGame;
    delete psngr.shipWillLaunchFromStation;
    delete psngr.guiScreenWillChange;
    delete psngr.guiScreenChanged;
    // intercept the _addPassengerToSystem function
    psngr._addPassengerToSystem = this.$ovr_addPassengerToSystem;
    // if contracts on BB is installed, we don't need this function
    if (worldScripts.ContractsOnBB) {
        delete this.playerCompletedContract;
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$removeExistingContracts = function () {
    // clear the array
    var psngr = worldScripts["oolite-contracts-passengers"];
    if (psngr.$passengers && psngr.length > 0) psngr.$passengers.length = 0;
    system.mainStation.setInterface("oolite-contracts-passengers", null);
    // remove any pre-existing BB passenger contracts
    if (worldScripts.ContractsOnBB) {
        var cobb = worldScripts.ContractsOnBB;
        var bb = worldScripts.BulletinBoardSystem;
        // remove anything already there
        var curr = cobb.$countContracts(32000);
        //log(this.name, "current " + curr);
        if (curr > 0) {
            for (var i = 0; i < curr; i++) {
                bb.$removeBBMission(32000 + i);
            }
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$ovr_addPassengerToSystem = function (passenger, custMenu) {
    var xui = false;
    var ovr = "";
    if (worldScripts.XenonReduxUI) {
        xui = true;
        ovr = { name: "xrui-boardingpass.png", height: 546 };
    }
    if (worldScripts.XenonUI) {
        xui = true;
        ovr = { name: "xui-boardingpass.png", height: 546 };
    }
    // we won't attempt to change any other passenger - just the ones we create
    // so we'll flag any contract that arrives here as having been changed
    if (passenger.hasOwnProperty("changed") === false) {
        passenger.changed = true;
    }
    var cust = [];
    if (passenger.advance > 0) cust.push({ heading: expandDescription("[epc_contract-advance]"), value: formatCredits(passenger.advance, true, true) });
    cust.push({ heading: expandDescription("[epc_contract-clientname]"), value: passenger.name + ", a " + passenger.species });
    if (passenger.hasOwnProperty("bounty") && passenger.bounty > 0) {
        cust.push({ heading: expandDescription("[epc_contract-bounty]"), value: (passenger.bounty >= 50 ? expandDescription("[epc_fugitive]") : expandDescription("[epc_offender]")) + " (" + passenger.bounty + ")" });
    }
    var additional = ".";
    var addCount = 0;
    var smooth_text = "";
    if (passenger.hasOwnProperty("smooth") === true && passenger.smooth === true) {
        smooth_text = expandDescription("[epc_contract-no-wildness]");
        addCount += 1;
    } else {
        passenger.smooth = false;
    }
    var scan_text = "";
    if (passenger.hasOwnProperty("noScan") === true && passenger.noScan === true) {
        scan_text = expandDescription("[epc_contract-no-scan]");
        addCount += 1;
    } else {
        passenger.noScan = false;
    }
    var avoidGov_text = "";
    if (passenger.hasOwnProperty("avoidGovType") === true && passenger.avoidGovType >= 0) {
        avoidGov_text = expandDescription("[epc_contract-avoid-gov]", { govType: this._govTypeNames[passenger.avoidGovType] });
        addCount += 1;
    } else {
        passenger.avoidGovType = -1;
    }
    var avoidSpecies_text = "";
    if (passenger.hasOwnProperty("avoidSpecies") === true && passenger.avoidSpecies != "") {
        avoidSpecies_text = expandDescription("[epc_contract-avoid-species]", { species: passenger.avoidSpecies });
        addCount += 1;
    } else {
        passenger.avoidSpecies = ""
    }
    var addMarkers = [];
    var avoidSysList_text = "";
    var govs = new Array();
    for (var i = 0; i < 8; i++) govs.push(String.fromCharCode(i));
    if (passenger.hasOwnProperty("avoidSysList") === true && Array.isArray(passenger.avoidSysList) === true && passenger.avoidSysList.length > 0) {
        var listText = "";
        for (var i = 0; i < passenger.avoidSysList.length; i++) {
            var sys = System.infoForSystem(galaxyNumber, passenger.avoidSysList[i]);
            listText += "\n -- " + sys.name + " (" + govs[sys.government] + " TL" + (sys.techlevel + 1) + ")";
            addMarkers.push({ destination: sys.systemID, markerColor: "yellowColor", markerShape: "MARKER_X" });
        }
        avoidSysList_text = expandDescription("[epc_contract-avoid-systems]", { syslist: listText });
        addCount += 1;
    }
    var bonusSysList_text = "";
    if (passenger.hasOwnProperty("bonusSysList") === true && Array.isArray(passenger.bonusSysList) === true && passenger.bonusSysList.length > 0) {
        var listText = "";
        for (var i = 0; i < passenger.bonusSysList.length; i++) {
            var sys = System.infoForSystem(galaxyNumber, passenger.bonusSysList[i]);
            listText += "\n -- " + sys.name + " (" + govs[sys.government] + " TL" + (sys.techlevel + 1) + ")";
            addMarkers.push({ destination: sys.systemID, markerColor: "greenColor", markerShape: "MARKER_X" });
        }
        bonusSysList_text = expandDescription("[epc_contract-bonus-systems]", { bonus: formatInteger(this._bonusSystemLoading * 100) + "% of payment", syslist: listText });
        addCount += 1;
    }
    if (addCount > 0) additional = expandDescription("[epc_additional-clauses]", { multiChange: (addCount === 1 ? "" : "s") });
    var customMenu = "";
    if (custMenu != undefined) customMenu = custMenu;
    var bb = worldScripts.BulletinBoardSystem;
    bb.$addBBMission({
        source: system.ID,
        destination: passenger.destination,
        stationKey: "mainStation",
        description: expandDescription("[epc_contract-passenger-title]"),
        details: expandDescription("[epc_contract-passenger-description]", {
            client: passenger.name,
            destination: System.systemNameForID(passenger.destination),
            extra: (additional + smooth_text + avoidGov_text + avoidSpecies_text + avoidSysList_text + bonusSysList_text + scan_text)
        }),
        payment: passenger.payment,
        allowTerminate: false,
        completionType: "IMMEDIATE",
        stopTimeAtComplete: true,
        allowPartialComplete: false,
        expiry: passenger.deadline,
        disablePercentDisplay: true,
        noEmails: true,
        playAcceptedSound: false,
        markerShape: "NONE", // marker control will be handled by contracts system
        overlay: (xui === true ? ovr : ""),
        customDisplayItems: cust,
        customMenuItems: customMenu,
        additionalMarkers: addMarkers,
        initiateCallback: "$acceptPassengerContract",
        availableCallback: "$passengerContractAvailable",
        worldScript: this.name,
        data: { type: "enhanced_passenger", details: passenger }
    });
}
//-------------------------------------------------------------------------------------------------------------
this.$acceptPassengerContract = function (missID) {
    //log(this.name, "accepted contract " + missID);
    var bb = worldScripts.BulletinBoardSystem;
    var item = bb.$getItem(missID);
    this.$acceptContract(item.data.details);
}
//-------------------------------------------------------------------------------------------------------------
// perform checks to see if the passenger contract can be accepted by the player
this.$passengerContractAvailable = function (missID) {
    var pc = worldScripts["oolite-contracts-passengers"];
    var item = worldScripts.BulletinBoardSystem.$getItem(missID);
    // temp variable to simplify code
    var passenger = item.data.details;
    if (passenger) {
        var playerrep = worldScripts["oolite-contracts-helpers"]._playerSkill(player.passengerReputationPrecise);
        if (player.ship.passengerCapacity <= player.ship.passengerCount) {
            return expandMissionText("oolite-contracts-passengers-command-unavailable").replace("(", "").replace(")", "");
        }
        else if (playerrep < passenger.skill) {
            var utype = "both";
            if (player.passengerReputationPrecise * 10 >= passenger.skill) {
                utype = "kills";
            } else if (Math.sqrt(player.score) >= passenger.skill) {
                utype = "rep";
            }
            return expandMissionText("oolite-contracts-passengers-command-unavailable-" + utype).replace("(", "").replace(")", "");
        }
    }
    return "";
}
//-------------------------------------------------------------------------------------------------------------
this.$acceptContract = function (passenger) {
    // give the passenger to the player
    this._internal = true;
    this.$disableEmails();
    this._sendEmailType = 1;
    var result = player.ship.addPassenger(passenger.name, system.ID, passenger.destination, passenger.deadline, passenger.payment, passenger.advance, passenger.risk);
    this.$enableEmails();
    this._sendEmailType = 0;
    this._internal = false;
    var helpers = worldScripts["oolite-contracts-helpers"];
    if (result) {
        // pay the advance
        player.credits += passenger.advance;
        helpers._soundSuccess();
        if (passenger.risk > 0) {
            // once for medium risk
            helpers._setClientName(passenger.name);
            if (passenger.risk > 1) {
                // three times for high risk
                helpers._setClientName(passenger.name);
                helpers._setClientName(passenger.name);
            }
        }
    } else {
        // else must have had another passenger board recently
        // (unlikely, but another OXP could have done it)
        helpers._soundFailure();
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$countAvailableContracts = function () {
    var count = 0;
    var bb = worldScripts.BulletinBoardSystem;
    var data = bb._data;
    for (var i = 0; i < data.length; i++) {
        var item = data[i];
        if (item.accepted === false && item.data && item.data.hasOwnProperty("type") && item.data.type === "enhanced_passenger") count += 1;
    }
    //log(this.name, "count contracts " + count);
    return count;
}
//-------------------------------------------------------------------------------------------------------------
this.$doContractChange = function $doContractChange() {
    // go through all the accepted passenger contracts and see if any of them want to change their destination
    var p = player.ship;
    if (player.alertCondition === 3) {
        if (Math.random() > 0.0) {
            this._changeTimer = new Timer(this, this.$doContractChange.bind(this), Math.random() * 60 + 30, 0);
        }
        return; // maybe try again shortly
    }
    // make sure we still have broadcast comms (might have been damaged)
    if (p.equipmentStatus("EQ_BROADCASTCOMMSMFD") !== "EQUIPMENT_OK") return;
    for (var i = 0; i < p.passengers.length; i++) {
        var passenger = p.passengers[i];
        if (Math.random() > 0.0 && passenger.destination != system.ID) {  // 0.8
            // how far away is the destination
            var sys = System.infoForSystem(galaxyNumber, passenger.destination);
            var rt = system.info.routeToSystem(sys, "OPTIMIZED_BY_TIME");
            // if there's no route to the system, or there's more than 2 jumps left, we're good to go
            if (!rt || rt.route.length > 2) {
                var sysList = system.info.systemsInRange(30);
                var newsys = sysList[Math.floor(Math.random() * sysList.length)];
                var newrt = system.info.routeToSystem(newsys, "OPTIMIZED_BY_TIME");
                if (newrt && newrt.route.length > 1 && newrt.route.length > rt.route.length) {
                    var bb = worldScripts.BulletinBoardSystem;
                    var data = bb._data;
                    var bbitem = null;
                    for (var j = 0; j < data.length; j++) {
                        var item = data[j];
                        if (item.accepted === true && item.worldScript === this.name) {
                            if (item.data.details.name === passenger.name && item.data.details.destination === passenger.destination) {
                                bbitem = item;
                                break;
                            }
                        }
                    }
                    // if this passenger has already done a mid-flight change, don't do another one
                    if (!bbitem) continue;
                    var dtls = bbitem.data.details;
                    if (dtls.changed === true) continue;
                    // make sure we dont conflict with other types
                    if (dtls.avoidGovType >= 0 && newsys.government === dtls.avoidGovType) continue;
                    if (dtls.avoidSpecies != "" && newsys.inhabitants.indexOf(dtls.avoidSpecies) >= 0) continue;
                    if (dtls.avoidSysList.length > 0 && dtls.avoidSysList.indexOf(newsys.systemID) >= 0) continue;
                    if (dtls.bonusSysList.length > 0 && dtls.bonusSysList.indexOf(newsys.systemID) >= 0) continue;
                    // found a target!
                    // time to power up the MFD
                    var diff = Math.abs(newrt.route.length - rt.route.length);
                    //log(this.name, "diff " + diff + ", risk " + passenger.risk + ", gov " + newsys.government);
                    var extra = Math.pow(diff, (passenger.risk + 1) * 1.5) * (14 - newsys.government);
                    if (extra < 50) extra = 50;
                    var txt = expandDescription("[epc_new-destination]", { client: passenger.name, original_system: sys.name, new_system: newsys.name, extra: formatCredits(extra, false, true) });
                    // store some data for quick pickup later
                    this._newDest = {
                        name: passenger.name,
                        original: sys.systemID,
                        source: item.source,
                        newDest: newsys.systemID,
                        deadline: clock.adjustedSeconds + (newrt.time * 3600),
                        species: passenger.species,
                        risk: passenger.risk,
                        skill: dtls.skill,
                        advance: passenger.premium,
                        payment: passenger.fee,
                        newRoute: newrt,
                        smooth: dtls.smooth,
                        noScan: dtls.noScan,
                        bounty: dtls.bounty,
                        avoidGovType: dtls.avoidGovType,
                        avoidSpecies: dtls.avoidSpecies,
                        // next two should always be empty (because we are preventing those types from changing their minds)
                        // but in case /we/ ever change our minds...
                        avoidSysList: (dtls.hasOwnProperty("avoidSysList") ? dtls.avoidSysList : []),
                        bonusSysList: (dtls.hasOwnProperty("bonusSysList") ? dtls.bonusSysList : []),
                        hiddenBounty: dtls.hiddenBounty,
                        found: dtls.found,
                        bbID: bbitem.ID,
                        extra: extra
                    };
                    mission.markSystem({ system: newsys.systemID, name: "epc_newDest", markerColor: "whiteColor", markerShape: "MARKER_X" });
                    this._informPlayerOfSystem = true;
                    // set this value so if the player declines, we won't keep asking on this passenger
                    bbitem.data.details.changed = true;
                    // communicate with player about the new mission
                    worldScripts.EnhancedPassengerMFD.$updateMFD(txt);
                    break;
                }
            }
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
// routine to check the combat simulator worldscript, to see if it's running or not
this.$simulatorRunning = function () {
    var w = worldScripts["Combat Simulator"];
    if (w && w.$checkFight && w.$checkFight.isRunning) return true;
    return false;
}
//-------------------------------------------------------------------------------------------------------------
this.$acceptPendingChange = function () {
    if (!this._newDest || this._newDest == null) return;
    // get a copy of the passenger details
    var p = player.ship;
    // remove the system mark
    mission.unmarkSystem({ system: this._newDest.newDest, name: "epc_newDest" });
    this._informPlayerOfSystem = false;
    // remove the old passenger
    p.removePassenger(this._newDest.name)
    // add them back in with the new details
    this._internal = true;
    this.$disableEmails();
    this._sendEmailType = 2;
    var result = p.addPassenger(this._newDest.name, this._newDest.source, this._newDest.newDest, this._newDest.deadline, parseInt(this._newDest.payment) + parseInt(this._newDest.extra), this._newDest.advance, this._newDest.risk);
    this._sendEmailType = 0;
    this.$enableEmails();
    this._internal = false;
    // update the bb
    var bb = worldScripts.BulletinBoardSystem;
    var item = bb.$getItem(this._newDest.bbID);
    item.destination = this._newDest.newDest;
    item.destinationName = System.systemNameForID(this._newDest.newDest);
    item.expiry = this._newDest.deadline;
    item.payment = this._newDest.payment + this._newDest.extra;
    item.data.details = {
        payment: this._newDest.payment + this._newDest.extra,
        destination: this._newDest.newDest,
        route: this._newDest.newRoute,
        species: this._newDest.species,
        name: this._newDest.name,
        risk: this._newDest.risk,
        deadline: this._newDest.deadline,
        skill: this._newDest.skill,
        advance: this._newDest.advance,
        smooth: this._newDest.smooth,
        noScan: this._newDest.noScan,
        bounty: this._newDest.bounty,
        avoidGovType: this._newDest.avoidGovType,
        avoidSpecies: this._newDest.avoidSpecies,
        avoidSysList: this._newDest.avoidSysList,
        bonusSysList: this._newDest.bonusSysList,
        hiddenBounty: this._newDest.hiddenBounty,
        found: this._newDest.found,
        changed: true
    };
    delete this._newDest;
}
//-------------------------------------------------------------------------------------------------------------
this.$declinePendingChange = function () {
    // remove the system mark
    mission.unmarkSystem({ system: this._newDest.newDest, name: "epc_newDest" });
    this._informPlayerOfSystem = false;
    // just delete the details
    delete this._newDest;
}
//-------------------------------------------------------------------------------------------------------------
this.$startTimers = function () {
    // check if we have any need to monitor for movement/attacks
    var bb = worldScripts.BulletinBoardSystem;
    var data = bb._data;
    var start = false;
    var found = false;
    // look for any passengers, and if we need to monitor for wild movement
    for (var i = 0; i < data.length; i++) {
        if (data[i].accepted === true && data[i].worldScript === this.name) {
            found = true;
            if (data[i].data.details.smooth === true) { start = true; break; }
        }
    }
    // set up the chat timer
    if (found === true && this._flags[8] === 1) {
        this._transmissions.length = 0;
        this._chatTimer = new Timer(this, this.$sendChatMessage, Math.floor(Math.random() * 120 + 60), Math.floor(Math.random() * 120) + 60);
        this.shipTargetDestroyed = this.$epc_shipTargetDestroyed;
        this.shipTakingDamage = this.$epc_shipTakingDamage;
    }
    // if we don't have a need for a wild movement monitor, exit here.
    if (start === false) return;
    // otherwise, set up the properties, events, fcb and monitor timer
    var p = player.ship;
    p.script._epc_wildMovement = 0;
    p.script._epc_wildMovementWarning = false;
    p.script._epc_attacked = 0;
    p.script._epc_stable = 0.0;
    // some ships will be sluggish, so "wild movement" isn't really possible for them
    // so max sure than wild movement will only be calculated for ships that have the possibility of it
    // for those with pitch/roll stats lower than these values, it won't trigger
    p.script._maxPitch = Math.max(0.75, p.maxPitch);
    p.script._maxRoll = Math.max(1, p.maxRoll);
    this.shipBeingAttacked = this.$epc_shipBeingAttacked;
    this._frameCallbackID = addFrameCallback(this.$checkForWildMovement.bind(this));
    this._monitorWildMovementTimer = new Timer(this, this.$monitorWildMovement.bind(this), 5, 5);
}
//-------------------------------------------------------------------------------------------------------------
this.$stopTimers = function () {
    if (this._chatTimer && this._chatTimer.isRunning) {
        this._chatTimer.stop();
        this._chatTimer = null;
    }
    if (this._changeTimer && this._changeTimer.isRunning) {
        this._changeTimer.stop();
        this._changeTimer = null;
    }
    if (this._monitorWildMovementTimer && this._monitorWildMovementTimer.isRunning) {
        this._monitorWildMovementTimer.stop();
        this._monitorWildMovementTimer = null;
    }
    if (this._complimentTimer && this._complimentTimer.isRunning) {
        this._complimentTimer.stop();
        this._complimentTimer = null;
    }
    if (this._damageTimer && this._damageTimer.isRunning) {
        this._damageTimer.stop();
        this._damageTimer = null;
    }
    if (this._impatientTimer && this._impatientTimer.isRunning) {
        this._impatientTimer.stop();
        this._impatientTimer = null;
    }
    delete this.shipTargetDestroyed;
    delete this.shipTakingDamage;
    this.$stopFrameCallback();
}
//-------------------------------------------------------------------------------------------------------------
this.$stopFrameCallback = function () {
    if (this._frameCallbackID && isValidFrameCallback(this._frameCallbackID)) {
        removeFrameCallback(this._frameCallbackID);
    }
    delete this._frameCallbackID;
    delete this.shipBeingAttacked;
}
//-------------------------------------------------------------------------------------------------------------
this.$checkForWildMovement = function $checkForWildMovement(delta) {
    var p = player.ship;
    var ps = p.script;
    if (!p || p.isValid === false || !ps) return;
    // are we moving around a lot?
    if (ps._maxPitch - Math.abs(p.pitch) < 0.0001) { ps._epc_wildMovement += delta; ps._epc_stable = 0; return; }
    if (ps._maxRoll - Math.abs(p.roll) < 0.0001) { ps._epc_wildMovement += delta; ps._epc_stable = 0; return; }
    // monitor how long we've been flying in a stable way
    ps._epc_stable += delta;
    // every 30 seconds or so, bring down our wild movement amount
    if (ps._epc_stable > 15) {
        ps._epc_stable = 0;
        if (ps._epc_wildMovement > 0) {
            ps._epc_wildMovement -= 1;
        }
        if (ps._epc_attacked > 0) {
            ps._epc_attacked -= 1;
        }
        // reset the warning flag if we get back to 0 again
        if (ps._epc_wildMovement <= 0 || ps._epc_attacked <= 0) {
            ps._epc_wildMovementWarning = false;
            ps._epc_wildMovement = 0;
            ps._epc_attacked = 0;
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$monitorWildMovement = function $monitorWildMovement() {
    var p = player.ship;
    var ps = p.script;
    var ding = false;
    if (!p || p.isValid === false || !ps) {
        this._monitorWildMovementTimer.stop();
        return;
    }
    //log(this.name, "wm = " + ps._epc_wildMovement + ", lh " + ps._epc_attacked);
    // send warning after 15 seconds of wild movement or 20 laser hits
    if ((ps._epc_wildMovement > 15 || ps._epc_attacked > 20) && ps._epc_wildMovementWarning === false) {
        // find a contract who can act as spokesperson for anyone else.
        var bb = worldScripts.BulletinBoardSystem;
        var data = bb._data;
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            if (item.accepted === true && item.worldScript === this.name) {
                var passenger = item.data.details;
                if (passenger.smooth === true) {
                    //log(this.name, "sending wild movement warning for " + passenger.name);
                    this.$transmitMessageFromPassenger(passenger, "[passenger-response-not-comfortable-warning]");
                    break;
                }
            }
        }
        ps._epc_wildMovementWarning = true;
        player.consoleMessage("");
        return;
    }
    // 30 seconds of wild movement or 40 hits
    if (ps._epc_wildMovement > 30 || ps._epc_attacked > 40) {
        ding = true;
        // reset the values so we don't keep sending the same message
        ps._epc_wildMovement = 0;
        ps._epc_attacked = 0;
    }
    if (ding === true) {
        // stop the frame callback - we're done
        this.$stopFrameCallback();
        var bb = worldScripts.BulletinBoardSystem;
        var data = bb._data;
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            if (item.accepted === true && item.worldScript === this.name) {
                var passenger = item.data.details;
                if (passenger.smooth === true) {
                    // oh dear
                    this.$transmitMessageFromPassenger(passenger, "[passenger-response-not-comfortable]");
                    item.payment = item.payment * 0.5; // 50% reduction
                    passenger.payment = item.payment;
                    passenger.smooth = false; // turn this off now, so it can't happen twice
                    item.data.details = passenger;
                    // find and update the actual passenger record
                    for (var j = 0; j < p.passengers.length; j++) {
                        var psngr = p.passengers[j];
                        if (psngr.name === passenger.name && psngr.destination === item.destination) {
                            //log(this.name, "failed for " + psngr.name);
                            // remove the old passenger
                            p.removePassenger(passenger.name)
                            // add them back in with the new details
                            this._internal = true;
                            this.$disableEmails();
                            this._sendEmailType = 3;
                            var result = p.addPassenger(passenger.name, item.source, item.destination, passenger.deadline, passenger.payment, passenger.advance, passenger.risk);
                            this._sendEmailType = 0;
                            this.$enableEmails();
                            this._internal = false;
                            break;
                        }
                    }
                }
            }
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$ovr_bountySystem_uncoverBounties = function (source) {
    var p = player.ship;
    var bb = worldScripts.BulletinBoardSystem;
    var data = bb._data;
    var done = false;
    // only do the check if the source of the scan is a police vessel, and we're in a high-level government system
    // (never in an anarchy, but more and more likely the higher the government level)
    if ((Math.random() * 7) < system.government) {
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            if (item.accepted === true && item.worldScript === "EnhancedPassengerContracts") {
                var passenger = item.data.details;
                // check for any passengers carrying a bounty - and potentially add a bounty to the player
                if (passenger.hiddenBounty > 0 || passenger.bounty > 0) {
                    // found one!
                    // ding the player and inform them
                    if (done === false && passenger.found === false && source.isPolice) {
                        done = true;
                        player.ship.setBounty(player.bounty + Math.floor(Math.random() * 5 + 2), "transporting offenders");
                        source.commsMessage(expandDescription("[police-comms-offender-passenger]"), player.ship);
                    }
                    passenger.found = true;
                    // add a custom display item to display the passenger bounty
                    if (passenger.bounty === 0) {
                        passenger.bounty = passenger.hiddenBounty;
                        passenger.hiddenBounty = 0;
                        item.customDisplayItems.push({ heading: expandDescription("[epc_contract-bounty]"), value: passenger.bounty });
                        item.data.details = passenger;
                    }
                }
                // check for any passengers who don't want to be scanned
                if (passenger.noScan === true) {
                    // oh dear
                    var epc = worldScripts.EnhancedPassengerContracts;
                    epc.$transmitMessageFromPassenger(passenger, "[passenger-response-got-scanned]");
                    item.payment = item.payment * 0.25; // 75% reduction
                    passenger.payment = item.payment;
                    passenger.noScan = false; // turn this off now, so it can't happen twice
                    for (var j = 0; j < p.passengers.length; j++) {
                        var psngr = p.passengers[j];
                        if (psngr.name === passenger.name && psngr.destination === item.destination) {
                            // remove the old passenger
                            p.removePassenger(passenger.name)
                            // add them back in with the new details
                            epc._internal = true;
                            epc.$disableEmails();
                            epc._sendEmailType = 3;
                            var result = p.addPassenger(passenger.name, item.source, item.destination, passenger.deadline, passenger.payment, passenger.advance, passenger.risk);
                            epc._sendEmailType = 0;
                            epc.$enableEmails();
                            epc._internal = false;
                            break;
                        }
                    }
                    item.data.details = passenger;
                }
            }
        }
    }
    // do the standard uncover bounties
    this.$uncoverBounties_endpoint(source);
}
//-------------------------------------------------------------------------------------------------------------
this.$doWarrantCheck = function (missID) {
    var p = player.ship;
    var bb = worldScripts.BulletinBoardSystem;
    var item = bb.$getItem(missID);
    var found = false;
    var passenger = item.data.details;
    if (passenger.hiddenBounty > 0) {
        passenger.bounty = passenger.hiddenBounty;
        passenger.hiddenBounty = 0;
        item.customDisplayItems.push({ heading: expandDescription("[epc_contract-bounty]"), value: passenger.bounty });
        player.consoleMessage(expandDescription("[epc_reveal-bounty]", { name: passenger.name, bounty: passenger.bounty }), 5);
        found = true;
    } else {
        player.consoleMessage(expandDescription("[epc_reveal-no-bounty]", { name: passenger.name }), 5);
    }
    item.payment = item.payment * 0.8; // 20% reduction
    passenger.payment = item.payment;
    item.data.details = passenger;
    for (var j = 0; j < p.passengers.length; j++) {
        var psngr = p.passengers[j];
        if (psngr.name === passenger.name && psngr.destination === item.destination) {
            // remove the old passenger
            p.removePassenger(passenger.name)
            // add them back in with the new details
            this._internal = true;
            this.$disableEmails();
            if (found === false) this._sendEmailType = 4;
            var result = p.addPassenger(passenger.name, item.source, item.destination, passenger.deadline, passenger.payment, passenger.advance, passenger.risk);
            this._sendEmailType = 0;
            this.$enableEmails();
            this._internal = false;
            break;
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$warrantCheckAvailable = function (missID) {
    var bb = worldScripts.BulletinBoardSystem;
    var item = bb.$getItem(missID);
    if (item.accepted === false) return expandDescription("[epc_check_contract_accepted]");
    if (item.data.details.bounty > 0) return expandDescription("[epc_check_bounty_shown]");
    return "";
}
//-------------------------------------------------------------------------------------------------------------
this.$resetFoundFlag = function () {
    var bb = worldScripts.BulletinBoardSystem;
    var data = bb._data;
    for (var i = 0; i < data.length; i++) {
        var item = data[i];
        if (item.accepted === true && item.worldScript === this.name) {
            item.data.details.found = false;
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
// turns off contract emails, if they aren't already
this.$disableEmails = function () {
    if (worldScripts.GalCopAdminServices) {
        var ga = worldScripts.GalCopAdminServices;
        this._emailDisable = ga._disableContracts;
        ga._disableContracts = true;
    }
}
//-------------------------------------------------------------------------------------------------------------
// turns on contracts emails, if it was previously
this.$enableEmails = function () {
    if (worldScripts.GalCopAdminServices) {
        var ga = worldScripts.GalCopAdminServices;
        ga._disableContracts = this._emailDisable;
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$customPassengerContractEmail = function (contract) {
    var w = worldScripts.EmailSystem;
    if (!w || this._emailDisable === true) return;
    // look up the BB item
    var bb = worldScripts.BulletinBoardSystem;
    var data = bb._data;
    var item = null;
    for (var i = 0; i < data.length; i++) {
        if (data[i].accepted === true &&
            data[i].worldScript === this.name &&
            data[i].destination === contract.destination &&
            data[i].payment === contract.fee &&
            data[i].source === contract.start) {
            item = data[i].data.details;
        }
    }
    if (item === null) return;
    var extras = this.$buildExtrasText(item);
    var sndr = expandDescription("[passenger-contract-sender]");
    var subj = expandDescription("[epc_contract_accepted]", { name: contract.name });
    var msg = expandDescription("[epc_email-passenger-contract-start]",
        {
            contractname: contract.name,
            systemname: System.systemNameForID(contract.destination),
            time: global.clock.clockStringForTime(contract.arrival_time),
            fee: formatCredits(contract.fee, true, true),
            extras: extras
        });
    if (sndr != "") {
        w.$createEmail({
            sender: sndr,
            subject: subj,
            date: global.clock.adjustedSeconds,
            message: msg
        });
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$contractAdjustmentEmail = function (contract) {
    var w = worldScripts.EmailSystem;
    if (!w || this._emailDisable === true) return;
    // look up the BB item
    var bb = worldScripts.BulletinBoardSystem;
    var data = bb._data;
    var item = null;
    for (var i = 0; i < data.length; i++) {
        if (data[i].accepted === true &&
            data[i].worldScript === this.name &&
            data[i].destination === contract.destination &&
            data[i].payment === contract.fee &&
            data[i].source === contract.start) {
            item = data[i].data.details;
        }
    }
    if (item === null) return;
    var adjType = "";
    switch (this._sendEmailType) {
        case 2:
            adjType = "[epc_email-contract-amend-destination]";
            break;
        case 3:
            adjType = "[epc_email-contract-amend-payment]";
            break;
        case 4:
            adjType = "[epc_email-contract-amend-privacy]";
            break;
    }
    var extras = this.$buildExtrasText(item);
    var sndr = expandDescription("[passenger-contract-sender]");
    var subj = expandDescription("[epc_contract_amended]", { name: contract.name });
    var msg = expandDescription(adjType,
        {
            contractname: contract.name,
            systemname: System.systemNameForID(contract.destination),
            time: global.clock.clockStringForTime(contract.arrival_time),
            fee: formatCredits(contract.fee, true, true),
            extras: extras
        });
    if (sndr != "") {
        w.$createEmail({
            sender: sndr,
            subject: subj,
            date: global.clock.adjustedSeconds,
            message: msg
        });
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$buildExtrasText = function (item) {
    var govs = new Array();
    for (var i = 0; i < 8; i++) govs.push(String.fromCharCode(i));
    var extras = "";
    if (item.hasOwnProperty("smooth") && item.smooth === true) {
        extras += expandDescription("[epc_contract-no-wildness]");
    }
    if (item.hasOwnProperty("noScan") && item.noScan === true) {
        extras += expandDescription("[epc_contract-no-scan]");
    }
    if (item.hasOwnProperty("avoidGovType") && item.avoidGovType >= 0) {
        extras += expandDescription("[epc_contract-avoid-gov]", { govType: this._govTypeNames[item.avoidGovType] });
    }
    if (item.hasOwnProperty("avoidSpecies") && item.avoidSpecies != "") {
        extras += expandDescription("[epc_contract-avoid-species]", { species: item.avoidSpecies });
    }
    if (item.hasOwnProperty("avoidSysList") && item.avoidSysList.length > 0) {
        var listText = "";
        for (var i = 0; i < item.avoidSysList.length; i++) {
            var sys = System.infoForSystem(galaxyNumber, item.avoidSysList[i]);
            listText += "\n -- " + sys.name + " (" + govs[sys.government] + " TL" + (sys.techlevel + 1) + ")";
        }
        extras += expandDescription("[epc_contract-avoid-systems]", { syslist: listText });
    }
    if (item.hasOwnProperty("bonusSysList") && item.bonusSysList.length > 0) {
        var listText = "";
        for (var i = 0; i < item.bonusSysList.length; i++) {
            var sys = System.infoForSystem(galaxyNumber, item.bonusSysList[i]);
            listText += "\n -- " + sys.name + " (" + govs[sys.government] + " TL" + (sys.techlevel + 1) + ")";
        }
        extras += expandDescription("[epc_contract-bonus-systems]", { bonus: formatInteger(this._bonusSystemLoading * 100), syslist: listText });
    }
    return extras;
}
//-------------------------------------------------------------------------------------------------------------
this.$sendChatMessage = function $sendChatMessage() {
    // don't send any messages if there is a pending contract change
    if (this._newDest) return;
    // don't sent chat messages in Interstellar space
    if (system.isInterstellarSpace) return;
    // overheating
    if (player.ship.temperature >= 0.65) {
        if (Math.random() < 0.5) {
            this.$transmitMessageFromPassenger(this.$pickRandomPassenger(), "[passenger-overheating]");
        }
        // if the ship is overheating, don't continue and potentially send a small talk message
        return;
    }
    // low energy
    if (player.ship.energy < 32) {
        if (Math.random() < 0.5) {
            this.$transmitMessageFromPassenger(this.$pickRandomPassenger(), "[passenger-low-energy-concern]");
        }
        return;
    }
    // low altitude
    if (player.alertAltitude) {
        // altitude could be low near the sun, too, but hopefully the temperature message above will prevent conflicts
        if (Math.random() < 0.5) {
            this.$transmitMessageFromPassenger(this.$pickRandomPassenger(), "[passenger-low-altitude-concern]");
        }
        return;
    }
    // don't send small talk during read alert
    if (player.alertCondition === 3) return;
    // small talk
    if (Math.random() < 0.2) {
        // pick a passenger
        var passenger = this.$pickRandomPassenger();
        if (passenger) {
            // set up any mission variables that might be used by the chat text
            var ds = this.$getSystemsByDescription(expandDescription("[epc_system_disease]"));
            missionVariables.diseaseSystem = ds[Math.floor(Math.random() * ds.length)].name;
            var es = this.$getSystemsByDescription(expandDescription("[epc_system_earthquake]"));
            missionVariables.earthquakeSystem = es[Math.floor(Math.random() * es.length)].name;
            missionVariables.finalDestination = System.systemNameForID(passenger.destination);
            var shipTypes = ["Cobra Mark III", "Boa", "Boa Class Cruiser", "Anaconda", "Fer-de-Lance"];
            for (var i = 0; i < shipTypes.length; i++) {
                if (player.ship.name !== shipTypes[i]) {
                    missionVariables.altShipName = shipTypes[i];
                    break;
                }
            }
            var data = expandDescription("[passenger-chat]");
            var idx = parseInt(data.split("|")[0]);
            var msg = data.split("|")[1];
            // clean up the mission variables we added
            delete missionVariables.altShipName;
            delete missionVariables.finalDestination;
            delete missionVariables.diseaseSystem;
            delete missionVariables.earthquakeSystem;
            if (this._transmissions.indexOf(idx) === -1) {
                this.$transmitMessageFromPassenger(passenger, msg);
                this._transmissions.push(idx);
            }
        }
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$niceShotMessage = function $niceShotMessage() {
    // pick a passenger
    this.$transmitMessageFromPassenger(this.$pickRandomPassenger(), "[passenger-nice-shooting]");
}
//-------------------------------------------------------------------------------------------------------------
this.$damageMessage = function $damageMessage() {
    this.$transmitMessageFromPassenger(this.$pickRandomPassenger(), "[passenger-damage-concern]");
}
//-------------------------------------------------------------------------------------------------------------
this.$impatientMessage = function $impatientMessage() {
    this.$transmitMessageFromPassenger(this.$pickRandomPassenger(), "[passenger-impatient]");
}
//-------------------------------------------------------------------------------------------------------------
this.$interstellarSpaceMessage = function $interstellarSpaceMessage() {
    this.$transmitMessageFromPassenger(this.$pickRandomPassenger(), "[passenger-interstellar]");
}
//-------------------------------------------------------------------------------------------------------------
this.$pickRandomPassenger = function () {
    var p = player.ship;
    return p.passengers[Math.floor(Math.random() * p.passengers.length)];
}
//-------------------------------------------------------------------------------------------------------------
this.$transmitMessageFromPassenger = function (passenger, message) {
    if (!passenger) return;
    if (message.indexOf("[") === 0) message = expandDescription(message);
    var notifySound = new SoundSource;
    notifySound.sound = "[@beep]";
    notifySound.play();
    player.commsMessage(expandDescription("[epc_passenger_message]", { name: passenger.name, message: message }), 5);
    // add to comms log (if installed) so that it doesn't disappear with other messages
    if (worldScripts.CommsLogMFD) {
        worldScripts.CommsLogMFD.commsMessageReceived(message, null, expandDescription("[epc_passenger_comms]", {name: passenger.name}));
    }
}
//-------------------------------------------------------------------------------------------------------------
this.$getSystemsByDescription = function (options) {
    var planets = SystemInfo.filteredSystems(this, function (other) {
        return (other.description.indexOf(options) >= 0 && other.systemID != system.ID);
    });
    return planets;
}
 |