| Scripts/PT-BVRM.js |
"use strict";
this.name = "PT-BVRM";
this.author = "Reval";
this.license = "CC-BY-NC-SA 4.0";
this.version = "1.4";
this.description = "This is the missile script for individual PT-BVRMs.";
this.$log = true; // logging missile events?
this.shipSpawned = function() {
var s = this.ship;
// if BVRM is not SDD-designated, designate it
if (!this._containsNumerals(s.displayName))
s.displayName = this._generateBVRMName();
// CHECK: who spawned me?
if (s.$spawnedByMother) {
s.displayName += "";
s.commsMessage("Launched. Guidance active.", player.ship);
if (this.$log) this._log(s.displayName+" spawned.");
s.$myKills = 0;
s.$creditsAwarded = 0;
// set up Progress timer (fires once per second)
if(!this.$ptmTimer) this.$ptmTimer=new Timer(this,this._ptmProgress.bind(this),0,1.0);
// prepare for possible later target change
this.$ptmLastTarget = s.target;
}
}
// check if spawned BVRM is SDD-designated
this._containsNumerals = function(str) {
return /\d/.test(str);
}
// PT-BVRM transmits periodic subspace updates to Mother
this._ptmProgress = function() {
var s = this.ship, pc = player.consoleMessage;
// invalid target: request change or retrieval
if ((s.target==null) || (s.target==undefined) || (!s.target.isValid)) {
// immediately change target to player (follow Mother)
// (avoid PriorityAI exceptions in the log).
s.target = player.ship;
pc("PT Tracking switched to Mother. Request target or retrieval.",9);
// flag our possible 'malfunction'
s.$malfunction = true;
return;
}
var t = s.target, ps = player.ship;
if (!t || !t.isValid) return;
var sn = s.displayName;
var ws = worldScripts['PT-BVRM Pteranodon'];
if (!ws) return;
// Check for target change
if (ws.$ptmTargetShip && ws.$ptmTargetShip.isValid && (ws.$ptmTargetShip !== this.$ptmLastTarget)) {
let oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0.545]; // new magenta
pc(sn + ": Target now " + ws.$ptmTargetShip.displayName, 9);
ps.messageGuiTextColor = oc;
this.$ptmLastTarget = ws.$ptmTargetShip;
this.ship.target = ws.$ptmTargetShip;
return;
}
var tn = t.displayName;
// 10-second interval: tracking-status report
this._ptmCounter = (this._ptmCounter || 0) + 1;
if (this._ptmCounter % 10 === 0) {
var oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0.545]; // new magenta
var dt = this._ptmDistanceKm(t);
var dm = this._ptmDistanceKm(player.ship);
var mn = player.ship.displayName;
if (ws.$ptmHasEQ) {
pc(sn + " Tracking:", 9);
pc("Target: " + tn + ", " + dt + " Lk", 9);
pc("Mother: " + mn + ", " + dm + " Lk", 9);
pc("Velocity " + s.speed.toFixed(1) + " Lm/s", 9);
}
ps.messageGuiTextColor = oc; // old colour
}
// 15-second interval: report within I-radius - then irradiate
if (this._ptmCounter % 15 === 0) {
dt = this._ptmDistanceKm(t);
if ((ws.$ptmHasEQ) && (dt <= 13.0) && (!t.$irradiated)) {
var oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0.545]; // magenta
if (t !== ps) pc(sn + " within Irradiation range.", 9);
// begin auto-irradiation and mark irradiated ships
if (ws.$ptmAutoIrr) {
// irradiate primary and 'collateral infractors'
// (if Mother is not the target (ie. retrieval))
if (t !== ps) {
pc(sn + " irradiating...",9);
this._ptmIrradiateAll();
} else pc(sn+" within Retrieval range.",9);
// 'courtesy boon' for primary (will total 2 cr)
player.credits += 1.0;
// add primary's boon to cumulative boons
ws.$ptmRBounty += 1.0;
// update F5F5 Mission screen display
ws._updateBVRMNetworkDisplay();
}
ps.messageGuiTextColor = oc; // old color
} else if (t.$irradiated) pc(sn+" awaiting Target or Retrieval.",9);
}
}
// get a filtered array of offenders in scanner range
this._ptmOffendersInScanner = function() {
// Use checkScanner for a fast scan
var scanResults = this.ship.checkScanner(true); // 'true' for powered-only
var offenders = [];
for (var i = 0; i < scanResults.length; i++) {
var entity = scanResults[i];
// Filter for powered, non-cargo, non-ally offender ships
if (entity.isShip && !entity.isCargo && (entity.bounty > 0)) {
offenders.push(entity);
}
}
return offenders;
};
// Irradiate all offenders in scanner range
this._ptmIrradiateAll = function() {
var infractors = this._ptmOffendersInScanner();
if ((infractors) && (infractors.length>0)) {
var ws = worldScripts['PT-BVRM Pteranodon'];
if (!ws) return;
var ps = player.ship;
var pc = player.consoleMessage;
var oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0.545]; // new magenta
for (var i=0; i<infractors.length; i++) {
// don't irradiate a victim more than once
if (!infractors[i].$irradiated) {
this._ptmIrradiateOne(infractors[i]);
pc(infractors[i].displayName,9);
// Galcop pays the 'courtesy boon'
player.credits += 1.0;
// record cumulative boons
ws.$ptmRKills ++;
ws.$ptmRBounty += 1.0;
}
}
ps.messageGuiTextColor = oc; // old colour
}
}
// Irradiate (cripple) a single ship
this._ptmIrradiateOne = function(victim) {
// Don't irradiate an already irradiated ship
if (!victim||victim.$irradiated||victim.$crippled||victim.$neutralized) return;
// 50/50 chance of having lasers knocked out completely
// (UNUSABLE due to known Oolite hard-code BUG.)
// (Here, REMOVING all lasers is the only practicable option.)
//var fiftyfifty = (Math.random() < 0.5);
//if (fiftyfifty) {
/* if (this._ptmRemoveLasers(victim))
if (this.$log) this._log(victim.name+"'s Lasers removed."); */
// 50/50 makes it a PEW Laser, simulating crippled weaponry
/*} else {
if (this._ptmGivePew(victim))
if (this.$log) this._log(victim.name+" given PEW laser.");
}*/
// As the above removal methods are UNUSABLE due to Oolite BUG,
// PT's only recourse is to set all weapons' status to 'DAMAGED'...
// *also* found UNUSABLE/INEFFECTUAL due to Oolite's core BUGGINESS!
this._ptmCrippleShipWeapons(victim);
// remove victim's missiles and mines to simulate disabling
this._ptmRemoveMissilesMines(victim);
// cripple speed and manoevrability
victim.$crippled = this._ptmCripple(victim,0.25);
// disable hyperdrive
victim.hyperspaceSpinTime = -1;
// disable fuel injector, if fitted, by removing it
if (this._ptmShipHasEquipment(victim, "EQ_FUEL_INJECTION"))
victim.removeEquipment("EQ_FUEL_INJECTION");
// neutralize scan class
victim.scanClass = "CLASS_NEUTRAL";
// mark victim ship as Irradiated
victim.$irradiated = true;
// prefix display name with 'Irradiated '
victim.displayName = "Irradiated "+victim.displayName;
// flash 'Irradiated' scanner blip: original colour + grey
victim.scannerDisplayColor2 = "grayColor";
// *TRULY* the only remaining no-weapons recourse: ALTER BEHAVIOUR...
var ws = worldScripts["PT-BVRM Pteranodon"];
if (!ws.$ptmWeaponsIntact)
victim.setAI('PT-BVRM_CrippledOffenderAI.js');
}
// Reduce a ship to a desired level of performance
this._ptmCripple = function(other, level) {
// reduce their speed, turn rate, acceleration
other.maxSpeed = other.maxSpeed * level;
other.maxThrust = other.maxThrust * level;
other.maxPitch = other.maxPitch * level;
other.maxRoll = other.maxRoll * level;
other.maxYaw = other.maxYaw * level;
// they are crippled
return true;
}
// UNUSABLE: writing 'EQUIPMENT_DAMAGED' is _cosmetic_... another Oolite core BUG)
// Disable a target ship's weapons by setting them to EQUIPMENT_DAMAGED
this._ptmCrippleShipWeapons = function(targetShip) {
// List of weapon equipment keys to target
// (can add custom OXP weapon keys here as needed)
var weaponKeys = [
"EQ_WEAPON_PULSE_LASER",
"EQ_WEAPON_BEAM_LASER",
"EQ_WEAPON_MILITARY_LASER",
"EQ_WEAPON_MINING_LASER"
];
// Blindly attempt to damage each weapon type.
// (if ship is without the weapon, setEquipmentStatus does nothing and throws no error.)
for (var i = 0; i < weaponKeys.length; i++) {
targetShip.setEquipmentStatus(weaponKeys[i], "EQUIPMENT_DAMAGED");
}
}
// Remove any and all lasers from NPC ship
// (UNUSABLE / INEFFECTUAL due to Oolite BUG)
this._ptmRemoveLasers = function(ship) {
var laserKeys = [
"EQ_WEAPON_PULSE_LASER",
"EQ_WEAPON_BEAM_LASER",
"EQ_WEAPON_MINING_LASER",
"EQ_WEAPON_MILITARY_LASER"
];
var lasersRemoved = false;
for (var i = 0; i < laserKeys.length; i++) {
var key = laserKeys[i];
var eStatus = ship.equipmentStatus(key);
if (eStatus === "EQUIPMENT_OK" || eStatus === "EQUIPMENT_DAMAGED") {
ship.removeEquipment(key);
lasersRemoved = true;
}
}
if (lasersRemoved) {
// Verify removal after a tiny delay (next tick) before logging
// Or simply check status again immediately
var stillArmed = false;
for (var j = 0; j < laserKeys.length; j++) {
if (ship.equipmentStatus(laserKeys[j]) !== undefined) {
stillArmed = true;
break;
}
}
if (!stillArmed) {
if (this.$log) this._log(ship.name + " successfully disarmed.");
} else {
if (this.$log) this._log("WARNING: " + ship.name + " removal failed (Ghost equipment).");
}
}
return lasersRemoved;
};
// remove any and all missiles and mines from a NPC ship
this._ptmRemoveMissilesMines = function(ship) {
var missileMineKeys = [
"EQ_MISSILE", "EQ_HARDENED_MISSILE", "EQ_QC_MINE"
];
var itemsRemoved = false;
for (var i = 0; i < missileMineKeys.length; i++) {
var key = missileMineKeys[i];
// Check status once, then remove
if (ship.equipmentStatus(key) === "EQUIPMENT_OK") {
ship.removeEquipment(key);
itemsRemoved = true;
}
}
return itemsRemoved;
};
// UNUSABLE due to a KNOWN OOLITE BUG (which effectively prevents the
// swapping or substitution of weapons in space.)
// Give victim a 'damaged' laser (EQ_WEAPON_PEW_LASER)
this._ptmGivePew = function(ship) {
// 1. Remove standard lasers
var otherWeapons = [
"EQ_WEAPON_PULSE_LASER", "EQ_WEAPON_BEAM_LASER",
"EQ_WEAPON_MINING_LASER", "EQ_WEAPON_MILITARY_LASER"
];
for (var i = 0; i < otherWeapons.length; i++) {
var wKey = otherWeapons[i];
// Remove repeatedly until status is undefined or unavailable
for (var attempt = 0; attempt < 8; attempt++) {
var eStatus = ship.equipmentStatus(wKey);
if (eStatus === undefined) break;
ship.removeEquipment(wKey);
}
}
// 2. (failed) workaround: Force equipment dictionary refresh
// Award and remove a dummy item to flush the "ghost" incompatibility memory
ship.awardEquipment("EQ_FUEL");
ship.removeEquipment("EQ_FUEL");
// 3. Now try to award the PEW Laser
if (ship.awardEquipment("EQ_WEAPON_PEW_LASER")) {
if (this.$log) this._log(ship.name + " given PEW laser.");
return true;
} else {
if (this.$log) this._log("ERROR: Could not award PEW laser to " + ship.name + " (Status: " + ship.equipmentStatus("EQ_WEAPON_PEW_LASER") + ")");
return false;
}
};
// UNUSABLE due to a KNOWN OOLITE BUG (which effectively prevents the
// swapping or substitution of weapons in space.)
// Give victim a Pulse Laser as its only armament
this._ptmGivePulse = function(ship) {
// already has a Pulse Laser?
if (ship.equipmentStatus("EQ_WEAPON_PULSE_LASER") === "EQUIPMENT_OK") {
return false;
}
// List of other lasers to remove
var otherWeapons = [
"EQ_WEAPON_BEAM_LASER",
"EQ_WEAPON_MINING_LASER",
"EQ_WEAPON_MILITARY_LASER"
];
// Remove any other lasers
for (var i = 0; i < otherWeapons.length; i++) {
if (ship.equipmentStatus(otherWeapons[i]) === "EQUIPMENT_OK") {
ship.removeEquipment(otherWeapons[i]);
}
}
// Award the Pulse Laser
ship.awardEquipment("EQ_WEAPON_PULSE_LASER");
return true;
};
this._ptmShipHasEquipment = function(ship, equip) {
return (ship.equipmentStatus(equip) === "EQUIPMENT_OK");
};
// distance from PT-BVRM to any ship, in Lave km
this._ptmDistanceKm = function(ship) {
var distanceInOU = ship.position.distanceTo(this.ship.position);
var distanceInKm = (distanceInOU / 1000);
return distanceInKm.toFixed(2);
};
this.shipRemoved = function(suppressDeathEvent) {
var s = this.ship, pc = player.commsMessage;
var ws = worldScripts['PT-BVRM Pteranodon'];
if (!ws) return;
var recovered = ws.$ptmRetrieved;
// BVRM was removed by player-script (e.g. ship.remove(true))
if (suppressDeathEvent) {
// successful recovery of BVRM - rewarded
if (recovered) {
// pay retrieval boon (handled in WS)
//player.credits += 100.0;
//ws.$ptmRBounty += 100.0;
// display it
ws._updateBVRMNetworkDisplay;
s.commsMessage("Recovered.", player.ship);
if (this.$log) this._log(s.displayName+" removed (recovered).");
if (this._Offscan())
player.consoleMessage(s.displayName+": recovered.", 5);
} else {
// system removed us (not successfully recovered)
// incurs Galcop negligence penalty (handled from WS)
//player.credits -= 500.0;
}
} else {
// Ship was destroyed in combat (normal death), final machine incoherence
s.commsMessage(this._generateJumbledString(24), player.ship);
if (s.$terminated) {
if (this.$log) this._log(s.displayName+" removed (operator termination).");
} else
if (this.$log) this._log(s.displayName+" removed (died).");
}
// cancel comms
if (this._ptmTimer) {
this._ptmTimer.stop();
this._ptmTimer = null;
}
};
this.shipDied = function(why) {
var ddm = this._generateDyingDroneMessage();
this.ship.commsMessage(ddm, player.ship);
if (this._Offscan())
player.consoleMessage(ddm, 5);
if (this.$log) this._log(this.ship.displayName+" dies.");
// cancel comms
if (this._ptmTimer) {
this._ptmTimer.stop();
this._ptmTimer = null;
}
if (why !== "removed") {
// charge player 500 cr for a destroyed BVRM
player.credits -= 500.0;
// keep a cumulative record
var ws = worldScripts[""];
if (ws && ws.$sddHasEQ) {
ws.$ptmRTax += 500.0;
ws.$ptmRLosses ++;
}
}
}
this.shipCollided = function(other) {
if (this.$log) this._log(this.ship.displayName+" collides with "+other.name+".");
}
this._hullStatus = function() {
// fraction of maximum energy remaining
var energyStatus = this.ship.energy / this.ship.maxEnergy;
// a 'hullStatus' property is likely undefined in Oolite
if (('hullStatus' in this.ship) && (this.ship.hullStatus != null))
return this.ship.hullStatus;
else return energyStatus;
}
// are we within player's scanner range?
this._Offscan = function() {
// Get the distance to the player's ship
var distance = this.ship.position.distanceTo(player.ship);
// Get the player's scanner range
var scannerRange = player.ship.scannerRange;
// Return true if the ship is beyond scanner range
if (distance > scannerRange) {
return true;
} else return false;
};
// distance to Mother ship in Lave km - call with (this.ship)
this._DistanceToMother = function(ship) {
var distanceInMeters = ship.position.distanceTo(player.ship.position);
return (distanceInMeters / 1000).toFixed(2);
};
// distance to Target ship in Lave km - call with (this.ship)
this._DistanceToTarget = function(ship) {
var distanceInMeters = ship.position.distanceTo(ship.target.position);
return (distanceInMeters / 1000).toFixed(2);
};
// this PT-BVRM's SDD-Network designation
this._generateBVRMName = function() {
// Generates a 3-digit number (100-999)
var randomNumber = Math.floor(Math.random() * 900) + 100;
return "PT-BVRM" + randomNumber;
}
// 'dying drone' message #1
this._generateJumbledString = function(length) {
var result = '';
var characters = ' !"#$&\'*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\^_`abcdefghijklmnopqrstuvwxyz|~';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
// 'dying drone' message #2
this._generateDyingDroneMessage = function() {
var chars = '01OIl!@#$&*?+=';
var segments = [];
for (var i = 0; i < 4; i++) {
var segment = '';
for (var j = 0; j < 6; j++) {
segment += chars.charAt(Math.floor(Math.random() * chars.length));
}
segments.push(segment);
}
return segments.join(' ** ');
}
// echo to Oolite log for this script only
this._log = function(msg) {
log(this.name+".debug", msg);
}
|
| Scripts/ptm-script.js |
"use strict";
this.name = "PT-BVRM Pteranodon";
this.author = "Reval";
this.license = "CC-BY-NC-SA 4.0";
this.version = "1.4";
this.description = "Galcop's 'Pteranodon' PT-BVR missile tracks and engages prioritized targets from up to full-system distance. Actuates on proximity, firing neutral particles locally in rapid sequence. Effects are mine-like, irradiative, mostly non-fatal to crew. Ships proximal to target can be affected. ECM-immune. SDD-Network access essential for mounting and deployment.";
// OPTION: keep an irradiated ship's weapons intact (true='yes', false='no')
this.$ptmWeaponsIntact = false; // default: 'no'
/*
Version 1.4
OPTION: keep an irradiated ship's weapons intact (true='yes', false='no').
True crippling ('no lasers') now achieved by switching the AI on irradiated vessels.
Suppressed 'marginal energy' in the ship-crippling procedure.
Low offender scanner colour changed to 'peach' (better contrast w/ other types).
Version 1.3
An Irradiated ship's scanner blip flashes original colour + grey.
High Offenders (often 'Fugitives') coloured magenta on scan.
Low Offenders coloured orange on scan (peach v.1.4).
In absence of data, ie. no irradiations or boons, show nothing on F5F5.
Added shipDied w.s. event handler for final cleanup.
In an attempt to facilitate 'damaged or disabled lasers?' logic,
ran into the Oolite BUG (GitHub Issue #369) preventing effectual swapping in of equipment on-the-fly.
Added _updateTargetInfo() + GETter HUD 1.6 timer integration, identical to SDD.
Tightened checks for non-zero length of HighOffenders array.
PT is now mode-of-death aware (killed, recovered, terminated, removed by system).
Sundry additional checks and logs implemented.
Version 1.2
F5->[i] Order an area-irradiation, if PT is following Mother.
F5->[d] BVRM 'destruct' button (if malfunction detected). Penalty 100 cr.
F5->[m] call PT-BVRM back to Mother (for retrieval or escort).
Version 1.1
Prevent launch by an offender ship.
Prevent launch while docked.
Fixes, erasures, cleanups, checks, optimizations.
Version 1.0
PT-BVRM Operator Statistics on F5F5 Mission screen.
Displayed statistics saved and loaded via missionVariables.
Galcop 'retrieval boon' paid on successful recovery of PT-BVRM.
Galcop 'couresy boon': 2 cr for primary, 1 cr each for collateral irradiations.
Retrieval ([r] when in scan range) and 500 cr penalty for non-retrieval.
PT-BVRM auto-irradiates multiple infractors including Primary, on proximity.
PT-BVRM switches targets when operator cycles through them from the F5 screen.
F5->[o] lists and re-sorts High Offenders, showing condition, bounty, distance.
F5->[c] cycles long-range infractor targets.
F5->[a] ARM launcher.
F5->[l] LAUNCH PT-BVRM.
F5->[r] RECOVER PT-BVRM.
F5->[s] SAFE (disarm) Launcher.
Version 0.0
Proof of concept - functional, launchable, trackable, integrates with SDD.
*/
/*
To Do
*/
// OPTION: are we logging ALL PT-BVRM activities?
this.$log = true;
// LAUNCH (spawn) the Pteranodon
this._ptmLaunchBVRM = function(num) {
if (typeof num === "undefined") num = 1;
if ((this.$ptmHasEQ) && (this.$ptmHens<this.$ptmMaxHens)) {
var ps = player.ship, pc = player.consoleMessage;
// spawn a new Ptera as a persistent WS array variable
this.$ptmBVRM = system.addShips("[pt-missile]", num, ps.position);
var ptera = this.$ptmBVRM; // abbreviate
// Belt'n'braces check for Ptera presence and validity
if (!ptera) {
var psh = player.ship, pcm = player.consoleMessage;
var ocolor = psh.messageGuiTextColor;
psh.messageGuiTextColor = "redColor";
pcm("PT-BVRM LAUNCH FAILURE:",9);
pcm("Report to CSDDA soonest.",9);
pcm("Until then, your status: Downgraded.",9);
psh.messageGuiTextColor = ocolor;
if (this.$log) this._log("PT-BVRM DEPLOYMENT FAILURE.");
return;
}
// set custom property on player-spawned Ptera(s)
for (var d = 0; d < ptera.length; d++)
ptera[d].$spawnedByMother = true;
// set scanner lollipop colour for player-spawned Pteras
for (var d = 0; d < ptera.length; d++) {
ptera[d].scannerDisplayColor1 = "blueColor";
ptera[d].scannerDisplayColor2 = "magentaColor";
}
// prevent collisions between new Ptera, player, and other Pteras
for (var d = 0; d < ptera.length; d++)
this._collisionAvoidance(ptera[d]);
// set the H.O. priority target
if (ptera && (ptera.length > 0)) {
for (var p=0; p<ptera.length; p++)
ptera[p].target = this.$ptmTargetShip;
}
// update Ptera count
this.$ptmHens += num;
// apply Launch Tax w/ increment
this._ptmLaunchTransactionDebits();
// confirm BVRM launch
pc("Launched PT-BVRM #"+this.$ptmHens+".", 9);
// no more Pteras to deploy
} else {
if (this.$ptmHasEQ) pc("All "+this.$ptmHens+" PT-BVRMs are deployed.",9);
}
}
this.shipSpawned = function(ship) {
// This is a system-spawned NPC ship,
// processed only if an offender.
if (this._ptmIsOffender(ship)) {
var b = ship.bounty;
var d = this._ptmDistanceKm(ship);
var c = this._ptmCompassDirection(ship);
var n = ship.shipClassName;
//if (this.$log) this._log("Spawned: "+n+" ("+b+") "+d+" km ");
// colour low offenders 'soft coral peach' on scanner
if (b<40) {
ship.scannerDisplayColor1 = [ 1.0, 0.65, 0.55 ];
return;
}
// add high offenders to recallable list
if (b>=40) {
// colour High Offenders magenta on scanner
ship.scannerDisplayColor1 = "magentaColor";
// only add NEW potential targets
var oTarget = this.$ptmTargetShip;
var newT = (oTarget !== ship);
// add the new high offender...
this._ptmAddHighOffender(ship);
// clean up H.O. list
this.$ptmHighOffenders = this.$ptmHighOffenders.filter(function(ship) {
return ship && ship.isValid;
});
var ps = player.ship;
// Re-sort by distance to player (nearest first)
this.$ptmHighOffenders.sort(function(a, b) {
return a.position.distanceTo(ps) - b.position.distanceTo(ps);
});
//player.consoleMessage("High Offender "+n+" at "+d+ " Lk "+c,9);
// Assign new top offender as target
if (this.$ptmHighOffenders.length > 0) {
var hf = this.$ptmHighOffenders[0];
// change primary target if new
if (hf.isValid && newT) {
this.$ptmTargetShip = hf;
}
}
}
}
};
// Establish whether ship has the PT-BVRM Launcher package
this.shipWillLaunchFromStation = function() {
var ps = player.ship;
this.$ptmHasEQ = (ps.equipmentStatus ("EQ_PT_BVRM_LAUNCHER") === "EQUIPMENT_OK");
this._ptmReset();
}
// Establish BVRM complement by ship-type
this.shipLaunchedFromStation = function(station) {
var pc = player.consoleMessage, ps = player.ship;
if (this.$ptmHasEQ) {
var oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0.545]; // new magenta
pc("Initializing link to PT-BVRM...", 9);
var d = this._ptmMaxDrones();
this.$ptmMaxHens = (d > 0) ? d : 7;
pc(this.$ptmMaxHens+" BVRM listening.", 9);
pc("F5->[l] to launch BVRM",9);
ps.messageGuiTextColor = oc; // old colour
// make sure Launcher is primed (if HUD set up)
ps.setPrimedEquipment("EQ_PT_BVRM_LAUNCHER", false);
// start realtime Target info update
// Use a Timer to update offender ship's distance and direction
// ONLY IF GETter HUD is loaded and SDD not.
if ((worldScripts['GETter HUD']) && (!worldScripts['Defence Rider Drones'])) {
this._updateTimer = new Timer(this, this._updateTargetInfo.bind(this), 0.5, 0.5);
this._updateTargetInfo();
}
} else
pc("Install PT-BVRM launcher package in shipyard.", 9);
// Reset Launch state
this.$ptmBVRMLaunched = false;
};
this.shipDockedWithStation = function() {
if (!this.$ptmHasEQ) return;
// disarm the Launcher
this.$ptmArmed = false;
// reset retrieval flag
this.$ptmRetrieved = false;
// don't proceed if there's no PT-BVRM
if ((!this.$ptmBVRM) || (this.$ptmBVRM[0]==undefined)) return;
// penalty for non-retrieval of BVRM
if ((this.$ptmBVRMLaunched) && (this.$ptmBVRM[0].isInSpace)) {
var ps = player.ship, pc = player.consoleMessage;
var oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0]; // dark red
pc("PT-BVRM non-retrieval penalty: -500 cr",9);
pc("Your Galcop status: Downgraded.",9);
ps.messageGuiTextColor = oc; // old colour
player.credits -= 500.0;
this.$ptmRTax += 500.0
player.ship.bounty = 5;
}
}
this.shipWillDockWithStation = function(station) {
// clear and reset H.O. list?
//this.$ptmHighOffenders = [];
this.$ptmTargetShip = null;
// update F5F5 Mission data
this._updateBVRMNetworkDisplay();
// Stop the HUD target info timer, if set
if (this._updateTimer) {
// clear target tracker when docked
player.ship.setCustomHUDDial("sddTargetInfo", "");
this._updateTimer.stop();
this._updateTimer = null;
}
}
this.playerStartedJumpCountdown = function(type, seconds) {
// disarm the Launcher
this.$ptmArmed = false;
// clear and reset H.O. list
this.$ptmHighOffenders = [];
this.$ptmTargetShip = null;
if ((!this.$ptmHasEQ) || (!this.$ptmBVRM) || (this.$ptmBVRM[0]==undefined)) return;
// penalty for non-retrieval of BVRM
if ((this.$ptmBVRMLaunched) && (this.$ptmBVRM[0].isInSpace)) {
var ps = player.ship, pc = player.consoleMessage;
var oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0]; // dark red
pc("PT-BVRM non-retrieval penalty: -500 cr",9);
pc("Your Galcop status: Downgraded.",9);
ps.messageGuiTextColor = oc; // old colour
player.credits -= 500;
// penalties included under total taxes
this.$ptmRTax += 500;
player.ship.bounty = 5;
}
// clean slate on H-jump
this._ptmReset();
}
// retrieve BVRM on receipt of docking clearance
this.playerRequestedDockingClearance = function() {
// unset tracker target on docking
this.$ptmTargetShip = null;
this._ptmReset();
}
this.playerBoughtEquipment = function(equipment, paid) {
var pc = player.consoleMessage;
if (equipment=="EQ_PT_BVRM_LAUNCHER") {
if (player.legalStatus=="Clean")
pc("Licence issued: PT-BVRM Launchers installed.",9);
else pc("Licence denied. No PT-BVRM Launchers installed.",9);
} else
// Licence termination, removal and refund
if (equipment == "EQ_PT_BVRM_LAUNCHER_REM") {
player.ship.removeEquipment("EQ_PT_BVRM_LAUNCHER");
player.ship.removeEquipment("EQ_PT_BVRM_LAUNCHER_REM");
pc("PT-BVRM launchers have been removed. Half refund.",9);
player.credits += 2500;
}
}
// put Mother's final affairs in order
this.shipDied = function(whom, why) {
// Stop the HUD target info timer, if set
if (this._updateTimer) {
// clear target tracker
player.ship.setCustomHUDDial("sddTargetInfo", "");
this._updateTimer.stop();
this._updateTimer = null;
}
// in case a BVRM is deployed...
// remove it from system
if ((this.$ptmBVRM!==undefined)&&(this.$ptmBVRM.length>0)) {
this.$ptmBVRM[0].remove();
this.$ptmBVRM = [];
this.$ptmBVRMLaunched = false;
this.$ptmArmed = false;
}
}
// prevent potential collisions within group
this._collisionAvoidance = function(ship) {
if (!ship || !ship.isValid) return;
// Prevent collision with player
ship.addCollisionException(player.ship);
// Prevent collision with other drones
for (let i = 0; i < this.$ptmBVRM.length; i++) {
let other = this.$ptmBVRM[i];
if (other && other.isValid && other !== ship) {
ship.addCollisionException(other);
}
}
}
// recall and re-attach BVRMs (remove from system space)
this._ptmReset = function() {
for (var i = 0; i < this.$ptmBVRM.length; i++)
this.$ptmBVRM[i].remove(true);
// Perform other housekeeping (clear the array, etc)
this.$ptmBVRM.length = 0;
this.$ptmHens = 0;
this.$ptmCharges = 0;
}
// detect an offending ship by its bounty
this._ptmIsOffender = function(ship) {
if ((ship.isShip) && (ship.shipClassName!=='Asteroid'))
return (ship.bounty > 10);
else return false;
}
this._ptmAddHighOffender = function(ship) {
// clear the array of invalid ships
this.$ptmHighOffenders = this.$ptmHighOffenders.filter(function(s) {
return s && s.isValid;
});
// Add the new ship if valid and not already in the list
if (ship && ship.isValid && this.$ptmHighOffenders.indexOf(ship) === -1) {
this.$ptmHighOffenders.push(ship);
}
// Re-sort by distance to player (nearest first)
var ps = player.ship;
this.$ptmHighOffenders.sort(function(a, b) {
return a.position.distanceTo(ps) - b.position.distanceTo(ps);
});
};
// list the system's high offenders by distance via comm
this.$ptmListHighOffenders = function() {
var ps = player.ship, pc = player.consoleMessage;
var hof = this.$ptmHighOffenders;
// 1. clean existing offender list
hof = hof.filter(function(s) { return s && s.isValid; });
// offenders list is empty
if (hof.length === 0) {
pc("PT sensor detects no high offenders in system.", 5);
return;
}
// 2. SORT the cleaned list by distance from the player's ship (closest first)
hof.sort(function(a, b) {
return ps.position.distanceTo(a.position) - ps.position.distanceTo(b.position);
});
// 3. send message reporting high offenders in system
// (recallable via log & updatable by pressing [o] on F5 screen)
var oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0.545]; // new magenta
pc("High Offenders:", 5);
for (var i = 0; i < hof.length; i++) {
var ship = hof[i];
var b = ship.bounty;
var d = this._ptmDistanceKm(ship);
var c = this._ptmCompassDirection(ship);
var n = ship.displayName;
pc("["+i+"] "+n+" ("+b+") at "+d+ " Lk "+c,9);
}
ps.messageGuiTextColor = oc; // old colour
};
// Compass direction of the given ship, 'N' = top of scanner, 'S' = bottom.
this._ptmCompassDirection = function(ship) {
var ps = player.ship;
// Get the vector from the player to the target ship
var vectorToTarget = ship.position.subtract(ps.position);
// Get the player's forward and right vectors (defining the horizontal plane)
var playerForward = ps.vectorForward;
var playerRight = ps.vectorRight;
// Project the target vector onto the player's horizontal plane (XZ plane)
var dotForward = vectorToTarget.dot(playerForward);
var dotRight = vectorToTarget.dot(playerRight);
// Calculate the angle in radians from the player's forward direction
var angle = Math.atan2(dotRight, dotForward);
// Convert angle from radians to degrees and normalize to 0-360
var degrees = (angle * 180 / Math.PI + 360) % 360;
// Determine the compass direction based on the angle
if (degrees >= 337.5 || degrees < 22.5) return 'N';
else if (degrees < 67.5) return 'NE';
else if (degrees < 112.5) return 'E';
else if (degrees < 157.5) return 'SE';
else if (degrees < 202.5) return 'S';
else if (degrees < 247.5) return 'SW';
else if (degrees < 292.5) return 'W';
else return 'NW';
};
// distance to any ship in km
this._ptmDistanceKm = function(ship) {
if ((!ship) || (!ship.isValid) || (ship==undefined)) return 1;
var distanceInMeters = ship.position.distanceTo(player.ship.position);
return (distanceInMeters / 1000).toFixed(2);
};
// echo to Oolite log for this script only
this._log = function(msg) {
log(this.name+".debug", msg);
}
// Galcop takes Launch Tax on every deployment
this._ptmLaunchTransactionDebits = function() {
// Only deduct credits if the tax amount is a valid number
if (!isNaN(this.$ptmDeployTax) && !isNaN(this.$ptmTaxLevel)) {
this.$ptmDeployTax += this.$ptmTaxLevel; // +0.05 cr nominal
// keep a record (total taxes)
this.$ptmRTax += this.$ptmDeployTax;
// refresh the F5F5 mission screen as the debit happens
this._updateBVRMNetworkDisplay();
// debit operator's account for the tax
player.credits -= this.$ptmDeployTax;
} else {
if (this.$log) this._log("Tax and debit transactions aborted (NaN).");
}
}
this.playerWillSaveGame = function(message) {
var mv = missionVariables;
mv.ptmRKills = this.$ptmRKills; // BVRM irradiations
mv.ptmRBounty = this.$ptmRBounty; // BVRM 'courtesy boons'
mv.ptmRLosses = this.$ptmRLosses; // count: BVRMs killed or collided
mv.ptmRTax = this.$ptmRTax; // debit: BVRM deployment taxes
// BVRM deployment taxation
// current tax level, incremented per launch (nominally +0.05 cr)
mv.ptmDeployTax= this.$ptmDeployTax;
}
this.startUp = function() {
this.$ptmDefaultMessageColor = 'cyanColor';
this.$ptmConds = ["DOCKED","GREEN","YELLOW","RED"];
this.$ptmTesting = false;
this.$ptmHasEQ = false;
this.$ptmMaxHens = 1; // arbitrary initialization
this.$ptmHens = 0;
this.$ptmAttackMsg = "SDD set condition RED.";
this.$ptmAlertMsg = "RED alert. RED alert.";
this.$ptmCharges = 0;
// Register the Launcher keys for the F5 screen
// a,s,o,r (v.1.92 +)
if (oolite.compareVersion("1.92") <= 0) {
setExtraGuiScreenKeys(this.name, {
guiScreen: "GUI_SCREEN_STATUS",
registerKeys: {
"cycle-target": [{key: "c"}],
"recall-pteras": [{key: "r"}],
"list-infractors": [{key: "o"}],
"arm-launcher": [{key: "a"}],
"disarm-launcher": [{key: "s"}],
"launch-ptera": [{key: "l"}],
"call-ptera": [{key: "m"}],
"destroy-ptera": [{key: "d"}],
"irradiate-area": [{key: "i"}]
},
callback: this._ptmKeyHandler.bind(this)
});
}
this.$ptmDeployTax = 25.0; // increases by <tax-level> with each 'drop'
this.$ptmTaxLevel = 0.05; // per 'drop' increment for deployment tax
// tallies to be passed from ship-scripts
// and saved/loaded via missionVariables.
this.$ptmRKills = 0; // BVRM irradiations
this.$ptmRBounty= 0.0; // Galcop 'courtesy boons' (retrievals and irradiations)
this.$ptmRTax = 0.0; // total BVRM deployment taxes & penalties
this.$ptmRLosses= 0; // count of BVRMs killed, lost or collided
// create and declare missile array once here
// (it will usually have a single element [0])
this.$ptmBVRM = [];
// create and declare high-offender shiplist
this.$ptmHighOffenders = [];
// index into H.O. array for target cycling
this.$ptmIndex = 0;
// Active target
this.$ptmTargetShip = null;
// the PT-BVRM Launcher must be activated (armed)
this.$ptmArmed = false;
// preserved by S.S. before silent respawning
this.$ptmPosition = null;
this.$ptmVelocity = null;
this.$ptmDesig = null;
// PT-BVRM Irradiates without operator-intervention
this.$ptmAutoIrr = true;
// was this BVRM successfully retrieved by operator?
this.$ptmRetrieved = false;
// Load persistent incremented variables
// -------------------------------------
var mv = missionVariables;
if (mv.ptmRKills !== undefined) this.$ptmRKills = mv.ptmRKills; // BVRM irradiations
if (mv.ptmRBounty!== undefined) this.$ptmRBounty= mv.ptmRBounty; // 'courtesy boons'
if (mv.ptmRLosses!== undefined) this.$ptmRLosses= mv.ptmRLosses; // BVRM destroyed debit
if (mv.ptmRTax !== null) this.$ptmRTax = mv.ptmRTax; // BVRM deployment taxes
// BVRM deployment taxation (floats)
// if undefined or NaN, don't assign
if (mv.ptmDeployTax!=null) this.$ptmDeployTax=mv.ptmDeployTax;
}
this._ptmKeyHandler = function(keyId) {
if (keyId === "recall-pteras") {
if (!this.$ptmBVRM) return;
var dist = this._ptmDistanceKm(this.$ptmBVRM[0]);
// Remove all PT-BVRMs from the system
if ((this.$ptmHasEQ) && (this.$ptmBVRMLaunched))
if (dist<=10.0) {
// flag retrieval as successful
this.$ptmRetrieved = true;
// recover (remove) BVRM
this._ptmReset();
// pay retrieval boon
player.credits += 100.0;
// record for posterity
this.$ptmRBounty += 100.0;
} else player.consoleMessage("PT out of range. Not retrieved.",9);
return true; // Consume the keypress
}
if (keyId === "call-ptera") {
let pc = player.consoleMessage;
// make Mother primary target
this.$ptmTargetShip = player.ship;
pc("PT-BVRM recalled. Target: Mother.",9);
return true; // Consume the keypress
}
if (keyId === "destroy-ptera") {
var pc = player.consoleMessage;
// PT destruct, only if malfunction and exists
if ((this.$ptmBVRM==undefined)||(this.$ptmBVRM.length==0)) return;
if ((this.$ptmBVRM) && (this.$ptmBVRM[0].$malfunction)) {
this.$ptmBVRM[0].$terminated = true;
this.$ptmBVRM[0].remove();
this.$ptmBVRM = [];
this.$ptmBVRMLaunched = false;
this.$ptmArmed = false;
pc("PT-BVRM TERMINATED by Operator.",9);
// Galcop destruction penalty
player.credits -= 100.0;
this.$ptmRTax += 100.0;
} else pc("PT NOT DESTROYED. No error diagnosed.",9);
return true; // Consume the keypress
}
if (keyId === "irradiate-area") {
var pc = player.consoleMessage;
// PT irradiates on operator's order, if following Mother
if ((this.$ptmBVRM==undefined)||(this.$ptmBVRM.length==0)) return;
if ((this.$ptmBVRM) && (this.$ptmBVRM[0].target==player.ship)) {
this.$ptmBVRM[0].script._ptmIrradiateAll();
pc("PT-BVRM IRRADIATE IMMEDIATE.",9);
} else pc("Vessels NOT IRRADIATED. PT beyond range.",9);
return true; // Consume the keypress
}
if (keyId === "list-infractors") {
// list system's high-offenders
var hof = this.$ptmHighOffenders;
this.$ptmListHighOffenders();
return true; // Consume the keypress
}
if (keyId === "arm-launcher") {
let pc = player.consoleMessage;
// arm the launcher
pc("ARMED PT-BVRM Launcher.",9);
this.$ptmArmed = true;
return true; // Consume the keypress
}
if (keyId === "disarm-launcher") {
let pc = player.consoleMessage;
// disarm (safe) the Launcher
pc("SAFED PT-BVRM Launcher.",9);
this.$ptmArmed = false;
return true; // Consume the keypress
}
if (keyId === "launch-ptera") {
var pc = player.consoleMessage;
// Launch a PT-BVRM (only in space & clean)
if (player.ship.docked) {
pc("PT cannot launch while docked.",9);
return true;
}
if (player.ship.bounty>0) {
pc("Launcher locked. Contact CSDDA.",9);
return true;
}
if ((this.$ptmHasEQ) && (this.$ptmArmed)) {
this._ptmLaunchBVRM(1);
this.$ptmBVRMLaunched = true;
this.$ptmArmed = false;
pc("LAUNCHED PT-BVRM.",9);
} else pc("PT NOT LAUNCHED. REQUIRES ARMING [a].",9);
return true; // Consume the keypress
}
// Cycle primary Target
var hof = this.$ptmHighOffenders;
if ((hof) && (hof.length>0)) {
if (keyId === "cycle-target") {
// Increment index and wrap around if it reaches the end
this.$ptmIndex = (this.$ptmIndex + 1) % hof.length;
var idx = this.$ptmIndex;
// assign this target
this.$ptmTargetShip = hof[idx];
let ps = player.ship, pc = player.consoleMessage;
let oc = ps.messageGuiTextColor; // old colour
ps.messageGuiTextColor = [0.545, 0, 0.545]; // new magenta
pc("["+idx+"] Target: " + hof[idx].displayName, 7);
ps.messageGuiTextColor = oc; // old colour
return true;
}
} else {
player.consoleMessage("No high-value targets in system.",7);
return true;
}
return false;
}
// Helper method to update the F5F5 SDD-Net Mission Screen
this._updateBVRMNetworkDisplay = function() {
if (this.$ptmHasEQ) {
var ps = player.ship, dname = ps.displayName, pn = player.name;
// Check if any data exists
var hasData = (this.$ptmRKills > 0) || (Number(this.$ptmRBounty) > 0.0);
if (hasData) {
// Show stats
mission.setInstructions([
"PT-BVRM (SDD-Net)",
"Operator: " + pn + ", " + dname,
"PT Irradiations: " + (this.$ptmRKills || 0),
"Taxes & Forfeits: " + (Number(this.$ptmRTax) || 0.0).toFixed(2) + " cr",
"BVRM Launch Tax: " + (Number(this.$ptmDeployTax) || 0.0).toFixed(2) + " cr",
"Galcop PT Boons: " + (Number(this.$ptmRBounty) || 0.0).toFixed(2) + " cr"
], this.name);
} else {
// Explicitly clear the section to hide it completely
mission.setInstructions(null, this.name);
}
}
};
// Update the F5F5 SDD-Net Mission Screen on displaying it
this.guiScreenChanged = function(from, to) {
if (this.$ptmHasEQ) {
if (guiScreen === "GUI_SCREEN_MANIFEST") {
this._updateBVRMNetworkDisplay();
}
}
};
// PT-BVRM capacities by ship class
this._ptmMaxDrones = function() {
// provisionally, only one BVRM mounted
return 1;
var name = player.ship.shipClassName.toLowerCase();
var map = {
"adder": 1,
"anaconda": 10,
"asp mk ii": 3,
"asp mark ii": 3,
"asp explorer": 3,
"boa": 7,
"boa class cruiser": 7,
"cobra mk i": 2,
"cobra mark i": 2,
"cobra mk ii": 3,
"cobra mk iii": 4,
"cobra mk 3": 4,
"cobra mark iii": 4,
"cobra mk iv": 5,
"constrictor": 5,
"fer-de-lance": 3,
"gecko": 1,
"krait": 2,
"mamba": 1,
"moray medical boat": 2,
"moray star boat": 2,
"python": 6,
"sidewinder": 1,
"sidewinder scout ship": 1,
"training fighter": 2,
"transporter": 1,
"viper": 1,
"galcop viper": 1,
"galcop viper interceptor": 2,
"worm": 1
};
return map[name] || 3;
};
// nearest High Offender realtime tracking onscreen (via GETter HUD's hud.plist)
this._updateTargetInfo = function() {
if (!player.ship.isValid) return;
if (this.$ptmTargetShip && this.$ptmTargetShip.isValid) {
var ps = player.ship, ts = this.$ptmTargetShip;
var distance = (ts.position.distanceTo(ps.position) / 1000).toFixed(2);
var direction = this._ptmCompassDirection(ts);
// Get the vector from player to target
var relPos = ts.position.subtract(ps.position);
// Project the relative position onto the player's up and forward vectors
var upComponent = relPos.dot(ps.orientation.vectorUp());
var forwardComponent = relPos.dot(ps.heading); // heading is the forward vector
// Determine elevation based on the ratio, avoiding division by zero
var elevation = "";
if (Math.abs(forwardComponent) > 1) { // Use a small threshold instead of zero
var verticalAngle = Math.abs(upComponent / forwardComponent);
elevation = (upComponent > 0) ? (verticalAngle > 0.1 ? "hi" : "") : (verticalAngle > 0.1 ? "lo" : "");
}
ps.setCustomHUDDial("sddTargetInfo", ts.name + " > " + direction + " " + elevation + " < " + distance + "km");
} else {
player.ship.setCustomHUDDial("sddTargetInfo", "");
}
};
|