Documentation
readme.txt
External Dock System
by phkb
Overview
========
This mod provides a system that allows external docking ports to be added to dockable stations. Ideally, the station model and textures will already have a place where an external dock has been included. This OXP allows for that external dock to be used by players.
Setup
=====
In order to give a station an external dock, there are certain pre-requisites for the station itself:
1. It should have clearly marked docking bays. While the external ports can be attached anywhere, it should be clear for a pilot approaching the station that there are external docking bays present.
2. The station must have at least one normal docking port. You cannot have a station that only has external ports.
3. It must actually be a station that can carry ships.
4. The station, and by extension, the external docking port, should not move. Docking on an external platform that is moving will be difficult for the player. While the system will handle a moving station, the player may not.
To attach an external port to a station, you should do the following, generally at the same time your station is spawned.
var eds = worldScripts.ExternalDockSystem;
eds.$addExternalDock({
station:my_station,
position:[2400, 0, 0],
preDockCallback:this.$preDockSetup.bind(this),
postDockCallback:this.$postDockSetup.bind(this)
});
The structure of the object passed to the function call is as follows:
station:ship, // The station to which you are attaching an external port
position:[x,y,z], // The x, y and z coordinates for the docking port, relative to the centre of your station
realPosition:pos, // The real in-game position for the docking port, independent to the station
// Note: you must provide either the position or realPosition
scale:1, // To change the size of the docking port (1 = no change; 0.5 = half size; 2 = double size; etc). Default is 1.
allowLaunch:true, // Indicates whether ships that dock at the external port should launch through it as well. Default is false.
dockRange:150, // Indicates how far away from the dock point the player must reach before docking commences. Default is 150 (meters).
launchDistanceBoost:1000, // Indicates how far further forward to position the player from the dock when launching. Default is 0 (meters).
launchSubEntityIndex:5 // If launches will be conducted through the external port, this specifies the index of the subentity that will be used to work out
// the orientation the player ship will have on launch
preDockCallback, // The function to call just before the player is docked at the station (and prior to all docking-related world events).
// The station object is passed as a property.
postDockCallback // The function to call just after the player is docked at the station (after shipWillDockWithStation but before shipDockedWithStation).
// The station object is passed as a property.
You should notify the player about the availability of the external docks when they are in range of your station. If you have different rules for external docking, you should also let the player know beforehand.
Notes
-----
"scale": By default, EDS will notify the player of excess speed when they are within 2000m of the dock port. If that distance is too great, such that it would conflict with other functions of the station, changing the size property will scale the distance as required. So, a value of 0.5 would reduce the distance of the point to 1000m.
"launchSubEntityIndex": When the player is launched through a normal dock, their ship is aligned to the orientation of the dock. If a launch is to take place through an external port, a similar subentity must be chosen to allow the player's ship to have the correct alignment, otherwise they may launch in the wrong direction and crash into the station.
Gameplay
========
For a station with an external dock, you approach the dock head on, pointing your ship directly at the green flasher. As you get close reduce your speed to less than 100m/s, otherwise you will crash into the station instead of docking. The EDS will give you a warning if you are above 100m/s when approaching a dock.
When you have completed your business at the station, when you launch, it will be from the same docking port you docked at. You will be facing away from the dock as you launch.
When you save your game and reload it, you should launch from the external docking port you docked at, if the external port allows launches.
License
=======
This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 4.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
Version History
===============
1.8
- Fixes JS error when player ship is destroyed.
- Corrected info link in manifest file.
1.7
- Removed debug message.
- Tightened the deviation angle when aligning ship with the visual effect.
1.6
- Reverted 1.5 tweaks as they didn't help. :(
1.5
- Tweaks to ensure 100% reliability when docking at an external point.
1.4
- Reduce speed message will now only be displayed if player ship is pointing at dock point.
- Warning message about not having docking clearance will now be sent when on approach to an external docking point.
- Added "realPosition" as option for creating external docks.
- Added "dockRange" to allow for docking at a greater distance from the dock point.
- Added "launchDistanceBoost" to allow for a greater launch distance.
- Call to $addExternalDock now returns the visual entity created by the routine.
- Saving and reloading your game will now restore your external dock position, allowing you to launch from that dock.
1.3
- Fixed issue where the internal array of docks was not being reset correctly when jumping to a new system.
- Added protection for when a station with external docks is destroyed.
1.2
- Reworked the interface code to use an object, rather than individual parameters.
- Reworked the code to correctly orient the player ship on launch when using an external dock.
- Added ability to easily prevent launches from the external port.
1.1
- Checking more docking status types when docking to determine if the player has actually requested to dock.
- Changed flashers to be a single invisible one, leaving it up to the station how they want to alert players to external docks.
- Moving stations can now be handled, but it is still recommended that only non-moving stations have external docks.
1.0
- Initial release.
Path |
Config/script.js |
"use strict";
this.name = "ExternalDockSystem";
this.author = "phkb";
this.description = "System for add external docks to stations";
this.copyright = "2024 phkb";
this.license = "CC BY-NC-SA 4.0";
this._fcb = null;
this._externalDocks = [];
this._checked = false;
//----------------------------------------------------------------------------------------
this.startUpComplete = function() {
if (missionVariables.ExternalDockSystem_Docked) {
var key = missionVariables.ExternalDockSystem_Docked;
var pos = missionVariables.ExternalDockSystem_Position;
var realPos = missionVariables.ExternalDockSystem_RealPosition;
var ed = this._externalDocks;
for (var i = 0; i < ed.length; i++) {
if (ed[i].station.dataKey == key && player.ship.dockedStation.dataKey == key && (!ed[i].relativePos || JSON.stringify(ed[i].relativePos) == pos) && (ed[i].relativePos || !ed[i].realPos || JSON.stringify(ed[i].realPos) == realPos)) {
player.ship.dockedStation._dockedViaED = ed[i];
break;
}
}
delete missionVariables.ExternalDockSystem_Docked;
delete missionVariables.ExternalDockSystem_Position;
delete missionVariables.ExternalDockSystem_RealPosition;
}
}
//----------------------------------------------------------------------------------------
this.playerWillSaveGame = function() {
var p = player.ship;
var stn = p.dockedStation;
if (stn.hasOwnProperty("_dockedViaED") && stn._dockedViaED.allowLaunch) {
missionVariables.ExternalDockSystem_Docked = stn.dataKey;
missionVariables.ExternalDockSystem_Position = JSON.stringify(stn._dockedViaED.relativePos);
missionVariables.ExternalDockSystem_RealPosition = JSON.stringify(stn._dockedViaED.realPos);
} else {
delete missionVariables.ExternalDockSystem_Docked;
delete missionVariables.ExternalDockSystem_Position;
delete missionVariables.ExternalDockSystem_RealPosition;
}
}
//----------------------------------------------------------------------------------------
this.shipWillEnterWitchspace = function() {
this.$stopFCB();
this._externalDocks.length = 0;
}
//----------------------------------------------------------------------------------------
this.shipWillDockWithStation = this.shipDied = function() {
this.$stopFCB();
}
//----------------------------------------------------------------------------------------
this.shipExitedWitchspace = function() {
if (this._externalDocks.length > 0) {
for (var i = 0; i < this._externalDocks.length; i++) {
this._externalDocks[i].externalDock._speedWarning = false;
this._externalDocks[i].externalDock._clearanceWarning = false;
}
this.$startFCB();
}
}
//----------------------------------------------------------------------------------------
this.shipWillLaunchFromStation = function(station) {
var p = player.ship;
if (station.hasOwnProperty("_dockedViaED")) {
var ed = station._dockedViaED;
var dve = ed.externalDock;
var subIndex = ed.launchSubEntityIndex;
dve._speedWarning = true;
dve._clearanceWarning = false;
p.position = dve.position.add(station.orientation.multiply(station.subEntities[subIndex].orientation).vectorForward().multiply(p.boundingBox.z + 50 + ed.launchDistanceBoost));
p.orientation = station.orientation.multiply(station.subEntities[subIndex].orientation);
p.velocity = p.vectorForward.multiply(p.speed);
delete station._dockedViaED;
}
if (this._externalDocks.length > 0) this.$startFCB();
}
//----------------------------------------------------------------------------------------
this.$addExternalDock = function(obj) {
if (!obj.station.isStation) throw "Invalid settings: target entity is not a station.";
if (obj.hasOwnProperty("position")) {
if (!Array.isArray(obj.position)) throw "Invalid settings: position property is not an array";
if (obj.position.length != 3) throw "Invalid settings: position property has incorrect number of elements";
}
if (obj.hasOwnProperty("realPosition")) {
if (!obj.realPosition.hasOwnProperty("x") || !obj.realPosition.hasOwnProperty("y") || !obj.realPosition.hasOwnProperty("z")) throw "Invalid settings: realPosition property is not an valid Vector3D";
}
if (!obj.hasOwnProperty("position") && !obj.hasOwnProperty("realPosition")) throw "Invalid settings: position or realPosition property missing";
if (obj.scale && isNaN(obj.scale)) throw "Invalid settings: scale property is no a valid number";
//stn, position, scale, allowLaunch, launch_orientation, predock_callback, postdock_callback
if (obj.hasOwnProperty("allowLaunch") && obj.allowLaunch == true && (!obj.launchSubEntityIndex || isNaN(obj.launchSubEntityIndex))) "Invalid settings: launch subEntity index missing or invalid";
if (!obj.hasOwnProperty("allowLaunch")) {obj.allowLaunch = false; obj.launchSubEntityIndex = -1;}
if (obj.position) {
var dve = system.addVisualEffect("external_dock_ve_blank",
obj.station.position.add(obj.station.vectorRight.multiply(obj.position[0])).add(obj.station.vectorUp.multiply(obj.position[1])).add(obj.station.vectorForward.multiply(obj.position[2])));
}
if (obj.realPosition) {
var dve = system.addVisualEffect("external_dock_ve_blank", obj.realPosition);
}
if (!obj.scale || obj.scale == 0) obj.scale = 1;
if (obj.scale && obj.scale != 1) dve.scale(obj.scale);
obj.station._savedPosition = obj.station.position;
obj.station._savedOrientation = obj.station.orientation;
obj.station._isMoving = false; // we'll change this to true if the position or orientation changes.
this._externalDocks.push({
station:obj.station,
externalDock:dve,
relativePos:(obj.hasOwnProperty("position") ? obj.position : null),
realPos:(obj.hasOwnProperty("realPosition") ? obj.realPosition : null),
scale:obj.scale,
dockRange:(obj.hasOwnProperty("dockRange") && !isNaN(obj.dockRange) ? obj.dockRange : 150),
allowLaunch:obj.allowLaunch,
launchDistanceBoost:(obj.hasOwnProperty("launchDistanceBoost") && !isNaN(obj.launchDistanceBoost) ? obj.launchDistanceBoost : 0),
launchSubEntityIndex:obj.launchSubEntityIndex,
preDockCallback:obj.preDockCallback,
postDockCallback:obj.postDockCallback
});
return dve;
}
//----------------------------------------------------------------------------------------
this.$startFCB = function() {
if (!this._fcb || !isValidFrameCallback(this._fcb)) {
this._fcb = addFrameCallback(this.$checkExternalDocks);
}
}
//----------------------------------------------------------------------------------------
this.$stopFCB = function() {
if (this._fcb && isValidFrameCallback(this._fcb)) removeFrameCallback(this._fcb);
this._fcb = null;
}
//----------------------------------------------------------------------------------------
this.$checkExternalDocks = function _checkExternalDocks(delta) {
// inline function to reposition docks on stations if they move
function reposition(obj) {
//log("external dock testing", "we are repositioning the dock point because the station (" + obj.station + ") has moved");
var dve = obj.externalDock;
var stn = obj.station;
var pos = obj.relativePos;
if (pos) {
dve.position = stn.position.add(stn.vectorRight.multiply(pos[0])).add(stn.vectorUp.multiply(pos[1])).add(stn.vectorForward.multiply(pos[2]));
} else {
dve.position = obj.realPos;
}
stn._savedPosition = stn.position;
stn._savedOrientation = stn.orientation;
}
var that = _checkExternalDocks;
var _p = (that._p = that._p || player.ship);
var _ed = (that._ed = that._ed || worldScripts.ExternalDockSystem._externalDocks);
var i = _ed.length;
while (i--) {
if (!_p || !_p.isValid) break;
var ed = _ed[i].externalDock;
var stn = _ed[i].station;
var scale = _ed[i].scale;
var dockRange = _ed[i].dockRange;
if (!stn.isValid) continue; // something has happened to the station - just continue the loop
// check to see if station has moved
// if so, perform function to adjust location of dock flashers
if (stn._isMoving || (stn.position.angleTo(stn._savedPosition) != 0 || stn.orientation.dot(stn._savedOrientation) != 1)) {
stn._isMoving = true;
reposition(_ed[i]);
}
// check player distance to dock
var dist = ed.position.distanceTo(_p);
var deviation = _p.vectorForward.angleTo(ed.position.subtract(_p.position));
if (dist > (2800 * scale) && ed._speedWarning) ed._speedWarning = false;
if (dist > (6500 * scale) && ed._clearanceWarning) ed._clearanceWarning = false;
if (dist <= (2000 * scale) && _p.speed < 100 && ed._speedWarning) ed._speedWarning = false;
if (dist <= (2000 * scale) && _p.speed >= 100 && !ed._speedWarning && deviation < 0.03) {
ed._speedWarning = true;
stn.commsMessage("Reduce speed to less than 100m/s for docking.", _p);
}
if (dist <= (5000 * scale) && !ed._clearanceWarning && deviation < 0.03 && stn.requiresDockingClearance && (player.dockingClearanceStatus != "DOCKING_CLEARANCE_STATUS_GRANTED" && player.dockingClearanceStatus != "DOCKING_CLEARANCE_STATUS_REQUESTED" && player.dockingClearanceStatus != "DOCKING_CLEARANCE_STATUS_TIMING_OUT")) {
ed._clearanceWarning = true;
stn.commsMessage(expandDescription("[oolite-station-docking-requires-clearance]"), _p);
}
if (dist <= (5000 * scale) && ed._clearanceWarning && deviation < 0.03 && stn.requiresDockingClearance && (player.dockingClearanceStatus == "DOCKING_CLEARANCE_STATUS_GRANTED" || player.dockingClearanceStatus == "DOCKING_CLEARANCE_STATUS_REQUESTED" || player.dockingClearanceStatus == "DOCKING_CLEARANCE_STATUS_TIMING_OUT")) {
ed._clearanceWarning = false;
}
if (dist < dockRange && _p.speed < 100 && deviation < 0.015) {
if (_ed[i].allowLaunch) {
stn._dockedViaED = _ed[i];
}
if (_ed[i].preDockCallback) {
try {
_ed[i].preDockCallback(stn);
} catch (ex) {
log("external docking", "ERROR calling pre-dock callback: " + ex);
}
}
// check if the player has requested docking clearance (and the station requires it)
// if they haven't requested it, fine the player
if (stn.requiresDockingClearance && (player.dockingClearanceStatus != "DOCKING_CLEARANCE_STATUS_GRANTED" && player.dockingClearanceStatus != "DOCKING_CLEARANCE_STATUS_REQUESTED" && player.dockingClearanceStatus != "DOCKING_CLEARANCE_STATUS_TIMING_OUT")) {
var fine = parseInt(player.credits * 0.05);
if (fine > 5000) fine = 5000;
player.credits -= fine;
var txt = expandDescription("[station-docking-clearance-fined-@-cr]").replace("%@", formatCredits(fine, false, true));
player.addMessageToArrivalReport(txt);
}
stn.dockPlayer();
if (_ed[i].postDockCallback) {
try {
_ed[i].postDockCallback(stn);
} catch (ex) {
log("external docking", "ERROR calling post-dock callback: " + ex);
}
}
return;
}
}
} |