Scripts/stationdockcontrol.js |
(function () {
"use strict";
this.name = "StationDockControl";
this.author = "phkb";
this.copyright = "2015 phkb";
this.description = "Displays list of ships docked at the current station, as well as controlling when ships launch.";
this.license = "CC BY-NC-SA 3.0";
/*
This OXP attempts to provide a list of docked ships at most stations. Ships in this list will launch when their allocated
launch slot becomes current, and ships that dock at the station are added to the list. See the readme.txt file for
more information.
Future ideas:
- Having some traders head to other trade stations in system, rather than just jumping out
- Change $clearLaunchedShips to move ships to their destination system instead of just removing them, possibly dropping a couple of
escorts or group members based on system danger level
- if their destination system is the player's system, then create them as ships in system after the player arrives.
- Look into ships entering wormholes, and decide how to process:
- need to wait until ship becomes invalid (which isn't as soon as they enter the wormhole)
- if the wormhole collapses, make a note of all the ships that entered and dock them at the destination station
(maybe drop a couple of escorts or group members based on system danger level)
- if the wormhole destination is the same as the players, possibly add the ships to the system just before the player arrives,
but some distance along the wp-planet lane.
- again need to check for invalid, as the core will do this itself if they're still valid when the player arrives
- Make player ship move from Docking to Unloading to Docked
- Announce arrivals via console messages (ie. "Cobra Mark III: The Iconic has just docked in docking bay CX-123",
"Anaconda (plus escorts) departing in 10 minutes")
- Give player ability to log an alert when a ship changes their destination or departure time
- Give player ability to make a note of a ship, and when it reaches 10 minutes to departure, alert the player
- Change commodity levels based on docking/departing ships. Take cargo from docking ships and add to manifest screen, take cargo
from manifest screen when ships are in "loading" status. Adjust prices as well.
- will need some sort of AI to decide how this works on non-main stations.
Limitations:
1. The method chosen to force Oolite to create a single ship (without any escorts) involves creating a shipdata.plist
entry for each ship that might have escorts.
This means, if a ship that is unknown to this OXP is added to Oolite, there many be cases where ships are added to the
system that don't appear on the traffic control list.
ISSUES:
- Occasionally seeing launches that didn't come from the list (eg Hognose evangelists).
Where are they coming from, and can/should this be addressed?
- Some shipdata.plist entries don't have a "auto_ai" value set at all - it's "undefined". In these instances, I'm making
the assumption that this means the ship can have an auto ai assigned to it.
- Because we are creating ships based on data keys, not roles, we need to be a bit more explicit when it comes to AI settings.
So we are generally setting the AI for every ship we create, even standard traders, to ensure everything has an AI.
- Lone escorts that dock with a station will be switched to "trader" if they have cargo space and a hyperdrive. If they
have no cargo but only a hyperdrive, they'll choose to become a "trader-courier". Otherwise they'll dry-dock their ship
and get some R&R (ship entry will be removed)
TESTS:
1. Station traffic at main stations in different systems is equivalent to original
2. Traffic at non-main stations is equivalent
3. Traffic at hasNPCTraffic = no is equivalent
4. Balance of ships is not affected too much
5. Test what happens to groups/escorts when they dock - does the system pick up all the group info correctly (leader, members
appear after leader, difference between escort and group)
6. Memory footprint - how much memory is being used by the data requirements, how do you test memory usage in Oolite. Task Manager?
*/
this._disable = false; // quick way to disable the OXP without removing it.
this._disableQueues = false; // flag to stop the population of all station dock queues
this._systemDockingData = []; // main dataset
this._stationAdjust = false; // flag to determine whether all system stations have had the new "otherShipDocked" function added to them
this._rsnInstalled = false; // indicates whether randomshipnames OXP is installed
this._launching = []; // subset of main array used for launching ships
this._docking = []; // list of ships that are currently attempting to dock with the players docked station
this._autoGenerationInProgress = false; // indicates that the process of auto-generating docked ships is in progress
this._stationBayCodes = []; // holding array of station docking bay codes, used during an auto-generation process
this._quickUpdateTimer = null;
this._launchTimer = null; // timer to move ships to the launching array
this._launchListUpdating = false; // flag to indicate the launching list is updating
this._jumpStarted = false;
this._randomShips = {}; // array of ship roles, types and data keys
this._shipData = {};
this._launchingGroups = []; // used to hold groups being launched at stations
this._launchingEscorts = []; // used to hold escort leaders being launched at stations
this._stationList = []; // list of stations and indexes for the current system
this._shuttleDest = null; // list of in-system destinations (stations) shuttles can be sent to
this._stationIndexesLoaded = false; // flag to indicate when the station indexes have been loaded
this._stationIndexCount = 0;
this._neighbours = null; // array of neighbouring systems
this._populatorVars = null;
this._repopulate = false; // flag to determine whether a repopulate process will take place (usually after a large time jump)
this._running = false;
this._repopulateFactor = 0; // determines how deep into the dock list ships will be added on repopulate
this._repopulateStation = null; // object to hold new station which didn't exist before but now needs to be populated
this._populateStation = [];
this._populateTimer = null;
this._adjustDepartureBands = 0; // counter to determine when to run the adjust departure routine
this._escortCounter = 1; // internal reference counter for escorts
this._groupCounter = 1; // internal reference counter for groups
this._shipMonitor = 0; // variable to keep track of when to do a population count to the log file, and also assess station docks
this._smuggling = false; // flag to indicate the smuggling OXP is in use
this._lateStations = []; // array of stations created after systemWillPopulate to be processed by the dock populator
this._limit = 0;
this._skilledNPCs = false;
//this._doShipDockDataGrab = {};
//=============================================================================================================
// user settings
this._debug = false; // set to true to output various messages to the log, false will disable all logging messages
this._logLaunchType = 0; // how to log launch events: 0 = not logged, 1 = minimal, 2 = full, 3 = turn on AI messages for launched ships
this._logDockingType = 0; // how to log docking events: 0 = not logged, 1 = minimal, 2 = full
this._logAdjustmentType = 0; // how to log adjustments: 0 = not logged, 1 = type only, 2 = full
this._logPopulatorType = 0; // how to log populator events: 0 = not logged, 1 = minimal, 2 = full, 3 = full + escort definition data
// The following settings can be adjusted to effect the number of ships added to the docking list.
// See the this.$populateStationList function for more information on what is happening with these numbers
this._traderSkipVesselFactor = 0; // increase to reduce the number of times a potential new trader vessel will be skipped.
// 0 = no chance of skipping vessels (disabled)
// 1 = high chance of skipping vessels
// 2
// 3
// 4
// ... lower and lower chance of skipping vessels
this._pirateSkipVesselFactor = 5; // increase to reduce the number of times a potential new pirate group will be skipped.
this._hunterSkipVesselFactor = 8; // increase to reduce the number of times a potential new hunter group will be skipped.
// these next 5 variables control the frequency and size of adjustments to the docking lists
this._adjustDepartureChance = 0.3; // decimal between 0 and 1, higher number means more ships will move from "docked" to "loading" or from "loading" to departing
this._adjustRuns = 2; // how many adjustment runs to do on each cycle (will reduce to 1 when in flight)
this._adjustDepartureFreq = 0; // number greater than or equal to 0. determines the frequency of the adjust Departure routine.
// 0 means every 20 seconds (after 1 systemWillRepopulate cycles, ie every time)
// 1 means every 40 seconds (after 2 systemWillRepopulate cycles)
// 2 means every 60 seconds (after 3 systemWillRepopulate cycles)
this._maxAdjustments = 3; // maximum possible number of adjustments to do on any adjustment cycle
this._noAdjustPeriod = 600; // the number of seconds that must elapse before a ship can be adjusted again (default is 600 = 10 minutes)
// types of adjustment functions to select from:
// 0 = do nothing,
// 1 = docking to unloading, unloading to docked, docked to loading, loading to departing,
// 2 = adjust departure time,
// 3 = adjust destination
this._adjustFunctions = [1, 0, 1, 1, 1, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 3, 3];
// these next items control the amount of traffic that will be generated at the different stations.
// the default is "High", and any station that doesn't fit any of the roles below will default to this.
// note: if a station has the "has_npc_traffic" flag set to false, it will override whatever is in this list
this._trafficMedium = ["constore", "random_hits_any_spacebar", "sfep_station", "wildShips_kiota2Disc", "wildShips_kiota4Disc",
"wildShips_kiota4Sphere", "wildShips_kiota2Sphere"
];
this._trafficLow = ["rrs_group_spacestation", "rrs_astromine", "rrs-mining-outpost", "casinoship", "comczgf", "bigTrader",
"ups_dependance", "ups_dependance2", "blackmonk_monastery", "aquatics_HQ", "wildShips_kiota2Comms", "wildShips_kiota4Comms",
"wildShips_kiota2Spur", "wildShips_kiota4Solar", "wildShips_kiota2Solar", "astrofactory", "free_trade_zone", "togy_station", "tgy_station",
"taxi_station"
];
this._trafficVeryLow = ["rockhermit", "rockhermit-chaotic", "rockhermit-pirate", "repaired-buoy-station", "anarchies_renegade_station",
"anarchies_salvage_gang", "GW-rock-hermit"
];
this._trafficNone = ["slaver_base", "rrs_slaverbase", "anarchies_hacker_outpost", "anarchies_sentinel_station", "bigTrader",
"thecollector_adck_coriolis-station", "jaguar_company_base", "GW-fuel-processor", "GW-comms-station", "laveAcademy_academy",
"planetFall_surface", "generationship", "IST_genship carrier", "behemoth"
];
this._shuttleTrafficNone = ["pirate-cove", "slaver_base", "rrs_slaverbase", "anarchies_hacker_outpost", "anarchies_sentinel_station",
"bigTrader", "thecollector_adck_coriolis-station", "jaguar_company_base", "GW-fuel-processor", "GW-comms-station", "generationship",
"IST_genship carrier", "rockhermit", "rockhermit-chaotic", "rockhermit-pirate"
];
// this is the list of primary roles we will be looking after as part of this OXP
// that is, we will create ships with these primary roles. Docking ships must have one of these primary roles for them to appear in the dock list after docking
this._controlledRoles = ["trader", "trader-courier", "trader-smuggler", "shuttle",
"pirate", "pirate-light-fighter", "pirate-medium-fighter", "pirate-heavy-fighter", "pirate-interceptor",
"pirate-light-freighter", "pirate-medium-freighter", "pirate-heavy-freighter",
"hunter", "hunter-medium", "hunter-heavy", "assassin-light", "assassin-medium", "assassin-heavy",
"escort", "escort-medium", "escort-heavy" // removed escort-light
];
// ships with these roles will be included in all docking processes
this._dockingRoleExceptions = ["strelka", "trident"];
// ships with these keys will not be created directly (although they may still dock)
this._dockingKeyExclusions = ["convoys-adder", "convoys-anaconda", "convoys-anaconda2", "convoys-anaconda3", "convoys-anaconda-admiral",
"convoys-anaconda-pirate", "convoys-asp", "convoys-asp-pirate", "convoys-boa", "convoys-boa-pirate", "convoys-boa-mk2", "convoys-boa-mk2-pirate",
"convoys-cobra3", "convoys-cobra3-pirate", "convoys-ferdelance", "convoys-ferdelance-pirate", "convoys-gecko-pirate", "convoys-krait",
"convoys-krait-pirate", "convoys-mamba", "convoys-moray", "convoys-moray-pirate", "convoys-moray-morayMED", "convoys-python", "convoys-python-team",
"convoys-python-pirate", "convoys-python-blackdog", "convoys-shuttle", "convoys-transporter", "convoys-transporter-miner", "convoys-worm",
"convoys-worm-miner", "freighterconvoys-anaconda", "freighterconvoys-anaconda2", "freighterconvoys-anaconda3", "freighterconvoys-anaconda4",
"freighterconvoys-boa", "freighterconvoys-boa-mk2", "freighterconvoys-cobra3", "freighterconvoys-moray", "freighterconvoys-morayMED",
"freighterconvoys-python", "freighterconvoys-python-team", "freighterconvoys-python-trader", "freighterconvoys-shuttle", "freighterconvoys-shuttle2",
"freighterconvoys-transporter", "freighterconvoys-transporter-miner", "freighterconvoys-worm", "freighterconvoys-worm-miner", "freighterconvoys-miner",
"anaconda-carrier-pirate", "anaconda-carrier", "empty-carrier-pirate", "cobra3-carrier-pirate", "cobra3-carrier",
"cobra1-carrier-pirate", "cobra1-carrier", "ferdelance-carrier", "ferdelance-carrier-pirate"
];
// these roles will switch any attached escorts into a ship group instead
this._switchEscortsToGroup = ["rhs_big_boss_cobra4_spacelane_shipset", "rhs_big_boss_iguana_spacelane_shipset", "rhs_big_boss_impcourier_spacelane_shipset",
"rhs_big_boss_imptrader_spacelane_shipset", "rhs_big_boss_pitviper_spacelane_shipset", "rhs_big_boss_supercobra_spacelane_shipset", "rhs_big_boss_vamppurg_spacelane_shipset",
"rhs_big_boss_wolfmk2SE_spacelane_shipset", "rhs_mark_arachnid_shipset", "rhs_mark_aspmk1_shipset", "rhs_mark_cobra3courier_shipset", "rhs_mark_cobra3rapier_shipset",
"rhs_mark_cobra4_shipset", "rhs_mark_cobras9_shipset", "rhs_mark_drake2_shipset", "rhs_mark_dttmk1_shipset", "rhs_mark_galtech_escort_fighter_shipset",
"rhs_mark_ghavial_shipset", "rhs_mark_gnat_shipset", "rhs_mark_imp_shipset", "rhs_mark_imptrader_shipset", "rhs_mark_phaze_shipset", "rhs_mark_pitviper_shipset",
"rhs_mark_supercobra_shipset", "rhs_mark_vamppurg_shipset", "rhs_mark_wolf_shipset"
];
// these script properties will be maintained from dock to relaunch
this._propertiesToKeep = [];
this._AIScriptExceptions = {};
// these are roles we don't control directly, but we might need to dock (eg special escort roles)
// they will be added during play
this._additionalRoles = [];
this._ignoreEquip = ["EQ_ILS"];
this._pendingDock = []; // array of data objects to search for when ships dock, so a third party OXP can be informed that a specific ship is now docked
this._trueValues = ["yes", "1", 1, "true", true];
this._stationNullError = [];
this._cleanUp = 0;
this._cleanUpComplete = false;
// mix of numbers to balance how many in each section
// band 1 < 3 minutes >> no longer used (creates too many launches)
// band 2 3-15 minutes
// band 3 15-30 mins
// band 4 30-60 mins
// band 5 1 - 2 hours
// band 6 2 - 12 hours
// band 7 12 - 16 hours (loading status)
// band 8 16 - 20 hours (docked status)
// band 9 20 - 23 hours (unloading status)
// band 10 23 - 24 hours (docking status)
this._bandSrc = {
2: [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10
],
3: [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10
],
4: [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10
],
5: [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10
],
6: [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10
],
7: [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10
],
8: [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10
],
9: [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10
],
10: [10, 10, 10],
};
this._bandRng = {
1: {
low: 1,
high: 3
},
2: {
low: 3,
high: 15
},
3: {
low: 15,
high: 30
},
4: {
low: 30,
high: 60
},
5: {
low: 60,
high: 120
},
6: {
low: 120,
high: 720
},
7: {
low: 720,
high: 960
},
8: {
low: 960,
high: 1200
},
9: {
low: 1200,
high: 1380
},
10: {
low: 1380,
high: 1440
},
11: {
low: 1440,
high: 1680
}
};
this._departureBands = [];
// configuration settings for use in Lib_Config
this._sdcConfig = {
Name: this.name,
Display: "Debug Options",
Alias: "Station Dock Control",
Alive: "_sdcConfig",
Bool: {
B0: {
Name: "_debug",
Def: false,
Desc: "Enable debug messages"
},
Info: "Turns on debug messages."
},
SInt: {
S0: {
Name: "_logLaunchType",
Def: 0,
Desc: "Debug: Launch info",
Min: 0,
Max: 3
},
S1: {
Name: "_logDockingType",
Def: 0,
Desc: "Debug: Docking info",
Min: 0,
Max: 2
},
S2: {
Name: "_logAdjustmentType",
Def: 0,
Desc: "Debug: Adjustments info",
Min: 0,
Max: 2
},
S3: {
Name: "_logPopulatorType",
Def: 0,
Desc: "Debug: Populator info",
Min: 0,
Max: 3
},
Info: "0 - Debug launch messages. 0 = none, 1 = min, 2 = full, 3 - inc AI msgs\n1 - Debug docking messages. 0 = none, 1 = min, 2 = full\n2 - Debug adjustment messages. 0 = none, 1 = min, 2 = full\n3 - Debug populator messages. 0 = none, 1 = min, 2 = full, 3 = full + escort info"
}
};
// dictionary of stationName/shipTypes with a boolean value indicating if the ship cannot dock at that station.
// no value means ship can dock
// this dictionary is being manually populated to increase performance
this._shipFits = {
"A Seedy Space Bar|Andromeda": 0,
"Astromine Penal Colony|Andromeda": 0,
"Axtech Dodecahedron Station|Andromeda": 0,
"Capt Kev Dodecahedron Station|Andromeda": 0,
"Collective SLAPU|Andromeda": 0,
"Collective ZGF|Andromeda": 0,
"Constellation Icosahedron Station|Andromeda": 0,
"Coriolis Station|Andromeda": 0,
"Dodecahedron Station|Andromeda": 0,
"DS-A1 Coriolis Station|Andromeda": 0,
"DS-A1 Dodecahedron Station|Andromeda": 0,
"DS-A1 Icosahedron Station|Andromeda": 0,
"Free Trade Zone|Andromeda": 0,
"FrontierTech Worldranger Icosahedron Station|Andromeda": 0,
"GalaxyMart Con Store|Andromeda": 0,
"GASECBlue Dodecahedron Station|Andromeda": 0,
"Globe Station II|Andromeda": 0,
"Globe Station III|Andromeda": 0,
"Greenline Coriolis Station|Andromeda": 0,
"GRS Buoy Factory|Andromeda": 0,
"G-X1 Coriolis Station|Andromeda": 0,
"G-X1 Icosahedron Station|Andromeda": 0,
"G-Z1 Dodecahedron Station|Andromeda": 0,
"G-Z1 Icosahedron Station|Andromeda": 0,
"G-Z1 Octahedron Outpost|Andromeda": 0,
"G-Z1 Tetrahedron Depot|Andromeda": 0,
"G-Z2 Octahedron Outpost|Andromeda": 0,
"G-Z2 Tetrahedron Depot|Andromeda": 0,
"Habitat Mark II|Andromeda": 0,
"Icosahedron Station|Andromeda": 0,
"Imperial AstroFactory|Andromeda": 0,
"Imperial Dodecahedron Station|Andromeda": 0,
"Kiota Biosphere Station|Andromeda": 0,
"Kiota Habitat Station|Andromeda": 0,
"Kiota Manufacturing Station|Andromeda": 0,
"Kiota Mega Habitat Station|Andromeda": 0,
"Kiota Relay Station|Andromeda": 0,
"Kiota Research Station|Andromeda": 0,
"Kiota Solar Station|Andromeda": 0,
"Leesti High|Andromeda": 0,
"LX5 Dodecahedron Station|Andromeda": 0,
"Mall-Wart Con Store|Andromeda": 0,
"Mayan Dodecahedron Station|Andromeda": 0,
"Metaforce Coriolis Station|Andromeda": 0,
"M-G2 Globe Station|Andromeda": 0,
"M-G3 Globe Station|Andromeda": 0,
"Mining Outpost|Andromeda": 0,
"M-T1 Torus Station|Andromeda": 0,
"M-T2 Torus Station|Andromeda": 0,
"N-A1 Coriolis Station|Andromeda": 0,
"N-X1 Dodecahedron Station|Andromeda": 0,
"Octahedron Outpost|Andromeda": 0,
"Oodles Con Store|Andromeda": 0,
"Pi-42 Con Store|Andromeda": 0,
"Pirate Rock|Andromeda": 0,
"RaTech Gold Coriolis Station|Andromeda": 0,
"RedTec Coriolis Station|Andromeda": 0,
"Renegade Station|Andromeda": 0,
"Rock Hermit|Andromeda": 0,
"Sainsboory's Con Store|Andromeda": 0,
"Salvage Gang|Andromeda": 0,
"Sodalite Station|Andromeda": 0,
"SolarTec Coriolis Station|Andromeda": 0,
"Star Con Store|Andromeda": 0,
"Tescoo Con Store|Andromeda": 0,
"Tetrahedron Depot|Andromeda": 0,
"Torus Station|Andromeda": 0,
"Trade Outpost|Andromeda": 0,
"Trontech Vulcan Dodecahedron Station|Andromeda": 0,
"Waspline Coriolis Station|Andromeda": 0,
"Worldbuilders Icosahedron Station|Andromeda": 0,
"Imperial AstroFactory|Serpent Class Cruiser": 0,
"Imperial AstroFactory|Hornet": 0,
"Imperial AstroFactory|Anaconda": 0,
"Imperial AstroFactory|Boa Class Cruiser": 0,
"Imperial AstroFactory|Boa": 0,
"Imperial AstroFactory|Cobra Mark IV": 0,
};
//-------------------------------------------------------------------------------------------------------------
// obj has following params:
// shipName name of ship to add menu item for (required if pilotname not specified)
// shipType ship type (required if pilotname not specified)
// pilotName name of pilot (optional, but required if shipname and shiptype are blank)
// worldScript worldScript name for the callback (required)
// callback worldScript function to call in the callback (required)
// parameter parameter to add to the function when called (optional)
// launchCallback worldScript function to call when the ship launches after docking (optional)
this.$awaitShipDocking = function $awaitShipDocking(obj) {
if ((!obj.shipName || obj.shipName === "") && (!obj.pilotName || obj.pilotName === "")) {
throw "Invalid settings: shipName or pilotName must be specified";
}
if ((!obj.shipType || obj.shipType === "") && (!obj.pilotName || obj.pilotName === "")) {
throw "Invalid settings: shipType or pilotName must be specified";
}
if (!obj.worldScript || obj.worldScript === "") {
throw "Invalid settings: worldScript (worldScript name) must be specified, and cannot be blank.";
}
if (!obj.callback || obj.callback === "") {
throw "Invalid settings: callback (function name) must be specified, and cannot be blank.";
}
this._pendingDock.push({
shipName: (obj.shipName ? obj.shipName : ""),
shipType: (obj.shipType ? obj.shipType : ""),
pilotName: (obj.pilotName ? obj.pilotName : ""),
callback: obj.callback,
worldScript: obj.worldScript,
launchCallback: (obj.launchCallback ? obj.launchCallback : "")
});
}
//-------------------------------------------------------------------------------------------------------------
// removes all ships from all docks in the current system
this.$emptyAllQueues = function $emptyAllQueues() {
this._systemDockingData[system.ID] = {};
for (var i = this._launching.length - 1; i >= 0; i--) {
if (this._launching[i].system === system.ID) this._launching.splice(i, 1);
}
}
//=============================================================================================================
// system functions
//-------------------------------------------------------------------------------------------------------------
this.startUp = function () {
if (this._disable === true) {
// delete all the routines that would initiate/update data
// that will effectively disable this OXP while leaving it in place.
delete this.startUpComplete;
delete this.shipDied;
delete this.playerWillSaveGame;
delete this.shipWillEnterWitchspace;
delete this.shipExitedWitchspace;
delete this.shipWillLaunchFromStation;
delete this.shipWillDockWithStation;
delete this.playerBoughtEquipment;
delete this.playerBoughtNewShip;
delete this.systemWillPopulate;
delete this.systemWillRepopulate;
delete this.startUp;
return;
}
if (missionVariables.StationDockControl_ShipFits) {
delete missionVariables.StationDockControl_ShipFits;
}
if (worldScripts["Skilled NPCs"]) {
this._skilledNPCs = true;
}
}
//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function () {
if (this._debug) {
log(this.name, "Logging levels:");
log(this.name, "Populator: " + this._logPopulatorType);
log(this.name, "Docking: " + this._logDockingType);
log(this.name, "Launching: " + this._logLaunchType);
log(this.name, "Adjustments: " + this._logAdjustmentType);
log(this.name, "===================================================");
}
// register our settings, if Lib_Config is present
if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this._sdcConfig);
// set a flag if smugglers is installed
if (worldScripts.Smugglers_Illegal) this._smuggling = true;
// set a flag if random ship names is installed
if (worldScripts["randomshipnames"]) this._rsnInstalled = true;
// add some AI script exceptions
if (worldScripts.spicy_hermits_relocator) {
this._AIScriptExceptions["Spicy Grunt AI"] = "spicy_hermits-gruntAI.js";
this._AIScriptExceptions["Spicy Pirate AI"] = "spicy_hermits-pirateAI.js";
this._AIScriptExceptions["In-System Trader AI"] = "in-system-traderAI.js";
}
// output the populator outgoing factors
if (this._debug && this._logPopulatorType >= 2) this.$logOolitePopulatorOutgoingFactors();
this._repopulate = false;
// restore saved data if any exists
if (missionVariables.StationDockControl_Data) delete missionVariables.StationDockControl_Data;
if (missionVariables.StationDockControl_Main) delete missionVariables.StationDockControl_Main;
if (missionVariables.StationDockControl_Info) {
if (this._debug) log(this.name, "Loading stored data...");
this._systemDockingData = JSON.parse(missionVariables.StationDockControl_Info);
if (this._debug) log(this.name, "Stored data loaded successfully.");
delete missionVariables.StationDockControl_Info;
this._escortCounter = missionVariables.StationDockControl_EscortCounter;
this._groupCounter = missionVariables.StationDockControl_GroupCounter;
// reset our escort/group counters if they reach a certain limit
if (this._escortCounter > 30000) this._escortCounter = 1;
if (this._groupCounter > 30000) this._groupCounter = 1;
this._quickUpdateTimer = new Timer(this, this.$basicSetup, 2, 0);
} else {
// otherwise create it from scratch
// create a timer to run the jumpstart routine
// we do this from a timer to try and make sure all OXP stations are in place - some OXP's might not have finished setting up yet
this._quickUpdateTimer = new Timer(this, this.$initialSetup, 2, 0);
}
// load the launching list, if there is one
if (missionVariables.StationDockControl_Launching) {
this._launching = JSON.parse(missionVariables.StationDockControl_Launching);
delete missionVariables.StationDockControl_Launching;
}
// load up any additional roles we found last time
if (missionVariables.StationDockControl_AdditionalRoles) {
this._additionalRoles = JSON.parse(missionVariables.StationDockControl_AdditionalRoles);
delete missionVariables.StationDockControl_AdditionalRoles;
// fixes bug with storing items repeatedly
this._additionalRoles = this.$compactArray(this._additionalRoles);
}
//this.$grabAllShipDockValues();
// start a timer to move ships to the launching queue every 60 seconds
this._launchTimer = new Timer(this, this.$updateLaunchingList, 60, 60);
this._populateTimer = new Timer(this, this.$runPopulation, 1, 3);
}
//-------------------------------------------------------------------------------------------------------------
// whenever we come into a new system, get a list of neighbouring systems
this.systemWillPopulate = function () {
if (system.ID === -1) return;
if (this._disable === true) {
delete this.systemWillPopulate;
return;
}
this._launchingGroups.length = 0;
this._launchingEscorts.length = 0;
this._neighbours = System.infoForSystem(galaxyNumber, system.ID).systemsInRange();
// remove ships that will have launched from array
//this.$clearLaunchedShips();
this._launching.length = 0;
// build our list of random ship names/roles/data keys
this.$loadShipNames();
this._stationAdjust = true; // tell the repopulator to adjust the station.otherShipDocked events
this._stationIndexesLoaded = false; // clear the setting that will load station indexes
this._repopulate = true; // tell the system to repopulate on the next repopulate run
this._repopulateFactor = 1440; // make the next repopulate run a complete one
this._shuttleDest = null; // clear out the array so it can be rebuilt later
}
//-------------------------------------------------------------------------------------------------------------
// add the docking script to all stations in system
this.systemWillRepopulate = function () {
if (system.ID === -1) return;
if (this._disable === true) {
delete this.systemWillRepopulate;
return;
}
if (this._cleanUpComplete === false) this.$dataCleanup();
this._shipMonitor = (this._shipMonitor + 1) % 6;
// check our docking queue situation
if (this._shipMonitor === 1 || this._shipMonitor === 4) this.$assessStationDocks();
// monitor the total ship count in a system to see how it fluctuates.
if (this._debug && this._logPopulatorType > 0) {
if (this._shipMonitor === 1) {
// only do this full count every 6 times
log(this.name, "Current ship count: " + system.allShips.length + ", system docked count: " + this.$countSystemItems() + ", main station count: " + this.$countStationItems(system.mainStation));
} else {
log(this.name, "Current ship count: " + system.allShips.length);
}
}
// some stations get created during launch or exit from witchspace (rather than through systemWillPopulate), so check here for a change to the number of stations
if (this._stationAdjust === false && system.stations.length > this._stationIndexCount) {
if (this.debug && this._logPopulatorType > 0) log(this.name, "New stations found in repopulate routine!");
this._stationAdjust = true;
this._stationIndexesLoaded = false;
this.$missingStationIndexes();
}
// adjust the station.otherShipDocked routines to use new version
if (this._stationAdjust === true) {
this._stationAdjust = false;
// update list of station indexes for this system
this.$stationIndexes();
var stns = system.stations;
stns.forEach(function (station) {
if (station.hasNPCTraffic && this.$checkStationRoleForNoTraffic(station) === false) {
// check for and save any previous script in otherShipDocked
if (station.script.otherShipDocked && station.script.otherShipDocked !== this.$sdc_otherShipDocked) {
if (this._debug) log(this.name, "Monkey patch being applied to " + station.name);
station.script.$sdc_hold_otherShipDocked = station.script.otherShipDocked;
}
// attach our script to the otherShipDocked event
station.script.otherShipDocked = this.$sdc_otherShipDocked;
}
}, this);
}
// check if the autoGeneration process failed (Javascript timed out before it completed)
//if (this._autoGenerationInProgress === true) {
// this._autoCount += 1;
// if (this._autoCount === 3) {
// this._autoCount = 0;
// this.$populateStationList(this._repopulateFactor);
// this._repopulateFactor = 0;
// }
//}
// if we've been asked to repopulate the station list, do it here.
if (this._repopulate === true && this._autoGenerationInProgress === false) {
this._repopulate = false;
this.$populateStationList(this._repopulateFactor);
this._repopulateFactor = 0;
} else {
// have we got a late station to process?
// if so, just do the repopulation process for one station at a time.
if (this._lateStations.length > 0) {
//log(this.name, "performing late station repopulate for " + this._lateStations[0]);
// get station at the top of the list and assign it to the repoulate station variable
// this will force the repopulate function to just run for a single station
this._repopulateStation = this._lateStations[0];
// remove it from the list so this only happens once
this._lateStations.splice(0, 1);
// run the repopulate routine for the full 12 hours
this.$populateStationList(1440);
// reset the repopulate station variable
this._repopulateStation = null;
}
}
// repopulate outgoing traffic
// look for any ships to launch and launch them
this.$launchPendingShips();
var adjruns = 1;
// only do the adjustment stuff while the player is docked.
if (player.ship.docked) {
// keep track of which ships are currently trying to dock, so we can make sure they're added to the list
this.$updateShipsDocking(player.ship.dockedStation);
// if we're docked, do more runs per cycle
adjruns = this._adjustRuns;
}
this._adjustDepartureBands = (this._adjustDepartureBands + 1) % (this._adjustDepartureFreq + 1);
// only do this process when our counter hits the frequency
if (this._adjustDepartureBands === this._adjustDepartureFreq) {
if (this._debug && this._logAdjustmentType > 0) log(this.name, "-------------------------------------------------");
for (var i = 1; i <= adjruns; i++) {
// pick a random acjustment function from the array
this.$adjustShipDepartureBands(this._adjustFunctions[this.$rand(this._adjustFunctions.length) - 1], 0);
}
}
// check for any ships that have been moved into a "rescheduled" status
if (this._populateStation.length === 0) this.$checkForRescheduledShips();
}
//-------------------------------------------------------------------------------------------------------------
this.shipDied = function (whom, why) {
// clean up any active timers
if (this._launchTimer && this._launchTimer.isRunning) this._launchTimer.stop();
delete this._launchTimer;
if (this._populateTimer && this._populateTimer.isRunning) this._populateTimer.stop();
delete this._populateTimer;
}
//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function () {
// save docking info array
missionVariables.StationDockControl_Info = JSON.stringify(this._systemDockingData);
missionVariables.StationDockControl_EscortCounter = this._escortCounter;
missionVariables.StationDockControl_GroupCounter = this._groupCounter;
// if there's anything in the launching list, save that as well
if (this._launching.length > 0) missionVariables.StationDockControl_Launching = JSON.stringify(this._launching);
// if there's anything in the additional roles list, save that too
if (this._additionalRoles.length > 0) missionVariables.StationDockControl_AdditionalRoles = JSON.stringify(this._additionalRoles);
}
//-------------------------------------------------------------------------------------------------------------
this.playerStartedJumpCountdown = function (type, seconds) {
this._jumpStarted = true;
}
//-------------------------------------------------------------------------------------------------------------
this.playerCancelledJumpCountdown = this.playerJumpFailed = function () {
this._jumpStarted = false;
}
//-------------------------------------------------------------------------------------------------------------
this.shipWillEnterWitchspace = function (cause, destination) {
this._populatorVars = null;
this._populateStation.length = 0;
this._autoGenerationInProgress = false;
//this._doShipDockDataGrab = {};
this._jumpStarted = false;
this._lateStations.length = 0;
this._launching.length = 0; // experimental - may need to remove
// remove all but the current and destination datasets
if (system.isInterstellarSpace === false) {
if (cause != "galactic jump" && destination) {
this.$dataPurge(system.ID, destination);
}
}
}
//-------------------------------------------------------------------------------------------------------------
this.shipExitedWitchspace = function () {
// output the populator outgoing factors
if (this._debug && this._logPopulatorType >= 2) this.$logOolitePopulatorOutgoingFactors();
this._pendingDock.length = 0;
}
//-------------------------------------------------------------------------------------------------------------
this.shipWillLaunchFromStation = function (station) {
// cleanup
if (missionVariables.StationDockControl_Data) delete missionVariables.StationDockControl_Data;
if (missionVariables.StationDockControl_AdditionalRoles) delete missionVariables.StationDockControl_AdditionalRoles;
this._docking.length = 0;
// ** should not need to do this -- oolite should auto-dock these ships
// dock any ship that is currently trying to dock
//if (this._docking && this._docking.length > 0) {
// for (var i = 0; i < this._docking.length; i++) {
// if (this._debug) log(this.name, "forcing dock of ship " + this._docking[i]);
// this.$logShipDocking(station, this._docking[i]);
// }
// }
// launch ships that are due in the next ten minutes
this.$dockingQueueShipClearance(station, 10);
}
//-------------------------------------------------------------------------------------------------------------
this.shipWillDockWithStation = function (station) {
// for a normal dock (ie. without a dock comp), there is no time jump when docking, so this call should do nothing
// for an insta-dock, the time jump is 20 miuntes
// for an escape pod, the time jump could be up to 8 days
this.$checkForLaunchedShips();
}
//-------------------------------------------------------------------------------------------------------------
// large time jumps can occur here...
this.playerBoughtEquipment = function (equipment) {
this.$checkForLaunchedShips();
}
//-------------------------------------------------------------------------------------------------------------
// ...and here
this.playerBoughtNewShip = function (ship) {
this.$checkForLaunchedShips();
}
//-------------------------------------------------------------------------------------------------------------
// ...and here
this.reportScreenEnded = this.missionScreenEnded = function () {
this.$checkForLaunchedShips();
}
//-------------------------------------------------------------------------------------------------------------
// launch (or remove) any ships that would have launched during a sudden jump in time
this.$checkForLaunchedShips = function $checkForLaunchedShips() {
// don't do the check if we're about to do a repopulate routine, or we're in the middle of one
if (this._repopulate) return;
// is a time jump happening (ie. seconds !== adjustedSeconds), and the difference is greater than 60 seconds
if (clock.seconds !== clock.adjustedSeconds && (clock.adjustedSeconds - clock.seconds) > 60) {
// work out how much the time jump is, in minutes
var diff = parseInt((clock.adjustedSeconds - clock.seconds) / 60);
if (this._debug && this._logLaunchType > 0) log(this.name, "Checking for launched ships within period of " + diff + " minutes...");
var stns = system.stations;
// go through each station in the system
stns.forEach(function (station) {
if (station.hasNPCTraffic && this.$checkStationRoleForNoTraffic(station) === false) {
this.$dockingQueueShipClearance(station, diff);
}
}, this);
// force docked certain ships, based on the timelapse
this.$forceDockController(diff);
// do some departure band adjustments, based on our time differential
// for 10 minutes, do 3 adjustments, for 20 minutes do 6 adjustments etc
if (diff > 10 && diff < 960) {
var adj = (diff / 10) * 3;
if (this._debug && this._logAdjustmentType > 0) log(this.name, "Bulk adjustments: " + adj);
this.$adjustShipDepartureBands(1, adj);
}
// set up the repopulation function
if (diff > 30) {
// add the diff onto the current repopulate factor, just in case there is already one in play and the repopulate routine hasn't run yet
// that's to cope with the situation where the player buys multiple things quickly
this._repopulateFactor += diff;
if (this._debug && this._logPopulatorType > 0) log(this.name, "Repopulate factor = " + this._repopulateFactor + " (diff = " + diff + ")");
// force the repopulator routine to run immediately
this.$populateStationList(this._repopulateFactor);
}
}
}
//-------------------------------------------------------------------------------------------------------------
// populates the randomShips array with ship names (eg "Cobra Mark III", "Asp Mark II" etc) and their associated role (eg "trader")
// because most of the time we will be working with a data array, and not an actual ship object, we need a way to link
// roles to ship types and ship keys. this routine re-compiles the list of possibilties which we can then use throughout the code.
// the result will be: each role and ship type will have one entry in the list, and the shipKeys element will have a list of
// possible versions of that ship.
this.$loadShipNames = function $loadShipNames() {
//-------------------------------------------------------------------------------------------------------------
// returns the role frequency out of a data string
function getRoleFrequency(roleName, roleData) {
var point1 = roleData.indexOf(roleName) + roleName.length;
var freq = "1"; // default of 1 (ie there is no bracket value)
if (roleData.charAt(point1) === "(") {
// extract the percentage
var point2 = roleData.indexOf(")", point1);
freq = roleData.substring(point1 + 1, point2);
}
return freq;
}
//-------------------------------------------------------------------------------------------------------------
// checks to see if shipkey can be spawned in this system (ie are any conditions in place);
function modelIsAllowed(shipkey) {
var shipdata = Ship.shipDataForKey(shipkey);
// are we allowed to include this data key in this system? check the conditions if there are some
var include = true;
if (shipdata.conditions) {
var cond = shipdata.conditions.toString().split(",");
//1,systemGovernment_number equal 4,systemGovernment_number,0,0,4,
//1,systemEconomy_number notequal 4,systemEconomy_number,1,0,4
//1,systemEconomy_number lessthan 4,systemEconomy_number,2,0,4
//1,systemEconomy_number greaterthan 4,systemEconomy_number,3,0,4
var offset = 0;
var finish = false;
var checking = -1;
do {
// get the value we're checking
checking = -1;
if (cond[offset + 2].substring(0, 7) === "mission") {
if (missionVariables[cond[offset + 2].replace("mission_", "")]) {
checking = missionVariables[cond[offset + 2].replace("mission_", "")];
log("SDC.$loadShipNames.modelIsAllowed", "field = " + cond[offset + 2] + ", value = " + checking);
} else {
log("SDC.$loadShipNames.modelIsAllowed", "!!NOTE: Condition value mission variable not set: " + cond[offset + 2]);
}
} else {
switch (cond[offset + 2]) {
case "systemGovernment_number":
checking = system.government;
break;
case "systemEconomy_number":
checking = system.economy;
break;
case "systemTechLevel_number":
checking = system.techLevel;
break;
case "score_number":
checking = player.score;
break;
case "galaxy_number":
checking = galaxyNumber;
break;
case "planet_number":
checking = system.ID;
break;
default:
log("SDC.$loadShipNames.modelIsAllowed", "!!NOTE: Condition value not catered for: " + cond[offset + 2]);
break;
}
}
// in case a mission variable is a text value of some sort
if (isNaN(parseInt(checking)) && isNaN(parseFloat(checking))) {
switch (cond[offset + 3]) {
case "0": // equals
if (checking != cond[offset + 5]) include = false;
break;
case "1": // not equals
if (checking == cond[offset + 5]) include = false;
break;
default:
log("SDC.$loadShipNames.modelIsAllowed", "!!NOTE: Condition comparison not catered for: " + cond[offset + 3]);
break;
}
} else {
if (checking >= 0) {
// work out the type of check, but in negative (or opposite)
switch (cond[offset + 3]) {
case "0": // equals
if (checking !== parseInt(cond[offset + 5])) include = false;
break;
case "1": // not equals
if (checking === parseInt(cond[offset + 5])) include = false;
break;
case "2": // lessthan
if (checking >= parseInt(cond[offset + 5])) include = false;
break;
case "3": // greaterthan
if (checking <= parseInt(cond[offset + 5])) include = false;
break;
default:
log("SDC.$loadShipNames.modelIsAllowed", "!!NOTE: Condition comparison not catered for: " + cond[offset + 3]);
break;
// others?
}
}
}
offset += 6;
if (offset >= cond.length - 1) finish = true;
} while (finish === false);
} else if (shipdata.condition_script) {
// or the condition script
// create a dummy object to attach the script to so it can be executed
var temppos = system.sun.position.cross(system.mainPlanet.position).direction().multiply(4E9).subtract(system.mainPlanet.position);
var tempalloy = system.addShips("alloy", 1, temppos, 0);
tempalloy[0].setScript(shipdata.condition_script);
include = tempalloy[0].script.allowSpawnShip(shipkey);
tempalloy[0].remove(true);
} else {
// otherwise we're free to play
include = true;
}
return include;
}
// compile a list of ships/roles
this._randomShips = {};
this._shipData = {};
var indexInList = this.$indexInList;
var excl = this._dockingKeyExclusions;
var cr = this._controlledRoles;
for (var i = 0; i < cr.length; i++) {
var ctrlRole = cr[i];
var shipKeys = Ship.keysForRole(ctrlRole);
var freq = "";
if (shipKeys) {
for (var j = 0; j < shipKeys.length; j++) {
var key = shipKeys[j];
// don't include any of these keys
if (indexInList(key, excl) >= 0) continue;
var include = modelIsAllowed(key);
if (include) {
var shipdata = Ship.shipDataForKey(key);
// populate our holding dictionary
if (!this._shipData[key]) {
this._shipData[key] = {
"auto_ai": shipdata["auto_ai"],
"auto_weapons": shipdata["auto_weapons"],
"name": shipdata.name,
"max_cargo": shipdata["max_cargo"],
"hyperspace_motor": shipdata["hyperspace_motor"],
"escorts": shipdata["escorts"],
"escort_role": shipdata["escort_data"],
"escort_ship": shipdata["escort_ship"],
"escort_roles": shipdata["escort_roles"],
"accuracy": shipdata["accuracy"],
"heat_insulation": shipdata["heat_insulation"],
};
}
freq = getRoleFrequency(ctrlRole, shipdata.roles);
// make sure we don't load our "dock" versions of ships
if (key.indexOf("dock_") === -1 && key.indexOf("bounty_") === -1) {
if (!this._randomShips[ctrlRole]) {
this._randomShips[ctrlRole] = {
shipKeys: [key],
frequency: [freq]
};
} else {
this._randomShips[ctrlRole].shipKeys.push(key);
this._randomShips[ctrlRole].frequency.push(freq);
}
}
} else {
if (this._debug && this._logPopulatorType >= 2) log(this.name, "!!Note: Conditions not met to include " + ctrlRole + " -- " + key);
}
}
} else {
log(this.name, "!!NOTE: No ship keys found for role " + ctrlRole);
}
}
}
//-------------------------------------------------------------------------------------------------------------
// this is a one-shot routine, called by a timer launched through startUpComplete
// essentially this jump-starts the populator routine so players don't have to wait 20 seconds to see the list of ships
this.$initialSetup = function $initialSetup() {
this._repopulate = false;
this.$stationIndexes();
this.$populateStationList(1440);
delete this._quickUpdateTimer;
}
//-------------------------------------------------------------------------------------------------------------
// this is a one-shot routine, called by a timer launched through startUpComplete when data is restored from a savegame file
// essentially this gets the station indexes populated early, so the player will be able to see data straight away
this.$basicSetup = function $basicSetup() {
this.$stationIndexes();
// if a new station has been added to the system since the last save (eg via an OXP) and it needs traffic, make sure it gets some
for (var i = 0; i < this._stationList.length; i++) {
var item = this._stationList[i];
if (this.$countStationItems(item.station) === 0) {
if (this._debug && this._logPopulatorType > 0) log(this.name, "Adding station dock entries for new station: " + item.name + "(" + item.index + ")");
this._repopulateStation = item.station;
this.$populateStationList(1440);
this._repopulateStation = null;
}
}
delete this._quickUpdateTimer;
}
//-------------------------------------------------------------------------------------------------------------
// build an array of stations with an index key, for when there is more than 1 of a particular station in a system
this.$stationIndexes = function $stationIndexes() {
if (this._stationIndexesLoaded) return;
this._stationIndexesLoaded = true;
var stns = system.stations;
this._stationList.length = 0;
this._stationIndexCount = system.stations.length;
stns.forEach(function (station) {
// only create an index for stations we have an interest in controlling
if ((station.status === "STATUS_ACTIVE" || station.status === "STATUS_IN_FLIGHT") && station.hasNPCTraffic && this.$checkStationRoleForNoTraffic(station) === false) {
var idx = 1;
for (var i = 0; i < this._stationList.length; i++) {
if (this._stationList[i].station.name === station.name) idx += 1;
}
this._stationList.push({
station: station,
name: station.name,
index: idx
});
if (this._debug && this._logPopulatorType >= 2)
log(this.name, "Creating stationList entry: " + station.name + " index = " + idx);
}
}, this);
}
//-------------------------------------------------------------------------------------------------------------
// adds any stations missed during the system population routines to the lateStations array (ie stations that get created via some other event)
this.$missingStationIndexes = function $missingStationIndexes() {
var stns = system.stations;
stns.forEach(function (station) {
var found = false;
for (var i = 0; i < this._stationList.length; i++) {
if (this._stationList[i].station === station) found = true;
}
if (found === false && this._lateStations.indexOf(station) === -1) {
if (this._debug && this._logPopulatorType >= 2) log(this.name, "Adding missing station: " + station.name);
this._lateStations.push(station);
}
}, this);
}
//-------------------------------------------------------------------------------------------------------------
// function to process ship docking (based on station "otherShipDocked" function)
this.$sdc_otherShipDocked = function $sdc_otherShipDocked(whom) {
// if there was a previous script in place, run it here.
if (this.ship.script.$sdc_hold_otherShipDocked) this.ship.script.$sdc_hold_otherShipDocked(whom);
if (whom.isPlayer === false) {
var w = worldScripts.StationDockControl;
w.$logShipDocking(this.ship, whom);
}
}
//-------------------------------------------------------------------------------------------------------------
// adjusts the departure times or destinations of random ships
// type 1 = change from docking to unloading, from unloading to docked, or from docked to loading
// type 2 = change departure time
// type 3 = change destination
this.$adjustShipDepartureBands = function $adjustShipDepartureBands(adjustType, override_thistime) {
if (this._debug && this._logAdjustmentType > 0) log(this.name, "Running adjustment type " + adjustType);
// type 0 = do nothing
if (adjustType === 0) return;
var chance = this._adjustDepartureChance;
var adjustCount = 0;
// work out how many adjustments we'll do this time
var thistime = this.$rand(this._maxAdjustments);
if (override_thistime > 0) {
thistime = override_thistime;
chance *= 2; // double the chance of making a change during a bulk update
}
if (this._systemDockingData[system.ID] == null) return;
// move a ship with unknown departure time to a defined time
// start from the bottom of the list
var skeys = Object.keys(this._systemDockingData[system.ID]);
for (var kc = 0; kc < skeys.length; kc++) {
var dta = this._systemDockingData[system.ID][skeys[kc]];
if (dta && dta.length > 0) {
for (var i = dta.length - 1; i >= 0; i--) {
var item = dta[i];
var done = false;
// if this record is for this system and we haven't changed it for over 10 minutes (this._noAdjustPeriod)
// and it's either a group leader or not in a group at all, or it's an escort leader or not in an escort at all
// this is so we only initiate a move for individual ships or leader ships
if ((clock.adjustedSeconds - item.lastChange) > this._noAdjustPeriod &&
((item.groupName === "" && item.escortName === "") ||
item.groupLeader === true || item.escortLeader === true) &&
Math.random() < chance) {
// get the current departure time in minutes
var depart = item.departureTime; //(item.departureTime - clock.adjustedSeconds) / 60;
var newDepart = 0;
var free = false;
var tries = 0;
// adjust from docking to unloading, unloading to docked, or docked to loading
if (adjustType === 1) {
// move from docking to unloading
if (done === false && depart >= 1380 && depart < 1440) {
// found one
tries = 0;
free = false;
do {
newDepart = (this.$rand(180) + 1380); // * 60 + clock.adjustedSeconds; // 20-23 hours
tries += 1;
free = this.$checkDepartureSlot(item.station, item.stationIndex, newDepart);
} while (free === false && tries < 5);
if (free === true) {
// do loading function here
// if (this._NPCTrading === true) {
// var td = worldScripts.StationDockInfo_NPCTrading;
// td.$processCargo_Unloading(item);
// }
done = true;
if (this._debug && this._logAdjustmentType === 2) log(this.name, "Moving " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : "") + " to unloading status");
item.departureTime = newDepart;
item.lastChange = clock.adjustedSeconds;
this.$updateGroupMemberData(skeys[kc], item.groupName, item.escortName, "departureTime", newDepart);
}
}
// move from unloading (1200-1380 minutes) to docked
if (done === false && depart >= 1200 && depart < 1380) {
// found one
tries = 0;
free = false;
do {
newDepart = (this.$rand(240) + 960); // * 60 + clock.adjustedSeconds; // 16-20 hours
tries += 1;
free = this.$checkDepartureSlot(item.station, item.stationIndex, newDepart);
} while (free === false && tries < 5);
if (free === true) {
done = true;
if (this._debug && this._logAdjustmentType === 2) log(this.name, "Moving " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : "") + " to docked status");
item.departureTime = newDepart;
item.lastChange = clock.adjustedSeconds;
this.$updateGroupMemberData(skeys[kc], item.groupName, item.escortName, "departureTime", newDepart);
}
}
// move from docked to loading status
if (done === false && depart >= 960 && depart < 1200) {
// found one
tries = 0;
free = false;
do {
newDepart = (this.$rand(240) + 720); // * 60 + clock.adjustedSeconds; // 12-16 hours
tries += 1;
free = this.$checkDepartureSlot(item.station, item.stationIndex, newDepart);
} while (free === false && tries < 5);
if (free === true) {
// do loading function here
// if (this._NPCTrading === true) {
// var td = worldScripts.StationDockInfo_NPCTrading;
// td.$processCargo_Loading(item);
// }
done = true;
if (this._debug && this._logAdjustmentType === 2) log(this.name, "Moving " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : "") + " to loading status");
item.departureTime = newDepart;
item.lastChange = clock.adjustedSeconds;
this.$updateGroupMemberData(skeys[kc], item.groupName, item.escortName, "departureTime", newDepart);
}
} // if depart between 16 and 47
// move from loading to departing
if (done === false && depart >= 720 && depart < 960) {
tries = 0;
free = false;
do {
newDepart = (this.$rand(660) + 60); // * 60 + clock.adjustedSeconds; // 1-12 hours
tries += 1;
free = this.$checkDepartureSlot(item.station, item.stationIndex, newDepart);
} while (free === false && tries < 5);
if (free === true) {
done = true;
if (this._debug && this._logAdjustmentType === 2) log(this.name, "Moving " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : "") + " to departing status");
item.departureTime = newDepart;
item.lastChange = clock.adjustedSeconds;
this.$updateGroupMemberData(skeys[kc], item.groupName, item.escortName, "departureTime", newDepart);
}
} // if depart between 720 and 960
} // if adjustType = 1
// straight change of departure time
if (adjustType === 2) {
if (done === false && depart > 120 && depart < 660) {
tries = 0;
free = false;
do {
// pick a random time (2 minutes to 11:42 hours)
newDepart = (this.$rand(700) + 2); // * 60 + clock.adjustedSeconds;
tries += 1;
free = this.$checkDepartureSlot(item.station, item.stationIndex, newDepart);
} while (free === false && tries < 5);
if (free === true) {
done = true;
if (this._debug && this._logAdjustmentType === 2) log(this.name, "Changing departure time for " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : ""));
item.departureTime = newDepart;
item.lastChange = clock.adjustedSeconds;
this.$updateGroupMemberData(skeys[kc], item.groupName, item.escortName, "departureTime", newDepart);
}
} // if depart
} // if adjustType = 2
// adjust destination
if (adjustType === 3) {
if (done === false && depart > 120 && depart < 660 &&
item.destinationSystem !== system.ID &&
item.destinationSystem >= 0 &&
item.destinationHidden === false) {
var newDest = -1;
var w = worldScripts["oolite-populator"];
switch (item.primaryRole) {
case "trader":
newDest = w._weightedNearbyTradeSystem();
break;
case "trader-courier":
if (Math.random() < 0.5) {
newDest = this.$nearbySystem(7);
} else {
newDest = this.$nearbySystem(25);
}
break;
case "trader-smuggler":
newDest = this.$nearbySystem(7);
break;
case "pirate-light-freighter":
case "pirate-medium-freighter":
newDest = w._nearbySafeSystem(system.info.government + 1);
break;
case "hunter-medium":
newDest = w._nearbyDangerousSystem(4);
break;
case "hunter-heavy":
newDest = w._nearbyDangerousSystem(1);
break;
}
if (item.destinationSystem !== newDest && newDest !== -1) {
done = true;
item.destinationSystem = newDest;
item.lastChange = clock.adjustedSeconds;
if (this._debug && this._logAdjustmentType === 2) log(this.name, "Updating destinationSystem for " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : "") + " to " + newDest);
this.$updateGroupMemberData(skeys[kc], item.groupName, item.escortName, "destinationSystem", newDest);
}
}
} // if adjustType === 3
}
// count the number of times we're adjusting a ship record
if (done === true) adjustCount += 1;
// stop once we hit the target for this round
if (adjustCount === thistime) break;
} // for i
} // if dta
} // for kc
}
//-------------------------------------------------------------------------------------------------------------
// look for any ships in the launching queue that have been moved to "Rescheduled"
// when found, give them a new departure slot
// hopefully, with our station dock assessment routine, no ships will ever need to be rescheduled, but just in case...
this.$checkForRescheduledShips = function $checkForRescheduledShips() {
if (this._launching.length > 0) {
for (var i = 0; i < this._launching.length; i++) {
var launch = this._launching[i];
var stationkey = launch.station + "_" + launch.stationIndex;
// if the departure time is set for "rescheduled" and this is either the group/escort leader or not in a group/escort at all...
if (Math.round(launch.departureTime) === -5) {
if (((launch.groupName === "" && launch.escortName === "") || (launch.groupLeader || launch.escortLeader))) {
// found one!
var newDepart = 0;
var tries = 0;
var free = false;
do {
// pick a random time (2 minutes to 11:42 hours)
newDepart = (this.$rand(700) + 2); // * 60 + clock.adjustedSeconds;
tries += 1;
free = this.$checkDepartureSlot(launch.station, launch.stationIndex, newDepart);
} while (free === false && tries < 5);
if (free === true) {
if (this._debug && this._logAdjustmentType >= 1) log(this.name, "Changing departure time for " + launch.shipType + (launch.shipName !== "" ? ": " + launch.shipName : ""));
launch.departureTime = newDepart;
launch.lastChange = clock.adjustedSeconds;
this.$updateGroupMemberLaunchingData(launch.groupName, launch.escortName, "departureTime", newDepart);
} else {
if (this._debug && this._logAdjustmentType >= 1) log(this.name, "Failed to find a new departure time for " + launch.shipType + (launch.shipName !== "" ? ": " + launch.shipName : ""));
}
} else {
// has the group leader launched?
if (this.$isGroupLeaderDocked(stationkey, (launch.groupLeader !== "" ? launch.groupLeader : launch.escortLeader)) === false) {
// get this ship launched ASAP!
var newDepart = 0;
var tries = 0;
var free = false;
do {
// pick a random time (2 minutes to 11:42 hours)
newDepart = this.$rand(3); // * 60 + clock.adjustedSeconds;
tries += 1;
free = this.$checkDepartureSlot(launch.station, launch.stationIndex, newDepart);
} while (free === false && tries < 5);
if (free === true) {
if (this._debug && this._logAdjustmentType >= 1) log(this.name, "Changing departure time for orphaned group/escort member " + launch.shipType + (launch.shipName !== "" ? ": " + launch.shipName : ""));
launch.departureTime = newDepart;
launch.lastChange = clock.adjustedSeconds;
} else {
if (this._debug && this._logAdjustmentType >= 1) log(this.name, "Failed to find a new departure time for orphaned group/escort member " + launch.shipType + (launch.shipName !== "" ? ": " + launch.shipName : ""));
}
}
}
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
// update group/escort members data
this.$updateGroupMemberData = function $updateGroupMemberData(stationkey, groupName, escortName, keyfield, newvalue) {
var dta = this._systemDockingData[system.ID][stationkey];
if (dta && dta.length > 0) {
for (var i = dta.length - 1; i >= 0; i--) {
var item = dta[i];
if (((escortName !== "" && item.escortName === escortName && item.escortLeader === false) ||
(groupName !== "" && item.groupName === groupName && item.groupLeader === false))) {
if (this._debug && this._logAdjustmentType === 2) log(this.name, "Updating " + keyfield + " of group/escort member " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : ""));
// update the keyfield with the new value
item[keyfield] = newvalue;
item.lastChange = clock.adjustedSeconds;
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
// update group/escort members data in the launching list
this.$updateGroupMemberLaunchingData = function $updateGroupMemberLaunchingData(groupName, escortName, keyfield, newvalue) {
for (var i = this._launching.length - 1; i >= 0; i--) {
var launch = this._launching[i];
if (launch.system === system.ID &&
((escortName !== "" && launch.escortName === escortName && launch.escortLeader === false) ||
(groupName !== "" && launch.groupName === groupName && launch.groupLeader === false))) {
if (this._debug && this._logAdjustmentType === 2) log(this.name, "Updating " + keyfield + " of group/escort member " + launch.shipType + (launch.shipName !== "" ? ": " + launch.shipName : ""));
// update the keyfield with the new value
launch[keyfield] = newvalue;
launch.lastChange = clock.adjustedSeconds;
}
}
}
//-------------------------------------------------------------------------------------------------------------
// log a new ship docking
// grab enough info to recreate a new ship later if needed, but don't keep a hold on the object so system resources are not consumed
this.$logShipDocking = function $logShipDocking(station, ship) {
// for some reason, we can occasionally end up with the station trying to dock itself here. So make sure we don't!
// don't log info about police, thargoids, cargo or unpiloted ships
// check that a station doesn't try to dock itself - bad things happen then!
if (ship.isPolice || ship.isStation || ship.isThargoid || ship.isCargo || !ship.isPiloted || ship.isPlayer) return;
// look for any ships with a primary role outside of the ones we are controlling (eg miner, scavenger)
if (this.$roleIsAllowed(ship.primaryRole) === false && this.$roleIsAllowedAdditional(ship.primaryRole) === false && this._dockingRoleExceptions.indexOf(ship.primaryRole) === -1) {
if (this._debug && this._logDockingType > 0) log(this.name, "Ship with primary role '" + ship.primaryRole + "' docked -- no data will be added");
return;
}
// make doubly sure the station isn't trying to dock itself!
if (station === ship) return;
// get the index of the station we're docking at
var stnIndex = this.$getStationIndex(station);
var stationkey = station.name + "_" + stnIndex;
if (this._debug && this._logDockingType > 0) log(this.name, "Docking ship at station " + station.name + "(" + stnIndex + "): " + ship.displayName + " -- " + ship.dataKey);
// set departure time to 23-24 hours by default (docking status) for all docking ships so that they follow through the statuses
var depart = this.$calculateRandomDepartureTime(station, stnIndex, 10, false);
var destSystem = -1;
var destHidden = true;
var isGroupLeader = false;
var fuel = 7;
// by default, it will be assumed that ships will refuel on docking
// however, as player ships can't refuel at constores anymore, NPC ships will also be unable to as well
// in that case, just set the fuel back to whatever they have
if (station.hasRole("constore")) fuel = ship.fuel;
// check if a ship from this group has already docked, and if so, get its departure time
var sdc_pop = worldScripts.StationDockControl_Populator;
var lookupgroup = "";
var leader;
// is there a group still attached to the ship?
if (ship.group && ship.group.count > 1) {
lookupgroup = ship.group.name;
leader = ship.group.leader;
if (this._debug && this._logDockingType === 2) log(this.name, "Found group: " + lookupgroup);
}
// if not, try looking up the group from our holding array
if (lookupgroup === "") {
lookupgroup = sdc_pop.$getGroupName(ship);
if (lookupgroup !== "") {
if (this._debug && this._logDockingType === 2) log(this.name, "Found original group: " + lookupgroup);
leader = sdc_pop.$getGroupLeader(lookupgroup);
}
}
if (lookupgroup !== "") {
if (leader && leader.isValid) {
if (this._debug && this._logDockingType === 2) log(this.name, "Group leader: " + leader.displayName + " -- " + leader);
if (leader == ship) {
isGroupLeader = true;
}
}
// can we find the leader anywhere in space or in dock
if ((!leader || leader.isValid === false) && this.$isGroupLeaderDocked(stationkey, lookupgroup) === false) {
// no leader
// todo: work out different scenarios for non-escort group members (hunters in particular)
if (this._debug && this._logDockingType === 2) log(this.name, "Leader no longer present - group is dissolved");
lookupgroup = "";
}
}
var departSecs = 0;
// do we still have a group? (We might have cleared it if it dissolved)
if (lookupgroup !== "") {
var groupData = this.$getShipGroupInfo(stationkey, lookupgroup);
if (groupData) {
// if the leader didn't dock first, make sure their departure time will be before any group members
if (groupData.depart !== 0) {
if (isGroupLeader) {
depart = groupData.depart;
departSecs = 0;
groupData.depart = 0;
}
}
// assuming the leader docked first, all subsequent group member dockings will have a departure time be one second after their leader
// so the ordering of ships is always leader first
if (groupData.depart !== 0) {
depart = groupData.depart;
departSecs = 5;
}
// if groupDepart is 0, that means we haven't docked a member from this group before.
// check the group leader, and set the flag if true
//if (groupDepart === 0 && lookupgroupleader) isGroupLeader = true;
if (groupData.destination >= 0) {
destSystem = groupData.destination;
destHidden = groupData.destinationHidden;
}
} else {
// no existing docked records for this group
// try this as an escort group
var escortData = this.$getShipEscortInfo(stationkey, lookupgroup);
if (escortData) {
if (escortData.depart !== 0) {
if (isGroupLeader) {
depart = escortData.depart;
departSecs = 0;
escortData.depart = 0;
}
}
if (escortData.depart !== 0) {
depart = escortData.depart;
departSecs = 0;
}
if (escortData.destination >= 0) {
destSystem = escortData.destination;
destHidden = escortData.destinationHidden;
}
}
}
}
// work out the current AI script name
// there's probably an easier way of doing this...
var aiName = "";
if (ship.autoAI && ship.AIScript) {
switch (ship.AIScript.name) {
case "Oolite Assassin AI":
aiName = "oolite-assassinAI.js";
break;
case "Oolite Bounty Hunter AI":
aiName = "oolite-bountyHunterAI.js";
break;
case "Oolite Bounty Hunter Leader AI":
aiName = "oolite-bountyHunterLeaderAI.js";
break;
case "Oolite Constrictor AI":
aiName = "oolite-constrictorAI.js";
break;
case "Oolite Defense Ship AI":
aiName = "oolite-defenceShipAI.js";
break;
case "Oolite Escort AI":
aiName = "oolite-escortAI.js";
break;
case "Oolite Pirate AI":
aiName = "oolite-pirateAI.js";
break;
case "Oolite Pirate Fighter AI":
aiName = "oolite-pirateFighterAI.js";
break;
case "Oolite Pirate Freighter AI":
aiName = "oolite-pirateFreighterAI.js";
break;
case "Oolite Pirate Interceptor AI":
aiName = "oolite-pirateInterceptorAI.js";
break;
case "Oolite Shuttle AI":
aiName = "oolite-shuttleAI.js";
break;
case "Oolite Trader AI":
aiName = "oolite-traderAI.js";
break;
case "Oolite Trader Opportunist AI":
aiName = "oolite-traderOpportunistAI.js";
break;
case "Null AI":
// pick up the plist version of the ai
if (ship.AI !== "nullAI.plist") {
aiName = ship.AI;
}
break;
default:
if (this._AIScriptExceptions[ship.AIScript.name] && this._AIScriptExceptions[ship.AIScript.name] !== "") aiName = this._AIScriptExceptions[ship.AIScript.name];
if (aiName === "") log(this.name, "!!ERROR: Unrecognised Auto AI Script: " + ship.AIScript.name);
break;
}
}
var prim_role = ship.primaryRole;
var isEscort = false;
if (ship.primaryRole === "escort" || ship.primaryRole === "escort-light" || ship.primaryRole === "escort-medium" || ship.primaryRole === "escort-heavy") {
isEscort = true;
// will we be turning this escort into something else?
// if the ship is not attached to a group, it's time to switch
if (lookupgroup === "") {
isEscort = false;
if (ship.cargoSpaceCapacity > 0 && ship.hasHyperspaceMotor === true) {
// if the ship has cargo space and a hyperdrive, make them a trader
if (this._debug && this._logDockingType === 2) log(this.name, "Switching lone escort " + ship.displayName + " to trader");
prim_role = "trader";
aiName = "oolite-traderAI.js";
} else {
if (ship.hasHyperspaceMotor === true) {
// if they just have a hyperdrive, there's a good chance they'll decide to be a courier
if (this._debug && this._logDockingType === 2) log(this.name, "Switching lone escort " + ship.displayName + " to courier");
prim_role = "trader-courier";
aiName = "oolite-traderAI.js";
} else {
// otherwise this escort is going for some R&R
if (this._debug && this._logDockingType === 2) log(this.name, "Dropping lone escort " + ship.displayName + "...heading for R&R");
return;
}
}
// reset the destination so a new one can be calculated
destSystem = -1;
} else {
// make sure the leader has the escort group name attached
// isGroupLeader should always be false for an escort ship, but just to be sure
if (isGroupLeader === false) {
this.$setGroupEscortLeader(stationkey, lookupgroup);
}
}
}
// work out what goods and destination this ship will have when it next launches
var destLoc = "";
if (destSystem === -1) {
var dest = this.$getDestinationByRole(prim_role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
if (dest.location !== "") destLoc = dest.location;
}
var goods = "";
switch (prim_role) {
case "trader":
goods = "PLENTIFUL_GOODS";
//if (ship.shipClassName.toLowerCase().indexOf("medical") >= 0) {
if (ship.shipClassName.search(new RegExp("medical", "i")) !== -1) {
goods = "MEDICAL_GOODS";
} else {
if (ship.bounty !== 0) {
goods = "PIRATE_GOODS";
}
}
break;
case "trader-smuggler":
goods = "ILLEGAL_GOODS";
break;
}
// make sure we're not putting cargo in a ship with no cargo space
if (ship.cargoSpaceCapacity === 0) goods = "";
// process cargo
var cargo = "";
// if (this._NPCTrading === true) {
// var td = worldScripts.StationDockInfo_NPCTrading;
// cargo = td.$processCargo_Docking(ship);
// }
// check for illegal imports and increase bounty as appropriate.
if (ship.cargoSpaceCapacity > 0 && ship.cargoList.length > 0) {
var bribe_attempt = Math.random();
for (var i = 0; i < ship.cargoList.length; i++) {
var itm = ship.cargoList[i];
if (station.market[itm.commodity].legality_import > 0) {
var chance = 0.7;
if (this._smuggling === true) {
chance = worldScripts.Smugglers_DockMaster._bribeChance[system.ID];
}
if (bribe_attempt < chance) {
ship.bounty += itm.quantity * station.market[itm.commodity].legality_import;
if (this._debug && this._logDockingType >= 1) {
log(this.name, "Ship was carrying illegal cargo (" + itm.commodity + ") - bounty increased by " + itm.quantity * station.market[itm.commodity].legality_import);
}
} else {
if (this._debug && this._logDockingType >= 1) {
log(this.name, "Ship was carrying illegal cargo (" + itm.commodity + ") - ship bribed dockmaster");
}
}
}
}
}
var pilot = {};
pilot["name"] = ship.crew[0].name;
pilot["insurance"] = ship.crew[0].insuranceCredits;
pilot["description"] = ship.crew[0].description;
pilot["homeSystem"] = ship.crew[0].homeSystem;
pilot["legalStatus"] = (ship.markedForFines ? 0 : ship.crew[0].legalStatus);
pilot["species"] = ship.crew[0].species;
var equip = "";
// pick up all installed equipment
var eq = ship.equipment;
for (var i = 0; i < eq.length; i++) {
var eqItem = eq[i];
if (this._ignoreEquip.indexOf(eqItem.equipmentKey) !== -1) equip += eqItem.equipmentKey + ",";
}
// pick up the installed weapons;
if (ship.forwardWeapon) equip += "FORE:" + ship.forwardWeapon.equipmentKey + ",";
if (ship.aftWeapon) equip += "AFT:" + ship.aftWeapon.equipmentKey + ",";
if (ship.portWeapon) equip += "PORT:" + ship.portWeapon.equipmentKey + ",";
if (ship.starboardWeapon) equip += "STARBOARD:" + ship.starboardWeapon.equipmentKey + ",";
var shipname = "";
shipname = ship.shipUniqueName;
// occasionally the shipUniqueName returns "" even though it's really there. In that instance, pull the unique name out of the display name
if (shipname === "") shipname = this.$getShipUniqueName(ship.displayName, ship.shipClassName);
// check to see if this ship has a callback for when it docks
var launchCB = "";
var launchWS = "";
if (this._pendingDock.length > 0) {
for (var i = this._pendingDock.length - 1; i >= 0; i--) {
var item = this._pendingDock[i];
if ((item.shipName === "" || item.shipName === shipname) &&
(item.shipType === "" || item.shipType === ship.shipClassName) &&
(item.pilotName === "" || item.pilotName === pilot.name)) {
// we found a match, so execute the callback
var w = worldScripts[item.worldScript];
if (w) w[item.callback](station, item.parameter);
if (item.launchCallback !== "") {
launchCB = item.launchCallback;
launchWS = item.worldScript;
}
// remove the item from the array
this._pendingDock.splice(i, 1);
}
}
}
var props = {};
for (var i = 0; i < this._propertiesToKeep.length; i++) {
var prop = this._propertiesToKeep[i];
if (ship.script.hasOwnProperty(prop) === true) {
props[prop] = ship.script[prop];
}
}
// store all the data in the array
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: prim_role,
shipType: ship.shipClassName,
shipDataKey: ship.dataKey,
shipName: shipname,
personality: ship.entityPersonality,
aiName: aiName,
accuracy: ship.accuracy,
equipment: equip,
heatInsulation: ship.heatInsulation,
fuel: fuel,
homeSystem: ship.homeSystem,
bounty: (ship.markedForFines ? 0 : ship.bounty),
escortName: (isEscort === true ? lookupgroup : ""),
escortLeader: isGroupLeader,
groupName: (isEscort === true ? "" : lookupgroup),
groupLeader: isGroupLeader,
destinationSystem: destSystem,
destinationHidden: destHidden,
destination: destLoc,
goods: goods,
cargo: cargo,
pilot: pilot,
dockTime: clock.seconds,
departureTime: depart,
departureSeconds: departSecs,
autoLog: 2,
lastChange: clock.adjustedSeconds,
launchCallback: launchCB,
worldScript: launchWS,
properties: props
});
if (this._debug && this._logDockingType === 2 && ship.markedForFines) log(this.name, "Ship was marked for fines and has been had their bounty cleared");
// remove the ship from the group data
sdc_pop.$removeShip(ship);
}
//-------------------------------------------------------------------------------------------------------------
// launch any ships whose departure time has expired
this.$launchPendingShips = function $launchPendingShips() {
function compare(a, b) {
return ((a.departureTime * 10 + a.departureSeconds) - (b.departureTime * 10 + b.departureSeconds));
}
//-------------------------------------------------------------------------------------------------------------
// returns the number of docked ships in the same group as the passed record
function countShipsInGroup(dockingData, shipdata) {
var items = 1;
if (shipdata.escortName === "" || shipdata.groupName === "") return 1;
var dta = dockingData[shipdata.station + "_" + shipdata.stationIndex];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
var item = dta[i];
if (item.groupName === shipdata.groupName || item.escortName === shipdata.escortName) items += 1;
}
}
return items;
}
// don't launch ships in the middle of an update to the array
if (this._launchListUpdating === true) return;
if (this._launching.length > 0) {
if (this._debug && this._logLaunchType > 0) log(this.name, "Launching pending ships " + this._launching.length);
var clearlist = [];
this._launching.sort(compare);
for (var i = 0; i < this._launching.length; i++) {
// launch it
// add the ship to the launch queue
// will this ship be part of a group?
var shp = this._launching[i];
if (shp.system === system.ID) {
var station = this.$getStationFromName(shp.station, shp.stationIndex);
if (station == null) {
// only output to the log the station is null error once for each station
if (this.$indexInList(shp.station + "_" + shp.stationIndex, this._stationNullError) === -1) {
log(this.name, "!!ERROR: Station is null (OXP removed?): Removing " + shp.shipName + " -- " + shp.station + " (" + shp.stationIndex + ")");
this._stationNullError.push(shp.station + "_" + shp.stationIndex);
}
// remove the ship if the station doesn't exist anymore
clearlist.push(i);
}
// make sure we have a valid station before trying to launch
// to catch the case when a station OXP was removed
if (station && Math.round(shp.departureTime) !== -5) {
// work out how many ships are going to be launching at the same time
var shipcount = countShipsInGroup(shp, this._systemDockingData[system.ID]);
// is there any space in the launch queue for this group of ships
// so, if there's only 1 slot, and there's 5 ships in this group, don't launch any of them
if (this.$launchAvailable(station, shipcount) === true) {
var shpKey = shp.shipDataKey;
// make sure a single ship is launched (ie without escorts)
if (this.$dockVersionExists(shpKey)) shpKey = "dock_" + shpKey;
if (this._debug && this._logLaunchType > 0) this.$writeDataToLog(shp, "Launching ship through " + shp.station + " (" + shp.stationIndex + ")");
var ship = station.launchShipWithRole("[" + shpKey + "]");
if (!ship) {
// Oops! no ship returned!
// skip it this time and maybe next time it will launch
if (this._debug && this._logLaunchType > 0) log(this.name, "!!NOTE: Did not create ship of type '" + shpKey + "' - launchShipWithRole returned null");
continue;
}
// make a note of the item to clear from the array
clearlist.push(i);
// update details of launched ship
this.$updateLaunchedShip(ship, shp);
// if there is an attached callback, call it now.
if (shp.launchCallback && shp.worldScript) {
var w = worldScripts[shp.worldScript];
if (w) w[shp.launchCallback]("launching", ship);
}
} else {
// when launch is not available
// options are: (1) try again on the next cycle > do nothing
// (2) schedule ship (group) for a new time
if (Math.random() < 0.4) {
if (this._debug && this._logLaunchType >= 2) log(this.name, "Ship " + shp.shipName + " being moved to 'rescheduled' status!");
shp.departureTime = -5;
shp.lastChange = clock.adjustedSeconds;
// move any escort/group members to -5, plus 0.1 so they can be sorted correctly
this.$updateGroupMemberLaunchingData(shp.groupName, shp.escortName, "departureTime", -5);
}
}
} // if station
} else {
// if there are launching items from another system, just purge them
if (this._debug && this._logLaunchType >= 2) log(this.name, "Ship " + shp.shipName + " was to be launched in system " + shp.system + " - purging");
clearlist.push(i);
}
} // for
// clear the array of all ships we launched
var i = clearlist.length;
while (i--) {
this._launching.splice(clearlist[i], 1);
}
if (this._launching.length > 0) {
if (this._debug && this._logLaunchType >= 2) {
log(this.name, "Launching queue length: " + this._launching.length);
if (this._shipMonitor === 1) {
log(this.name, "=====Current launching queue data=====");
for (var i = 0; i < this._launching.length; i++) {
this.$writeDataToLog(this._launching[i], "Launching queue item " + i);
}
}
}
}
} // if
}
//-------------------------------------------------------------------------------------------------------------
// populates the docked vessels in each station in the system
this.$populateStationList = function $populateStationList(timeRange) {
// don't create anything if the system is nova or going nova
if (system.sun && (system.sun.isGoingNova || system.sun.hasGoneNova)) return;
this._autoGenerationInProgress = true;
// has the station indexes function been run?
if (this.$getStationIndex(system.mainStation) === 0) {
this.$stationIndexes();
}
// work out the min value to send to the calculateRandomDepartureTime routine
var slots = this.$calculateSlots(timeRange);
if (this._debug && this._logPopulatorType >= 2) log(this.name, "timeRange = " + timeRange + ", slot = " + slots);
// go through all stations in the system
var stns = system.stations;
// make sure the player's station goes in first
if (player.ship.dockedStation && this._repopulateStation == null && player.ship.dockedStation.hasNPCTraffic && this.$checkStationRoleForNoTraffic(player.ship.dockedStation) === false) this.$applyTimeRange(player.ship.dockedStation, slots, timeRange);
// loop through the available stations
stns.forEach(function (station) {
if (station.hasNPCTraffic && this.$checkStationRoleForNoTraffic(station) === false && (this._repopulateStation == null || station === this._repopulateStation)) {
this.$applyTimeRange(station, slots, timeRange)
}
}, this);
}
//-------------------------------------------------------------------------------------------------------------
this.$getPopulatorVars = function $getPopulatorVars() {
// What follows is an attempt to recreate the oolite-populator functions for outgoing ships.
// The idea is to put the ships into the docking queue of any stations in the system.
// Then, on each call of the systemWillRepopulate function, the new function will look for any ships
// that are due to launch and launch them, based on the template stored in _systemDockingData array.
// The goal is to have a similar spread of ships launching from stations as would happen with the
// original script. The only part of the original script I couldn't do was to check the count of
// various ship types in system, and then launch extras as required. This would maintain a reasonable
// balance of different ship types. But because this system assumes the ships have to be in a station
// somewhere, that balancing aspect is gone. Hopefully the deeper sense of realism counteracts this
// to some degree.
// This function will be called each time a player enters a new system to populate the docks of all
// stations in system.
// the logic here is to use the default populator chance values in ranges:
// eg for the traders: from 0 - 0.09 is trader freighters, 0.09 to 0.095 is couriers, 0.095 to 0.10 is smugglers, 0.10 to 0.11 is assassins, 0.11 to 0.12 is shuttles
// finally, we add on the possibility of skipping a vessel.
// then, a random number is selected from the total range, between 0 and the max value
// that will hopefully return a similar spread of vessels as the original code.
var w = worldScripts["oolite-populator"];
// set up all the ranges for data selection
var base = 0;
this._populatorVars = {};
this._populatorVars["rangeTradeFreight"] = [0, w.$repopulatorFrequencyOutgoing.traderFreighters];
base += w.$repopulatorFrequencyOutgoing.traderFreighters;
this._populatorVars["rangeTradeCourier"] = [base, base + w.$repopulatorFrequencyOutgoing.traderCouriers];
base += w.$repopulatorFrequencyOutgoing.traderCouriers;
this._populatorVars["rangeTradeSmuggl"] = [base, base + w.$repopulatorFrequencyOutgoing.traderSmugglers];
base += w.$repopulatorFrequencyOutgoing.traderSmugglers;
this._populatorVars["rangeAssassin"] = [base, base + w.$repopulatorFrequencyOutgoing.assassins];
base += w.$repopulatorFrequencyOutgoing.assassins;
this._populatorVars["rangeShuttle"] = [base, base + (0.005 * system.info.techlevel)];
base += (0.005 * system.info.techlevel);
// set the max for this data set, including a skip vessel factor, if required
this._populatorVars["traderMax"] = base + (this._traderSkipVesselFactor > 0 ? ((5 - base) / this._traderSkipVesselFactor) : 0);
base = 0;
this._populatorVars["rangePirateInd"] = [0, w.$repopulatorFrequencyOutgoing.pirateIndependents];
base += w.$repopulatorFrequencyOutgoing.pirateIndependents;
this._populatorVars["rangePirateLight"] = [base, base + w.$repopulatorFrequencyOutgoing.pirateLightPacks];
base += w.$repopulatorFrequencyOutgoing.pirateLightPacks;
this._populatorVars["rangePirateMedium"] = [base, base + w.$repopulatorFrequencyOutgoing.pirateMediumPacks];
base += w.$repopulatorFrequencyOutgoing.pirateMediumPacks;
this._populatorVars["rangePirateHeavy"] = [base, base + w.$repopulatorFrequencyOutgoing.pirateHeavyPacks];
base += w.$repopulatorFrequencyOutgoing.pirateHeavyPacks;
// set the max for this data set, including a skip vessel factor, if required
this._populatorVars["pirateMax"] = base + (this._pirateSkipVesselFactor > 0 ? ((4 - base) / this._pirateSkipVesselFactor) : 0);
base = 0;
this._populatorVars["rangeHunterLight"] = [0, w.$repopulatorFrequencyOutgoing.hunterLightPacks];
base += w.$repopulatorFrequencyOutgoing.hunterLightPacks;
this._populatorVars["rangeHunterMedium"] = [base, base + w.$repopulatorFrequencyOutgoing.hunterMediumPacks];
base += w.$repopulatorFrequencyOutgoing.hunterMediumPacks;
this._populatorVars["rangeHunterHeavy"] = [base, base + w.$repopulatorFrequencyOutgoing.hunterHeavyPacks];
base += w.$repopulatorFrequencyOutgoing.hunterHeavyPacks;
// set the max for this data set, including a skip vessel factor, if required
this._populatorVars["hunterMax"] = base + (this._hunterSkipVesselFactor > 0 ? ((3 - base) / this._hunterSkipVesselFactor) : 0);
}
//-------------------------------------------------------------------------------------------------------------
this.$stationIsRepopulating = function $stationIsRepopulating(station) {
if (this._populateStation.length === 0) return false;
for (var i = 0; i < this._populateStation.length; i++) {
if (this._populateStation[i].stn.displayName === station.displayName) return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
this.$applyTimeRange = function $applyTimeRange(station, slots, timeRange) {
// go through all stations in the system
var stns = system.stations;
if (slots === 0) slots = this.$calculateSlots(timeRange);
// make sure the player's station goes in first
if (this.$stationIsRepopulating(station) === false) {
this._populateStation.push({
stn: station,
slots: slots,
timeRange: timeRange
});
} else {
for (var i = 0; i < this._populateStation.length; i++) {
var item = this._populateStation[i];
if (item.stn === station) {
if (item.timeRange < timeRange) {
item.slots = slots;
item.timeRange = timeRange;
}
break;
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
this.$calculateSlots = function $calculateSlots(timeRange) {
var slots = 0;
if (timeRange < 10) slots = 10;
//if (10 <= timeRange && timeRange < 60) slots = 10;
//if (60 <= timeRange && timeRange < 180) slots = 9;
//if (180 <= timeRange && timeRange < 420) slots = 8;
//if (420 <= timeRange && timeRange < 660) slots = 7;
//if (660 <= timeRange && timeRange < 1260) slots = 6;
//if (1260 <= timeRange && timeRange < 1320) slots = 5;
//if (1320 <= timeRange && timeRange < 1350) slots = 4;
//if (1350 <= timeRange && timeRange < 1435) slots = 3;
//if (1435 <= timeRange) slots = 2; // full refresh from this point
if (10 <= timeRange && timeRange < 60) slots = 10;
if (60 <= timeRange && timeRange < 180) slots = 8;
if (180 <= timeRange && timeRange < 420) slots = 6;
if (420 <= timeRange && timeRange < 660) slots = 4;
if (660 <= timeRange) slots = 2; // full refresh from this point
return slots;
}
//-------------------------------------------------------------------------------------------------------------
this.$runPopulation = function $runPopulation() {
// is there anything in our processing array?
if (this._populateStation.length === 0) return;
// check for being in space and red alert - skip doing anything to prevent any glitches
if (player.ship.isInSpace && player.alertCondition === 3) return;
// have we done a pre-population of the ship dock values yet?
// if not, run the process now
/*if (!this._doShipDockDataGrab[this._populateStation[0].stn.shipClassName]) {
this.$grabAllShipDockValues(this._populateStation[0].stn);
this._doShipDockDataGrab[this._populateStation[0].stn.shipClassName] = true;
}*/
if (system.isInterstellarSpace) {
// remove all interstellar stations from the array
this._populateStation.splice(0, 1);
return;
}
// make sure we don't trip over ourselves
if (this._running === true) return;
if (this._jumpStarted === true) return;
this._running = true;
var trueValues = this._trueValues;
var logging = 0;
if (this._debug && this._logPopulatorType >= 2) logging = 1;
if (!this._populatorVars) this.$getPopulatorVars();
var popVars = this._populatorVars;
var station = this._populateStation[0].stn;
var slots = this._populateStation[0].slots;
var random = Math.random;
var randomInt = this.$rand;
var indexInList = this.$indexInList;
var calcIns = this.$calcInsurance;
var shipDataRef = this._shipData;
// && "galcop neutral chaotic pirate hunter".indexOf(station.allegiance) >= 0
if (this._debug && this._logPopulatorType >= 2) {
var startDate = new Date();
log(this.name, "==============================================================================");
log(this.name, "station === " + station);
}
// get the index for this station
var stnIndex = this.$getStationIndex(station);
var stationkey = station.name + "_" + stnIndex;
var countTraderFreighter = 0;
var countTraderCourier = 0;
var countTraderSmuggler = 0;
var countAssassin = 0;
var countShuttle = 0;
var countPirateInd = 0;
var countPirateLight = 0;
var countPirateMedium = 0;
var countPirateHeavy = 0;
var countHunterLight = 0;
var countHunterMedium = 0;
var countHunterHeavy = 0;
// create some random data if our array is empty
// work out number of ships to add to array
var calctype = 0; // type 0 = original calculation seeds, type 1 = beo's version
switch (calctype) {
case 0:
var factor = 0;
// hour many trading partners does this system have
factor += this._neighbours.length * 5;
// more stable governments mean more ships in system
factor += system.government;
// more advanced systems mean more ships in system
factor += system.techLevel;
// how many ships are already in dock
var curr = this.$countStationItems(station);
// work out the max and min values
var max = factor * 6;
var min = factor * 2;
break;
case 1: // variation suggested by forum member beo
var factor = 1;
// hour many trading partners does this system have
factor += this._neighbours.length * 3;
// more stable governments mean more ships in system
factor += Math.round(system.government / 4);
// more advanced systems mean more ships in system
factor += Math.round(system.techLevel / 7);
// how many ships are already in dock
var curr = this.$countStationItems(station);
// work out the max and min values
var max = factor * 5;
var min = factor * 2;
break;
}
// work out high/medium/low traffic variations:
if (this.$checkStationRoleForMediumTraffic(station)) {
max = parseInt(max / 1.8);
min = parseInt(min / 1.8);
//if (this._debug && this._logPopulatorType >= 2)
//log(this.name, station.name + " selected for medium traffic (F:" + factor + ", Max:" + max + ", Min:" + min + ")");
} else if (this.$checkStationRoleForLowTraffic(station)) {
max = parseInt(max / 4);
min = parseInt(min / 4);
//if (this._debug && this._logPopulatorType >= 2)
//log(this.name, station.name + " selected for low traffic (F:" + factor + ", Max:" + max + ", Min:" + min + ")");
} else if (this.$checkStationRoleForVeryLowTraffic(station)) {
max = parseInt(max / 10);
min = parseInt(min / 10);
//if (this._debug && this._logPopulatorType >= 2)
//log(this.name, station.name + " selected for very low traffic (F:" + factor + ", Max:" + max + ", Min:" + min + ")");
} else {
//if (this._debug && this._logPopulatorType >= 2)
//log(this.name, station.name + " selected for normal traffic (F:" + factor + ", Max:" + max + ", Min:" + min + ")");
}
// calculate a random number in our range
var ships = (randomInt(max - min) + min) - curr;
if (ships > 300) ships = 300; // upper limit
//if (this._debug && this._logPopulatorType > 0) log(this.name, "Initial estimate of " + ships + " for " + station.name + "(" + stnIndex + ") (" + station.primaryRole + ")");
if (ships > 0) {
// work out which ship roles to add to the station
if (this._debug) log(this.name, stationkey + ": Estimated ship count = " + ships);
// create the departure slots array for this station
this.$createDepartureSlots();
// if we have existing items in this station, remove their slots from the array
if (this.$countStationItems(station) !== 0) {
this.$removeExistingSlots(station);
}
var actualCount = 0;
var i = ships;
while (i--) {
var shpType = "";
var shpDataKey = "";
var shpName = "";
var role = "";
var selection = 0;
var selPirate = random();
var selHunter = random();
var depart = 0;
var pilot = {};
var bounty = 0;
var home = 0;
var species = "";
var shipDockTime = 0;
var insurance = 0;
var destSystem = -1;
var destHidden = false;
var aiName = "";
var skill = 0;
var weapons = 0;
var heat = 0;
var groupName = "";
var isLeader = false;
var equip = "";
var escortGroupName = "";
var goods = "";
var escorts = [];
var groupData = [];
var autoAI = "";
var choose = 0;
depart = this.$calculateRandomDepartureTime(station, stnIndex, slots, true);
if (depart === -1) continue;
shipDockTime = (depart * 60 + clock.adjustedSeconds) - (randomInt(720) * 60); // arrival time was up to 12 hours prior to their scheduled departure time
// work out what type of ship we are going to add
// some of this will be based on what station type we are currently looking at
// note: some OXP stations can end up with "null" for station allegiance - in this instance assume "neutral";
var alleg = station.allegiance;
if (alleg == null) alleg = "neutral";
if (alleg === "neutral" || (alleg === "galcop" && selHunter < 0.5) || (alleg === "chaotic" && selPirate < 0.5)) {
choose = random() * popVars.traderMax;
if (choose >= popVars.rangeTradeFreight[0] && choose < popVars.rangeTradeFreight[1]) selection = 1;
if (choose >= popVars.rangeTradeCourier[0] && choose < popVars.rangeTradeCourier[1]) selection = 2;
if (choose >= popVars.rangeTradeSmuggl[0] && choose < popVars.rangeTradeSmuggl[1]) selection = 3;
if (choose >= popVars.rangeAssassin[0] && choose < popVars.rangeAssassin[1]) selection = 4;
if (choose >= popVars.rangeShuttle[0] && choose < popVars.rangeShuttle[1]) selection = 5;
}
if (alleg === "pirate" || (alleg === "chaotic" && selPirate >= 0.5)) {
choose = random() * popVars.pirateMax;
if (choose >= popVars.rangePirateInd[0] && choose < popVars.rangePirateInd[1]) selection = 6;
if (choose >= popVars.rangePirateLight[0] && choose < popVars.rangePirateLight[1]) selection = 7;
if (choose >= popVars.rangePirateMedium[0] && choose < popVars.rangePirateMedium[1]) selection = 8;
if (choose >= popVars.rangePirateHeavy[0] && choose < popVars.rangePirateHeavy[1]) selection = 9;
}
if (alleg === "hunter" || (alleg === "galcop" && selHunter >= 0.5)) {
choose = random() * popVars.hunterMax;
if (choose >= popVars.rangeHunterLight[0] && choose < popVars.rangeHunterLight[1]) selection = 10;
if (choose >= popVars.rangeHunterMedium[0] && choose < popVars.rangeHunterMedium[1]) selection = 11;
if (choose >= popVars.rangeHunterHeavy[0] && choose < popVars.rangeHunterHeavy[1]) selection = 12;
}
if (selection > 0) actualCount += 1;
switch (selection) {
case 1: // trader
countTraderFreighter += 1;
role = "trader";
shpDataKey = this.$getRandomShipKeyWithDockCheck(role, station);
shpName = this.$getRandomShipName(role);
autoAI = shipDataRef[shpDataKey]["auto_ai"];
shpType = shipDataRef[shpDataKey].name;
home = system.ID;
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
// occasionally make the home system the same as the dest system so not every ship in dock calls this system home
if (random() < 0.3) home = this.$nearbySystem(7);
var hasAutoAI = (indexInList(autoAI, trueValues) >= 0);
// not sure if the destination will stick for ships with a specific AI, so just hide the destination so there's no confusion
if (destHidden === false && hasAutoAI === false) destHidden = true;
goods = "PLENTIFUL_GOODS";
if (typeof autoAI === "undefined" || hasAutoAI === true) aiName = "oolite-traderAI.js";
if (shpType.search(new RegExp("medical", "i")) !== -1) {
goods = "MEDICAL_GOODS";
bounty = 0;
} else {
if (random() < 0.05) {
bounty = Math.ceil(random() * 20);
if (random() < 0.5 && hasAutoAI === true) {
aiName = "oolite-traderOpportunistAI.js";
if (indexInList(shipDataRef[shpDataKey]["auto_weapons"], trueValues) >= 0) {
skill = 2;
weapons = 2.5;
}
goods = "PIRATE_GOODS";
}
} else {
bounty = 0;
}
}
pilot = this.$createPilot((random() < 0.5 ? home : destSystem), bounty, calcIns(bounty));
if (indexInList(shpDataKey, this._switchEscortsToGroup) >= 0) {
var groupInfo = this.$processTraderEscorts(shpDataKey);
groupData = groupInfo.escortData;
isLeader = groupInfo.leader;
if (isLeader === true) {
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
}
} else {
var escortInfo = this.$processTraderEscorts(shpDataKey);
escorts = escortInfo.escortData;
isLeader = escortInfo.leader;
escortGroupName = escortInfo.name;
}
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, skill),
equipment: this.$setWeapons(shpDataKey, weapons),
heatInsulation: this.$setHeatInsulation(shpDataKey, 0),
homeSystem: home,
bounty: bounty,
escortName: escortGroupName,
escortLeader: (escortGroupName != "" ? isLeader : false),
groupName: groupName,
groupLeader: (groupName != "" ? isLeader : false),
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: goods,
pilot: pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: 0,
autoLog: logging
});
break;
case 2: // courier
countTraderCourier += 1;
role = "trader-courier";
if (this.$roleExists(role) === false) role = "trader";
shpDataKey = this.$getRandomShipKeyWithDockCheck(role, station);
shpName = this.$getRandomShipName(role);
autoAI = shipDataRef[shpDataKey]["auto_ai"];
shpType = shipDataRef[shpDataKey].name;
var hasAutoAI = (indexInList(autoAI, trueValues) >= 0);
if (typeof autoAI === "undefined" || hasAutoAI === true) aiName = "oolite-traderAI.js";
bounty = 0;
heat = 6;
home = system.ID;
// occasionally make the home system the same as the dest system so not every ship in dock calls this system home
if (random() < 0.3) home = this.$nearbySystem(7);
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
// not sure if the destination will stick for ships with a specific AI, so just hide the destination so there's no confusion
if (destHidden === false && hasAutoAI === false) destHidden = true;
pilot = this.$createPilot((random() < 0.5 ? home : destSystem), bounty, calcIns(bounty));
goods = "PLENTIFUL_GOODS";
if (indexInList(shpDataKey, this._switchEscortsToGroup) >= 0) {
var groupInfo = this.$processTraderEscorts(shpDataKey);
groupData = groupInfo.escortData;
isLeader = groupInfo.leader;
if (isLeader === true) {
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
}
} else {
var escortInfo = this.$processTraderEscorts(shpDataKey);
escorts = escortInfo.escortData;
isLeader = escortInfo.leader;
escortGroupName = escortInfo.name;
}
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, skill),
equipment: this.$setWeapons(shpDataKey, weapons),
heatInsulation: heat,
homeSystem: home,
bounty: bounty,
escortName: escortGroupName,
escortLeader: (escortGroupName != "" ? isLeader : false),
groupName: groupName,
groupLeader: (groupName != "" ? isLeader : false),
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: goods,
pilot: pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: 0,
autoLog: logging
});
break;
case 3: // smuggler
countTraderSmuggler += 1;
role = "trader-smuggler";
if (this.$roleExists(role) === false) role = "trader";
shpDataKey = this.$getRandomShipKeyWithDockCheck(role, station);
shpName = this.$getRandomShipName(role);
autoAI = shipDataRef[shpDataKey]["auto_ai"];
shpType = shipDataRef[shpDataKey].name;
var hasAutoAI = (indexInList(autoAI, trueValues) >= 0);
if (typeof autoAI === "undefined" || hasAutoAI === true) aiName = "oolite-traderAI.js";
bounty = Math.ceil(random() * 20);
weapons = 1.2;
home = system.ID;
// occasionally make the home system the same as the dest system so not every ship in dock calls this system home
if (random() < 0.3) home = this.$nearbySystem(7);
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
// not sure if the destination will stick for ships with a specific AI, so just hide the destination so there's no confusion
if (destHidden === false && hasAutoAI === false) destHidden = true;
var maxcargo = parseInt(shipDataRef[shpDataKey]["max_cargo"]);
if (bounty > maxcargo * 2) bounty = maxcargo * 2;
goods = "ILLEGAL_GOODS";
if (indexInList(shpDataKey, this._switchEscortsToGroup) >= 0) {
var groupInfo = this.$processTraderEscorts(shpDataKey);
groupData = groupInfo.escortData;
isLeader = groupInfo.leader;
if (isLeader === true) {
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
}
} else {
var escortInfo = this.$processTraderEscorts(shpDataKey);
escorts = escortInfo.escortData;
isLeader = escortInfo.leader;
escortGroupName = escortInfo.name;
}
equip = this.$setWeapons(shpDataKey, weapons);
if (indexInList(shipDataRef[shpDataKey]["auto_weapons"], trueValues) >= 0) equip += "EQ_FUEL_INJECTION,"; // smugglers always have injectors
pilot = this.$createPilot((random() < 0.5 ? home : destSystem), bounty, calcIns(bounty));
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, skill),
equipment: equip,
heatInsulation: this.$setHeatInsulation(shpDataKey, 0),
homeSystem: home,
bounty: bounty,
escortName: escortGroupName,
escortLeader: (escortGroupName != "" ? isLeader : false),
groupName: groupName,
groupLeader: (groupName != "" ? isLeader : false),
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: goods,
pilot: pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: 0,
autoLog: logging
});
break;
case 4: // assassin
countAssassin += 1;
role = "assassin-light";
var g = system.info.government + 2;
skill = 0;
weapons = 2;
if (random() > g / 10) {
role = "assassin-medium";
skill = 1;
weapons = 2.5;
if (random() > g / 5) {
role = "assassin-heavy";
weapons = 2.8;
}
}
if (this.$roleExists(role) === false) break; // if there are no assassin roles defined, don't try to create one.
shpDataKey = this.$getRandomShipKeyWithDockCheck(role, station);
shpName = this.$getRandomShipName(role);
autoAI = shipDataRef[shpDataKey]["auto_ai"];
shpType = shipDataRef[shpDataKey].name;
home = system.ID;
// occasionally make the home system the same as the dest system so not every ship in dock calls this system home
if (random() < 0.3) home = this.$nearbySystem(7);
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;;
destHidden = dest.destinationHidden;
equip = this.$setWeapons(shpDataKey, weapons);
if (indexInList(shipDataRef[shpDataKey]["auto_weapons"], trueValues) >= 0) {
equip += "EQ_FUEL_INJECTION,";
equip += "EQ_ECM,";
if (2 + random() < weapons) {
equip += "EQ_SHIELD_BOOSTER,";
}
// assassins don't respect escape pods and won't expect anyone
// else to either.
equip += "X:EQ_ESCAPE_POD,";
}
if (skill > 0) {
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
isLeader = true;
}
var hasAutoAI = (indexInList(autoAI, trueValues) >= 0);
// this should be set by the role, but just in case
if (typeof autoAI === "undefined" || hasAutoAI === true) aiName = "oolite-assassinAI.js";
// not sure if the destination will stick for ships with a specific AI, so just hide the destination so there's no confusion
if (destHidden === false && hasAutoAI === false) destHidden = true;
pilot = this.$createPilot(system.ID, 0, calcIns(0));
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, skill),
equipment: equip,
heatInsulation: this.$setHeatInsulation(shpDataKey, 1),
homeSystem: home,
bounty: 0,
escortName: "",
escortLeader: false,
groupName: groupName,
groupLeader: isLeader,
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: "",
pilot: pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: 0,
autoLog: logging
});
if (skill > 0) {
var numext = Math.floor(random() * 3) + 1;
var ext_role = "";
var ext_shpType = "";
var ext_shpDataKey = "";
var ext_shpName = "";
var ext_pilot = [];
var ext_equip = "";
for (var j = 0; j < numext; j++) {
countAssassin += 1;
if (role === "assassin-heavy") {
ext_role = "assassin-medium";
} else {
ext_role = "assassin-light";
}
ext_shpDataKey = this.$getRandomShipKey(ext_role);
ext_shpName = this.$getRandomShipName(ext_role);
ext_shpType = shipDataRef[ext_shpDataKey].name;
ext_equip = this.$setWeapons(ext_shpDataKey, 1.8);
if (indexInList(shipDataRef[ext_shpDataKey]["auto_weapons"], trueValues) >= 0) {
ext_equip += "EQ_FUEL_INJECTION,";
ext_equip += "X:EQ_ESCAPE_POD,";
}
aiName = "";
// this should be set by the role, but just in case
if (typeof shipDataRef[ext_shpDataKey]["auto_ai"] === "undefined" || indexInList(shipDataRef[ext_shpDataKey]["auto_ai"], trueValues) >= 0) aiName = "oolite-assassinAI.js";
ext_pilot = this.$createPilot(this.$nearbySystem(7), 0, calcIns(0));
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: ext_role,
shipType: ext_shpType,
shipDataKey: ext_shpDataKey,
shipName: ext_shpName,
aiName: aiName,
accuracy: this.$setAccuracy(ext_shpDataKey, 0),
equipment: ext_equip,
heatInsulation: this.$setHeatInsulation(shpDataKey, 1),
homeSystem: home,
bounty: 0,
escortName: "",
escortLeader: false,
groupName: groupName,
groupLeader: false,
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: "",
pilot: ext_pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: 5,
autoLog: logging
});
}
}
break;
case 5: // shuttle
countShuttle += 1;
role = "shuttle";
if (this.$roleExists(role) === false) break; // if there are no shuttles defined, don't try to create one.
shpDataKey = this.$getRandomShipKey(role);
shpName = this.$getRandomShipName(role);
autoAI = shipDataRef[shpDataKey]["auto_ai"];
shpType = shipDataRef[shpDataKey].name;
// this should be set by the role, but just in case
aiName = "";
var hasAutoAI = (indexInList(autoAI, trueValues) >= 0);
// some of griffs have auto_ai = "undefined" which I'm assuming means "yes"
if (typeof autoAI === "undefined" || hasAutoAI === true) aiName = "oolite-shuttleAI.js";
home = system.ID;
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
var destLoc = dest.location;
// even shuttles will hide their destination
//if (random() < 0.3) destHidden = true;
// hide their destination if they have a custom AI - we don't know what they'll do!
if (aiName === "") destHidden = true;
pilot = this.$createPilot(home, 0, calcIns(0));
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, 1),
equipment: this.$setWeapons(shpDataKey, 0),
heatInsulation: this.$setHeatInsulation(shpDataKey, 1),
homeSystem: home,
bounty: 0,
escortName: "",
escortLeader: false,
groupName: "",
groupLeader: false,
destinationSystem: destSystem,
destination: destLoc,
destinationHidden: destHidden,
goods: "",
pilot: pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: 0,
autoLog: logging
});
break;
case 6: // pirate independant
countPirateInd += 1;
role = "pirate";
var groupSize = 0;
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
goods = ""; // pirates launching from station will have empty holds so they can scoop some
home = system.ID;
// occasionally make the home system the same as the dest system so not every ship in dock calls this system home
if (random() < 0.3) home = this.$nearbySystem(7);
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
// same logic as orginal for calculating group size
groupSize = Math.floor(random() * 3) + Math.floor(random() * 3) + 2;
if (groupSize > 8 - system.info.government) {
// in the safer systems may have lost some ships already, though
groupSize = 1 + Math.floor(random() * groupSize);
}
isLeader = true;
escorts.length = 0;
for (var j = 0; j < groupSize; j++) {
countPirateInd += 1;
shpDataKey = this.$getRandomShipKeyWithDockCheck(role, station);
shpName = this.$getRandomShipName(role);
autoAI = shipDataRef[shpDataKey]["auto_ai"];
shpType = shipDataRef[shpDataKey].name;
bounty = 20 + system.info.government + groupSize + Math.floor(random() * 8);
if (indexInList(shipDataRef[shpDataKey]["hyperspace_motor"], trueValues) >= 0) {
weapons = 1.75;
} else {
weapons = 1.3;
}
aiName = "";
if (typeof autoAI === "undefined" || indexInList(autoAI, trueValues) >= 0) aiName = "oolite-pirateAI.js";
pilot = this.$createPilot(this.$nearbySystem(7), bounty, calcIns(bounty));
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, 4 - system.info.government),
equipment: this.$setWeapons(shpDataKey, 1.75),
heatInsulation: this.$setHeatInsulation(shpDataKey, 0),
homeSystem: home,
bounty: bounty,
escortName: "",
escortLeader: false,
groupName: groupName,
groupLeader: isLeader,
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: goods,
pilot: pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: (isLeader ? 0 : 5),
autoLog: logging
});
// only the first ship is the leader
isLeader = false;
}
break;
case 7: // light pirate group
countPirateLight += 1;
role = "pirate-light-freighter";
if (this.$roleExists(role) === false) role = "pirate";
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
var leader = this.$addPirateLeader(station, stnIndex, 0.83, role, groupName, shipDockTime, depart);
// add the rest of the group
this.$addPirateGroup(station, stnIndex, groupName, leader.homeSystem, leader.destination, leader.destinationHidden, 2, 1, -1, 0, shipDockTime, depart);
home = leader.homeSystem;
destSystem = leader.destination;
destHidden = leader.destinationHidden;
if (indexInList(leader.dataKey, this._switchEscortsToGroup) >= 0) {
groupData = this.$processPirateEscorts(stationkey, leader, groupName);
} else {
escorts = this.$processPirateEscorts(stationkey, leader, "");
}
break;
case 8: // medium pirate group
countPirateMedium += 1;
role = "pirate-medium-freighter";
if (this.$roleExists(role) === false) role = "pirate";
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
var leader = this.$addPirateLeader(station, stnIndex, 0.5, role, groupName, shipDockTime, depart);
// add the rest of the group
this.$addPirateGroup(station, stnIndex, groupName, leader.homeSystem, leader.destination, leader.destinationHidden, 3, 2, 0, 1, shipDockTime, depart);
home = leader.homeSystem;
destSystem = leader.destination;
destHidden = leader.destinationHidden;
if (indexInList(leader.dataKey, this._switchEscortsToGroup) >= 0) {
groupData = this.$processPirateEscorts(stationkey, leader, groupName);
} else {
escorts = this.$processPirateEscorts(stationkey, leader, "");
}
break;
case 9: // heavy pirate group
countPirateHeavy += 1;
role = "pirate-medium-freighter";
if (this.$roleExists(role) === false) role = "pirate";
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
var leader = this.$addPirateLeader(station, stnIndex, 0.5, role, groupName, shipDockTime, depart);
// add the rest of the group
this.$addPirateGroup(station, stnIndex, groupName, leader.homeSystem, leader.destination, leader.destinationHidden, 4, 4, 2, 2, shipDockTime, depart);
home = leader.homeSystem;
destSystem = leader.destination;
destHidden = leader.destinationHidden;
if (indexInList(leader.dataKey, this._switchEscortsToGroup) >= 0) {
groupData = this.$processPirateEscorts(stationkey, leader, groupName);
} else {
escorts = this.$processPirateEscorts(stationkey, leader, "");
}
break;
case 10: // light hunter group
countHunterLight += 1;
role = "hunter";
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
groupSize = Math.floor(random() * 2) + Math.floor(random() * 2) + 2;
home = system.ID;
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
weapons = 1.5;
skill = -1;
isLeader = true;
for (var j = 0; j < groupSize; j++) {
shpDataKey = this.$getRandomShipKeyWithDockCheck(role, station);
shpName = this.$getRandomShipName(role);
autoAI = shipDataRef[shpDataKey]["auto_ai"];
shpType = shipDataRef[shpDataKey].name;
aiName = "";
// this should normally be set by the role, but just in case...
if (typeof autoAI === "undefined" || indexInList(autoAI, trueValues) >= 0) aiName = "oolite-bountyHunterAI.js";
pilot = this.$createPilot(home, 0, calcIns(0));
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, 0),
equipment: this.$setWeapons(shpDataKey, weapons),
heatInsulation: this.$setHeatInsulation(shpDataKey, 0),
homeSystem: home,
bounty: 0,
escortName: "",
escortLeader: false,
groupName: groupName,
groupLeader: isLeader,
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: "",
pilot: pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: (isLeader ? 0 : 5),
autoLog: logging
});
// only the first ship is the leader
isLeader = false;
}
break;
case 11: // medium hunter group
countHunterMedium += 1;
role = "hunter-medium";
if (this.$roleExists(role) === false) role = "hunter";
home = system.ID;
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
this.$addHunterLeader(station, stnIndex, role, groupName, destSystem, destHidden, shipDockTime, depart);
this.$addHunterGroup(station, stnIndex, groupName, destSystem, destHidden, shipDockTime, depart);
break;
case 12: // heavy hunter group
countHunterHeavy += 1;
role = "hunter-heavy";
if (this.$roleExists(role) === false) role = "hunter";
home = system.ID;
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
groupName = "shipgroup-" + this._groupCounter;
this._groupCounter += 1;
this.$addHunterLeader(station, stnIndex, role, groupName, destSystem, destHidden, shipDockTime, depart);
this.$addHunterGroup(station, stnIndex, groupName, destSystem, destHidden, shipDockTime, depart);
break;
} // switch
// add any escorts that were defined
if ((escorts && escorts.length > 0) || (groupData && groupData.length > 0)) {
var dta = escorts;
if (groupData) dta = groupData;
for (var j = 0; j < dta.length; j++) {
var esc_role = dta[j].role;
var esc_key = dta[j].shipKey;
var esc_bounty = 0;
var esc_name = this.$getRandomShipName(esc_role);
// make sure the key is in the dictionary
if (!shipDataRef[esc_key]) {
log(this.name, "!NOTE: Escort ship key not in core dictionary - " + esc_role + "/" + esc_key);
shipDataRef[esc_key] = Ship.shipDataForKey(esc_key);
}
var esc_autoAI = shipDataRef[esc_key]["auto_ai"];
if (shipDataRef[esc_key]) {
var esc_type = shipDataRef[esc_key].name;
if (bounty !== 0) esc_bounty = 3 + Math.floor(random() * 12);
var esc_pilot = this.$createPilot(this.$nearbySystem(7), esc_bounty, calcIns(esc_bounty));
aiName = "";
if (typeof esc_autoAI === "undefined" || indexInList(esc_autoAI, trueValues) >= 0) aiName = "oolite-escortAI.js";
if (indexInList(shipDataRef[esc_key]["auto_weapons"], trueValues) >= 0) {
if (esc_role === "escort" || esc_role === "pirate-light-fighter") {
weapons = 1.3; // usually lightly armed as escorts
} else if (esc_role === "escort-medium" || esc_role === "pirate-medium-fighter") {
weapons = 1.8; // usually heavily armed as escorts
} else if (esc_role === "escort-heavy" || esc_role === "pirate-heavy-fighter") {
weapons = 2.05; // rarely have an aft laser
}
}
// note: add 0.5 seconds to the escort members so they will always come after the leader in any sorted list
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: esc_role,
shipType: esc_type,
shipDataKey: esc_key,
shipName: esc_name,
aiName: aiName,
accuracy: this.$setAccuracy(esc_key, skill),
equipment: this.$setWeapons(esc_key, weapons),
heatInsulation: this.$setHeatInsulation(esc_key, heat),
homeSystem: home,
bounty: esc_bounty,
escortName: (dta === escorts ? escortGroupName : ""),
escortLeader: false,
groupName: (dta === groupData ? groupName : ""),
groupLeader: false,
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: "",
pilot: esc_pilot,
dockTime: shipDockTime,
departureTime: depart,
departureSeconds: 5,
autoLog: logging
});
} else {
log(this.name, "!!NOTE: No ship data returned for ship data key " + dta[j].shipKey);
}
} // for j
} // if escorts/groupdata
} // while i--
/*if (this._debug && this._logPopulatorType > 0) {
log(this.name, "Actual ships created: " + actualCount + ", difference of " + (ships - actualCount));
log(this.name, "Traders added: " + countTraderFreighter);
log(this.name, "Couriers added: " + countTraderCourier);
log(this.name, "Smugglers added: " + countTraderSmuggler);
log(this.name, "Assassins added: " + countAssassin);
log(this.name, "Shuttles added: " + countShuttle);
log(this.name, "Pirate Ind added: " + countPirateInd);
log(this.name, "Pirate Light groups: " + countPirateLight);
log(this.name, "Pirate Medium groups: " + countPirateMedium);
log(this.name, "Pirate Heavy groups: " + countPirateHeavy);
log(this.name, "Hunter Light groups: " + countHunterLight);
log(this.name, "Hunter Medium groups: " + countHunterMedium);
log(this.name, "Hunter Heavy groups: " + countHunterHeavy);
}*/
this._departureBands.length = 0;
} // if ships
// set up process for adding docking bay codes
this.$addBayCodes(station);
// remove this station from the array
this._populateStation.splice(0, 1);
// check if the array is empty - if so, turn off the auto generation flag
if (this._populateStation.length === 0) {
this._autoGenerationInProgress = false;
}
this._running = false;
if (this._debug && this._logPopulatorType > 0)
log(this.name, "Populator run complete in ms: " + (new Date().getTime() - startDate.getTime()));
}
//-------------------------------------------------------------------------------------------------------------
// returns a pilot object
this.$createPilot = function $createPilot(home, legal, insurance) {
var pilot = {};
// create the pilot
pilot["name"] = expandDescription("%N [nom]");
pilot["description"] = "a " + System.infoForSystem(galaxyNumber, home).inhabitant.toLowerCase() + " from " + System.systemNameForID(home); // check
pilot["homeSystem"] = home;
pilot["legalStatus"] = legal;
pilot["insurance"] = (!insurance == null ? insurance : 0);
pilot["species"] = System.infoForSystem(galaxyNumber, home).inhabitant.toLowerCase();
return pilot;
}
//-------------------------------------------------------------------------------------------------------------
// adds a ship to the stations docked list
// used by the populator function
this.$addShipToArray = function $addShipToArray(dockObj) {
// work out any defaults
var stnIndex = (dockObj.hasOwnProperty("stationIndex") ? dockObj.stationIndex : this.$getStationIndex(dockObj.station));
var bay = ((this._autoGenerationInProgress === true) ? "" : this.$getDockingBayCode(dockObj.station, stnIndex));
var hide = (dockObj.hasOwnProperty("destinationHidden") && dockObj.destinationHidden === true ? true : false);
var pers = (dockObj.hasOwnProperty("personality") ? dockObj.personality : (this.$rand(32768) - 1));
var fuel = (dockObj.hasOwnProperty("fuel") ? dockObj.fuel : 7);
var lastChange = (dockObj.hasOwnProperty("lastChange") ? dockObj.lastChange : clock.adjustedSeconds);
var cargo = (dockObj.hasOwnProperty("cargo") ? dockObj.cargo : "");
var tradeComplete = (dockObj.hasOwnProperty("tradeComplete") ? dockObj.tradeComplete : 0);
var dest = (dockObj.hasOwnProperty("destination") && dockObj.destination !== "" ? dockObj.destination : "");
if (this._systemDockingData[system.ID] == null) {
this._systemDockingData[system.ID] = {};
}
if (this._systemDockingData[system.ID][dockObj.station.name + "_" + stnIndex] == null) {
this._systemDockingData[system.ID][dockObj.station.name + "_" + stnIndex] = [];
}
this._systemDockingData[system.ID][dockObj.station.name + "_" + stnIndex].push({
system: system.ID,
station: dockObj.station.name,
stationIndex: stnIndex,
shipName: dockObj.shipName,
shipType: dockObj.shipType,
shipDataKey: dockObj.shipDataKey,
personality: pers,
primaryRole: dockObj.role,
shipAI: dockObj.aiName,
accuracy: dockObj.accuracy,
equipment: dockObj.equipment,
heatInsulation: dockObj.heatInsulation,
fuel: fuel,
homeSystem: dockObj.homeSystem,
bounty: dockObj.bounty,
escortName: dockObj.escortName,
escortLeader: dockObj.escortLeader,
groupName: dockObj.groupName,
groupLeader: dockObj.groupLeader,
destinationSystem: dockObj.destinationSystem,
destination: dest,
destinationHidden: hide,
goods: dockObj.goods,
cargo: cargo,
tradeComplete: tradeComplete,
pilotDescription: dockObj.pilot.description,
pilotName: dockObj.pilot.name,
pilotHomeSystem: dockObj.pilot.homeSystem,
pilotLegalStatus: dockObj.pilot.legalStatus,
pilotInsurance: dockObj.pilot.insurance,
pilotSpecies: dockObj.pilot.species,
isPlayer: false,
dockTime: dockObj.dockTime,
dockingBay: bay,
departureTime: dockObj.departureTime,
departureSeconds: dockObj.departureSeconds,
lastChange: lastChange,
launchCallback: (dockObj.hasOwnProperty("launchCallback") && dockObj.launchCallback !== "" ? dockObj.launchCallback : null),
worldScript: (dockObj.hasOwnProperty("worldScript") && dockObj.worldScript !== "" ? dockObj.worldScript : null),
additionalProperties: (dockObj.hasOwnProperty("properties") && dockObj.properties !== "" ? dockObj.properties : "")
});
if (dockObj.autoLog === 2) {
this.$writeDataToLog(this._systemDockingData[system.ID][dockObj.station.name + "_" + stnIndex][this._systemDockingData[system.ID][dockObj.station.name + "_" + stnIndex].length - 1], "Docking ship at station " + dockObj.station.name + "(" + stnIndex + ")");
} else if (dockObj.autoLog === 1) {
this.$writeShortDataToLog(this._systemDockingData[system.ID][dockObj.station.name + "_" + stnIndex][this._systemDockingData[system.ID][dockObj.station.name + "_" + stnIndex].length - 1]);
}
}
//-------------------------------------------------------------------------------------------------------------
// adds a pirate leader and returns the ship data key
this.$addPirateLeader = function $addPirateLeader(station, stnIndex, localFactor, role, groupName, dockTime, depart) {
var bounty = 60 + system.government + Math.floor(Math.random() * 8);
var home = system.ID;
var destSystem;
var destHidden = true;
var aiName = "";
var trueValues = this._trueValues;
// occasionally make the home system the same as the dest system so not every ship in dock calls this system home
if (Math.random() < 0.3) home = this.$nearbySystem(7);
if (Math.random() < localFactor) {
destSystem = system.ID;
} else {
var dest = this.$getDestinationByRole(role, station);
destSystem = dest.destination;
destHidden = dest.destinationHidden;
}
var shpDataKey = this.$getRandomShipKeyWithDockCheck(role, station);
var shpName = this.$getRandomShipName(role);
var autoAI = this._shipData[shpDataKey]["auto_ai"];
var shpType = this._shipData[shpDataKey].name;
if (typeof autoAI === "undefined" || this.$indexInList(autoAI, trueValues) >= 0) aiName = "oolite-pirateFreighterAI.js";
var pilot = this.$createPilot(this.$nearbySystem(7), bounty, this.$calcInsurance(bounty));
var equip = this.$setWeapons(shpDataKey, 2.8);
if (this.$indexInList(this._shipData[shpDataKey]["auto_weapons"], trueValues) >= 0) {
equip += "EQ_SHIELD_BOOSTER,";
equip += "EQ_ECM,";
}
// add the leader
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, 3),
equipment: equip,
heatInsualtion: this.$setHeatInsulation(shpDataKey, 0),
homeSystem: home,
bounty: bounty,
escortName: "",
escortLeader: false,
groupName: groupName,
groupLeader: true,
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: "",
pilot: pilot,
dockTime: dockTime,
dockingBay: "",
departureTime: depart,
departureSeconds: 0,
autoLog: (this._debug && this._logPopulatorType >= 2 ? 1 : 0)
});
return {
dataKey: shpDataKey,
name: shpName,
type: shpType,
homeSystem: home,
destination: destSystem,
destinationHidden: destHidden
};
}
//-------------------------------------------------------------------------------------------------------------
// adds members of a pirate group
this.$addPirateGroup = function $addPirateGroup(station, stnIndex, groupName, home, dest, destHidden, lf, mf, hf, thug, dockTime, depart) {
for (var i = Math.floor(lf + (0.5 + Math.random() - Math.random())); i > 0; i--) {
this.$addPirateAssistant("pirate-light-fighter", station, stnIndex, groupName, home, dest, destHidden, dockTime, depart);
}
for (var i = Math.floor(mf + (0.5 + Math.random() - Math.random())); i > 0; i--) {
this.$addPirateAssistant("pirate-medium-fighter", station, stnIndex, groupName, home, dest, destHidden, dockTime, depart);
}
for (var i = Math.floor(hf + (0.5 + Math.random() - Math.random())); i > 0; i--) {
this.$addPirateAssistant("pirate-heavy-fighter", station, stnIndex, groupName, home, dest, destHidden, dockTime, depart);
}
for (var i = Math.floor(thug + (0.5 + Math.random() - Math.random())); i > 0; i--) {
this.$addPirateAssistant("pirate-interceptor", station, stnIndex, groupName, home, dest, destHidden, dockTime, depart);
}
}
//-------------------------------------------------------------------------------------------------------------
// adds the individual member of a pirate group
this.$addPirateAssistant = function $addPirateAssistant(role, station, stnIndex, groupName, home, dest, destHidden, dockTime, depart) {
if (this.$roleExists(role) === false) role = "pirate";
var shpDataKey = this.$getRandomShipKey(role);
var shpName = this.$getRandomShipName(role);
var shpType = this._shipData[shpDataKey].name;
var trueValues = this._trueValues;
var bounty = 0;
var equip = "";
var aiName = "";
var autoAI = this._shipData[shpDataKey]["auto_ai"];
var autoWeapons = this._shipData[shpDataKey]["auto_weapons"];
var hasAutoAI = (this.$indexInList(autoAI, trueValues) >= 0);
if (typeof autoAI === "undefined" || hasAutoAI === true) aiName = "oolite-pirateFighterAI.js";
if (role === "pirate-interceptor") {
if (typeof autoAI === "undefined" || hasAutoAI === true) aiName = "oolite-pirateInterceptorAI.js";
equip += this.$setWeapons(shpDataKey, 2.3);
if (this.$indexInList(autoWeapons, trueValues) >= 0) equip += "EQ_FUEL_INJECTION,";
bounty = 50 + system.government + Math.floor(Math.random() * 36);
} else {
bounty = 20 + system.government + Math.floor(Math.random() * 12);
if (role === "pirate-light-fighter") {
equip += this.$setWeapons(shpDataKey, 1.2); // basic fighters
} else if (role === "pirate-medium-fighter") {
equip += this.$setWeapons(shpDataKey, 1.8); // often beam weapons
} else if (role === "pirate-heavy-fighter") {
equip += this.$setWeapons(shpDataKey, 2.05); // very rarely aft lasers
}
}
var pilot = this.$createPilot(this.$nearbySystem(7), bounty, this.$calcInsurance(bounty));
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, 0),
equipment: equip,
heatInsulation: this.$setHeatInsulation(shpDataKey, 0),
homeSystem: home,
bounty: bounty,
escortName: "",
escortLeader: false,
groupName: groupName,
groupLeader: false,
destinationSystem: dest,
destinationHidden: destHidden,
goods: "",
pilot: pilot,
dockTime: dockTime,
dockingBay: "",
departureTime: depart,
departureSeconds: 5,
autoLog: (this._debug && this._logPopulatorType >= 2 ? 1 : 0)
});
}
//-------------------------------------------------------------------------------------------------------------
// process pirate escort data for a given leader object
this.$processPirateEscorts = function $processPirateEscorts(stationkey, leader, groupName) {
if (typeof leader === "undefined" || typeof groupName === "undefined") return null;
var escorts = this.$getEscortData(leader.dataKey);
if (escorts && escorts.length > 0) {
if (groupName === "") {
this._escortCounter += 1;
var escortGroupName = "escortgroup-" + this._escortCounter;
} else {
this._groupCounter += 1;
}
// find leader in data and update escort info
// should be last entry, so start from the end
var dta = this._systemDockingData[system.ID][stationkey];
if (dta && dta.length > 0) {
var i = dta.length;
while (i--) {
var item = dta[i];
if (item.shipDataKey === leader.dataKey && item.shipType === leader.type && item.shipName === leader.name) {
if (groupName === "") {
item.escortName = escortGroupName;
item.escortLeader = true;
} else {
item.groupName = groupName;
item.groupLeader = true;
}
break;
}
}
}
}
return escorts;
}
//-------------------------------------------------------------------------------------------------------------
// process escort info for a given ship data key
this.$processTraderEscorts = function $processTraderEscorts(shipDataKey) {
var escorts = this.$getEscortData(shipDataKey);
var isLeader = false;
var escortName = "";
if (escorts && escorts.length > 0) {
this._escortCounter += 1;
escortName = "escortgroup-" + this._escortCounter;
isLeader = true;
}
return {
escortData: escorts,
leader: isLeader,
name: escortName
};
}
//-------------------------------------------------------------------------------------------------------------
// adds a hunter leader
this.$addHunterLeader = function $addHunterLeader(station, stnIndex, role, groupName, destSystem, destHidden, dockTime, depart) {
var shpDataKey = this.$getRandomShipKeyWithDockCheck(role, station);
var shpName = this.$getRandomShipName(role);
var autoAI = this._shipData[shpDataKey]["auto_ai"];
var shpType = this._shipData[shpDataKey].name;
var equip = "";
if (role === "hunter-heavy") {
// occasionally give heavy hunters aft lasers
equip += this.$setWeapons(shpDataKey, 2.2);
} else {
// usually ensure medium hunters have beam lasers
equip += this.$setWeapons(shpDataKey, 1.9);
}
var skill = 3; // likely to be good pilot
var aiName = "";
// auto AI will normally get this already but not if the hunter
// addition used fallback roles
if (typeof autoAI === "undefined" || this.$indexInList(autoAI, this._trueValues) >= 0) aiName = "oolite-bountyHunterLeaderAI.js";
var pilot = this.$createPilot(system.ID, 0, this.$calcInsurance(0));
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, skill),
equipment: equip,
heatInsulation: this.$setHeatInsulation(shpDataKey, 1),
homeSystem: system.ID,
bounty: 0,
escortName: "",
escortLeader: false,
groupName: groupName,
groupLeader: true,
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: "",
pilot: pilot,
dockTime: dockTime,
dockingBay: "",
departureTime: depart,
departureSeconds: 0,
autoLog: (this._debug && this._logPopulatorType >= 2 ? 1 : 0)
});
}
//-------------------------------------------------------------------------------------------------------------
// adds the members of a hunter group
this.$addHunterGroup = function $addHunterGroup(station, stnIndex, groupName, destSystem, destHidden, dockTime, depart) {
var role = "hunter";
var groupSize = 1 + Math.floor(Math.random() * 4) + Math.floor(Math.random() * 4);
for (var i = 0; i < groupSize; i++) {
var shpDataKey = this.$getRandomShipKey(role);
var shpName = this.$getRandomShipName(role);
var autoAI = this._shipData[shpDataKey]["auto_ai"];
var shpType = this._shipData[shpDataKey].name;
var equip = this.$setWeapons(shpDataKey, 1.5);
var pilot = this.$createPilot(this.$nearbySystem(7), 0, this.$calcInsurance(0));
var aiName = "";
// this should normally be set by the role, but just in case...
if (typeof autoAI === "undefined" || this.$indexInList(autoAI, this._trueValues) >= 0) aiName = "oolite-bountyHunterAI.js";
this.$addShipToArray({
station: station,
stationIndex: stnIndex,
role: role,
shipType: shpType,
shipDataKey: shpDataKey,
shipName: shpName,
aiName: aiName,
accuracy: this.$setAccuracy(shpDataKey, 0),
equipment: equip,
heatInsulation: this.$setHeatInsulation(shpDataKey, 1),
homeSystem: system.ID,
bounty: 0,
escortName: "",
escortLeader: false,
groupName: groupName,
groupLeader: false,
destinationSystem: destSystem,
destinationHidden: destHidden,
goods: "",
pilot: pilot,
dockTime: dockTime,
dockingBay: "",
departureTime: depart,
departureSeconds: 5,
autoLog: (this._debug && this._logPopulatorType >= 2 ? 1 : 0)
});
}
}
//=============================================================================================================
// helper functions
//-------------------------------------------------------------------------------------------------------------
// return a random number between 1 and max
this.$rand_old = function $rand_old(max) {
return Math.floor((Math.random() * max) + 1);
}
// contributed by Day
this.$rand = function $rand(max) {
var that = $rand;
var floor = (that.floor = that.floor || Math.floor);
var random = (that.random = that.random || Math.random);
return floor((random() * max) + 1);
}
//-------------------------------------------------------------------------------------------------------------
// gets a subset of vessels for viewing
this.$getViewingList = function $getViewingList(station, mfd) {
var stationList = [];
var idx = this.$getStationIndex(station);
if (station != null && idx > 0) {
// add entries from the main data repository
var dta = this._systemDockingData[system.ID][station.name + "_" + idx];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
var item = dta[i];
if (item.departureTime > 0) {
stationList.push(item);
}
}
}
// add any ships on the launching queue to the list
if (this._launching.length > 0) {
for (var i = 0; i < this._launching.length; i++) {
var item = this._launching[i];
if (item.system === system.ID && item.station === station.name && item.stationIndex === idx) {
stationList.push(item);
}
}
}
// add any docking ships to the list, if this isn't an MFD call
if (mfd === false) this.$addShipsDockingToList(station, stationList);
}
return stationList;
}
//-------------------------------------------------------------------------------------------------------------
this.$addShipsDockingToList = function $addShipsDockingToList(station, viewlist) {
var shipname = "";
if (this._docking && this._docking.length > 0) {
for (var i = 0; i < this._docking.length; i++) {
var shp = this._docking[i];
if (shp.isValid) {
shipname = shp.shipUniqueName;
// occasionally the shipUniqueName returns "" even though it's really there. In that instance, pull the unique name out of the display name
if (shipname === "") shipname = this.$getShipUniqueName(shp.displayName, shp.shipClassName);
var escortlead = (shp.escortGroup && shp.escortGroup.leader === shp ? true : false);
var grouplead = (shp.group && shp.group.leader === shp ? true : false);
// add just enough info to the list, rather than all the details
viewlist.push({
system: system.ID,
station: station.name,
shipName: shipname,
shipType: shp.shipClassName,
shipDataKey: shp.dataKey,
personality: shp.entityPersonality,
primaryRole: shp.primaryRole,
bounty: shp.bounty,
destinationSystem: -1,
escortName: ((shp.escortGroup) ? shp.escortGroup.name : ""),
escortLeader: (escortlead ? true : false),
groupName: ((shp.group) ? shp.group.name : ""),
groupLeader: (grouplead ? true : false),
pilotDescription: (shp.crew ? shp.crew[0].description : ""),
pilotName: (shp.crew ? shp.crew[0].name : ""),
pilotHomeSystem: (shp.crew ? shp.crew[0].homeSystem : -1),
pilotLegalStatus: (shp.crew ? shp.crew[0].legalStatus : 0),
pilotSpecies: (shp.crew ? shp.crew[0].species : ""),
isPlayer: false,
dockTime: -1,
departureTime: -10,
departureSeconds: (escortlead || grouplead ? 5 : 0)
});
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
// returns the number of docked ships at a given station
this.$countStationItems = function $countStationItems(station) {
var items = 0;
var idx = this.$getStationIndex(station);
if (this._systemDockingData[system.ID] != null) {
var dta = this._systemDockingData[system.ID][station.name + "_" + idx];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
if (dta[i].departureTime > -5) items += 1;
}
}
}
return items;
}
//-------------------------------------------------------------------------------------------------------------
// returns the number of docked ships in the system
this.$countSystemItems = function $countSystemItems() {
var items = 0;
if (!this._systemDockingData[system.ID]) return 0;
var skeys = Object.keys(this._systemDockingData[system.ID]);
for (var kc = 0; kc < skeys.length; kc++) {
var dta = this._systemDockingData[system.ID][skeys[kc]];
if (dta && dta.length > 0) items += dta.length;
}
return items;
}
//-------------------------------------------------------------------------------------------------------------
// returns data for a particular group
this.$getShipGroupInfo = function $getShipGroupInfo(stationkey, groupName) {
var groupData = null;
var dta = this._systemDockingData[system.ID][stationkey];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
var item = dta[i];
if (item.groupName === groupName && item.groupLeader === true) {
// if we find the leader of the group, return their departure time immediately
return {
depart: item.departureTime,
departSecs: item.departureSeconds,
destination: item.destinationSystem,
destinationHidden: item.destinationHidden
};
}
if (item.groupName === groupName && item.groupLeader === false) {
// if we just find a group member, hold the departure value, in case the leader is stored later in the array
groupData = {
depart: item.departureTime,
departSecs: item.departureSeconds,
destination: item.destinationSystem,
destinationHidden: item.destinationHidden
};
}
}
}
return groupData;
}
//-------------------------------------------------------------------------------------------------------------
// returns data for a particular escort group
this.$getShipEscortInfo = function $getShipEscortInfo(stationkey, escortName) {
var escortData = null;
var dta = this._systemDockingData[system.ID][stationkey];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
var item = dta[i];
if (item.escortName === escortName && item.escortLeader === true) {
// if we find the leader of the group, return their departure time immediately
return {
depart: item.departureTime,
departSecs: item.departureSeconds,
destination: item.destinationSystem,
destinationHidden: item.destinationHidden
};
}
if (item.system === system.ID && item.escortName === escortName && item.escortLeader === false) {
// if we just find a group member, hold the departure value, in case the leader is stored later in the array
escortData = {
depart: item.departureTime,
departSecs: item.departureSeconds,
destination: item.destinationSystem,
destinationHidden: item.destinationHidden
};
}
}
}
return escortData;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if the leader of a particular group is docked, otherwise false
this.$isGroupLeaderDocked = function $isGroupLeaderDocked(stationkey, groupName) {
var dta = this._systemDockingData[system.ID][stationkey];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
var item = dta[i];
if ((item.groupName === groupName && item.groupLeader === true) ||
(item.escortName === groupName && item.escortLeader === true)) {
return true;
}
}
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// updates the leader of an escort with an escort group name
// we need to do this because, when the leader docks, we may not know whether their group is for an escort or a normal group
// when a ship with an escort role docks and links up to the leader, we need to update the leader record
this.$setGroupEscortLeader = function $setGroupEscortLeader(stationkey, escortName) {
var dta = this._systemDockingData[system.ID][stationkey];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
var item = dta[i];
if (item.groupName === escortName && item.escortLeader === true) {
if (this._debug && this._logDockingType > 0) log(this.name, "Updating escort name of group leader " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : ""));
item.escortName = escortName;
item.groupName = "";
item.groupLeader = false;
break;
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
// clears any ships that would have launched after player exits witchspace, regardless of system
// note: not called any longer, as a shorter method is in place
this.$clearLaunchedShips = function $clearLaunchedShips() {
// clear the launching list
this._launching.length = 0;
var remove = false;
// compile short list of neighbouring systems
var neighbour_list = [];
for (var i = 0; i < this._neighbours.length; i++) {
neighbour_list.push(this._neighbours[i].systemID);
}
var syslist = Object.keys(this._systemDockingData);
for (var sl = 0; sl < syslist.length; sl++) {
var skeys = Object.keys(this._systemDockingData[syslist[sl]]);
for (var kc = 0; kc < skeys.length; kc++) {
var dta = this._systemDockingData[syslist[sl]][skeys[kc]];
if (dta && dta.length > 0) {
for (var i = dta.length - 1; i >= 0; i--) {
var item = dta[i];
remove = false;
// remove all ships that are outside the list of neighbours
if (this.$indexInList(syslist[sl], neighbour_list) === -1) remove = true;
// remove all ships that would have launched, regardless of system
if (remove === false && (item.departureTime * 60 + item.lastChange) <= clock.adjustedSeconds) remove = true;
if (remove) dta.splice(i, 1);
}
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
// moves items from the main dataset into the launching array so the repopulator doesn't have to work so hard
this.$updateLaunchingList = function $updateLaunchingList() {
// don't do the check if we're about to do a repopulate routine, or we're in the middle of one
if (this._repopulate || this._autoGenerationInProgress) return;
this._launchListUpdating = true;
if (this._systemDockingData[system.ID]) {
var skeys = Object.keys(this._systemDockingData[system.ID]);
for (var kc = 0; kc < skeys.length; kc++) {
var dta = this._systemDockingData[system.ID][skeys[kc]];
if (dta && dta.length > 0) {
for (var i = dta.length - 1; i >= 0; i--) {
var item = dta[i];
var lt = (item.departureTime * 60) + item.lastChange;
if (lt <= clock.adjustedSeconds) {
// move it do the launching array (if it's within 4 minutes of the adjusted seconds, regardless of the state of the dock
if ((clock.adjustedSeconds - lt) / 60 <= 4) {
if (this._debug && this._logLaunchType >= 2) log(this.name, item.station + " adding launching item " + i + " - " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : ""));
this._launching.push(item);
} else {
if (this._debug && this._logLaunchType >= 2) log(this.name, item.station + " removing expired item " + i + " - " + item.shipType + (item.shipName !== "" ? ": " + item.shipName : ""));
}
// remove it from the main array
dta.splice(i, 1);
} else {
item.departureTime -= 1;
if (item.departureTime < 0) item.departureTime = 0;
item.lastChange = clock.adjustedSeconds;
}
}
}
}
}
this._launchListUpdating = false;
}
//-------------------------------------------------------------------------------------------------------------
// this is used when the player launches from a station to launch any ships that would have been in the queue ahead of them
this.$dockingQueueShipClearance = function $dockingQueueShipClearance(station, timeLapse) {
function compare(a, b) {
return (a.departureTime * 10 + a.departureSeconds) - (b.departureTime * 10 + b.departureSeconds);
}
//log(this.name, station.displayName + " - " + timeLapse);
var forceLaunch = [];
var idx = this.$getStationIndex(station);
var stationkey = station.name + "_" + idx;
if (this._systemDockingData[system.ID] == null) return;
var dta = this._systemDockingData[system.ID][stationkey];
if (dta && dta.length > 0) {
var i = dta.length;
while (i--) {
var item = dta[i];
if (((item.departureTime * 60) + item.lastChange) <= clock.adjustedSeconds) {
// move it do the launching array
forceLaunch.push(item);
// remove it from the main array
dta.splice(i, 1);
} else {
item.departureTime -= timeLapse;
item.lastChange = clock.adjustedSeconds;
}
}
}
// do the same with the launching list
if (this._launching.length > 0) {
var i = this._launching.length;
while (i--) {
var item = this._launching[i];
// only do it if the departure time is still current (ie it hasn't been moved to "Rescheduled")
if (item.station === station.name && item.stationIndex === idx && Math.round(item.departureTime) !== -5) {
if (((item.departureTime * 60) + item.lastChange) <= clock.adjustedSeconds) {
// move it do the launching array
forceLaunch.push(item);
// remove it from the array
this._launching.splice(i, 1);
} else {
if (item.departureTime - timeLapse > 0) {
item.departureTime -= timeLapse;
item.lastChange = clock.adjustedSeconds;
}
}
}
}
}
forceLaunch.sort(compare);
// push the ships out with the addShips function
if (this._debug && this._logLaunchType > 0) log(this.name, "Clearing pending ships:" + forceLaunch.length + " -- timeLapse:" + timeLapse);
var dock = null;
var dockpos = null;
// find the dock object of the station so we can position launched ships
for (var i = 0; i < station.subEntities.length; i++) {
var subent = station.subEntities[i];
if (subent.isDock) {
dock = subent;
dockpos = station.position.add( //better formula for non-centered docks
Vector3D(
station.heading.direction().multiply(dock.position.z),
station.vectorUp.direction().multiply(dock.position.y),
station.vectorRight.direction().multiply(dock.position.x)
)
);
break;
}
}
var launchperiod = 4; // this defines the time for which ships will be created. If the difference in minutes between the departure time and the adjusted seconds is
// less than or equal to this value, the ship will be created. Anything more than this and it will be assumed the ship jumped after launch.
// the larger this number, the more crowded it will be around the station after launch
var spread = 0;
var dist = 2000;
var last_pos = null;
var last_group = "";
var last_escort = "";
if (forceLaunch.length > 0) {
for (var i = 0; i < forceLaunch.length; i++) {
var shp = forceLaunch[i];
var lt = (shp.departureTime * 60) + shp.lastChange;
// if their departure time is > 4 minutes and their destination is somewhere else, don't create them.
// group members and escorts should still fall into this logic, as the destination system is added to each group/escort member, regardless of whether
// they have a hyperdrive motor or not, and their departure time should be (almost) the same as their leader
if ((clock.adjustedSeconds - lt) / 60 <= launchperiod) {
// launch it
var timediff = Math.abs(clock.adjustedSeconds - lt) / 60;
timediff += 1; // make sure we never have a zero value
// if it's a group or escort member, use the last position
if ((shp.escortName !== "" && shp.escortName === last_escort) || (shp.groupName !== "" && shp.groupName === last_group) && last_pos !== null) {
var pos = last_pos;
spread = 1000;
} else {
// work out the position of this launch
// the first items in the array will need to be further away, as they would have launched first
var side = this.$rand(8);
// move away from dock in straight line first...
var pos = dockpos.add(station.vectorForward.multiply(timediff * dist));
//var pos = dockpos.add(station.heading.direction().multiply(dist + timediff * dist));
// based on which side was picked, move the ship in that direction
switch (side) {
case 1: // right
pos = pos.add(station.vectorRight.multiply(timediff * dist));
break;
case 2: // up
pos = pos.add(station.vectorUp.multiply(timediff * dist));
break;
case 3: // left
pos = pos.add(station.vectorRight.multiply(timediff * dist * -1));
break;
case 4: // down
pos = pos.add(station.vectorUp.multiply(timediff * dist * -1));
break;
case 5: // up and right
pos = pos.add(station.vectorRight.multiply(timediff * dist));
pos = pos.add(station.vectorUp.multiply(timediff * dist));
break;
case 6: // up and left
pos = pos.add(station.vectorRight.multiply(timediff * dist * -1));
pos = pos.add(station.vectorUp.multiply(timediff * dist));
break;
case 7: // down and left
pos = pos.add(station.vectorRight.multiply(timediff * dist * -1));
pos = pos.add(station.vectorUp.multiply(timediff * dist * -1));
break;
case 8: // down and right
pos = pos.add(station.vectorRight.multiply(timediff * dist));
pos = pos.add(station.vectorUp.multiply(timediff * dist * -1));
break;
}
spread = 50;
}
// add ship to system
var shpKey = shp.shipDataKey;
// make sure a single version is launched (ie without escorts)
if (this.$dockVersionExists(shpKey)) shpKey = "dock_" + shpKey;
if (this._debug && this._logLaunchType >= 2) this.$writeDataToLog(shp, "Adding ship to system from " + shp.station);
var ship = system.addShips("[" + shpKey + "]", 1, pos, spread);
if (!ship || !ship[0]) {
// Oops! no ship returned!
// skip it this time and maybe next time it will launch
log(this.name, "!!ERROR: No ship created");
continue;
}
if (this._debug && this._logLaunchType >= 2) log(this.name, "Added ship: " + ship[0]);
// update ship details
this.$updateLaunchedShip(ship[0], shp);
// if there is an attached callback, call it now.
if (shp.launchCallback && shp.worldScript) {
log(this.name, "callback " + shp.worldScript + "." + shp.launchCallback);
var w = worldScripts[shp.worldScript];
if (w) w[shp.launchCallback]("launched", ship[0]);
}
last_pos = pos;
last_group = shp.groupName;
last_escort = shp.escortName;
} else { // if
// if there is an attached callback, call it now.
if (shp.launchCallback && shp.worldScript) {
log(this.name, "callback " + shp.worldScript + "." + shp.launchCallback);
var w = worldScripts[shp.worldScript];
if (w) w[shp.launchCallback]("cleared", shp);
}
// logic here to add ships to destination system directly (ie. add to _systemDockingData)
//if (this._debug) this.$writeDataToLog(shp, "Removing ship from data - assumed jump to other system or reached destination");
}
} // for
} // if
}
//-------------------------------------------------------------------------------------------------------------
// pushes details from the data array onto a launched ship
this.$updateLaunchedShip = function $updateLaunchedShip(ship, shipData) {
// push all the stored data onto the ship;
if (oolite.compareVersion("1.80") < 0) {
ship.entityPersonality = shipData.personality; // can only do this in Oolite 1.81 or greater
}
ship.primaryRole = shipData.primaryRole;
// if skilled NPC's is in play, don't worry about the accuracy - it will be adjusted later
if (this._skilledNPCs == false) {
ship.accuracy = shipData.accuracy;
}
ship.heatInsulation = shipData.heatInsulation;
ship.setBounty(shipData.bounty, "setup actions");
ship.shipUniqueName = shipData.shipName;
ship.homeSystem = shipData.homeSystem;
// this is the "lying" option - essentially doing a bait-n-switch on the player.
// but because there's no visible clue that this is happening it's kind of unfair.
// one option would be to send some sort of comms message saying "Changing destination now", but that feels out of place to me.
// anyway, for the moment this code is disabled
// if this ship hasn't been hiding their destination, and they're not a shuttle, and either an independant ship or a group/escort leader, then....
//if (shipData.destinationHidden === false && ship.primaryRole !== "shuttle" && ((shipData.groupName === "" && shipData.escortName === "") || (shipData.groupLeader === true || shipData.escortLeader === true))) {
// // there's a chance they're going to change their destination
// if (Math.random() < 0.2) {
// var newDest = this.$getDestinationByRole(shipData.primaryRole);
// if (newDest !== shipData.destinationSystem) {
// log(this.name, "!!ALERT: Ship " + ship.displayName + " is changing their destination on launch from " + shipData.destinationSystem + " to " + newDest);
// }
// shipData.destinationSystem = newDest;
// }
// }
ship.destinationSystem = shipData.destinationSystem;
ship.fuel = shipData.fuel;
// set the seed values so that the pilot's species can be defined by their home system
var seed = "0 0 0 0 " + shipData.pilotHomeSystem.toString() + " 2";
// todo: check what happens to "species"
if (shipData.pilotDescription !== "") {
ship.setCrew({
name: shipData.pilotName,
origin: shipData.pilotHomeSystem,
insurance: shipData.pilotInsurance,
bounty: shipData.pilotLegalStatus,
short_description: shipData.pilotDescription,
random_seed: seed
});
} else {
ship.setCrew({
name: shipData.pilotName,
origin: shipData.pilotHomeSystem,
insurance: shipData.pilotInsurance,
bounty: shipData.pilotLegalStatus,
random_seed: seed
});
}
// if we have goods defined (but not 'cargo')
if (shipData.goods !== "" && (!shipData.cargo || shipData.cargo === "")) {
// put some cargo in the hold
ship.setCargoType(shipData.goods);
}
// process any cargo data we have stored
//if (this._NPCTrading === true && shipData.cargo !== "") {
// var td = worldScripts.StationDockInfo_NPCTrading;
// td.$processCargo_Launching(ship, shipData.cargo);
// }
if (shipData.equipment !== "") {
// process all the equipment additions
var items = shipData.equipment.split(",");
for (var j = 0; j < items.length; j++) {
var eq = items[j];
if (eq.indexOf(":") >= 0) {
var subitems = eq.split(":");
switch (subitems[0]) {
case "X":
ship.removeEquipment(subitems[1]);
break;
case "FORE":
ship.forwardWeapon = subitems[1];
break;
case "AFT":
ship.aftWeapon = subitems[1];
break;
case "PORT":
ship.portWeapon = subitems[1];
break;
case "STARBOARD":
ship.starboardWeapon = subitems[1];
break;
}
} else {
if (eq !== "") {
ship.awardEquipment(eq);
}
}
}
}
// set the ai of the ship (if required)
if (shipData.shipAI !== "") ship.switchAI(shipData.shipAI);
// if a intra-system destination is set, set the destination to its position
if (shipData.destination && shipData.destination !== "") {
if (this._debug && this._logLaunchType >= 2) log(this.name, "Shuttle " + ship.displayName + " - attaching launch script");
var w = worldScripts.StationDockControl_ShuttleLaunch;
ship.script.$initialise = w.$initialise;
ship.script.$setDestination = w.$setDestination;
if (!ship.script.$sdc_hold_shipDied && ship.script.shipDied) ship.script.$sdc_hold_shipDied = ship.script.shipDied;
ship.script.shipDied = w.npc_shipDied;
if (!ship.script.$sdc_hold_shipWillEnterWormhole && ship.script.shipWillEnterWormhole) ship.script.$sdc_hold_shipWillEnterWormhole = ship.script.shipWillEnterWormhole;
ship.script.shipWillEnterWormhole = w.npc_shipWillEnterWormhole;
// set the mothership in that script
ship.script._dest = shipData.destination;
ship.script.$initialise();
}
// add any additional properties to the launching ship
if (shipData.hasOwnProperty("additionalProperties") === true && shipData.additionalProperties !== "") {
var keys = Object.keys(shipData.additionalProperties);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
ship.script[key] = shipData.additionalProperties[key];
}
}
// is this ship part of a group or escort?
if (shipData.groupName !== "") {
var stdGroup = null;
// look for an existing group
if (this._launchingGroups.length > 0) {
for (var j = 0; j < this._launchingGroups.length; j++) {
if (this._launchingGroups[j].name === shipData.groupName) {
stdGroup = this._launchingGroups[j];
break;
}
}
}
// if we didn't find a group, create a new one
if (!stdGroup) {
// is this ship the leader
if (shipData.groupLeader) {
// create a group with the ship as the leader
stdGroup = new ShipGroup(shipData.groupName, ship);
ship.group = stdGroup;
if (this._debug && this._logLaunchType >= 2) log(this.name, "Creating new group with leader: " + ship.displayName + ", " + shipData.groupName + ", " + ship.scanClass);
} else {
// otherwise just create the group and add the ship individually
stdGroup = new ShipGroup(shipData.groupName);
ship.group = stdGroup;
stdGroup.addShip(ship);
if (this._debug && this._logLaunchType >= 2) log(this.name, "Creating new group: " + ship.displayName + ", " + shipData.groupName + ", " + ship.scanClass);
}
this._launchingGroups.push(stdGroup);
} else {
// if we found a group, and this is a standard group, just add the ship
ship.group = stdGroup;
stdGroup.addShip(ship);
if (this._debug && this._logLaunchType >= 2) log(this.name, "Adding to existing group: " + ship.displayName + ", " + shipData.groupName + ", " + ship.scanClass);
// is the ship the leader
if (shipData.groupLeader) {
// add the ship as the leader
stdGroup.leader = ship;
} else {
ship.performEscort();
}
}
}
if (shipData.escortName !== "") {
// process this as an escort
if (shipData.escortLeader) {
// for the leader, add the ship to the escort list
if (this.$leaderIsInArray(shipData.escortName, ship) === false) {
this._launchingEscorts.push({
escortName: shipData.escortName,
leader: ship
});
}
if (this._debug && this._logLaunchType >= 2) log(this.name, "Escort leader details: " + ship.displayName + ", " + shipData.escortName + ", " + ship.scanClass);
// monkey patch the ship, but only if we haven't already done so.
if (!ship.script.$sdc_hold_shipLaunchedFromStation && ship.script.shipLaunchedFromStation) {
ship.script.$sdc_hold_shipLaunchedFromStation = ship.script.shipLaunchedFromStation;
}
if (!ship.script.$sdc_hold_shipDied && ship.script.shipDied) {
ship.script.$sdc_hold_shipDied = ship.script.shipDied;
}
if (!ship.script.$sdc_hold_shipWillEnterWormhole && ship.script.shipWillEnterWormhole) {
ship.script.$sdc_hold_shipWillEnterWormhole = ship.script.shipWillEnterWormhole;
}
if (!ship.script.$addEscort) {
var esc = worldScripts.StationDockControl_EscortLaunch;
ship.script.$addEscort = esc.$addEscort;
ship.script.$removeEscort = esc.$removeEscort;
ship.script.$attachEscort = esc.$attachEscort;
ship.script.shipLaunchedFromStation = esc.npc_shipLaunchedFromStation;
ship.script.shipDied = esc.npc_shipDied;
ship.script.shipWillEnterWormhole = esc.npc_shipWillEnterWormhole;
ship.script._done = false;
}
} else {
// for an escort, find the leader, and offer to escort them
if (this._debug && this._logLaunchType >= 2) log(this.name, "Escort member details: " + ship.displayName + ", " + shipData.escortName + ", " + ship.scanClass);
for (var i = 0; i < this._launchingEscorts.length; i++) {
if (this._launchingEscorts[i].escortName === shipData.escortName) {
var mShip = this._launchingEscorts[i].leader;
if (this._debug && this._logLaunchType >= 2) log(this.name, "Found leader (" + mShip.displayName + ") - attaching launch script");
// if the mothership is already in space, we can do the offer to escort here
if (mShip.status === "STATUS_IN_FLIGHT" && ship.status === "STATUS_IN_FLIGHT") {
var ret = ship.offerToEscort(mShip);
if (ret) {
if (this._debug && this._logLaunchType >= 2) log(this.name, ship.displayName + ": found leader (" + mShip.displayName + ") - offer to escort: " + ret);
} else {
if (!mShip) log(this.name, "!!ERROR: Mothership is null");
if (mShip && mShip.isValid === false) log(this.name, "!!ERROR: Mothership is invalid");
log(this.name, "!!ERROR: Ship launch script failed to escort mothership! " + (mShip && mShip.isValid ? "(Mthr: " + mShip.displayName + " (" + mShip.status + "), " : "") + "Ship: " + ship.displayName + " (" + ship.status + ")");
}
} else {
// otherwise, attach the escort launch script
if (!ship.script.$sdc_hold_shipLaunchedFromStation) {
if (ship.script.shipLaunchedFromStation) ship.script.$sdc_hold_shipLaunchedFromStation = ship.script.shipLaunchedFromStation;
}
if (!ship.script.$sdc_hold_shipDied && ship.script.shipDied) {
ship.script.$sdc_hold_shipDied = ship.script.shipDied;
}
if (!ship.script.$sdc_hold_shipWillEnterWormhole && ship.script.shipWillEnterWormhole) {
ship.script.$sdc_hold_shipWillEnterWormhole = ship.script.shipWillEnterWormhole;
}
var esc = worldScripts.StationDockControl_EscortLaunch;
ship.script.$addEscort = esc.$addEscort;
ship.script.$removeEscort = esc.$removeEscort;
ship.script.$attachEscort = esc.$attachEscort;
ship.script._done = false;
ship.script.shipLaunchedFromStation = esc.npc_shipLaunchedFromStation;
ship.script.shipDied = esc.npc_shipDied;
ship.script.shipWillEnterWormhole = esc.npc_shipWillEnterWormhole;
// set the mothership in that script
ship.script._mothership = mShip;
// add escort to mothership script
if (!mShip.script.$addEscort) {
if (!mShip.script.$sdc_hold_shipLaunchedFromStation && mShip.script.shipLaunchedFromStation) {
mShip.script.$sdc_hold_shipLaunchedFromStation = mship.script.shipLaunchedFromStation;
}
if (!mShip.script.$sdc_hold_shipDied && mShip.script.shipDied) {
mShip.script.$sdc_hold_shipDied = mShip.script.shipDied;
}
if (!mShip.script.$sdc_hold_shipWillEnterWormhole && mShip.script.shipWillEnterWormhole) {
mShip.script.$sdc_hold_shipWillEnterWormhole = mShip.script.shipWillEnterWormhole;
}
mShip.script.$addEscort = esc.$addEscort;
mShip.script.$removeEscort = esc.$removeEscort;
mShip.script.$attachEscort = esc.$attachEscort;
mShip.script.shipLaunchedFromStation = esc.npc_shipLaunchedFromStation;
mShip.script.shipDied = esc.npc_shipDied;
mShip.script.shipWillEnterWormhole = esc.npc_shipWillEnterWormhole;
mShip.script._done = false;
}
mShip.script.$addEscort(ship);
}
}
}
}
} // if
if (this._debug && this._logLaunchType >= 2) {
// lets see what our ships do...
ship.reportAIMessages = true;
ship.script._aiTimer = new Timer(this, this.$setAIReporting.bind(this, ship), 1, 0);
}
// tell ships with a destination system other than the current system to jump out
if (ship.destinationSystem != system.ID) {
ship.script._destTimer = new Timer(this, this.$setEnterWitchspaceBehaviour.bind(this, ship), 1, 0);
}
}
//-------------------------------------------------------------------------------------------------------------
// set the witchspace destination parameter, and tell ship to reconsider it's AI options
this.$setEnterWitchspaceBehaviour = function $setEnterWitchspaceBehaviour(ship) {
if (ship.AIScript.oolite_priorityai) {
ship.AIScript.oolite_priorityai.setParameter("oolite_witchspaceDestination", ship.destinationSystem);
ship.AIScript.oolite_priorityai.reconsiderNow();
}
}
//-------------------------------------------------------------------------------------------------------------
// turn on AI message logging
this.$setAIReporting = function $setAIReporting(ship) {
if (ship.AIScript.oolite_priorityai) {
ship.AIScript.oolite_priorityai.setParameter("oolite_flag_behaviourLogging", true);
}
}
//-------------------------------------------------------------------------------------------------------------
// checks if the escort leader is already in the launching array in the given escort group
this.$leaderIsInArray = function $leaderIsInArray(escortName, leader) {
for (var i = 0; i < this._launchingEscorts.length; i++) {
var item = this._launchingEscorts[i];
if (item.escortName === escortName && item.leader === leader) return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// return a random departure time, based on a number of different slots
// returned time is in seconds, and should refer to a time in the future
this.$calculateRandomDepartureTime = function $calculateRandomDepartureTime(station, idx, slots, allowAbort) {
// work out min value, based on timeFrame
var depart = 0;
// pick a departure band
if (slots < 2) slots = 2;
if (slots > 10) slots = 10;
var band = this._bandSrc[slots];
var free = false;
var band_num = 0;
var tries = 0;
var secondAttempt = 0;
do {
band_num = band[this.$rand(band.length) - 1];
switch (band_num) {
//case 1:
// depart = this.$rand(5) + 1;
// break;
case 2: // 3-15 minutes
depart = this.$rand(12) + 3;
break;
case 3: // 15-30 minutes
depart = this.$rand(15) + 15;
break;
case 4: // 30-60 minutes
depart = this.$rand(30) + 30;
break;
case 5: // 60-120 minutes (1-2 hours)
depart = this.$rand(60) + 60;
break;
case 6: // 120-720 minutes (2-12 hours)
depart = this.$rand(600) + 120;
break;
case 7: // 720-960 minutes (12-16 hours) loading
depart = this.$rand(240) + 720;
break;
case 8: // 960-1200 minutes (16-20 hours) docked
depart = this.$rand(240) + 960;
break;
case 9: // 1200-1380 minutes (20-23 hours) unloading
depart = this.$rand(180) + 1200;
break;
case 10: // 1380-1440 minutes (23-24 hours) docking
depart = this.$rand(60) + 1380;
break;
case 11: // 1440-1680 minutes (24-28 hours) docking (failsafe)
depart = this.$rand(240) + 1440;
break;
}
// check this launch slot for free positions
free = true;
if (this._departureBands.length > 0) {
var check = this._departureBands[depart];
if (check === 1) {
this._departureBands[depart] = 0;
} else {
free = false;
}
} else {
free = this.$checkDepartureSlot(station.name, idx, depart);
}
if (free === false) tries++;
// if we've tried 7 (formerly 10) times to find a slot, jump into the failsafe zone
if (tries === 7) {
if (this.$indexInList(11, band) >= 0 && allowAbort === true) {
log(this.name, "!!NOTE: Search for free launch time failed - " + station.name + " (" + idx + ") " + slots);
return -1;
}
// make a note of when we hit the retry limit
log(this.name, "!!NOTE: Search for free launch time hit limit - " + station.name + " (" + idx + ") " + slots);
this._limit++;
if (this._limit >= 30) {
this.$displayDetails(station.name, idx);
this._limit = 0;
}
// have a really good go at the slot period
//if (secondAttempt === 0) {
// band = new Array(30).join(slots);
// secondAttempt += 1;
//} else
if (secondAttempt === 0) {
// do a slot search the hard way
var slow_val = this.$nextFreeDepartureSlot(station.name, idx, band_num);
if (slow_val !== -1) {
free = true;
depart = slow_val;
}
secondAttempt += 1;
} else {
// then try anywhere
band = [3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11];
}
tries = 0;
}
} while (free === false);
return depart;
}
//-------------------------------------------------------------------------------------------------------------
// checks a departure slot to see if it's free at a particular station
this.$checkDepartureSlot = function $checkDepartureSlot(stationName, stnIndex, departureTime) {
if (!this._systemDockingData[system.ID]) return false;
var dta = this._systemDockingData[system.ID][stationName + "_" + stnIndex];
if (dta && dta.length > 0) {
var i = dta.length;
while (i--) {
// if the time difference is less than 60 seconds...
if (dta[i].departureTime == departureTime) return false;
}
}
return true;
}
//-------------------------------------------------------------------------------------------------------------
// find a free departure slot in a particular time band the hard way
this.$nextFreeDepartureSlot = function $nextFreeDepartureSlot(stationName, stnIndex, band) {
function compareDeparture(a, b) {
if ((a.departureTime * 10 + a.departureSeconds) < (b.departureTime * 10 + b.departureSeconds)) return (worldScripts.StationDockControl_Interface._sortDir * -1);
if ((a.departureTime * 10 + a.departureSeconds) > (b.departureTime * 10 + b.departureSeconds)) return (worldScripts.StationDockControl_Interface._sortDir);
return 0;
}
if (this._debug) log(this.name, "Hit slow slot selection process on " + stationName + " (" + stnIndex + ") - band " + band);
if (!this._systemDockingData[system.ID]) {
log(this.name, "!ERROR - No docking data found for system " + system.ID);
return -1;
}
var rng = this._bandRng[band];
if (!rng) {
log(this.name, "!ERROR - No docking band data found for band " + band);
return -1
}
if (this._departureBands.length === 0) {
// during a std population (non-full)
var dta = this._systemDockingData[system.ID][stationName + "_" + stnIndex];
dta.sort(compareDeparture);
var point = startPoint;
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
var item = dta[i];
if (item.departureTime >= rng.low && item.departureTime < rng.high) {
if (item.departureTime != point) {
if (this._debug && this._logPopulatorType >= 2) log(this.name, "found slot " + point);
return point;
}
point += 1;
}
if (point > item.departureTime) break;
}
}
} else {
// during a full repop, when the _departureBands array is available
for (var i = rng.low; i <= rng.high; i++) {
if (this._departureBands[i] === 1) {
if (this._debug && this._logPopulatorType >= 2) log(this.name, "found quick slot " + i);
this._departureBands[i] = 0;
return i;
}
}
}
log(this.name, "Failed!")
return -1;
}
//-------------------------------------------------------------------------------------------------------------
this.$displayDetails = function $displayDetails(stationName, stnIndex) {
function compareDeparture(a, b) {
if ((a.departureTime * 10 + a.departureSeconds) < (b.departureTime * 10 + b.departureSeconds)) return (worldScripts.StationDockControl_Interface._sortDir * -1);
if ((a.departureTime * 10 + a.departureSeconds) > (b.departureTime * 10 + b.departureSeconds)) return (worldScripts.StationDockControl_Interface._sortDir);
return 0;
}
if (!this._systemDockingData[system.ID]) return;
var dta = this._systemDockingData[system.ID][stationName + "_" + stnIndex];
dta.sort(compareDeparture);
for (var i = 0; i < dta.length; i++) {
log(this.name, dta[i].departureTime + ": " + dta[i].shipName);
}
}
//-------------------------------------------------------------------------------------------------------------
// put all available slots into an array for easy checking
this.$createDepartureSlots = function $createDepartureSlots() {
// create 1 minute departure slots for the current station, up to 25 hours (buffer for potential time slot overruns)
this._departureBands.length = 0;
for (var i = 1; i <= 1500; i++) {
this._departureBands.push(1);
}
}
//-------------------------------------------------------------------------------------------------------------
// remove any slots already taken by currently docked ships
this.$removeExistingSlots = function $removeExistingSlots(station) {
// get list of ships at this station
var docklist = this.$getViewingList(station, true);
// for each item, work out the number of minutes till launch
for (var i = 0; i < docklist.length; i++) {
//var mins = parseInt(((docklist.departureTime * 60 + docklist.lastChange) - clock.adjustedSeconds) / 60);
var mins = docklist[i].departureTime;
this._departureBands[mins] = 0;
}
}
//-------------------------------------------------------------------------------------------------------------
// checks if the role exists in our array of ships
this.$roleExists = function $roleExists(role) {
return (this._randomShips[role] ? true : false);
}
//-------------------------------------------------------------------------------------------------------------
// returns a system ID of a system in a particular range
// borrowed from oolite-populator.js
this.$nearbySystem = function $nearbySystem(range) {
if (range !== 7) {
var poss = system.info.systemsInRange(range);
} else {
var poss = this._neighbours;
}
if (poss.length === 0) return system.ID;
return poss[Math.floor(Math.random() * poss.length)].systemID;
}
//-------------------------------------------------------------------------------------------------------------
// select a random ship type key (eg "noshaders_z_groovy_cobra1_india_npc") for a particular the role (eg "trader")
this.$getRandomShipKey_old = function $getRandomShipKey_old(role) {
var items = null;
var freq = null;
if (this._randomShips[role]) {
items = this._randomShips[role].shipKeys;
freq = this._randomShips[role].frequency;
}
if (!items) {
// should never happen...
return "";
}
var choice = -1;
do {
choice = this.$rand(items.length) - 1;
// will we keep this choice or reset it?
if (Math.random() > parseFloat(freq[choice])) choice = -1;
} while (choice === -1);
var result = items[choice];
return result;
}
// contributed by Day
this.$getRandomShipKey = function $getRandomShipKey(role) {
var items = null;
var freq = null;
if (this._randomShips[role]) {
items = this._randomShips[role].shipKeys;
freq = this._randomShips[role].frequency;
}
if (!items) {
// should never happen...
return "";
}
var choice = -1;
var that = $getRandomShipKey;
var random = (that.random = that.random || Math.random);
do {
choice = this.$rand(items.length) - 1;
// will we keep this choice or reset it?
if (random() > parseFloat(freq[choice])) choice = -1;
} while (choice === -1);
var result = items[choice];
return result;
}
//-------------------------------------------------------------------------------------------------------------
// runs the "getRandomShipKey" function, but also checks whether the ship will actually fit in the stations dock
this.$getRandomShipKeyWithDockCheck = function $getRandomShipKeyWithDockCheck(role, station) {
// checks if a ship will fit in the dock of a station, just ensuring we don't put a ship inside a station that could never have fitted
function checkShipCanDock(shipType, station, shipFits) {
var lookup = station.shipClassName + "|" + shipType;
if (shipFits[lookup] != undefined) return (shipFits[lookup] === 1 ? true : false);
return true;
}
var loopCount = 0;
var result = "";
var finished = false;
do {
result = $getRandomShipKey(role);
var shiptype = this._shipData[result].name;
if (checkShipCanDock(shiptype, station, this._shipFits) === true) finished = true;
loopCount += 1;
} while (finished === false && loopCount < 5);
return result;
}
//-------------------------------------------------------------------------------------------------------------
// use randomshipnames OXP to generate ship names (if installed) - otherwise just return a blank string (no name)
this.$getRandomShipName = function $getRandomShipName(role) {
var randomShipName = "";
if (this._rsnInstalled) {
if (player.ship) {
try {
if (Ship.roleIsInCategory(role, "oolite-bounty-hunter") || Ship.roleIsInCategory(role, "oolite-assassin"))
randomShipName = worldScripts["randomshipnames"].$randomHunterName(player.ship);
else if (Ship.roleIsInCategory(role, "oolite-pirate"))
randomShipName = worldScripts["randomshipnames"].$randomPirateName(player.ship);
// catch everything else as a trader
if (randomShipName === "")
randomShipName = worldScripts["randomshipnames"].$randomTraderName(player.ship);
} catch (err) {
log(this.name, "!!ERROR: Unable to get ship name from RSN: " + err);
}
}
}
if (randomShipName === undefined) randomShipName = "";
return randomShipName;
}
//-------------------------------------------------------------------------------------------------------------
// create list of escort ships to be included with parent ship
// escort data keys are accessed directly, not from our data array
this.$getEscortData = function $getEscortData(shipKey) {
var escorts = this._shipData[shipKey]["escorts"]; // number
var escort_role = this._shipData[shipKey]["escort_role"]; // eg "asp-pirate"
var escort_shipKey = this._shipData[shipKey]["escort_ship"]; // eg "cobra3-alternate"
var escort_roles = this._shipData[shipKey]["escort_roles"]; // dict
var escort_type = "";
var ships = [];
var count = 0;
if (escort_roles && escort_roles.length > 0 && escorts && parseInt(escorts) > 0) {
if (this._debug) log(this.name, "!!NOTE: Ship key '" + shipKey + "' has both types of escort definitions - new type will be used");
}
if ((!escort_roles || escort_roles.length === 0) && escorts && parseInt(escorts) > 0) {
if (this.$dockVersionExists(shipKey) === false) {
log(this.name, "!!NOTE: Escorts required for '" + shipKey + "' (Escorts: " + escorts + ") but no 'dock' version of the shipdata exists");
// in this case, don't try to create any - it will only conflict with the automatically generated ones. Just return
return;
}
// old style
// this will need adjusting for better matching of Oolite weighting
if (!escort_role || escort_role === "") escort_role = "escort";
var allowed = this.$roleIsAllowed(escort_role);
var i = this.$rand(parseInt(escorts));
while (i--) {
// is this role one of ours? then get a ship key from our array
if (allowed) {
escort_shipKey = this.$getRandomShipKey(escort_role);
} else {
// otherwise, this mothership has a custom escort role defined, so use that instead
var keys = Ship.keysForRole(escort_role);
escort_shipKey = keys[this.$rand(keys.length) - 1];
if (escort_shipKey === undefined) escort_shipKey = this.$getRandomShipKey(escort_role);
// check if we have this in the additional roles array
if (this.$indexInList(escort_role, this._additionalRoles) === -1) this._additionalRoles.push(escort_role);
}
ships.push({
role: (escort_role.indexOf("[") === 0 ? "escort" : escort_role),
shipKey: escort_shipKey
});
count += 1;
if (count === 16) break;
}
if (count > 0) {
if (this._debug && this._logPopulatorType > 2) log(this.name, "Processing role:" + escort_role + ", max:" + escorts + ", result:" + count);
}
} else if (escort_roles && escort_roles.length > 0) {
if (this.$dockVersionExists(shipKey) === false) {
log(this.name, "!!NOTE: Escorts required for '" + shipKey + "' (Types: " + escort_roles.length + ", first:" + escort_roles[0].role + ", max:" + escort_roles[0].max + ", min:" + escort_roles[0].min + ") but no 'dock' version of the shipdata exists");
// in this case, don't try to create any - it will only conflict with the automatically generated ones. Just return
return;
}
// new style
var i = escort_roles.length;
while (i--) {
var item = escort_roles[i];
var role = item.role;
var min = parseInt(item.min);
var max = parseInt(item.max);
// this will need adjusting for better matching of Oolite weighting
if (role !== "") {
var inc = this.$rand(max - min) + min;
if (this._debug && this._logPopulatorType > 2) log(this.name, "Processing role:" + role + ", min:" + min + ", max:" + max + ", result:" + inc);
if (inc > 0) {
var allowed = this.$roleIsAllowed(role);
var j = inc;
//for (var j = 0; j < inc; j++) {
while (j--) {
if (count < 16) {
// is this role one of ours? then get a ship key from our array
if (allowed) {
escort_shipKey = this.$getRandomShipKey(role);
} else {
// otherwise, this mothership has a custom escort role defined, so use that instead
if (role.indexOf("[") === 0 && role.indexOf("]") > 0) {
escort_shipKey = role.substring(1, role.length - 1);
} else {
var keys = Ship.keysForRole(role);
if (!keys) {
log(this.name, "!!ERROR: requested " + role + " for escorts, but no data keys found - no escorts created");
return;
}
escort_shipKey = keys[this.$rand(keys.length) - 1];
// check if we have this in the additional roles array
if (this.$indexInList(escort_role, this._additionalRoles) === -1) this._additionalRoles.push(escort_role);
}
}
// in the case where a specific ship has been specified in the role (eg "[sidewinder-escort]"), output "escort" instead of the actual role
ships.push({
role: (role.indexOf("[") === 0 ? "escort" : role),
shipKey: escort_shipKey
});
count += 1;
}
// max of 16 escorts
if (count === 16) break;
}
}
}
// max of 16 escorts
if (count === 16) break;
}
}
return ships;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if a "dock_shipkey" entry exists in the shipdata.plist file. Otherwise false
// used when launching/adding ships to the system to ensure that only 1 ship is created, without escorts
// escorts are created manually
this.$dockVersionExists = function $dockVersionExists(shipKey) {
var shipdata = Ship.shipDataForKey("dock_" + shipKey);
if (shipdata) {
return true;
} else {
return false;
}
}
//-------------------------------------------------------------------------------------------------------------
// sets up the weapon info for a particular ship key
this.$setWeapons = function $setWeapons(shpDataKey, level) {
var autoWeapons = true;
if (this._shipData[shpDataKey]) {
var def = this._shipData[shpDataKey]["auto_weapons"];
if (this.$indexInList(def, this._trueValues) === -1) autoWeapons = false;
}
if (autoWeapons === false) {
// default is not to change anything
return "";
}
var equip = "";
var choice = Math.floor(level);
if (level - Math.floor(level) > Math.random()) {
choice++;
}
if (choice <= 1) {
equip += "FORE:EQ_WEAPON_PULSE_LASER,";
} else if (choice === 2) {
equip += "FORE:EQ_WEAPON_BEAM_LASER,";
} else if (choice === 3) {
equip += "FORE:EQ_WEAPON_BEAM_LASER,";
equip += "AFT:EQ_WEAPON_BEAM_LASER,";
} else if (choice === 4) {
equip += "FORE:EQ_WEAPON_MILITARY_LASER,";
equip += "ALT:EQ_WEAPON_BEAM_LASER,";
} else if (choice >= 5) {
equip += "FORE:EQ_WEAPON_MILITARY_LASER,";
equip += "AFT:EQ_WEAPON_MILITARY_LASER,";
}
return equip;
}
//-------------------------------------------------------------------------------------------------------------
// sets up the accuracy of a particular ship key
this.$setAccuracy = function $setAccuracy(shpDataKey, skill) {
// set an initial accuracy value, based on the shipdata value
var init = this.$rand(10) - 5;
var autoWeapons = true;
if (this._shipData[shpDataKey]) {
if (this._shipData[shpDataKey]["accuracy"] != undefined) init = this._shipData[shpDataKey]["accuracy"];
var def = this._shipData[shpDataKey]["auto_weapons"];
if (this.$indexInList(def, this._trueValues) === -1) autoWeapons = false;
}
if (autoWeapons === true && init < 5 && skill !== 0) {
// shift skill towards end of accuracy range
var target = 4.99;
if (skill < 0) target = -5;
var acc = init;
for (var i = Math.abs(skill); i > 0; i--) {
acc += (target - acc) * Math.random();
}
return acc;
} else {
return init;
}
}
//-------------------------------------------------------------------------------------------------------------
// returns a destination system based on a primary role
this.$getDestinationByRole = function $getDestinationByRole(primaryRole, dockedStation) {
//-------------------------------------------------------------------------------------------------------------
// returns true if player has a role similar to roletype, otherwise false
// if abortType1 or abortType2 is not blank and found, will force an automatic false response
var $checkPlayerRoles = function (cycles, roleType, abortType1, abortType2) {
var result = false;
var prw = player.roleWeights;
for (var i = 1; i <= cycles; i++) {
var checkrole = prw[parseInt(Math.random() * prw.length)];
if (checkrole.indexOf(roleType) >= 0) result = true;
if (abortType1 !== "" && checkrole.indexOf(abortType1) >= 0) return false;
if (abortType2 !== "" && checkrole.indexOf(abortType2) >= 0) return false;
}
return result;
}
var wpop = worldScripts["oolite-populator"];
var destSystem = -1;
var destHidden = true;
var destLoc = "";
// the number in each of these calls basically says how hard do we look for the role. 1 = try once, 4 = try four times
var playerIsTrader = $checkPlayerRoles(1, "trader", "pirate", "");
var playerIsSmuggler = $checkPlayerRoles(2, "smuggler", "pirate", "");
var playerIsCourier = $checkPlayerRoles(1, "courier", "assassin", "pirate");
var playerIsHunter = $checkPlayerRoles(3, "hunter", "assassin", "pirate");
var playerIsPirate = $checkPlayerRoles(4, "pirate", "hunter", "");
var playerIsAssassin = $checkPlayerRoles(4, "assassin", "hunter", "");
switch (primaryRole) {
case "trader":
destSystem = wpop._weightedNearbyTradeSystem();
// if the player is a trader and not a pirate and the danger level of the destination system is high, they will probably want some company.
if ((playerIsTrader || (playerIsHunter && player.score >= 256)) && !playerIsPirate && ((4 - System.infoForSystem(galaxyNumber, destSystem).government) / 5) > Math.random())
destHidden = false;
// sometimes let an inexperienced player have some launch before/after slot
if (!playerIsHunter && !playerIsCourier && !playerIsSmuggler && !playerIsPirate && !playerIsAssassin && System.infoForSystem(galaxyNumber, destSystem).government > 5 && Math.random() > (0.8 + (player.score / 64)))
destHidden = false;
break;
case "trader-courier":
if (Math.random() < 0.5) {
destSystem = this.$nearbySystem(7);
} else {
destSystem = this.$nearbySystem(25);
}
// if the player is a courier and not a pirate or assassin and the danger level of the destination system is high, they will probably want some company.
if (((playerIsCourier && (player.passengerReputation + player.parcelReputation) >= 3) || (playerIsHunter && player.score >= 512)) &&
!playerIsPirate &&
!playerIsAssassin &&
((3 - System.infoForSystem(galaxyNumber, destSystem).government) / 5) > Math.random())
destHidden = false;
break;
case "trader-smuggler":
destSystem = this.$nearbySystem(7);
// if the player is a trader and not a pirate and the danger level of the destination system is high, they will probably want some company.
if ((playerIsTrader || playerIsSmuggler || (playerIsHunter && player.score >= 512)) && !playerIsPirate && ((3 - System.infoForSystem(galaxyNumber, destSystem).government) / 5) > Math.random())
destHidden = false;
break;
case "assassin-light":
case "assassin-medium":
case "assassin-heavy":
destSystem = system.ID;
/* Note: this routine doesn't work yet. Assassins default AI won't jump to a target system, and there is no guarantee they'll see the player as their target.
// will this assassin try to lure the player?
if ((playerIsHunter || playerIsCourier) && !playerIsAssassin && Math.random() > 0.1) {
destSystem = wpop._nearbySafeSystem(4);
log(this.name, "got here with " + destSystem + ", curr system " + system.ID);
if (destSystem !== system.ID) {
destHidden = false;
if (this._debug && this._logPopulatorType >= 1) log(this.name, "Assassin trap set - destination " + System.infoForSystem(galaxyNumber, destSystem).name);
}
}
*/
break;
case "hunter":
destSystem = system.ID;
break;
case "hunter-medium":
destSystem = wpop._nearbyDangerousSystem(4);
// if the player is a pirate and not a hunter and the danger level of the destination system is high, they will probably want some company.
if (playerIsHunter && !playerIsPirate && !playerIsAssassin && player.score >= 256 && ((4 - System.infoForSystem(galaxyNumber, destSystem).government) / 5) > Math.random())
destHidden = false;
break;
case "hunter-heavy":
destSystem = wpop._nearbyDangerousSystem(1);
// if the player is a pirate and not a hunter and the danger level of the destination system is high, they will probably want some company.
if (playerIsHunter && !playerIsPirate && !playerIsAssassin && player.score >= 512 && ((4 - System.infoForSystem(galaxyNumber, destSystem).government) / 5) > Math.random())
destHidden = false;
break;
case "shuttle":
destSystem = system.ID;
destLoc = this.$getRandomSystemLocation(dockedStation);
// if the player is a trader and not a pirate and the danger level of the destination system is high, they will probably want some company.
if ((playerIsTrader || playerIsCourier) && !playerIsPirate && ((3 - system.info.government) / 5) > Math.random())
destHidden = false;
break;
case "pirate":
destSystem = system.ID;
// if the player is a pirate and not a hunter and the danger level of the destination system is high, they will probably want some company.
if (playerIsPirate && !playerIsHunter && player.score >= 256 && ((4 - System.infoForSystem(galaxyNumber, destSystem).government) / 5) > Math.random())
destHidden = false;
break;
case "pirate-light-freighter":
case "pirate-medium-freighter":
case "pirate-heavy-freighter":
destSystem = wpop._nearbySafeSystem(system.info.government + 1);
if (playerIsPirate && !playerIsHunter && player.score >= 512 && ((4 - System.infoForSystem(galaxyNumber, destSystem).government) / 5) > Math.random()) destHidden = false;
break;
}
return {
destination: destSystem,
destinationHidden: destHidden,
location: destLoc
};
}
//-------------------------------------------------------------------------------------------------------------
// gets the station obbect from a given station name and index
this.$getStationFromName = function $getStationFromName(stationName, stationIndex) {
for (var i = 0; i < this._stationList.length; i++) {
var item = this._stationList[i];
if (item.name === stationName && item.index === stationIndex) return item.station;
}
return null;
}
//-------------------------------------------------------------------------------------------------------------
// returns the index of a particular station
this.$getStationIndex = function $getStationIndex(station) {
for (var i = 0; i < this._stationList.length; i++) {
if (this._stationList[i].station === station) return this._stationList[i].index;
}
return 0;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if station has a role in the list of roles for medium traffic stations
this.$checkStationRoleForMediumTraffic = function $checkStationRoleForMediumTraffic(station) {
if (station.roles) {
for (var i = 0; i < station.roles.length; i++) {
if (this.$indexInList(station.roles[i], this._trafficMedium) >= 0) return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if station has a role in the list of roles for low traffic stations
this.$checkStationRoleForLowTraffic = function $checkStationRoleForLowTraffic(station) {
if (station.roles) {
for (var i = 0; i < station.roles.length; i++) {
if (this.$indexInList(station.roles[i], this._trafficLow) >= 0) return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if station has a role in the list of roles for low traffic stations
this.$checkStationRoleForVeryLowTraffic = function $checkStationRoleForVeryLowTraffic(station) {
if (station.roles) {
for (var i = 0; i < station.roles.length; i++) {
if (this.$indexInList(station.roles[i], this._trafficVeryLow) >= 0) return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if station has a role in the list of roles for no traffic stations
this.$checkStationRoleForNoTraffic = function $checkStationRoleForNoTraffic(station) {
if (station.roles) {
for (var i = 0; i < station.roles.length; i++) {
if (this.$indexInList(station.roles[i], this._trafficNone) >= 0) return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// returns true if the station has a role in the list of roles for no shuttle traffic
this.$checkShuttleDestForNoTraffic = function $checkShuttleDestForNoTraffic(station) {
if (station.roles) {
for (var i = 0; i < station.roles.length; i++) {
if (this.$indexInList(station.roles[i], this._shuttleTrafficNone) >= 0) return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------------------
// sets up the heat insulation for a particular ship key
this.$setHeatInsulation = function $setHeatInsulation(shpDataKey, initial) {
var shipHeat = 1;
if (this._shipData[shpDataKey] && this._shipData[shpDataKey]["heat_insulation"]) shipHeat = parseInt(this._shipData[shpDataKey]["heat_insulation"]);
if (initial !== 0) shipHeat = initial;
return shipHeat;
}
//-------------------------------------------------------------------------------------------------------------
// calculates a random insurance amount for a new pilot
this.$calcInsurance = function $calcInsurance(bounty) {
var result = 0;
if (bounty === 0) {
if (Math.random() > 0.9) {
result = parseInt(Math.random() * 100) + 100;
} else if (Math.random() > 0.6) {
result = parseInt(Math.random() * 50) + 50;
} else {
result = parseInt(Math.random() * 20) + 10;
}
}
return result;
}
//-------------------------------------------------------------------------------------------------------------
// determines whether there is room to launch a certain number of ships
this.$launchAvailable = function $launchAvailable(station, ships) {
if (station) {
var subent = station.subEntities;
var space = 0;
var queued = 0;
if (subent) {
for (var i = 0; i < subent.length; i++) {
var ent = subent[i];
if (ent.isDock) {
space += 16 - ent.launchingQueueLength;
queued += ent.launchingQueueLength;
}
}
} else {
if (this._debug) log(this.name, "!NOTE: $launchAvailable -- subent is null (no subentities on station), could not find dock, returning false");
}
if (queued === 0 && space === 0 && this._debug) log(this.name, "!NOTE: $launchAvailable -- no queued ships but space is 0 - no docks found?");
return (space >= ships);
} else {
if (this._debug) log(this.name, "!ERROR: $launchAvailable -- station is null, defaulting to false");
return false;
}
}
//-------------------------------------------------------------------------------------------------------------
// returns an array of ships currently attempting to dock at the station
this.$getShipsDocking = function $getShipsDocking(station) {
function $isDocking(entity) {
return entity.isShip && entity.isValid && entity.isPiloted && !entity.isStation && entity.dockingInstructions && entity.dockingInstructions.station === station;
}
return system.filteredEntities(this, $isDocking, station, station.scannerRange);
}
//-------------------------------------------------------------------------------------------------------------
// forces a specific ship to dock at a specific station
this.$forceShipToDock = function $forceShipToDock(station, ship) {
var died = false;
// if this is an individual ship, or not the leader of a group/escort, then decide if they "make it"
// note: this code should never get used, as we aren't forcing docked ships that aren't trying to dock anymore
if (ship.position.distanceTo(station) > (station.scannerRange * 1.4)) {
var chance = (system.government + 1) / 8;
// three strike policy
if (Math.random() > chance && Math.random() > chance && Math.random() > chance) died = true;
if (this._debug && this._logDockingType === 2 && died) log(this.name, "Ship reckoned as destroyed: " + ship);
}
// dock the ship
if (died === false) {
if (this._debug && this._logDockingType === 2) log(this.name, "Forcing ship to dock..." + ship);
this.$logShipDocking(station, ship);
}
// remove the ship from the system
ship.remove(false);
}
//-------------------------------------------------------------------------------------------------------------
// controller to look after the force-dock of ships in a given time lapse
this.$forceDockController = function $forceDockController(timeLapse) {
var stns = system.stations;
// any ships currently trying to dock? if the time lapse is 10 minutes or greater, dock them
if (timeLapse >= 10) {
stns.forEach(function (station) {
var docking = this.$getShipsDocking(station);
if (docking && docking.length > 0) {
for (var i = 0; i < docking.length; i++) {
var dock = docking[i];
if (dock.isValid && dock.isStation === false && dock.isPlayer === false) {
var group = [];
var escort = [];
if (dock.group) {
for (var j = 0; j < dock.group.ships.length; j++) {
group.push(dock.group.ships[j]);
}
}
if (dock.escortGroup) {
for (var j = 0; j < dock.escortGroup.ships.length; j++) {
escort.push(dock.escortGroup.ships[j]);
}
}
this.$forceShipToDock(station, dock);
// dock all his mates as well
if (group.length > 0) {
for (var j = 0; j < group.length; j++) {
var grp = group[j];
if (grp.isValid && grp.isStation === false) {
this.$forceShipToDock(station, grp);
}
}
}
if (escort.length > 0) {
for (var j = 0; j < escort.length; j++) {
var esc = escort[j];
if (esc.isValid && esc.isStation === false) {
this.$forceShipToDock(station, esc);
}
}
}
} // if docking is valid
} // for i (loop on docking ships)
} // if docking and length > 0
docking.length = 0;
}, this); // stns.foreach
} // if timelapse
}
//-------------------------------------------------------------------------------------------------------------
// these docking bay number functions are only used when docking a single ship
// the checking function is too slow when running in a full population routine
//-------------------------------------------------------------------------------------------------------------
// gets a new docking back code
this.$getDockingBayCode = function $getDockingBayCode(station, stationIndex) {
if (!this._systemDockingData[system.ID]) return "";
var stnmax = parseInt(station.mass / 800000);
var baynum = "";
var exclude = 0;
// if this is the players docked station, pull up the docking bay code allocated to the player
if (player.ship.docked) {
if (player.ship.dockedStation === station) exclude = worldScripts.StationDockControl_Interface._playerDockingBay
}
do {
// get a random docking bay code that isn't the players
do {
baynum = this.$rand(stnmax);
} while (exclude > 0 && baynum === exclude);
// check to make sure this bay is clear of any other ships
} while (this.$checkForClearBay(baynum, station, stationIndex) === false);
return baynum;
}
//-------------------------------------------------------------------------------------------------------------
// check if this docking bay number has been used at this station
this.$checkForClearBay = function $checkForClearBay(baynum, station, stationIndex) {
var dta = this._systemDockingData[system.ID][station.name + "_" + stationIndex];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
if (dta[i].dockingBay === baynum) return false;
}
}
return true;
}
//-------------------------------------------------------------------------------------------------------------
// these docking bay code functions are used in isolation, after the main populator has added all the docked ships
// this is to prevent timeout issues during the main populator routine.
//-------------------------------------------------------------------------------------------------------------
// add missing docking bay codes to docked ships
this.$addBayCodes = function $addBayCodes(station) {
if (this._debug && this._logPopulatorType > 0) log(this.name, "Adding docking bay codes");
var idx = this.$getStationIndex(station);
// check station has traffic and for which we will be possibly showing docked ship info...
if (station.hasNPCTraffic && worldScripts.StationDockControl_Interface.$checkStationRoleForView(station)) {
// create the list of available docking bay codes for this station
if (this._debug && this._logPopulatorType >= 2) log(this.name, "Prepopulating docking bay codes for " + station.name + " (index " + idx + ")");
this.$populateStationBayCodes(station);
// add a docking bay code to each vessel
if (this._debug && this._logPopulatorType >= 2) log(this.name, "Applying docking bay codes to data");
this.$applyCodesToStation(station, idx);
}
if (this._debug && this._logPopulatorType >= 2) log(this.name, "Adding docking bay codes complete");
}
//-------------------------------------------------------------------------------------------------------------
// adds docking bay codes to station data in bulk
this.$applyCodesToStation = function $applyCodesToStation(station, stnIndex) {
var bay = 0;
if (!this._systemDockingData[system.ID]) return;
var dta = this._systemDockingData[system.ID][station.name + "_" + stnIndex];
if (dta && dta.length > 0) {
for (var j = 0; j < dta.length; j++) {
var item = dta[j];
if (item.dockingBay === "") {
var found = false;
do {
bay += 1;
if (this._stationBayCodes[bay] !== "") {
item.dockingBay = this._stationBayCodes[bay];
found = true;
}
} while (found === false);
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
// we're doing this for performance reasons - it's faster to have all the codes pre-entered, and then cross them off
// than to check each one every time
this.$populateStationBayCodes = function $populateStationBayCodes(station) {
var stnmax = parseInt(station.mass / 800000);
this._stationBayCodes = new Array(stnmax);
for (var i = 0; i < stnmax; i++) {
this._stationBayCodes[i] = i + 1;
}
}
//-------------------------------------------------------------------------------------------------------------
// updates the _docking array with the list of currently docking ships
this.$updateShipsDocking = function $updateShipsDocking(station) {
function compare(a, b) {
if (a.shipClassName < b.shipClassName) return -1;
if (a.shipClassName > b.shipClassName) return 1;
return 0;
}
this._docking.length = 0;
this._docking = this.$getShipsDocking(station);
// we going to sort the list by classname, as we don't know (and can't know) the order ships will dock,
// so sorting by class is as good a sort as any
this._docking.sort(compare);
}
//-------------------------------------------------------------------------------------------------------------
// occasionally the shipUniqueName returns "" even though it's really there. In that instance, pull the unique name out of the display name
this.$getShipUniqueName = function $getShipUniqueName(displayName, shipClassName) {
return displayName.replace(shipClassName + ": ", "");
}
//-------------------------------------------------------------------------------------------------------------
// checks that the primary role of a ship is in our list of controlled roles.
this.$roleIsAllowed = function $roleIsAllowed(primaryRole) {
if (this._controlledRoles.indexOf(primaryRole) !== -1) return true;
return false;
}
//-------------------------------------------------------------------------------------------------------------
// checks the additional array for any extra roles we might have come across
this.$roleIsAllowedAdditional = function $roleIsAllowedAdditional(primaryRole) {
if (this._additionalRoles.indexOf(primaryRole) !== -1) return true;
return false;
}
//-------------------------------------------------------------------------------------------------------------
// assesses the current state of the station dock. Logic is:
// If 1 or more ships are trying to dock, and there's either more than 4 ships in the launch queue already, or theres more than 4 ships trying to dock,
// and there are ships within 3 minutes of departure,
// bump all station traffic back 90 seconds.
this.$assessStationDocks = function $assessStationDocks() {
if (this._stationIndexesLoaded === false) return; // no point doing the assessment if the indexes aren't loaded
if (this._debug && this._logLaunchType > 0) log(this.name, "Assessing station docks");
if (this._systemDockingData[system.ID] == null) {
log(this.name, "!!NOTE: No data for system found! Unable to assess docks. Resetting data entry for system.");
this._systemDockingData[system.ID] = {};
//return;
}
var stns = system.stations;
stns.forEach(function (station) {
if (station.hasNPCTraffic && this.$checkStationRoleForNoTraffic(station) === false) {
var docking = 0;
var queue = 0;
// how many ships are already in the launch queue?
for (var i = 0; i < station.subEntities.length; i++) {
var subent = station.subEntities[i];
if (subent.isDock) {
docking += subent.dockingQueueLength;
queue += subent.launchingQueueLength;
}
}
// if there's more than 4, check the number of ships about to launch
if (this._debug && this._logDockingType >= 1) log(this.name, station.name + " inbound length: " + docking);
if (this._debug && this._logLaunchType >= 2) log(this.name, station.name + " launch queue length: " + queue);
if (docking >= 1 && (queue >= 4 || docking > 4)) {
var idx = this.$getStationIndex(station);
var dta = this._systemDockingData[system.ID][station.name + "_" + idx];
var found = false;
if (dta && dta.length > 0) {
// count ships less then 3 minutes from departure
for (var i = 0; i < dta.length; i++) {
//if (dta[i].departureTime - clock.adjustedSeconds <= 180) {
if (dta[i].departureTime <= 3) {
found = true;
break;
}
}
}
if (!dta && this._lateStations.indexOf(station) === -1) {
// no data? add this station to the late station list so it can be repopulated
this._lateStations.push(station);
//log(this.name, "adding last station " + station);
}
if (this._debug && this._logLaunchType >= 2) log(this.name, station.name + "(" + idx + ") pending launch found: " + found);
if (found) this.$bumpStationTraffic(station, idx);
} // if docking >= 1 && queue >= 4
} // if station has NPC traffic...
}, this);
}
//-------------------------------------------------------------------------------------------------------------
// adds 90 seconds to all the ships at a particular station
this.$bumpStationTraffic = function $bumpStationTraffic(station, stnIndex) {
if (this._debug && this._logLaunchType > 0) log(this.name, "** Bumping traffic at " + station.name + "(" + stnIndex + ") **");
var dta = this._systemDockingData[system.ID][station.name + "_" + stnIndex];
if (dta && dta.length > 0) {
for (var i = 0; i < dta.length; i++) {
var item = dta[i];
if (item.departureTime > 0) {
item.departureTime += 2;
item.lastChange = clock.adjustedSeconds;
}
}
}
}
//-------------------------------------------------------------------------------------------------------------
// writes the Oolite populator outgoing factors to the log
this.$logOolitePopulatorOutgoingFactors = function $logOolitePopulatorOutgoingFactors() {
var w = worldScripts["oolite-populator"];
log(this.name, "Populator outgoing frequency values for " + system.name + " (" + system.ID + "):");
log(this.name, "trader-freighters: " + w.$repopulatorFrequencyOutgoing.traderFreighters);
log(this.name, "trader-couriers: " + w.$repopulatorFrequencyOutgoing.traderCouriers);
log(this.name, "trader-smugglers: " + w.$repopulatorFrequencyOutgoing.traderSmugglers);
log(this.name, "assassin: " + w.$repopulatorFrequencyOutgoing.assassins);
log(this.name, "shuttle: " + (0.005 * system.info.techlevel));
log(this.name, "pirate-ind: " + w.$repopulatorFrequencyOutgoing.pirateIndependents);
log(this.name, "pirate-light: " + w.$repopulatorFrequencyOutgoing.pirateLightPacks);
log(this.name, "pirate-medium: " + w.$repopulatorFrequencyOutgoing.pirateMediumPacks);
log(this.name, "pirate-heavy: " + w.$repopulatorFrequencyOutgoing.pirateHeavyPacks);
log(this.name, "hunter-light: " + w.$repopulatorFrequencyOutgoing.hunterLightPacks);
log(this.name, "hunter-medium: " + w.$repopulatorFrequencyOutgoing.hunterMediumPacks);
log(this.name, "hunter-heavy: " + w.$repopulatorFrequencyOutgoing.hunterHeavyPacks);
}
//-------------------------------------------------------------------------------------------------------------
// writes the details of the selected record to the log
this.$writeDataToLog = function $writeDataToLog(record, reason) {
if (typeof record === "undefined") return;
if (reason.indexOf("Docking ship") >= 0) {
if (this._logDockingType === 0) return;
}
if (reason.indexOf("Launching ship") >= 0) {
if (this._logLaunchType === 0) return;
}
log(this.name, "=========================================================");
log(this.name, reason + ":");
log(this.name, "---------------------------------------------------------");
log(this.name, "System: " + record.system);
log(this.name, "Station: " + record.station);
log(this.name, "Station index: " + record.stationIndex);
log(this.name, "Ship name: " + record.shipName);
log(this.name, "Ship type: " + record.shipType);
log(this.name, "Ship DataKey: " + record.shipDataKey);
log(this.name, "Ship personality: " + record.personality);
log(this.name, "Primary role: " + record.primaryRole);
if (reason.indexOf("Docking ship") >= 0) {
if (this._logDockingType === 1) return;
}
if (reason.indexOf("Launching ship") >= 0) {
if (this._logLaunchType === 1) return;
}
log(this.name, "AI Name: " + record.shipAI);
log(this.name, "Accuracy: " + record.accuracy);
log(this.name, "Equipment: " + record.equipment);
log(this.name, "Heat insulation: " + record.heatInsulation);
log(this.name, "Home system: " + record.homeSystem);
log(this.name, "Bounty: " + record.bounty);
log(this.name, "Escort name: " + record.escortName);
log(this.name, "Is escort leader: " + record.escortLeader);
log(this.name, "Group name: " + record.groupName);
log(this.name, "Is group leader: " + record.groupLeader);
log(this.name, "Destination: " + record.destinationSystem);
log(this.name, "Intra-system dest: " + (record.destination ? record.destination : ""));
log(this.name, "Destination hidden: " + record.destinationHidden);
log(this.name, "Goods: " + record.goods);
log(this.name, "Dock time: " + record.dockTime);
log(this.name, "Docking bay: " + record.dockingBay);
log(this.name, "Departure time: " + record.departureTime);
log(this.name, "Departure seconds: " + record.departureSeconds);
log(this.name, "Pilot name: " + record.pilotName);
log(this.name, "Pilot description: " + record.pilotDescription);
log(this.name, "Pilot home system: " + record.pilotHomeSystem);
log(this.name, "Pilot species: " + record.pilotSpecies);
log(this.name, "Pilot legal status: " + record.pilotLegalStatus);
log(this.name, "Pilot insurance: " + record.pilotInsurance);
log(this.name, "Last change: " + record.lastChange);
}
//-------------------------------------------------------------------------------------------------------------
// writes a short version of the data record to the log
this.$writeShortDataToLog = function $writeShortDataToLog(record) {
log(this.name, "Ship details: " + record.shipType + (record.shipName !== "" ? ": " + record.shipName : "") + " (" + record.shipDataKey + ") -- " + record.primaryRole);
}
//-------------------------------------------------------------------------------------------------------------
// get a random station from one of the ones in the system, excluding the origin station
this.$getRandomSystemLocation = function $getRandomSystemLocation(originStation) {
// pick the planet as the destination
if (Math.random() < 0.2) return "Planet|0";
if (this._shuttleDest == null) this._shuttleDest = this.$getShuttleDestinations();
var sel = -1;
var max_dist = 0;
var min_dist = -1;
var max_id = -1;
var min_id = -1;
var dist = 0;
// only do the distance checks if there are more than two stations in system
if (this._shuttleDest.length > 2) {
for (var i = 0; i < this._shuttleDest.length; i++) {
var dest = this._shuttleDest[i];
if (dest.position) {
dist = originStation.position.distanceTo(dest);
if (dist > max_dist) {
max_dist = dist;
max_id = i;
}
if ((min_dist !== -1 && dist < min_dist) || min_dist === -1) {
min_dist = dist;
min_id = i;
}
}
}
}
var count = 0;
do {
sel = this.$rand(this._shuttleDest.length) - 1;
if (this._shuttleDest[sel] === originStation) sel = -1;
// if this is the most distance place in the region, make it less likely as a destination
if (sel >= 0 && sel === max_id && Math.random() < 0.9) sel = -1;
// if it's not the closest or the furthest, reduce the chance, to increase the chance that the closest stations get more traffic
if (sel >= 0 && sel !== min_id && Math.random() < 0.4) sel = -1;
// keep track over home many times we go through this loop
count += 1;
// if we hit the limit, default to the planet
if (count >= 10) return "Planet|0";
} while (sel === -1);
var idx = this.$getStationIndex(this._shuttleDest[sel]);
return this._shuttleDest[sel].name + "|" + idx;
}
//-------------------------------------------------------------------------------------------------------------
// convert a "station|index" value into the actual station location
this.$convertSystemLocationToPosition = function $convertSystemLocationToPosition(location) {
var items = location.split("|");
var stnName = items(0);
var idx = parseInt(items(1));
if (stnName !== "Planet") {
dest = this.$getStationFromName(shpName, idx);
if (dest) {
return dest.position;
} else {
// station mustn't exist anymore -- return null
return null;
}
} else {
// return the planetary coordinates
return null;
}
}
//-------------------------------------------------------------------------------------------------------------
// compiles list of stations we can send shuttles to
this.$getShuttleDestinations = function $getShuttleDestinations() {
var stns = system.stations;
var dest = [];
// only add stations not in the "trafficNone" list and aren't police ships
for (var i = 0; i < stns.length; i++) {
var stn = stns[i];
if (this.$checkShuttleDestForNoTraffic(stn) === false && !stn.isPolice) {
dest.push(stn);
}
}
return dest;
}
//-------------------------------------------------------------------------------------------------------------
// reduces the size of an array by removing duplicates
this.$compactArray = function $compactArray(array) {
// get a distinct list of items
var items = [];
for (var i = 0; i < array.length; i++) {
var item = array[i];
if (item != null && item !== "" && typeof item != "undefined") {
if (items.indexOf(item) === -1) items.push(item);
}
}
// return the new array
return items;
}
//-------------------------------------------------------------------------------------------------------------
// removes empty data elements from the main dataset
this.$dataCleanup = function $dataCleanup() {
// look for empty data for a system, but only in small batches so we don't cause any slowdowns
for (var i = this._cleanUp; i < (this._cleanUp + 5); i++) {
if (i >= 256) {
if (this._debug) log(this.name, "cleanup complete!");
this._cleanUpComplete = true;
break;
}
if (i !== system.ID && this._systemDockingData[i]) {
var dta = JSON.stringify(this._systemDockingData[i]);
// if there's no mention of "system", there is no docking data for that system, so delete the entire entry
if (dta && dta.indexOf("system:") === -1) {
if (this._debug) log(this.name, "cleaning up system " + i);
delete this._systemDockingData[i];
}
}
}
if (this._cleanUpComplete === false) this._cleanUp += 5;
}
//-------------------------------------------------------------------------------------------------------------
// removes all but the data for systems 1 and 2
this.$dataPurge = function $dataPurge(id1, id2) {
var keys = Object.keys(this._systemDockingData);
for (var i = 0; i < keys.length; i++) {
if (parseInt(keys[i]) != id1 && parseInt(keys[i]) != id2) {
delete this._systemDockingData[keys[i]];
}
}
}
//-------------------------------------------------------------------------------------------------------------
// by cag for speed improvements instead of using "indexOf"
this.$indexInList = function $indexInList(item, list) { // for arrays only
var k = list.length;
while (k--) {
if (list[k] === item) return k;
}
return -1;
}
}).call(this); |