Now much more possible for OXPs to add more comms messages. Priority system to allow occasional messages on particular topics without overwhelming the comms with constant updates. Also, fix my text editor using 2 tabs as the indent level for JS...
3897 lines
94 KiB
JavaScript
3897 lines
94 KiB
JavaScript
/*
|
|
|
|
oolite-ailib.js
|
|
|
|
Priority-based Javascript AI library
|
|
|
|
|
|
Oolite
|
|
Copyright © 2004-2013 Giles C Williams and contributors
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
/* AI Library */
|
|
this.name = "oolite-libPriorityAI";
|
|
this.version = "1.79";
|
|
this.copyright = "© 2008-2013 the Oolite team.";
|
|
this.author = "cim";
|
|
|
|
|
|
/* Constructor */
|
|
|
|
this.AILib = function(ship)
|
|
{
|
|
// the ship property must be read-only
|
|
Object.defineProperty(this, "ship", {
|
|
value: ship,
|
|
writable: false,
|
|
enumerable: true,
|
|
configurable: false
|
|
});
|
|
|
|
|
|
this.ship.AIScript.oolite_intership = {};
|
|
this.ship.AIScript.oolite_priorityai = this;
|
|
var activeHandlers = [];
|
|
var priorityList = null;
|
|
var parameters = {};
|
|
var lastCommSent = 0;
|
|
var lastCommHeard = 0;
|
|
var commsRole = "generic";
|
|
var commsPersonality = "generic";
|
|
var waypointgenerator = null;
|
|
|
|
/* Private utility functions. Cannot be called from external code */
|
|
|
|
/* Considers a priority list, potentially recursively */
|
|
function _reconsiderList(priorities) {
|
|
var l = priorities.length;
|
|
for (var i = 0; i < l; i++)
|
|
{
|
|
var priority = priorities[i];
|
|
if (this.getParameter("oolite_flag_behaviourLogging"))
|
|
{
|
|
if (priority.label)
|
|
{ log(this.ship.name,"Considering: "+priority.label);
|
|
}
|
|
else
|
|
{
|
|
log(this.ship.name,"Considering: entry "+i);
|
|
}
|
|
}
|
|
// always call the preconfiguration function at this point
|
|
// to set up condition parameters
|
|
if (priority.preconfiguration)
|
|
{
|
|
priority.preconfiguration.call(this);
|
|
}
|
|
// allow inverted conditions
|
|
var condmet = true;
|
|
if (priority.notcondition)
|
|
{
|
|
condmet = !priority.notcondition.call(this);
|
|
}
|
|
else if (priority.condition)
|
|
{
|
|
condmet = priority.condition.call(this);
|
|
}
|
|
// absent condition is always true
|
|
if (condmet)
|
|
{
|
|
if (this.getParameter("oolite_flag_behaviourLogging"))
|
|
{
|
|
log(this.ship.name,"Conditions met");
|
|
}
|
|
|
|
// always call the configuration function at this point
|
|
if (priority.configuration)
|
|
{
|
|
priority.configuration.call(this);
|
|
}
|
|
// this is what we're doing
|
|
if (priority.behaviour)
|
|
{
|
|
if (this.getParameter("oolite_flag_behaviourLogging"))
|
|
{
|
|
log(this.ship.name,"Executing behaviour");
|
|
}
|
|
|
|
if (priority.reconsider)
|
|
{
|
|
_resetReconsideration.call(this,priority.reconsider);
|
|
}
|
|
return priority.behaviour;
|
|
}
|
|
// otherwise this is what we might be doing
|
|
if (priority.truebranch)
|
|
{
|
|
if (this.getParameter("oolite_flag_behaviourLogging"))
|
|
{
|
|
log(this.ship.name,"Entering truebranch");
|
|
}
|
|
|
|
var branch = _reconsiderList.call(this,priority.truebranch);
|
|
if (branch != null)
|
|
{
|
|
return branch;
|
|
}
|
|
// otherwise nothing in the branch was usable, so move on
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (priority.falsebranch)
|
|
{
|
|
if (this.getParameter("oolite_flag_behaviourLogging"))
|
|
{
|
|
log(this.ship.name,"Entering falsebranch");
|
|
}
|
|
|
|
var branch = _reconsiderList.call(this,priority.falsebranch);
|
|
if (branch != null)
|
|
{
|
|
return branch;
|
|
}
|
|
// otherwise nothing in the branch was usable, so move on
|
|
}
|
|
}
|
|
}
|
|
if (this.getParameter("oolite_flag_behaviourLogging"))
|
|
{
|
|
log(this.ship.name,"Exiting branch");
|
|
}
|
|
|
|
return null; // nothing in the list is usable, so return
|
|
};
|
|
|
|
/* Only call this from aiAwoken to avoid loops */
|
|
function _reconsider() {
|
|
if (!this.ship || !this.ship.isValid || !this.ship.isInSpace)
|
|
{
|
|
return;
|
|
}
|
|
var newBehaviour = _reconsiderList.call(this,priorityList);
|
|
if (newBehaviour == null) {
|
|
log(this.name,"AI '"+this.ship.AIScript.name+"' for ship "+this.ship+" had all priorities fail. All priority based AIs should end with an unconditional entry.");
|
|
return false;
|
|
}
|
|
|
|
if (this.getParameter("oolite_flag_behaviourLogging"))
|
|
{
|
|
log(this.ship.name,newBehaviour);
|
|
}
|
|
newBehaviour.call(this);
|
|
return true;
|
|
};
|
|
|
|
|
|
/* Resets the reconsideration timer. */
|
|
function _resetReconsideration(delay)
|
|
{
|
|
this.ship.AIScriptWakeTime = clock.adjustedSeconds + delay;
|
|
};
|
|
|
|
|
|
/* ****************** General AI functions. ************** */
|
|
|
|
/* These privileged functions interface with the private functions
|
|
* and variables. Do not override them. */
|
|
|
|
this.applyHandlers = function(handlers)
|
|
{
|
|
// step 1: go through activeHandlers, and delete those
|
|
// functions from this.ship.AIScript
|
|
for (var i=0; i < activeHandlers.length ; i++)
|
|
{
|
|
delete this.ship.AIScript[activeHandlers[i]];
|
|
}
|
|
|
|
/* This handler must always exist for a priority AI, and must
|
|
* be set here. */
|
|
handlers.aiAwoken = function()
|
|
{
|
|
_reconsider.call(this);
|
|
}
|
|
|
|
// step 2: go through the keys in handlers and put those handlers
|
|
// into this.ship.AIScript and the keys into activeHandlers
|
|
activeHandlers = Object.keys(handlers);
|
|
for (var i=0; i < activeHandlers.length ; i++)
|
|
{
|
|
this.ship.AIScript[activeHandlers[i]] = handlers[[activeHandlers[i]]].bind(this);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
this.communicate = function(key,params,priority)
|
|
{
|
|
if (priority > 1)
|
|
{
|
|
var send = clock.adjustedSeconds - lastCommSent;
|
|
if (priority == 2)
|
|
{
|
|
if (send < 10)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
var recv = clock.adjustedSeconds - lastCommHeard;
|
|
if (priority == 3)
|
|
{
|
|
if (recv < 10 || send < 10)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (recv < 60 || send < 60)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
var template = worldScripts["oolite-libPriorityAI"]._getCommunication(commsRole,commsPersonality,key);
|
|
if (template != "")
|
|
{
|
|
var message = expandDescription(template,params);
|
|
if (message != "")
|
|
{
|
|
this.ship.commsMessage(message);
|
|
lastCommSent = clock.adjustedSeconds;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
this.getParameter = function(key)
|
|
{
|
|
if (key in parameters)
|
|
{
|
|
return parameters[key];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
this.getWaypointGenerator = function()
|
|
{
|
|
return waypointgenerator;
|
|
}
|
|
|
|
|
|
this.noteCommsHeard = function()
|
|
{
|
|
lastCommHeard = clock.adjustedSeconds;
|
|
}
|
|
|
|
|
|
/* Requests reconsideration of behaviour ahead of schedule. */
|
|
this.reconsiderNow = function()
|
|
{
|
|
_resetReconsideration.call(this,0.05);
|
|
}
|
|
|
|
|
|
this.setCommunicationsRole = function(role)
|
|
{
|
|
commsRole = role;
|
|
}
|
|
|
|
|
|
this.setCommunicationsPersonality = function(personality)
|
|
{
|
|
commsPersonality = personality;
|
|
}
|
|
|
|
// parameters created by Oolite must always be prefixed oolite_
|
|
this.setParameter = function(key, value)
|
|
{
|
|
parameters[key] = value;
|
|
}
|
|
|
|
this.setPriorities = function(priorities)
|
|
{
|
|
priorityList = priorities;
|
|
this.applyHandlers({});
|
|
this.reconsiderNow();
|
|
}
|
|
|
|
|
|
// set the waypoint generator function
|
|
this.setWaypointGenerator = function(value)
|
|
{
|
|
waypointgenerator = value;
|
|
}
|
|
|
|
|
|
}; // end object constructor
|
|
|
|
/* Object prototype */
|
|
AILib.prototype.constructor = AILib;
|
|
AILib.prototype.name = this.name;
|
|
|
|
/* ****************** AI utility functions. ************** */
|
|
|
|
/* These functions provide standard checks for consistency in
|
|
* conditions and other functions. */
|
|
|
|
AILib.prototype.allied = function(ship1,ship2)
|
|
{
|
|
// ships in same group
|
|
if (ship1.group && ship1.group.containsShip(ship2))
|
|
{
|
|
return true;
|
|
}
|
|
if (ship1.group && ship1.group.leader)
|
|
{
|
|
// ship1 is escort of ship in same group as ship2
|
|
if (ship1.group.leader.group && ship1.group.leader.group.containsShip(ship2))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// or in reverse, ship2 is the escort
|
|
if (ship2.group && ship2.group.leader)
|
|
{
|
|
// ship2 is escort of ship in same group as ship1
|
|
if (ship2.group.leader.group && ship2.group.leader.group.containsShip(ship1))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// ship1 is escort of a ship, ship2 is escort of a ship, both
|
|
// those ships are in the same group
|
|
if (ship1.group && ship1.group.leader && ship2.group && ship2.group.leader && ship1.group.leader.group && ship1.group.leader.group.containsShip(ship2.group.leader))
|
|
{
|
|
return true;
|
|
}
|
|
// Okay, these ships really do have nothing to do with each other...
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.broadcastDistressMessage = function()
|
|
{
|
|
this.ship.broadcastDistressMessage();
|
|
if (this.ship.AIPrimaryAggressor)
|
|
{
|
|
this.communicate("oolite_makeDistressCall",this.entityCommsParams(this.ship.AIPrimaryAggressor),2);
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.checkScannerWithPredicate = function(predicate)
|
|
{
|
|
var scan = this.getParameter("oolite_scanResults");
|
|
if (scan == null || predicate == null)
|
|
{
|
|
return false;
|
|
}
|
|
for (var i = 0 ; i < scan.length ; i++)
|
|
{
|
|
if (predicate.call(this,scan[i]))
|
|
{
|
|
this.setParameter("oolite_scanResultSpecific",scan[i]);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.cruiseSpeed = function()
|
|
{
|
|
var cruise = this.ship.maxSpeed * 0.8;
|
|
if (this.ship.group)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.ships.length ; i++)
|
|
{
|
|
if (this.ship.group.ships[i].maxSpeed >= this.ship.maxSpeed/4)
|
|
{
|
|
if (cruise > this.ship.group.ships[i].maxSpeed)
|
|
{
|
|
cruise = this.ship.group.ships[i].maxSpeed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (this.ship.escortGroup)
|
|
{
|
|
for (var i = 0 ; i < this.ship.escortGroup.ships.length ; i++)
|
|
{
|
|
if (this.ship.escortGroup.ships[i].maxSpeed >= this.ship.maxSpeed/4)
|
|
{
|
|
if (cruise > this.ship.escortGroup.ships[i].maxSpeed)
|
|
{
|
|
cruise = this.ship.escortGroup.ships[i].maxSpeed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return cruise;
|
|
}
|
|
|
|
|
|
// gets a standard comms params object
|
|
AILib.prototype.entityCommsParams = function(entity)
|
|
{
|
|
var params = {};
|
|
if (entity.isShip)
|
|
{
|
|
// TODO: extend the ship object so more precise names can be
|
|
// returned?
|
|
params["oolite_entityClass"] = entity.name; //ship.shipClassName;
|
|
params["oolite_entityName"] = entity.displayName; // ship.shipName;
|
|
}
|
|
else if (entity.name)
|
|
{
|
|
params["oolite_entityClass"] = entity.name;
|
|
params["oolite_entityName"] = entity.name;
|
|
}
|
|
return params;
|
|
}
|
|
|
|
|
|
AILib.prototype.fineThreshold = function()
|
|
{
|
|
return 50 - (system.info.government * 7);
|
|
}
|
|
|
|
|
|
AILib.prototype.friendlyStation = function(station)
|
|
{
|
|
if (station.isMainStation && this.ship.bounty > this.fineThreshold())
|
|
{
|
|
return false;
|
|
}
|
|
return (station.target != this.ship || !station.hasHostileTarget);
|
|
}
|
|
|
|
|
|
AILib.prototype.homeStation = function()
|
|
{
|
|
// home station might be the owner of the ship, or might just
|
|
// be a group member
|
|
if (this.ship.owner && this.ship.owner.isStation && this.friendlyStation(this.ship.owner))
|
|
{
|
|
return this.ship.owner;
|
|
}
|
|
if (this.ship.group)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.ships.length ; i++)
|
|
{
|
|
if (this.ship.group.ships[i] != this.ship && this.ship.group.ships[i].isStation && this.friendlyStation(this.ship.group.ships[i].isStation))
|
|
{
|
|
return this.ship.group.ships[i];
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
AILib.prototype.isAggressive = function(ship)
|
|
{
|
|
if (ship && ship.isPlayer)
|
|
{
|
|
return !ship.isFleeing;
|
|
}
|
|
return ship && ship.hasHostileTarget && !ship.isFleeing && !ship.isDerelict;
|
|
}
|
|
|
|
AILib.prototype.isFighting = function(ship)
|
|
{
|
|
if (ship.isStation)
|
|
{
|
|
return ship.alertCondition == 3 && ship.target;
|
|
}
|
|
return ship && ship.hasHostileTarget;
|
|
}
|
|
|
|
|
|
/* ****************** Condition functions ************** */
|
|
|
|
/* Conditions. Any function which returns true or false can be used as
|
|
* a condition. They do not have to be part of the AI library, but
|
|
* several common conditions are provided here. */
|
|
|
|
|
|
/*** Combat-related conditions ***/
|
|
|
|
|
|
AILib.prototype.conditionCascadeDetected = function()
|
|
{
|
|
var cpos = this.getParameter("oolite_cascadeDetected");
|
|
if (cpos != null)
|
|
{
|
|
if (cpos.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
this.setParameter("oolite_cascadeDetected",null);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionCombatOddsGood = function()
|
|
{
|
|
// TODO: this should consider what the ships are, somehow
|
|
var us = 1;
|
|
if (this.ship.group)
|
|
{
|
|
us += this.ship.group.count - 1;
|
|
}
|
|
if (this.ship.escortGroup)
|
|
{
|
|
us += this.ship.escortGroup.count - 1;
|
|
}
|
|
|
|
var them = 1;
|
|
if (!this.ship.target)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (this.ship.target.group)
|
|
{
|
|
them += this.ship.target.group.count - 1;
|
|
}
|
|
if (this.ship.target.escortGroup)
|
|
{
|
|
them += this.ship.target.escortGroup.count - 1;
|
|
}
|
|
}
|
|
return us >= them;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionInCombat = function()
|
|
{
|
|
if (this.isFighting(this.ship))
|
|
{
|
|
return true;
|
|
}
|
|
var dts = this.ship.defenseTargets;
|
|
for (var i=0; i < dts.length; i++)
|
|
{
|
|
if (dts[i].position.squaredDistanceTo(this.ship) < this.ship.scannerRange * this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (this.ship.group != null)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.count ; i++)
|
|
{
|
|
if (this.isFighting(this.ship.group.ships[i]))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (this.ship.escortGroup != null)
|
|
{
|
|
for (var i = 0 ; i < this.ship.escortGroup.count ; i++)
|
|
{
|
|
if (this.isFighting(this.ship.escortGroup.ships[i]))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete this.ship.AIScript.oolite_intership.cargodemandpaid;
|
|
return false;
|
|
}
|
|
|
|
/* Ships being attacked are firing back */
|
|
AILib.prototype.conditionInCombatWithHostiles = function()
|
|
{
|
|
if (this.isFighting(this.ship) && this.isAggressive(this.ship.target))
|
|
{
|
|
return true;
|
|
}
|
|
var dts = this.ship.defenseTargets;
|
|
for (var i=0; i < dts.length; i++)
|
|
{
|
|
if (this.isAggressive(dts[i]) && dts[i].position.squaredDistanceTo(this.ship) < this.ship.scannerRange * this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// this is safe to do mid-loop as dts is a copy of the
|
|
// actual defense target list
|
|
this.ship.removeDefenseTarget(dts[i]);
|
|
}
|
|
}
|
|
if (this.ship.group != null)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.count ; i++)
|
|
{
|
|
if (this.isFighting(this.ship.group.ships[i]) && this.isAggressive(this.ship.group.ships[i].target))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (this.ship.escortGroup != null)
|
|
{
|
|
for (var i = 0 ; i < this.ship.escortGroup.count ; i++)
|
|
{
|
|
if (this.isFighting(this.ship.escortGroup.ships[i]) && this.isAggressive(this.ship.escortGroup.ships[i].target))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete this.ship.AIScript.oolite_intership.cargodemandpaid;
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionLosingCombat = function()
|
|
{
|
|
var cascade = this.getParameter("oolite_cascadeDetected");
|
|
if (cascade != null)
|
|
{
|
|
if (cascade.distanceTo(this.ship) < 25600)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
this.setParameter("oolite_cascadeDetected",null);
|
|
}
|
|
}
|
|
if (this.ship.energy == this.ship.maxEnergy)
|
|
{
|
|
// forget previous defeats
|
|
this.setParameter("oolite_lastFleeing",null);
|
|
}
|
|
if (!this.conditionInCombat())
|
|
{
|
|
return false;
|
|
}
|
|
var lastThreat = this.getParameter("oolite_lastFleeing");
|
|
if (lastThreat != null && this.ship.position.distanceTo(lastThreat) < 25600)
|
|
{
|
|
// the thing that attacked us is still nearby
|
|
return true;
|
|
}
|
|
if (this.ship.energy * 4 < this.ship.maxEnergy)
|
|
{
|
|
// TODO: adjust threshold based on group odds
|
|
return true; // losing if less than 1/4 energy
|
|
}
|
|
var dts = this.ship.defenseTargets;
|
|
for (var i = 0 ; i < dts.length ; i++)
|
|
{
|
|
if (dts[i].scanClass == "CLASS_MISSILE" && dts[i].target == this.ship)
|
|
{
|
|
return true;
|
|
}
|
|
if (dts[i].scanClass == "CLASS_MINE")
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// if we've dumped cargo or the group leader has, then we're losing
|
|
if (this.ship.AIScript.oolite_intership.cargodemandpaid)
|
|
{
|
|
return true;
|
|
}
|
|
if (this.ship.group && this.ship.group.leader && this.ship.group.leader.AIScript.oolite_intership && this.ship.group.leader.AIScript.oolite_intership.cargodemandpaid)
|
|
{
|
|
return true;
|
|
}
|
|
// TODO: add some reassessment of odds based on group size
|
|
return false; // not losing yet
|
|
}
|
|
|
|
AILib.prototype.conditionMothershipInCombat = function()
|
|
{
|
|
if (this.ship.group && this.ship.group.leader && this.ship.group.leader != this.ship)
|
|
{
|
|
var leader = this.ship.group.leader;
|
|
if (leader.position.distanceTo(this.ship) > this.ship.scannerRange)
|
|
{
|
|
return false; // can't tell
|
|
}
|
|
if (this.isFighting(leader))
|
|
{
|
|
return true;
|
|
}
|
|
if (leader.target && leader.target.target == leader && leader.target.hasHostileTarget)
|
|
{
|
|
return true;
|
|
}
|
|
var dts = leader.defenseTargets;
|
|
for (var i = 0 ; i < dts.length ; i++)
|
|
{
|
|
if (dts[i].target == leader && dts[i].hasHostileTarget)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// no mothership
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionMothershipIsAttacking = function()
|
|
{
|
|
if (this.ship.group && this.ship.group.leader != this.ship)
|
|
{
|
|
var leader = this.ship.group.leader;
|
|
if (leader.target && this.isFighting(leader) && leader.target.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// as MothershipIsAttacking, but leader.target must be aggressive
|
|
AILib.prototype.conditionMothershipIsAttackingHostileTarget = function()
|
|
{
|
|
if (this.ship.group && this.ship.group.leader != this.ship)
|
|
{
|
|
var leader = this.ship.group.leader;
|
|
if (leader.target && this.isFighting(leader) && this.isAggressive(leader.target) && leader.target.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
AILib.prototype.conditionMothershipUnderAttack = function()
|
|
{
|
|
if (this.ship.group && this.ship.group.leader != this.ship)
|
|
{
|
|
var leader = this.ship.group.leader;
|
|
if (leader.target && leader.target.target == leader && leader.target.hasHostileTarget && leader.target.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
var dts = leader.defenseTargets;
|
|
for (var i = 0 ; i < dts.length ; i++)
|
|
{
|
|
if (dts[i].target == leader && dts[i].hasHostileTarget && dts[i].position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/*** Navigation-related conditions ***/
|
|
|
|
|
|
AILib.prototype.conditionCanWitchspaceOut = function()
|
|
{
|
|
if (!this.ship.hasHyperspaceMotor)
|
|
{
|
|
return false;
|
|
}
|
|
return (system.info.systemsInRange(this.ship.fuel).length > 0);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionFriendlyStationExists = function()
|
|
{
|
|
var stations = system.stations;
|
|
for (var i = 0 ; i < stations.length ; i++)
|
|
{
|
|
var station = stations[i];
|
|
if (this.friendlyStation(station))
|
|
{
|
|
// this is not a very good check for friendliness, but
|
|
// it will have to do for now
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
AILib.prototype.conditionFriendlyStationNearby = function()
|
|
{
|
|
var stations = system.stations;
|
|
for (var i = 0 ; i < stations.length ; i++)
|
|
{
|
|
var station = stations[i];
|
|
if (this.friendlyStation(station))
|
|
{
|
|
// this is not a very good check for friendliness, but
|
|
// it will have to do for now
|
|
if (station.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionGroupIsSeparated = function()
|
|
{
|
|
if (!this.ship.group || !this.ship.group.leader)
|
|
{
|
|
return false;
|
|
}
|
|
if (this.ship.group.leader.isStation)
|
|
{
|
|
// can get 2x as far from station
|
|
return (this.ship.position.distanceTo(this.ship.group.leader) > this.ship.scannerRange * 2);
|
|
}
|
|
else
|
|
{
|
|
return (this.ship.position.distanceTo(this.ship.group.leader) > this.ship.scannerRange);
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHasSelectedPlanet = function()
|
|
{
|
|
var planet = this.getParameter("oolite_selectedPlanet");
|
|
if (planet && (!planet.isValid || !planet.isPlanet))
|
|
{
|
|
this.setParameter("oolite_selectedPlanet",null);
|
|
return false;
|
|
}
|
|
return planet != null;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHasSelectedStation = function()
|
|
{
|
|
var station = this.getParameter("oolite_selectedStation");
|
|
if (station && (!station.isValid || !station.isStation))
|
|
{
|
|
this.setParameter("oolite_selectedStation",null);
|
|
return false;
|
|
}
|
|
return station != null;
|
|
}
|
|
|
|
|
|
|
|
AILib.prototype.conditionHomeStationExists = function()
|
|
{
|
|
return (this.homeStation() != null);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHomeStationNearby = function()
|
|
{
|
|
var home = this.homeStation();
|
|
if (home == null)
|
|
{
|
|
return false;
|
|
}
|
|
return this.ship.position.distanceTo(home) < this.ship.scannerRange;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionInInterstellarSpace = function()
|
|
{
|
|
return system.isInterstellarSpace;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionMainPlanetNearby = function()
|
|
{
|
|
if (!system.mainPlanet)
|
|
{
|
|
return false;
|
|
}
|
|
if (this.ship.position.distanceTo(system.mainPlanet) < system.mainPlanet.radius * 4)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionNearDestination = function()
|
|
{
|
|
return (this.ship.destination.squaredDistanceTo(this.ship) < this.ship.desiredRange * this.ship.desiredRange);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionPlayerNearby = function()
|
|
{
|
|
return this.ship.position.distanceTo(player.ship) < this.ship.scannerRange;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionReadyToSunskim = function()
|
|
{
|
|
return (system.sun && this.ship.position.distanceTo(system.sun) < system.sun.radius * 1.15);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionSelectedStationNearby = function()
|
|
{
|
|
var station = this.getParameter("oolite_selectedStation");
|
|
if (station && station.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
AILib.prototype.conditionSelectedStationNearMainPlanet = function()
|
|
{
|
|
if (!system.mainPlanet)
|
|
{
|
|
return false;
|
|
}
|
|
var station = this.getParameter("oolite_selectedStation");
|
|
if (station && station.position.distanceTo(system.mainPlanet) < system.mainPlanet.radius * 4)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionSunskimPossible = function()
|
|
{
|
|
return (system.sun &&
|
|
!system.sun.hasGoneNova &&
|
|
!system.sun.isGoingNova &&
|
|
this.ship.fuel < 7 &&
|
|
this.ship.equipmentStatus("EQ_FUEL_SCOOPS") == "EQUIPMENT_OK" &&
|
|
(this.ship.heatInsulation > 1000/this.ship.maxSpeed || this.ship.heatInsulation >= 12));
|
|
}
|
|
|
|
|
|
/*** Pirate conditions ***/
|
|
|
|
|
|
AILib.prototype.conditionCargoDemandsMet = function()
|
|
{
|
|
if (!this.getParameter("oolite_flag_watchForCargo"))
|
|
{
|
|
log(this.name,"AI '"+this.ship.AIScript.name+"' for ship "+this.ship+" is asking if cargo demands are met but has not set 'oolite_flag_watchForCargo'");
|
|
return true;
|
|
}
|
|
var seen = this.getParameter("oolite_cargoDropped");
|
|
if (seen != null)
|
|
{
|
|
var recorder = null;
|
|
var demand = 0;
|
|
if (this.ship.group)
|
|
{
|
|
if (this.ship.group.leader && this.ship.group.leader.AIScript.oolite_intership && this.ship.group.leader.AIScript.oolite_intership.cargodemanded > 0)
|
|
{
|
|
if (this.ship.group.leader.AIScript.oolite_intership.cargodemandmet)
|
|
{
|
|
return true;
|
|
}
|
|
recorder = this.ship.group.leader;
|
|
demand = this.ship.group.leader.AIScript.oolite_intership.cargodemanded;
|
|
}
|
|
else if (this.ship.group.ships[0].AIScript.oolite_intership && this.ship.group.ships[0].AIScript.oolite_intership.cargodemanded > 0)
|
|
|
|
{
|
|
demand = this.ship.group.ships[0].AIScript.oolite_intership.cargodemanded;
|
|
if (this.ship.group.ships[0].AIScript.oolite_intership.cargodemandmet)
|
|
{
|
|
return true;
|
|
}
|
|
recorder = this.ship.group.ships[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.ship.AIScript.oolite_intership.cargodemanded > 0)
|
|
{
|
|
if (this.ship.AIScript.oolite_intership.cargodemandmet)
|
|
{
|
|
return true;
|
|
}
|
|
demand = this.ship.AIScript.oolite_intership.cargodemanded;
|
|
recorder = this.ship;
|
|
}
|
|
}
|
|
|
|
if (demand == 0)
|
|
{
|
|
return true; // no demand made
|
|
}
|
|
if (demand <= seen)
|
|
{
|
|
recorder.AIScript.oolite_intership.cargodemandmet = true;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionGroupHasEnoughLoot = function()
|
|
{
|
|
var used = 0;
|
|
var available = 0;
|
|
if (!this.ship.group)
|
|
{
|
|
used = this.ship.cargoSpaceUsed;
|
|
if (this.ship.equipmentStatus("EQ_FUEL_SCOOPS") == "EQUIPMENT_OK")
|
|
{
|
|
available = this.ship.cargoSpaceAvailable;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (var i = 0; i < this.ship.group.ships.length; i++)
|
|
{
|
|
used += this.ship.group.ships[i].cargoSpaceUsed;
|
|
if (this.ship.equipmentStatus("EQ_FUEL_SCOOPS") == "EQUIPMENT_OK")
|
|
{
|
|
available += this.ship.group.ships[i].cargoSpaceAvailable;
|
|
}
|
|
}
|
|
}
|
|
if (available < used || available == 0)
|
|
{
|
|
/* Over half-full. This will do for now. TODO: cutting
|
|
* losses if group is taking damage, losing ships, running
|
|
* low on consumables, etc. */
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionPiratesCanBePaidOff = function()
|
|
{
|
|
if (this.ship.AIScript.oolite_intership.cargodemandpaid)
|
|
{
|
|
return false;
|
|
}
|
|
// TODO: need some way for the player to set this
|
|
if (!this.ship.AIScript.oolite_intership.cargodemand)
|
|
{
|
|
return false;
|
|
}
|
|
if (this.ship.cargoSpaceUsed < this.ship.AIScript.oolite_intership.cargodemand)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*** Scanner conditions ***/
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsEscapePods = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.primaryRole == "escape-capsule" && s.isInSpace && s.scanClass == "CLASS_CARGO" && s.velocity.magnitude() < this.ship.maxSpeed && this.conditionCanScoopCargo();
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsFineableOffender = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
var threshold = this.fineThreshold();
|
|
return s.isInSpace && s.bounty <= threshold && s.bounty > 0 && !s.markedForFines && (s.scanClass == "CLASS_NEUTRAL" || s.isPlayer) && !s.isDerelict;
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsFugitive = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.isInSpace && s.bounty > 50 && s.scanClass != "CLASS_CARGO" && s.scanClass != "CLASS_ROCK";
|
|
});
|
|
}
|
|
|
|
AILib.prototype.conditionScannerContainsHuntableOffender = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
var threshold = this.fineThreshold();
|
|
return s.isInSpace && s.bounty > threshold && s.scanClass != "CLASS_CARGO" && s.scanClass != "CLASS_ROCK";
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsHunters = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.primaryRole == "hunter" || s.scanClass == "CLASS_POLICE" || (s.isStation && s.isMainStation);
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsMiningOpportunity = function()
|
|
{
|
|
// if hold full, no
|
|
if (!this.conditionCanScoopCargo())
|
|
{
|
|
return false;
|
|
}
|
|
// need a mining laser, and for now a forward one
|
|
if (!this.ship.forwardWeapon == "EQ_WEAPON_MINING_LASER")
|
|
{
|
|
return false;
|
|
}
|
|
return this.conditionScannerContainsRocks();
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsNonThargoid = function()
|
|
{
|
|
var prioritytargets = this.checkScannerWithPredicate(function(s) {
|
|
return s.scanClass != "CLASS_THARGOID" && s.scanClass != "CLASS_ROCK" && s.scanClass != "CLASS_CARGO";
|
|
});
|
|
if (prioritytargets)
|
|
{
|
|
return true;
|
|
}
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.scanClass != "CLASS_THARGOID";
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsPirateVictims = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
// is a pirate victim
|
|
// has some cargo on board
|
|
// hasn't already paid up
|
|
return s.isPirateVictim && s.cargoSpaceUsed > 0 && (!s.AIScript || !s.AIScript.oolite_intership || !s.AIScript.oolite_intership.cargodemandpaid);
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsReadyThargoidMothership = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.hasRole("thargoid-mothership") && (!s.escortGroup || s.escortGroup.count <= 16);
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsRocks = function()
|
|
{
|
|
var scan1 = this.checkScannerWithPredicate(function(s) {
|
|
return s.isInSpace && s.isBoulder;
|
|
});
|
|
if (scan1)
|
|
{
|
|
return true;
|
|
}
|
|
// no boulders, what about asteroids?
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.isInSpace && s.hasRole("asteroid");
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsSalvage = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.isInSpace && s.scanClass == "CLASS_CARGO";
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsSalvageForGroup = function()
|
|
{
|
|
var maxspeed = 0;
|
|
if (this.conditionCanScoopCargo())
|
|
{
|
|
maxspeed = this.ship.maxSpeed;
|
|
}
|
|
if (this.ship.group)
|
|
{
|
|
for (var i = 0; i < this.ship.group.ships.length ; i++)
|
|
{
|
|
var ship = this.ship.group.ships[i];
|
|
if (ship.cargoSpaceAvailable > 0 && ship.equipmentStatus("EQ_FUEL_SCOOPS") == "EQUIPMENT_OK" && ship.maxSpeed > maxspeed)
|
|
{
|
|
maxspeed = ship.maxSpeed;
|
|
}
|
|
}
|
|
}
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.isInSpace && s.scanClass == "CLASS_CARGO" && s.velocity.magnitude() < maxspeed;
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsSalvageForMe = function()
|
|
{
|
|
if (!this.conditionCanScoopCargo())
|
|
{
|
|
return false;
|
|
}
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.isInSpace && s.scanClass == "CLASS_CARGO" && s.velocity.magnitude() < this.ship.maxSpeed;
|
|
});
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsShipNeedingEscort = function()
|
|
{
|
|
if (this.ship.bounty == 0)
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.scanClass == this.ship.scanClass && s.bounty == 0 && (!s.escortGroup || s.escortGroup.count <= s.maxEscorts);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.scanClass == this.ship.scanClass && s.bounty > 0 && (!s.escortGroup || s.escortGroup.count <= s.maxEscorts);
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionScannerContainsThargoidMothership = function()
|
|
{
|
|
return this.checkScannerWithPredicate(function(s) {
|
|
return s.hasRole("thargoid-mothership");
|
|
});
|
|
}
|
|
|
|
|
|
/*** State conditions ***/
|
|
|
|
|
|
AILib.prototype.conditionAllEscortsInFlight = function()
|
|
{
|
|
if (!this.ship.escortGroup)
|
|
{
|
|
return true; // there are no escorts not in flight
|
|
}
|
|
for (var i = 0 ; i < this.ship.escortGroup.ships.length ; i++)
|
|
{
|
|
if (this.ship.escortGroup.ships[i].status != "STATUS_IN_FLIGHT")
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// if just exited witchspace, escorts might not have rejoined escort
|
|
// group yet.
|
|
if (!this.ship.group)
|
|
{
|
|
return true;
|
|
}
|
|
for (var i = 0 ; i < this.ship.group.ships.length ; i++)
|
|
{
|
|
if (this.ship.group.ships[i].status != "STATUS_IN_FLIGHT")
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
AILib.prototype.conditionCanScoopCargo = function()
|
|
{
|
|
if (this.ship.cargoSpaceAvailable == 0 || this.ship.equipmentStatus("EQ_FUEL_SCOOPS") != "EQUIPMENT_OK")
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionCargoIsProfitableHere = function()
|
|
{
|
|
/* TODO: in the Mainly X systems, it's not impossible for
|
|
* PLENTIFUL_GOODS to generate a hold which is profitable in that
|
|
* system, and SCARCE_GOODS not to do so. Cargo should never be
|
|
* profitable in its origin system. */
|
|
|
|
if (!system.mainStation)
|
|
{
|
|
return false;
|
|
}
|
|
if (this.ship.cargoSpaceUsed == 0)
|
|
{
|
|
return false;
|
|
}
|
|
var cargo = this.ship.cargoList;
|
|
var profit = 0;
|
|
var multiplier = (system.info.economy <= 3)?-1:1;
|
|
for (var i = 0 ; i < cargo.length ; i++)
|
|
{
|
|
var commodity = cargo[i].commodity;
|
|
var quantity = cargo[i].quantity;
|
|
var adjust = system.mainStation.market[commodity].marketEcoAdjustPrice * multiplier * quantity / system.mainStation.market[commodity].marketMaskPrice;
|
|
profit += adjust;
|
|
}
|
|
return (profit >= 0);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionGroupLeaderIsStation = function()
|
|
{
|
|
return (this.ship.group && this.ship.group.leader && this.ship.group.leader.isStation);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHasInterceptCoordinates = function()
|
|
{
|
|
return (this.getParameter("oolite_interceptCoordinates") != null);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHasMothership = function()
|
|
{
|
|
return (this.ship.group && this.ship.group.leader && this.ship.group.leader != this.ship);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHasNonThargoidTarget = function()
|
|
{
|
|
return (this.ship.target && this.ship.target.scanClass != "CLASS_THARGOID");
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHasReceivedDistressCall = function()
|
|
{
|
|
var aggressor = this.getParameter("oolite_distressAggressor");
|
|
var sender = this.getParameter("oolite_distressSender");
|
|
var ts = this.getParameter("oolite_distressTimestamp");
|
|
|
|
if (aggressor == null || !aggressor.isInSpace || sender == null || !sender.isInSpace || sender.position.distanceTo(this.ship) > this.ship.scannerRange || ts+30 < clock.adjustedSeconds)
|
|
{
|
|
// no, or it has expired
|
|
this.setParameter("oolite_distressAggressor",null);
|
|
this.setParameter("oolite_distressSender",null);
|
|
this.setParameter("oolite_distressTimestamp",null);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHasTarget = function()
|
|
{
|
|
return this.ship.target != null;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionHasWaypoint = function()
|
|
{
|
|
return this.getParameter("oolite_waypoint") != null;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionIsActiveThargon = function()
|
|
{
|
|
return this.ship.scanClass == "CLASS_THARGOID" && this.ship.hasRole("EQ_THARGON");
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionIsEscorting = function()
|
|
{
|
|
if (!this.ship.group || !this.ship.group.leader || this.ship.group.leader == this.ship)
|
|
{
|
|
return false;
|
|
}
|
|
if (this.ship.group.leader.escortGroup && this.ship.group.leader.escortGroup.containsShip(this.ship))
|
|
{
|
|
if (this.ship.group.leader.status == "STATUS_ENTERING_WITCHSPACE")
|
|
{
|
|
var hole = this.getParameter("oolite_witchspaceWormhole");
|
|
if (hole == null || hole.expiryTime < clock.seconds)
|
|
{
|
|
// has been left behind
|
|
this.configurationLeaveEscortGroup();
|
|
this.setParameter("oolite_witchspaceWormhole",false);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionIsGroupLeader = function()
|
|
{
|
|
if (!this.ship.group)
|
|
{
|
|
return true;
|
|
}
|
|
return (this.ship.group.leader == this.ship);
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionMissileOutOfFuel = function()
|
|
{
|
|
var range = 30000; // 30 km default
|
|
if (this.ship.scriptInfo.oolite_missile_range)
|
|
{
|
|
range = this.ship.scriptInfo.oolite_missile_range;
|
|
}
|
|
return range < this.ship.distanceTravelled;
|
|
}
|
|
|
|
|
|
AILib.prototype.conditionWitchspaceEntryRequested = function()
|
|
{
|
|
return (this.getParameter("oolite_witchspaceWormhole") != null);
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ****************** Behaviour functions ************** */
|
|
|
|
/* Behaviours. Behaviours are effectively a state definition,
|
|
* defining a set of events and responses. They are aided in this
|
|
* by the 'responses', which mean that the event handlers for the
|
|
* behaviour within the definition can itself be templated. */
|
|
|
|
|
|
|
|
AILib.prototype.behaviourApproachDestination = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
|
|
handlers.shipAchievedDesiredRange = function()
|
|
{
|
|
var waypoints = this.getParameter("oolite_waypoints");
|
|
if (waypoints != null)
|
|
{
|
|
if (waypoints.length > 0)
|
|
{
|
|
waypoints.pop();
|
|
if (waypoints.length == 0)
|
|
{
|
|
waypoints = null;
|
|
}
|
|
this.setParameter("oolite_waypoints",waypoints);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var patrol = this.getParameter("oolite_waypoint");
|
|
if (patrol != null && this.ship.destination.distanceTo(patrol) < 1000+this.getParameter("oolite_waypointRange"))
|
|
{
|
|
// finished patrol to waypoint
|
|
// clear route
|
|
this.communicate("oolite_waypointReached",{},3);
|
|
this.setParameter("oolite_waypoint",null);
|
|
this.setParameter("oolite_waypointRange",null);
|
|
if (this.getParameter("oolite_flag_patrolStation"))
|
|
{
|
|
if (this.ship.group)
|
|
{
|
|
var station = this.ship.group.leader;
|
|
if (station != null && station.isStation)
|
|
{
|
|
this.communicate("oolite_patrolReportIn",this.entityCommsParams(station),4);
|
|
this.ship.patrolReportIn(station);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.reconsiderNow();
|
|
};
|
|
|
|
var waypoints = this.getParameter("oolite_waypoints");
|
|
if (waypoints != null)
|
|
{
|
|
this.ship.destination = waypoints[waypoints.length-1];
|
|
this.ship.desiredRange = 1000;
|
|
}
|
|
var blocker = this.ship.checkCourseToDestination();
|
|
if (blocker)
|
|
{
|
|
if (blocker.isPlanet || blocker.isSun)
|
|
{
|
|
// the selected planet can't block
|
|
if (blocker.isSun || this.getParameter("oolite_selectedPlanet") != blocker)
|
|
{
|
|
if (this.ship.position.distanceTo(blocker) < blocker.radius * 3)
|
|
{
|
|
if (waypoints == null)
|
|
{
|
|
waypoints = [];
|
|
}
|
|
waypoints.push(this.ship.getSafeCourseToDestination());
|
|
this.ship.destination = waypoints[waypoints.length-1];
|
|
this.ship.desiredRange = 1000;
|
|
}
|
|
}
|
|
}
|
|
else if (blocker.isShip)
|
|
{
|
|
if (this.ship.position.distanceTo(blocker) < 25600)
|
|
{
|
|
if (!blocker.group || !blocker.group.leader == this.ship)
|
|
{
|
|
// our own escorts are not a blocker!
|
|
if (waypoints == null)
|
|
{
|
|
waypoints = [];
|
|
}
|
|
waypoints.push(this.ship.getSafeCourseToDestination());
|
|
this.ship.destination = waypoints[waypoints.length-1];
|
|
this.ship.desiredRange = 1000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.setParameter("oolite_waypoints",waypoints);
|
|
this.applyHandlers(handlers);
|
|
this.ship.performFlyToRangeFromDestination();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourAvoidCascadeExplosion = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
|
|
var cascade = this.getParameter("oolite_cascadeDetected");
|
|
if (cascade != null)
|
|
{
|
|
if (cascade.distanceTo(this.ship) < 25600)
|
|
{
|
|
if (this.ship.defenseTargets.length > 0 && this.ship.defenseTargets[0].scanClass == "CLASS_MINE")
|
|
{
|
|
// if the mine is still visible, conventional fleeing works
|
|
this.ship.target = this.ship.defenseTargets[0];
|
|
this.ship.desiredRange = 30000;
|
|
this.ship.performFlee();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (this.ship.destination != cascade)
|
|
{
|
|
this.communicate("oolite_quiriumCascade",{},3);
|
|
}
|
|
this.ship.destination = cascade;
|
|
this.ship.desiredRange = 30000;
|
|
this.ship.desiredSpeed = 10*this.ship.maxSpeed;
|
|
this.ship.performFlyToRangeFromDestination();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.setParameter("oolite_cascadeDetected",null);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourBecomeInactiveThargon = function()
|
|
{
|
|
this.applyHandlers({});
|
|
this.ship.scanClass = "CLASS_CARGO";
|
|
this.ship.target = null;
|
|
this.ship.clearDefenseTargets();
|
|
if (this.ship.group)
|
|
{
|
|
this.ship.group.removeShip(this.ship);
|
|
this.ship.group = null;
|
|
}
|
|
if (this.ship.escortGroup)
|
|
{
|
|
this.ship.escortGroup.removeShip(this.ship);
|
|
}
|
|
this.ship.desiredSpeed = 0;
|
|
this.ship.performStop();
|
|
var nearby = this.ship.checkScanner();
|
|
for (var i = 0 ; i < nearby.length ; i++)
|
|
{
|
|
var ship = nearby[i];
|
|
if (ship.target == this.ship && !ship.isPlayer && ship.hasHostileTarget)
|
|
{
|
|
ship.target = null;
|
|
}
|
|
ship.removeDefenseTarget(this.ship);
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourCollectSalvage = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
handlers.shipScoopedOther = function(other)
|
|
{
|
|
this.setParameter("oolite_cargoDropped",null);
|
|
this.reconsiderNow();
|
|
}
|
|
this.applyHandlers(handlers);
|
|
this.ship.performCollect();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourDestroyCurrentTarget = function()
|
|
{
|
|
this.setParameter("oolite_witchspaceEntry",null);
|
|
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
if (this.ship.target && !this.ship.hasHostileTarget)
|
|
{
|
|
// entering attack mode
|
|
this.communicate("oolite_beginningAttack",this.entityCommsParams(this.ship.target),2);
|
|
}
|
|
else
|
|
{
|
|
this.communicate("oolite_continuingAttack",this.entityCommsParams(this.ship.target),4);
|
|
}
|
|
this.ship.performAttack();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourDockWithStation = function()
|
|
{
|
|
// may need to release escorts
|
|
if (this.ship.escortGroup && this.ship.escortGroup.count > 1)
|
|
{
|
|
this.ship.dockEscorts();
|
|
}
|
|
var station = this.getParameter("oolite_dockingStation");
|
|
this.ship.target = station;
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.responsesAddDocking(handlers);
|
|
this.ship.requestDockingInstructions();
|
|
if (!this.ship.dockingInstructions)
|
|
{
|
|
this.ship.performIdle();
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
switch (this.ship.dockingInstructions.ai_message)
|
|
{
|
|
case "TOO_BIG_TO_DOCK":
|
|
case "DOCKING_REFUSED":
|
|
this.ship.setParameter("oolite_dockingStation",null);
|
|
this.ship.target = null;
|
|
this.reconsiderNow();
|
|
break;
|
|
case "TRY_AGAIN_LATER":
|
|
if (this.ship.target.position.distanceTo(this.ship) < 10000)
|
|
{
|
|
this.ship.destination = this.ship.target.position;
|
|
this.ship.desiredRange = 12500;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
this.ship.performFlyToRangeFromDestination();
|
|
break;
|
|
}
|
|
// else fall through
|
|
case "HOLD_POSITION":
|
|
this.ship.destination = this.ship.target.position;
|
|
this.ship.performFaceDestination();
|
|
// and will reconsider in a little bit
|
|
break;
|
|
case "APPROACH":
|
|
case "APPROACH_COORDINATES":
|
|
case "BACK_OFF":
|
|
this.ship.performFlyToRangeFromDestination();
|
|
break;
|
|
}
|
|
this.applyHandlers(handlers);
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourEnterWitchspace = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
var wormhole = this.getParameter("oolite_witchspaceWormhole");
|
|
if (wormhole && wormhole.expiryTime < clock.adjustedSeconds)
|
|
{
|
|
// the wormhole we were trying for has expired
|
|
this.setParameter("oolite_witchspaceWormhole",null);
|
|
}
|
|
else if (wormhole)
|
|
{
|
|
|
|
handlers.playerWillEnterWitchspace = function()
|
|
{
|
|
var wormhole = this.getParameter("oolite_witchspaceWormhole");
|
|
if (wormhole != null)
|
|
{
|
|
this.ship.enterWormhole(wormhole);
|
|
}
|
|
else
|
|
{
|
|
this.ship.enterWormhole();
|
|
}
|
|
}
|
|
this.ship.destination = wormhole.position;
|
|
this.ship.desiredRange = 0;
|
|
this.ship.desiredSpeed = this.ship.maxSpeed;
|
|
this.ship.performFlyToRangeFromDestination();
|
|
this.applyHandlers(handlers);
|
|
return;
|
|
}
|
|
|
|
var destID = this.getParameter("oolite_witchspaceDestination");
|
|
if (destID == null)
|
|
{
|
|
// look for wormholes out of here
|
|
// no systems in range
|
|
handlers.wormholeSuggested = function(hole)
|
|
{
|
|
this.ship.destination = hole.position;
|
|
this.ship.desiredRange = 0;
|
|
this.ship.desiredSpeed = this.ship.maxSpeed;
|
|
this.ship.performFlyToRangeFromDestination();
|
|
this.setParameter("oolite_witchspaceWormhole",hole);
|
|
// don't reconsider
|
|
}
|
|
handlers.playerWillEnterWitchspace = function()
|
|
{
|
|
var wormhole = this.getParameter("oolite_witchspaceWormhole");
|
|
if (wormhole != null)
|
|
{
|
|
this.ship.enterWormhole(wormhole);
|
|
}
|
|
else
|
|
{
|
|
this.ship.enterWormhole();
|
|
}
|
|
}
|
|
this.applyHandlers(handlers);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
handlers.shipWitchspaceBlocked = function(blocker)
|
|
{
|
|
this.ship.setDestination = blocker.position;
|
|
this.ship.setDesiredRange = 30000;
|
|
this.ship.setDesiredSpeed = this.cruiseSpeed();
|
|
this.ship.performFlyToRangeFromDestination();
|
|
this.setParameter("oolite_witchspaceEntry",null);
|
|
// no reconsidering yet
|
|
}
|
|
// set up the handlers before trying it
|
|
this.applyHandlers(handlers);
|
|
|
|
var entry = this.getParameter("oolite_witchspaceEntry");
|
|
// wait for escorts to launch
|
|
if (!this.conditionAllEscortsInFlight())
|
|
{
|
|
this.ship.destination = this.ship.position;
|
|
this.ship.desiredRange = 10000;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
if (this.ship.checkCourseToDestination())
|
|
{
|
|
this.ship.destination = this.ship.getSafeCourseToDestination();
|
|
}
|
|
this.ship.performFlyToRangeFromDestination();
|
|
|
|
}
|
|
else if (entry != null && entry < clock.seconds)
|
|
{
|
|
// this should work
|
|
var result = this.ship.exitSystem(destID);
|
|
// if it doesn't, we'll get blocked
|
|
if (result)
|
|
{
|
|
this.setParameter("oolite_witchspaceEntry",null);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (entry == null)
|
|
{
|
|
this.communicate("oolite_engageWitchspaceDrive",{},4);
|
|
this.setParameter("oolite_witchspaceEntry",clock.seconds + 15);
|
|
}
|
|
this.ship.destination = this.ship.position;
|
|
this.ship.desiredRange = 10000;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
if (this.ship.checkCourseToDestination())
|
|
{
|
|
this.ship.destination = this.ship.getSafeCourseToDestination();
|
|
}
|
|
this.ship.performFlyToRangeFromDestination();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourEscortMothership = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.responsesAddEscort(handlers);
|
|
this.applyHandlers(handlers);
|
|
this.ship.desiredRange = 0;
|
|
this.ship.performEscort();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourFineCurrentTarget = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
|
|
if (this.ship.scanClass == "CLASS_POLICE" && this.ship.target)
|
|
{
|
|
this.communicate("oolite_markForFines",this.entityCommsParams(this.ship.target),1);
|
|
|
|
this.ship.markTargetForFines();
|
|
}
|
|
|
|
this.ship.performIdle();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourFleeCombat = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
|
|
var cascade = this.getParameter("oolite_cascadeDetected");
|
|
if (cascade != null)
|
|
{
|
|
if (cascade.distanceTo(this.ship) < 25600)
|
|
{
|
|
if (this.ship.defenseTargets.length > 0 && this.ship.defenseTargets[0].scanClass == "CLASS_MINE")
|
|
{
|
|
// if the mine is still visible, conventional fleeing works
|
|
this.ship.target = this.ship.defenseTargets[0];
|
|
this.ship.desiredRange = 30000;
|
|
this.ship.performFlee();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (this.ship.destination != cascade)
|
|
{
|
|
this.communicate("oolite_quiriumCascade",{},4);
|
|
}
|
|
this.ship.destination = cascade;
|
|
this.ship.desiredRange = 30000;
|
|
this.ship.desiredSpeed = 10*this.ship.maxSpeed;
|
|
this.ship.performFlyToRangeFromDestination();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.setParameter("oolite_cascadeDetected",null);
|
|
}
|
|
}
|
|
this.ship.target = this.ship.AIPrimaryAggressor;
|
|
if (!this.ship.target || this.ship.position.distanceTo(this.ship.target) > 25600)
|
|
{
|
|
var dts = this.ship.defenseTargets;
|
|
for (var i = 0 ; i < dts.length ; i++)
|
|
{
|
|
this.ship.position.distanceTo(dts[i]) < 25600;
|
|
this.ship.target = dts[i];
|
|
break;
|
|
}
|
|
}
|
|
this.setParameter("oolite_lastFleeing",this.ship.target);
|
|
this.ship.desiredRange = this.ship.scannerRange;
|
|
this.ship.performFlee();
|
|
}
|
|
|
|
|
|
/* Follow the group leader in a less organised way than escorting them */
|
|
AILib.prototype.behaviourFollowGroupLeader = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
if (!this.ship.group || !this.ship.group.leader)
|
|
{
|
|
this.ship.performIdle();
|
|
}
|
|
else
|
|
{
|
|
this.ship.destination = this.ship.group.leader.position;
|
|
this.ship.desiredRange = 2000+Math.random()*2000;
|
|
this.ship.desiredSpeed = this.ship.maxSpeed;
|
|
this.ship.performFlyToRangeFromDestination();
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourGuardTarget = function()
|
|
{
|
|
if (!this.ship.target)
|
|
{
|
|
this.ship.destination = this.ship.position;
|
|
}
|
|
else
|
|
{
|
|
this.ship.destination = this.ship.target.position;
|
|
}
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
this.ship.desiredRange = 2500;
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
this.ship.performFlyToRangeFromDestination();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourLandOnPlanet = function()
|
|
{
|
|
this.ship.desiredSpeed = this.ship.maxSpeed / 4;
|
|
this.ship.performLandOnPlanet();
|
|
this.ship.AIScriptWakeTime = 0; // cancel reconsiderations
|
|
this.applyHandlers({}); // cancel interruptions
|
|
this.communicate("oolite_landingOnPlanet",{},4);
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourLeaveVicinityOfTarget = function()
|
|
{
|
|
if (!this.ship.target)
|
|
{
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
this.ship.destination = this.ship.target.position;
|
|
this.ship.desiredRange = 27500;
|
|
this.ship.desiredSpeed = this.ship.maxSpeed;
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
this.ship.performFlyToRangeFromDestination();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourMineTarget = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
this.ship.performMining();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourOfferToEscort = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
|
|
var possible = this.getParameter("oolite_scanResultSpecific");
|
|
if (possible == null)
|
|
{
|
|
this.reconsiderNow();
|
|
}
|
|
else
|
|
{
|
|
if (this.ship.offerToEscort(possible))
|
|
{
|
|
// accepted
|
|
this.reconsiderNow();
|
|
}
|
|
// if rejected, wait for next scheduled reconsideration
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourPayOffPirates = function()
|
|
{
|
|
this.ship.dumpCargo(this.ship.AIScript.oolite_intership.cargodemand);
|
|
this.communicate("oolite_agreeingToDumpCargo",{"oolite_demandSize":this.ship.AIScript.oolite_intership.cargodemand},2);
|
|
delete this.ship.AIScript.oolite_intership.cargodemand;
|
|
this.ship.AIScript.oolite_intership.cargodemandpaid = true;
|
|
this.behaviourFleeCombat();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourReconsider = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
this.reconsiderNow();
|
|
}
|
|
|
|
|
|
// Separate behaviour to EscortMothership in case we want to change it later
|
|
// This is the one to catch up with a distant mothership
|
|
AILib.prototype.behaviourRejoinMothership = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.responsesAddEscort(handlers);
|
|
this.applyHandlers(handlers);
|
|
// to consider: should this behaviour use injectors if
|
|
// possible? so few escorts have them that it's probably not
|
|
// worth it.
|
|
this.ship.desiredRange = 0;
|
|
this.ship.performEscort();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourRepelCurrentTarget = function()
|
|
{
|
|
this.setParameter("oolite_witchspaceEntry",null);
|
|
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
if (!this.ship.target || !this.ship.target.isValid || !this.ship.target.isShip)
|
|
{
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
if (!this.isAggressive(this.ship.target))
|
|
{
|
|
var target = this.ship.target;
|
|
// repelling succeeded
|
|
if (this.ship.escortGroup)
|
|
{
|
|
// also tell escorts to stop attacking it
|
|
for (var i = 0 ; i < this.ship.escortGroup.ships.length ; i++)
|
|
{
|
|
this.ship.escortGroup.ships[i].removeDefenseTarget(target);
|
|
if (this.ship.escortGroup.ships[i].target == target)
|
|
{
|
|
this.ship.escortGroup.ships[i].target = null;
|
|
}
|
|
}
|
|
}
|
|
this.ship.removeDefenseTarget(target);
|
|
this.ship.target = null;
|
|
}
|
|
else
|
|
{
|
|
if (!this.ship.hasHostileTarget)
|
|
{
|
|
// entering attack mode
|
|
this.communicate("oolite_beginningAttack",this.entityCommsParams(this.ship.target),3);
|
|
}
|
|
this.ship.performAttack();
|
|
}
|
|
}
|
|
|
|
|
|
/* Standard "help the innocent" distress call response. Perhaps
|
|
* there should be a 'blood in the water' response available
|
|
* too... */
|
|
AILib.prototype.behaviourRespondToDistressCall = function()
|
|
{
|
|
var aggressor = this.getParameter("oolite_distressAggressor");
|
|
var sender = this.getParameter("oolite_distressSender");
|
|
if (aggressor && aggressor.isShip && sender && sender.isShip)
|
|
{
|
|
if (sender.bounty > aggressor.bounty)
|
|
{
|
|
var tmp = sender;
|
|
sender = aggressor;
|
|
aggressor = tmp;
|
|
}
|
|
if (aggressor.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
this.ship.target = aggressor;
|
|
this.ship.performAttack();
|
|
this.reconsiderNow();
|
|
this.communicate("oolite_distressResponseAggressor",this.entityCommsParams(aggressor),2);
|
|
}
|
|
else
|
|
{ // we can't actually see what's attacking the sender yet
|
|
this.ship.destination = sender.position;
|
|
this.ship.desiredRange = 1000+sender.collisionRadius+this.ship.collisionRadius;
|
|
this.ship.desiredSpeed = 7 * this.ship.maxSpeed; // use injectors if possible
|
|
this.ship.performFlyToRangeFromDestination();
|
|
// and when we next reconsider, hopefully the aggressor will be on the scanner
|
|
this.communicate("oolite_distressResponseSender",this.entityCommsParams(sender),2);
|
|
}
|
|
}
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourRobTarget = function()
|
|
{
|
|
var demand = null;
|
|
if (this.ship.group && this.ship.group.leader)
|
|
{
|
|
if (this.ship.group.leader.AIScript.oolite_intership && this.ship.group.leader.AIScript.oolite_intership.cargodemanded)
|
|
{
|
|
demand = this.ship.group.leader.AIScript.oolite_intership.cargodemanded;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.ship.AIScript.oolite_intership.cargodemanded)
|
|
{
|
|
demand = this.ship.AIScript.oolite_intership.cargodemanded;
|
|
}
|
|
}
|
|
if (demand == null)
|
|
{
|
|
var hascargo = this.ship.target.cargoSpaceUsed+this.ship.target.cargoSpaceAvailable;
|
|
// blowing them up probably gets ~10%, so how much we feel
|
|
// confident in demanding depends on how likely patrols
|
|
// are to come along and interfere.
|
|
demand = (hascargo/20);
|
|
demand = demand * (1+Math.random()+(8-system.info.government)/8);
|
|
// between 5% and 15% of cargo
|
|
demand = Math.ceil(demand); // round it up so there's always at least 1
|
|
|
|
var maxdemand = 0;
|
|
var gc = 1;
|
|
if (!this.ship.group)
|
|
{
|
|
if (this.ship.equipmentStatus("EQ_FUEL_SCOOPS") == "EQUIPMENT_OK")
|
|
{
|
|
maxdemand = this.ship.cargoSpaceAvailable;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gc = this.ship.group.ships.length;
|
|
for (var i = 0; i < gc ; i++)
|
|
{
|
|
var ship = this.ship.group.ships[i];
|
|
if (ship.equipmentStatus("EQ_FUEL_SCOOPS") == "EQUIPMENT_OK")
|
|
{
|
|
maxdemand += ship.cargoSpaceAvailable;
|
|
}
|
|
else
|
|
{
|
|
gc--; // this ship can't help scoop
|
|
}
|
|
}
|
|
}
|
|
if (demand > maxdemand)
|
|
{
|
|
demand = maxdemand; // don't ask for more than we can carry
|
|
}
|
|
while (demand > gc * 5)
|
|
{
|
|
// asking for more than 5TC each probably means there
|
|
// won't be time to pick it all up anyway
|
|
demand = Math.ceil(demand/2);
|
|
}
|
|
if (demand < 2)
|
|
{
|
|
demand = 2;
|
|
}
|
|
|
|
/* Record our demand with the group leader */
|
|
if (this.ship.group && this.ship.group.leader)
|
|
{
|
|
this.ship.group.leader.AIScript.oolite_intership.cargodemanded = demand;
|
|
}
|
|
else
|
|
{
|
|
this.ship.AIScript.oolite_intership.cargodemanded = demand;
|
|
}
|
|
/* Inform the victim of the demand, if possible */
|
|
if (this.ship.target.AIScript && this.ship.target.AIScript.oolite_intership)
|
|
{
|
|
this.ship.target.AIScript.oolite_intership.cargodemand = demand;
|
|
}
|
|
var commsparams = this.entityCommsParams(this.ship.target);
|
|
commsparams["oolite_demandSize"] = demand;
|
|
this.communicate("oolite_makePirateDemand",commsparams,1);
|
|
this.ship.requestHelpFromGroup();
|
|
/* }
|
|
else
|
|
{
|
|
log(this.ship.displayName,"Already asked for "+demand); */
|
|
}
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.applyHandlers(handlers);
|
|
this.ship.performAttack();
|
|
this.ship.requestHelpFromGroup();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourSunskim = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStandard(handlers);
|
|
this.responsesAddScooping(handlers);
|
|
this.applyHandlers(handlers);
|
|
this.ship.performFlyToRangeFromDestination();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourTumble = function()
|
|
{
|
|
this.applyHandlers({});
|
|
this.ship.performTumble();
|
|
}
|
|
|
|
|
|
/* Missile behaviours: have different standard handler sets */
|
|
|
|
AILib.prototype.behaviourMissileInterceptTarget = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddMissile(handlers);
|
|
this.applyHandlers(handlers);
|
|
if (this.ship.scriptInfo.oolite_missile_proximity)
|
|
{
|
|
this.ship.desiredRange = this.ship.scriptInfo.oolite_missile_proximity;
|
|
}
|
|
else
|
|
{
|
|
this.ship.desiredRange = 25;
|
|
}
|
|
|
|
this.ship.performIntercept();
|
|
}
|
|
|
|
AILib.prototype.behaviourMissileInterceptCoordinates = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddMissile(handlers);
|
|
this.applyHandlers(handlers);
|
|
if (this.ship.scriptInfo.oolite_missile_proximity)
|
|
{
|
|
this.ship.desiredRange = this.ship.scriptInfo.oolite_missile_proximity;
|
|
}
|
|
else
|
|
{
|
|
this.ship.desiredRange = 25;
|
|
}
|
|
var dest = this.getParameter("oolite_interceptCoordinates");
|
|
if (dest == null)
|
|
{
|
|
return;
|
|
}
|
|
this.ship.destination = dest
|
|
this.ship.desiredSpeed = this.ship.maxSpeed;
|
|
this.ship.performFlyToRangeFromDestination();
|
|
|
|
// if we have an intercept target, try to restore it
|
|
var oldtarget = this.getParameter("oolite_interceptTarget");
|
|
if (oldtarget && !oldtarget.isCloaked && oldtarget.isInSpace)
|
|
{
|
|
this.ship.target = oldtarget;
|
|
}
|
|
}
|
|
|
|
AILib.prototype.behaviourMissileSelfDestruct = function() {
|
|
this.ship.explode();
|
|
}
|
|
|
|
|
|
|
|
/* Station behaviours: have different standard handler sets */
|
|
|
|
AILib.prototype.behaviourStationLaunchDefenseShips = function()
|
|
{
|
|
if (this.ship.target && this.isAggressive(this.ship.target))
|
|
{
|
|
this.alertCondition = 3;
|
|
this.ship.launchDefenseShip();
|
|
this.ship.requestHelpFromGroup();
|
|
}
|
|
var handlers = {};
|
|
this.responsesAddStation(handlers);
|
|
this.applyHandlers(handlers);
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourStationLaunchMiner = function()
|
|
{
|
|
if (this.alertCondition > 1)
|
|
{
|
|
this.alertCondition--;
|
|
}
|
|
var handlers = {};
|
|
this.responsesAddStation(handlers);
|
|
this.applyHandlers(handlers);
|
|
if (this.ship.group)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.ships.length ; i++)
|
|
{
|
|
if (this.ship.group.ships[i].primaryRole == "miner")
|
|
{
|
|
// only one in flight at once
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
this.ship.launchMiner();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourStationLaunchPatrol = function()
|
|
{
|
|
if (this.alertCondition > 1)
|
|
{
|
|
this.alertCondition--;
|
|
}
|
|
var handlers = {};
|
|
this.responsesAddStation(handlers);
|
|
this.applyHandlers(handlers);
|
|
|
|
if (this.ship.group)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.ships.length ; i++)
|
|
{
|
|
if (this.ship.group.ships[i].primaryRole == this.getParameter("oolite_stationPatrolRole"))
|
|
{
|
|
// only one in flight at once
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.ship.launchPatrol();
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourStationLaunchSalvager = function()
|
|
{
|
|
if (this.alertCondition > 1)
|
|
{
|
|
this.alertCondition--;
|
|
}
|
|
this.ship.launchScavenger();
|
|
|
|
var handlers = {};
|
|
this.responsesAddStation(handlers);
|
|
this.applyHandlers(handlers);
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourStationManageTraffic = function()
|
|
{
|
|
var handlers = {};
|
|
this.responsesAddStation(handlers);
|
|
this.applyHandlers(handlers);
|
|
if (this.ship.hasNPCTraffic)
|
|
{
|
|
if (Math.random() < 0.3)
|
|
{
|
|
var trader = this.ship.launchShipWithRole("trader");
|
|
trader.setCargoType("PLENTIFUL_GOODS");
|
|
}
|
|
if (Math.random() < 0.1)
|
|
{
|
|
this.ship.launchShuttle();
|
|
}
|
|
|
|
// TODO: integrate with system repopulator rather than just
|
|
// launching ships at random
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.behaviourStationRespondToDistressCall = function()
|
|
{
|
|
var aggressor = this.getParameter("oolite_distressAggressor");
|
|
var sender = this.getParameter("oolite_distressSender");
|
|
if (sender.bounty > aggressor.bounty)
|
|
{
|
|
var tmp = sender;
|
|
sender = aggressor;
|
|
aggressor = tmp;
|
|
}
|
|
if (aggressor.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
this.ship.target = aggressor;
|
|
this.ship.alertCondition = 3;
|
|
this.ship.launchDefenseShip();
|
|
this.communicate("oolite_distressResponseAggressor",this.entityCommsParams(aggressor),2);
|
|
this.ship.requestHelpFromGroup();
|
|
}
|
|
else
|
|
{
|
|
this.communicate("oolite_distressResponseSender",this.entityCommsParams(sender),3);
|
|
}
|
|
|
|
var handlers = {};
|
|
this.responsesAddStation(handlers);
|
|
this.applyHandlers(handlers);
|
|
}
|
|
|
|
|
|
/* ****************** Configuration functions ************** */
|
|
|
|
/* Configurations. Configurations are set up actions for a behaviour
|
|
* or behaviours. They can also be used on a fall-through conditional
|
|
* to set parameters for later tests */
|
|
|
|
/*** Target acquisition configuration ***/
|
|
|
|
AILib.prototype.configurationAcquireCombatTarget = function()
|
|
{
|
|
if (this.ship.target && this.allied(this.ship,this.ship.target))
|
|
{
|
|
// don't shoot at allies even if they have ended up as a target...
|
|
this.ship.removeDefenseTarget(this.ship.target);
|
|
this.ship.target = null;
|
|
}
|
|
if (this.ship.target && this.ship.target.scanClass == "CLASS_CARGO")
|
|
{
|
|
this.ship.target = null;
|
|
}
|
|
/* Iff the ship does not currently have a target, select a new one
|
|
* from the defense target list. */
|
|
if (this.ship.target)
|
|
{
|
|
if (this.ship.target.isInSpace)
|
|
{
|
|
return;
|
|
}
|
|
this.ship.removeDefenseTarget(this.ship.target);
|
|
this.ship.target = null;
|
|
}
|
|
var dts = this.ship.defenseTargets
|
|
for (var i = 0; i < dts.length ; i++)
|
|
{
|
|
if (dts[i].position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
this.ship.target = dts[0];
|
|
return;
|
|
}
|
|
}
|
|
if (this.ship.group != null)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.count ; i++)
|
|
{
|
|
if (this.ship.group.ships[i] != this.ship)
|
|
{
|
|
if (this.ship.group.ships[i].target && this.isFighting(this.ship.group.ships[i]) && this.ship.group.ships[i].target.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
this.ship.target = this.ship.group.ships[i].target;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (this.ship.escortGroup != null)
|
|
{
|
|
for (var i = 0 ; i < this.ship.escortGroup.count ; i++)
|
|
{
|
|
if (this.ship.escortGroup.ships[i] != this.ship)
|
|
{
|
|
if (this.ship.escortGroup.ships[i].target && this.isFighting(this.ship.escortGroup.ships[i]) && this.ship.escortGroup.ships[i].target.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
this.ship.target = this.ship.escortGroup.ships[i].target;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationAcquireDefensiveEscortTarget = function()
|
|
{
|
|
if (this.ship.group && this.ship.group.leader)
|
|
{
|
|
var leader = this.ship.group.leader;
|
|
if (leader.target && leader.target.target == leader && this.isFighting(leader) && leader.target.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
this.ship.target = leader.target;
|
|
}
|
|
else
|
|
{
|
|
var dts = leader.defenseTargets;
|
|
for (var i = 0 ; i < dts.length ; i++)
|
|
{
|
|
if (dts[i].target == leader && this.isAggressive(dts[i]) && dts[i].position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
this.ship.target = dts[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// TODO: reuse code from AcquireCombatTarget better
|
|
AILib.prototype.configurationAcquireHostileCombatTarget = function()
|
|
{
|
|
if (this.ship.target && this.allied(this.ship,this.ship.target))
|
|
{
|
|
// don't shoot at allies even if they have ended up as a target...
|
|
this.ship.removeDefenseTarget(this.ship.target);
|
|
this.ship.target = null;
|
|
}
|
|
/* Iff the ship does not currently have a target, select a new one
|
|
* from the defense target list. */
|
|
if (this.ship.target)
|
|
{
|
|
if (this.ship.target.isInSpace && this.isAggressive(this.ship.target))
|
|
{
|
|
return;
|
|
}
|
|
this.ship.removeDefenseTarget(this.ship.target);
|
|
this.ship.target = null;
|
|
}
|
|
var dts = this.ship.defenseTargets
|
|
for (var i = 0; i < dts.length ; i++)
|
|
{
|
|
if (dts[i].position.distanceTo(this.ship) < this.ship.scannerRange && this.isAggressive(dts[i]))
|
|
{
|
|
this.ship.target = dts[0];
|
|
return;
|
|
}
|
|
}
|
|
if (this.ship.group != null)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.count ; i++)
|
|
{
|
|
if (this.ship.group.ships[i] != this.ship)
|
|
{
|
|
if (this.ship.group.ships[i].target && this.isFighting(this.ship.group.ships[i]) && this.ship.group.ships[i].target.position.distanceTo(this.ship) < this.ship.scannerRange && this.isAggressive(this.ship.group.ships[i].target))
|
|
{
|
|
this.ship.target = this.ship.group.ships[i].target;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (this.ship.escortGroup != null)
|
|
{
|
|
for (var i = 0 ; i < this.ship.escortGroup.count ; i++)
|
|
{
|
|
if (this.ship.escortGroup.ships[i] != this.ship)
|
|
{
|
|
if (this.ship.escortGroup.ships[i].target && this.isFighting(this.ship.escortGroup.ships[i]) && this.ship.escortGroup.ships[i].target.position.distanceTo(this.ship) < this.ship.scannerRange && this.isAggressive(this.ship.escortGroup.ships[i].target))
|
|
{
|
|
this.ship.target = this.ship.escortGroup.ships[i].target;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationAcquireOffensiveEscortTarget = function()
|
|
{
|
|
if (this.ship.group && this.ship.group.leader && this.ship.group.leader.target && this.ship.group.leader.hasHostileTarget)
|
|
{
|
|
if (this.ship.position.distanceTo(this.ship.group.leader.target) < this.ship.scannerRange)
|
|
{
|
|
this.ship.target = this.ship.group.leader.target;
|
|
this.ship.addDefenseTarget(this.ship.target);
|
|
}
|
|
}
|
|
}
|
|
|
|
AILib.prototype.configurationAcquirePlayerAsTarget = function()
|
|
{
|
|
this.ship.target = player.ship;
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationAcquireScannedTarget = function()
|
|
{
|
|
this.ship.target = this.getParameter("oolite_scanResultSpecific");
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationCheckScanner = function()
|
|
{
|
|
this.setParameter("oolite_scanResults",this.ship.checkScanner());
|
|
this.setParameter("oolite_scanResultSpecific",null);
|
|
}
|
|
|
|
|
|
/*** Navigation configuration ***/
|
|
|
|
|
|
AILib.prototype.configurationSelectRandomTradeStation = function()
|
|
{
|
|
var stations = system.stations;
|
|
var threshold = 1E16;
|
|
var chosenStation = null;
|
|
if (this.ship.bounty == 0)
|
|
{
|
|
if (Math.random() < 0.9 && this.friendlyStation(system.mainStation))
|
|
{
|
|
this.setParameter("oolite_selectedStation",system.mainStation);
|
|
return;
|
|
}
|
|
}
|
|
else if (this.ship.bounty <= this.fineThreshold())
|
|
{
|
|
if (Math.random() < 0.5 && this.friendlyStation(system.mainStation))
|
|
{
|
|
this.setParameter("oolite_selectedStation",system.mainStation);
|
|
return;
|
|
}
|
|
}
|
|
var friendlies = 0;
|
|
for (var i = 0 ; i < stations.length ; i++)
|
|
{
|
|
var station = stations[i];
|
|
if (this.friendlyStation(station))
|
|
{
|
|
friendlies++;
|
|
// equivalent to filtering the list to only contain
|
|
// friendlies, then picking a random element.
|
|
if (Math.random() < 1/friendlies)
|
|
{
|
|
chosenStation = station;
|
|
}
|
|
}
|
|
}
|
|
this.setParameter("oolite_selectedStation",chosenStation);
|
|
this.communicate("oolite_selectedStation",this.entityCommsParams(chosenStation),4);
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSelectShuttleDestination = function()
|
|
{
|
|
var possibles = system.planets.concat(system.stations);
|
|
var destinations1 = [];
|
|
var destinations2 = [];
|
|
for (var i = 0; i < possibles.length ; i++)
|
|
{
|
|
var possible = possibles[i];
|
|
// travel at least a little way
|
|
var distance = possible.position.distanceTo(this.ship);
|
|
if (distance > possible.collisionRadius + 10000)
|
|
{
|
|
// must be friendly destination and not moving too fast
|
|
if (possible.isPlanet || this.friendlyStation(possible) || possible.maxSpeed > this.ship.maxSpeed / 5)
|
|
{
|
|
if (distance > system.mainPlanet.radius * 5)
|
|
{
|
|
destinations2.push(possible);
|
|
}
|
|
else
|
|
{
|
|
destinations1.push(possible);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// no nearby destinations
|
|
if (destinations1.length == 0)
|
|
{
|
|
destinations1 = destinations2;
|
|
}
|
|
// no destinations
|
|
if (destinations1.length == 0)
|
|
{
|
|
return;
|
|
}
|
|
var destination = destinations1[Math.floor(Math.random()*destinations1.length)];
|
|
if (destination.isPlanet)
|
|
{
|
|
this.setParameter("oolite_selectedPlanet",destination);
|
|
this.setParameter("oolite_selectedStation",null);
|
|
}
|
|
else
|
|
{
|
|
this.setParameter("oolite_selectedStation",destination);
|
|
this.setParameter("oolite_selectedPlanet",null);
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSelectWitchspaceDestination = function()
|
|
{
|
|
if (!this.ship.hasHyperspaceMotor)
|
|
{
|
|
this.setParameter("oolite_witchspaceDestination",null);
|
|
return;
|
|
}
|
|
var preselected = this.getParameter("oolite_witchspaceDestination");
|
|
if (preselected != system.ID && system.info.distanceToSystem(System.infoForSystem(galaxyNumber,preselected)) <= this.ship.fuel)
|
|
{
|
|
// we've already got a destination
|
|
return;
|
|
}
|
|
var possible = system.info.systemsInRange(this.ship.fuel);
|
|
var selected = possible[Math.floor(Math.random()*possible.length)];
|
|
this.setParameter("oolite_witchspaceDestination",selected.systemID);
|
|
this.communicate("oolite_selectedWitchspaceDestination",{"oolite_witchspaceDestination":selected.name},4);
|
|
}
|
|
|
|
|
|
/*** Destination configuration ***/
|
|
|
|
AILib.prototype.configurationSetDestinationToNearestFriendlyStation = function()
|
|
{
|
|
var stations = system.stations;
|
|
var threshold = 1E16;
|
|
var chosenStation = null;
|
|
for (var i = 0 ; i < stations.length ; i++)
|
|
{
|
|
var station = stations[i];
|
|
if (this.friendlyStation(station))
|
|
{
|
|
var distance = station.position.distanceTo(this.ship);
|
|
if (distance < threshold)
|
|
{
|
|
threshold = distance;
|
|
chosenStation = station;
|
|
}
|
|
}
|
|
}
|
|
if (chosenStation == null)
|
|
{
|
|
this.ship.destination = this.ship.position;
|
|
this.ship.desiredRange = 0;
|
|
}
|
|
else
|
|
{
|
|
this.ship.destination = chosenStation.position;
|
|
this.ship.desiredRange = 15000;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToHomeStation = function()
|
|
{
|
|
var home = this.homeStation();
|
|
if (home != null)
|
|
{
|
|
this.ship.destination = home.position;
|
|
this.ship.desiredRange = 15000;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
else
|
|
{
|
|
this.ship.destination = this.ship.position;
|
|
this.ship.desiredRange = 0;
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToGroupLeader = function()
|
|
{
|
|
if (!this.ship.group || !this.ship.group.leader)
|
|
{
|
|
this.ship.destination = this.ship.position;
|
|
}
|
|
else
|
|
{
|
|
this.ship.destination = this.ship.group.leader.position;
|
|
}
|
|
this.ship.desiredRange = 2000;
|
|
this.ship.desiredSpeed = this.ship.maxSpeed;
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToMainPlanet = function()
|
|
{
|
|
if (system.mainPlanet)
|
|
{
|
|
this.ship.destination = system.mainPlanet.position;
|
|
this.ship.desiredRange = system.mainPlanet.radius * 3;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToMainStation = function()
|
|
{
|
|
this.ship.destination = system.mainStation.position;
|
|
this.ship.desiredRange = 15000;
|
|
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToPirateLurk = function()
|
|
{
|
|
var lurk = this.getParameter("oolite_pirateLurk");
|
|
if (lurk != null)
|
|
{
|
|
this.ship.destination = lurk;
|
|
}
|
|
else
|
|
{
|
|
var code = "WITCHPOINT";
|
|
var p = this.ship.position;
|
|
// if already on a lane, stay on it
|
|
if (p.z < system.mainPlanet.position.z && ((p.x * p.x) + (p.y * p.y)) < this.scannerRange * this.scannerRange * 4)
|
|
{
|
|
lurk = p;
|
|
}
|
|
else if (p.subtract(system.mainPlanet).dot(p.subtract(system.sun)) < -0.9)
|
|
{
|
|
lurk = p;
|
|
}
|
|
else if (p.dot(system.sun.position) > 0.9)
|
|
{
|
|
lurk = p;
|
|
}
|
|
else // not on a lane, so pick somewhere at random
|
|
{
|
|
var choice = Math.random();
|
|
if (choice < 0.8)
|
|
{
|
|
code = "LANE_WP";
|
|
}
|
|
else
|
|
{
|
|
code = "LANE_PS";
|
|
}
|
|
// code = "LANE_WS"? "WITCHPOINT"?
|
|
// what about other locations in less policed systems?
|
|
lurk = system.locationFromCode(code);
|
|
}
|
|
this.setParameter("oolite_pirateLurk",lurk);
|
|
}
|
|
this.ship.desiredRange = 1000;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToSelectedPlanet = function()
|
|
{
|
|
var planet = this.getParameter("oolite_selectedPlanet");
|
|
if (planet)
|
|
{
|
|
this.ship.destination = planet.position;
|
|
this.ship.desiredRange = planet.radius+100;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToSelectedStation = function()
|
|
{
|
|
var station = this.getParameter("oolite_selectedStation");
|
|
if (station)
|
|
{
|
|
this.ship.destination = station.position;
|
|
this.ship.desiredRange = 15000;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToSunskimEnd = function()
|
|
{
|
|
if (system.sun)
|
|
{
|
|
var direction = Vector3D.random().cross(this.ship.position.subtract(system.sun.position));
|
|
// 2km parallel to local sun surface for every LY of fuel
|
|
this.ship.destination = this.ship.position.add(direction.multiply(2000*(7-this.ship.fuel)));
|
|
// max sunskim height is sqrt(4/3) radius
|
|
this.ship.desiredRange = 0;
|
|
this.ship.desiredSpeed = this.ship.maxSpeed;
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToSunskimStart = function()
|
|
{
|
|
if (system.sun)
|
|
{
|
|
this.ship.destination = system.sun.position;
|
|
// max sunskim height is sqrt(4/3) radius
|
|
this.ship.desiredRange = system.sun.radius * 1.125;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToWaypoint = function()
|
|
{
|
|
if (this.getParameter("oolite_waypoint") != null && this.getParameter("oolite_waypointRange") != null)
|
|
{
|
|
this.ship.destination = this.getParameter("oolite_waypoint");
|
|
this.ship.desiredRange = this.getParameter("oolite_waypointRange");
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetDestinationToWitchpoint = function()
|
|
{
|
|
this.ship.destination = new Vector3D(0,0,0);
|
|
this.ship.desiredRange = 10000;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetWaypoint = function()
|
|
{
|
|
var gen = this.getWaypointGenerator();
|
|
if(gen != null)
|
|
{
|
|
gen.call(this);
|
|
this.configurationSetDestinationToWaypoint();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** Docking configurations ***/
|
|
|
|
|
|
|
|
AILib.prototype.configurationSetNearbyFriendlyStationForDocking = function()
|
|
{
|
|
var stations = system.stations;
|
|
for (var i = 0 ; i < stations.length ; i++)
|
|
{
|
|
var station = stations[i];
|
|
if (this.friendlyStation(station))
|
|
{
|
|
// this is not a very good check for friendliness, but
|
|
// it will have to do for now
|
|
if (station.position.distanceTo(this.ship) < this.ship.scannerRange)
|
|
{
|
|
this.setParameter("oolite_dockingStation",station)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetHomeStationForDocking = function()
|
|
{
|
|
if (this.ship.owner && this.ship.owner.isStation && this.friendlyStation(this.ship.owner))
|
|
{
|
|
this.setParameter("oolite_dockingStation",this.ship.owner)
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationSetSelectedStationForDocking = function()
|
|
{
|
|
this.setParameter("oolite_dockingStation",this.getParameter("oolite_selectedStation"));
|
|
}
|
|
|
|
|
|
/*** Miscellaneous configuration ***/
|
|
|
|
|
|
AILib.prototype.configurationAppointGroupLeader = function()
|
|
{
|
|
if (this.ship.group && !this.ship.group.leader)
|
|
{
|
|
this.ship.group.leader = this.ship.group.ships[0];
|
|
for (var i = 0 ; i < this.ship.group.ships.length ; i++)
|
|
{
|
|
if (this.ship.group.ships[i].hasHyperspaceMotor)
|
|
{
|
|
// bias towards jump-capable ships
|
|
this.ship.group.leader = this.ship.group.ships[i];
|
|
break;
|
|
}
|
|
}
|
|
var leadrole = this.getParameter("oolite_leaderRole")
|
|
if (leadrole != null)
|
|
{
|
|
this.ship.group.leader.primaryRole = leadrole;
|
|
}
|
|
}
|
|
}
|
|
|
|
AILib.prototype.configurationEscortGroupLeader = function()
|
|
{
|
|
if (!this.ship.group || !this.ship.group.leader || this.ship.group.leader == this.ship)
|
|
{
|
|
return;
|
|
}
|
|
if (this.ship.group.leader.escortGroup && this.ship.group.leader.escortGroup.containsShip(this.ship))
|
|
{
|
|
return;
|
|
}
|
|
var escrole = this.getParameter("oolite_escortRole")
|
|
if (escrole != null)
|
|
{
|
|
var oldrole = this.ship.primaryRole;
|
|
this.ship.primaryRole = escrole;
|
|
var accepted = this.ship.offerToEscort(this.ship.group.leader);
|
|
if (!accepted)
|
|
{
|
|
this.ship.primaryRole = oldrole;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationForgetCargoDemand = function()
|
|
{
|
|
/* if (this.ship.group && this.ship.group.leader && this.ship.group.leader.AIScript.oolite_intership.cargodemanded)
|
|
{
|
|
delete this.ship.group.leader.AIScript.oolite_intership.cargodemanded;
|
|
} */ // not sure about this, maybe not needed
|
|
|
|
if (this.ship.AIScript.oolite_intership.cargodemanded)
|
|
{
|
|
delete this.ship.AIScript.oolite_intership.cargodemanded;
|
|
delete this.ship.AIScript.oolite_intership.cargodemandmet;
|
|
// and make the group lose the cargo count from the last demand
|
|
if (this.ship.group)
|
|
{
|
|
for (var i = 0 ; i < this.ship.group.ships.length ; i++)
|
|
{
|
|
var ship = this.ship.group.ships[i];
|
|
if (ship.AIScript && ship.AIScript.oolite_priorityai)
|
|
{
|
|
ship.AIScript.oolite_priorityai.setParameter("oolite_cargoDropped",0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.configurationLeaveEscortGroup = function()
|
|
{
|
|
if (this.ship.group && this.ship.group.leader && this.ship.group.leader != this.ship && this.ship.group.leader.escortGroup && this.ship.group.leader.escortGroup.containsShip(this.ship))
|
|
{
|
|
this.ship.group.leader.escortGroup.removeShip(this.ship);
|
|
if (this.ship.group)
|
|
{
|
|
this.ship.group.removeShip(this.ship);
|
|
this.ship.group = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*** Station configuration ***/
|
|
|
|
AILib.prototype.configurationStationReduceAlertLevel = function()
|
|
{
|
|
if (this.ship.alertCondition > 1)
|
|
{
|
|
this.ship.alertCondition--;
|
|
}
|
|
}
|
|
|
|
AILib.prototype.configurationStationValidateTarget = function()
|
|
{
|
|
if (this.ship.target)
|
|
{
|
|
if(this.ship.position.distanceTo(this.ship.target) > this.ship.scannerRange)
|
|
{
|
|
// station behaviour does not generally validate target
|
|
this.ship.target = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ****************** Response definition functions ************** */
|
|
|
|
/* Standard state-machine responses. These set up a set of standard
|
|
* state machine responses where incoming events will cause reasonable
|
|
* default behaviour and often force a reconsideration of
|
|
* priorities. Many behaviours will need to supplement the standard
|
|
* responses with additional definitions. */
|
|
|
|
AILib.prototype.responsesAddStandard = function(handlers)
|
|
{
|
|
handlers.commsMessageReceived = function(message)
|
|
{
|
|
this.noteCommsHeard();
|
|
}
|
|
handlers.cascadeWeaponDetected = function(weapon)
|
|
{
|
|
this.ship.clearDefenseTargets();
|
|
this.ship.addDefenseTarget(weapon);
|
|
this.setParameter("oolite_cascadeDetected",weapon.position);
|
|
this.ship.target = weapon;
|
|
this.ship.performFlee();
|
|
this.reconsiderNow();
|
|
};
|
|
|
|
handlers.shipAttackedWithMissile = function(missile,whom)
|
|
{
|
|
if (!this.ship.hasHostileTarget && this.getParameter("oolite_flag_sendsDistressCalls"))
|
|
{
|
|
this.broadcastDistressMessage();
|
|
}
|
|
if (this.ship.equipmentStatus("EQ_ECM") == "EQUIPMENT_OK")
|
|
{
|
|
this.ship.fireECM();
|
|
this.ship.addDefenseTarget(missile);
|
|
this.ship.addDefenseTarget(whom);
|
|
// but don't reconsider immediately
|
|
}
|
|
else
|
|
{
|
|
this.ship.addDefenseTarget(missile);
|
|
this.ship.addDefenseTarget(whom);
|
|
var tmp = this.ship.target;
|
|
this.ship.target = whom;
|
|
this.ship.requestHelpFromGroup();
|
|
this.ship.target = tmp;
|
|
this.reconsiderNow();
|
|
}
|
|
};
|
|
|
|
handlers.shipBeingAttacked = function(whom)
|
|
{
|
|
if (whom.target != this.ship && whom != player.ship)
|
|
{
|
|
// was accidental
|
|
if (this.allied(whom,this.ship))
|
|
{
|
|
this.communicate("oolite_friendlyFire",this.entityCommsParams(whom),3);
|
|
// ignore it
|
|
return;
|
|
}
|
|
if (Math.random() > 0.1)
|
|
{
|
|
// usually ignore it anyway
|
|
return;
|
|
}
|
|
}
|
|
if (!this.ship.hasHostileTarget && this.getParameter("oolite_flag_sendsDistressCalls"))
|
|
{
|
|
this.broadcastDistressMessage();
|
|
}
|
|
if (this.ship.defenseTargets.indexOf(whom) < 0)
|
|
{
|
|
this.ship.addDefenseTarget(whom);
|
|
this.reconsiderNow();
|
|
}
|
|
else
|
|
{
|
|
// else we know about this attacker already
|
|
if (this.ship.energy * 4 < this.ship.maxEnergy)
|
|
{
|
|
// but at low energy still reconsider
|
|
this.reconsiderNow();
|
|
this.ship.requestHelpFromGroup();
|
|
}
|
|
}
|
|
if (this.ship.hasHostileTarget)
|
|
{
|
|
if (!this.isAggressive(this.ship.target))
|
|
{
|
|
// if our current target is running away, switch targets
|
|
this.ship.target = whom;
|
|
}
|
|
else if (this.ship.target.target != this.ship)
|
|
{
|
|
// if our current target isn't aiming at us
|
|
if (Math.random() < 0.2)
|
|
{
|
|
// occasionally switch
|
|
this.ship.target = whom;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.ship.escortGroup != null)
|
|
{
|
|
this.ship.requestHelpFromGroup();
|
|
}
|
|
};
|
|
handlers.shipBeingAttackedUnsuccessfully = function(whom)
|
|
{
|
|
if (!this.ship.hasHostileTarget && this.getParameter("oolite_flag_sendsDistressCalls"))
|
|
{
|
|
this.broadcastDistressMessage();
|
|
}
|
|
if (this.ship.defenseTargets.indexOf(whom) < 0)
|
|
{
|
|
this.ship.addDefenseTarget(whom);
|
|
this.reconsiderNow();
|
|
}
|
|
};
|
|
handlers.shipTargetLost = function(target)
|
|
{
|
|
this.reconsiderNow();
|
|
};
|
|
// overridden for escorts
|
|
handlers.helpRequestReceived = function(ally, enemy)
|
|
{
|
|
this.ship.addDefenseTarget(enemy);
|
|
if (enemy.scanClass == "CLASS_MISSILE" && enemy.position.distanceTo(this.ship) < this.ship.scannerRange && this.ship.equipmentStatus("EQ_ECM") == "EQUIPMENT_OK")
|
|
{
|
|
this.ship.fireECM();
|
|
}
|
|
if (!this.ship.hasHostileTarget)
|
|
{
|
|
this.reconsiderNow();
|
|
return; // not in a combat mode
|
|
}
|
|
if (ally.energy / ally.maxEnergy < this.ship.energy / this.ship.maxEnergy)
|
|
{
|
|
// not in worse shape than ally
|
|
if (this.ship.target.target != ally && this.ship.target != ally.target)
|
|
{
|
|
// not already helping, go for it...
|
|
this.ship.target = enemy;
|
|
this.reconsiderNow();
|
|
}
|
|
}
|
|
}
|
|
handlers.cargoDumpedNearby = function(cargo,ship)
|
|
{
|
|
if (this.getParameter("oolite_flag_watchForCargo"))
|
|
{
|
|
var previously = this.getParameter("oolite_cargoDropped");
|
|
if (previously == null)
|
|
{
|
|
previously = 0;
|
|
}
|
|
previously++;
|
|
this.setParameter("oolite_cargoDropped",previously);
|
|
}
|
|
}
|
|
handlers.approachingPlanetSurface = function()
|
|
{
|
|
if (this.getParameter("oolite_flag_allowPlanetaryLanding"))
|
|
{
|
|
this.ship.desiredSpeed = this.ship.maxSpeed / 4;
|
|
this.ship.performLandOnPlanet();
|
|
this.ship.AIScriptWakeTime = 0; // cancel reconsiderations
|
|
this.applyHandlers({}); // cancel interruptions
|
|
this.communicate("oolite_landingOnPlanet",{},4);
|
|
}
|
|
else
|
|
{
|
|
this.reconsiderNow();
|
|
}
|
|
}
|
|
handlers.shipLaunchedFromStation = function(station)
|
|
{
|
|
// clear the station
|
|
this.ship.destination = station.position;
|
|
this.ship.desiredSpeed = this.cruiseSpeed();
|
|
this.ship.desiredRange = 15000;
|
|
this.ship.performFlyToRangeFromDestination();
|
|
}
|
|
handlers.shipWillEnterWormhole = function()
|
|
{
|
|
this.setParameter("oolite_witchspaceWormhole",null);
|
|
this.applyHandlers({});
|
|
}
|
|
handlers.shipExitedWormhole = function()
|
|
{
|
|
this.ship.AIScript.oolite_intership = {};
|
|
// this.reconsiderNow();
|
|
}
|
|
|
|
handlers.distressMessageReceived = function(aggressor, sender)
|
|
{
|
|
if (this.getParameter("oolite_flag_listenForDistressCall") != true)
|
|
{
|
|
return;
|
|
}
|
|
this.setParameter("oolite_distressAggressor",aggressor);
|
|
this.setParameter("oolite_distressSender",sender);
|
|
this.setParameter("oolite_distressTimestamp",clock.adjustedSeconds);
|
|
this.reconsiderNow();
|
|
}
|
|
handlers.offenceCommittedNearby = function(attacker, victim)
|
|
{
|
|
if (this.getParameter("oolite_flag_markOffenders"))
|
|
{
|
|
attacker.setBounty(attacker.bounty | 7,"seen by police");
|
|
this.ship.addDefenseTarget(attacker);
|
|
this.reconsiderNow();
|
|
}
|
|
}
|
|
handlers.playerWillEnterWitchspace = function()
|
|
{
|
|
var wormhole = this.getParameter("oolite_witchspaceWormhole");
|
|
if (wormhole != null && wormhole.isWormhole)
|
|
{
|
|
this.ship.enterWormhole(wormhole);
|
|
}
|
|
}
|
|
handlers.wormholeSuggested = function(hole)
|
|
{
|
|
this.setParameter("oolite_witchspaceWormhole",hole);
|
|
this.reconsiderNow();
|
|
}
|
|
// TODO: more event handlers
|
|
}
|
|
|
|
/* Additional handlers for use while docking */
|
|
AILib.prototype.responsesAddDocking = function(handlers)
|
|
{
|
|
handlers.stationWithdrewDockingClearance = function()
|
|
{
|
|
this.setParameter("oolite_dockingStation",null);
|
|
this.reconsiderNow();
|
|
};
|
|
|
|
handlers.shipAchievedDesiredRange = function()
|
|
{
|
|
var message = this.ship.dockingInstructions.ai_message;
|
|
if (message == "APPROACH" || message == "BACK_OFF" || message == "APPROACH_COORDINATES")
|
|
{
|
|
this.reconsiderNow();
|
|
}
|
|
};
|
|
}
|
|
|
|
/* Override of standard handlers for use while escorting */
|
|
AILib.prototype.responsesAddEscort = function(handlers)
|
|
{
|
|
handlers.helpRequestReceived = function(ally, enemy)
|
|
{
|
|
// always help the leader
|
|
if (ally == this.ship.group.leader)
|
|
{
|
|
if (!this.ship.target || this.ship.target.target != ally)
|
|
{
|
|
this.ship.target = enemy;
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
}
|
|
this.ship.addDefenseTarget(enemy);
|
|
if (!this.ship.hasHostileTarget)
|
|
{
|
|
this.reconsiderNow();
|
|
return; // not in a combat mode
|
|
}
|
|
if (ally.energy / ally.maxEnergy < this.ship.energy / this.ship.maxEnergy)
|
|
{
|
|
// not in worse shape than ally
|
|
if (this.ship.target.target != ally && this.ship.target != ally.target)
|
|
{
|
|
// not already helping, go for it...
|
|
this.ship.target = enemy;
|
|
this.reconsiderNow();
|
|
}
|
|
}
|
|
}
|
|
handlers.escortDock = function()
|
|
{
|
|
this.reconsiderNow();
|
|
}
|
|
|
|
}
|
|
|
|
/* Additional handlers for scooping */
|
|
AILib.prototype.responsesAddScooping = function(handlers)
|
|
{
|
|
handlers.shipAchievedDesiredRange = function()
|
|
{
|
|
this.reconsiderNow();
|
|
}
|
|
handlers.shipScoopedFuel = function()
|
|
{
|
|
if (this.ship.fuel == 7)
|
|
{
|
|
this.reconsiderNow();
|
|
}
|
|
}
|
|
}
|
|
|
|
// shorter list than before
|
|
AILib.prototype.responsesAddStation = function(handlers)
|
|
{
|
|
handlers.commsMessageReceived = function(message)
|
|
{
|
|
this.noteCommsHeard();
|
|
}
|
|
handlers.cascadeWeaponDetected = function(weapon)
|
|
{
|
|
this.ship.alertCondition = 3;
|
|
this.reconsiderNow();
|
|
};
|
|
|
|
handlers.shipAttackedWithMissile = function(missile,whom)
|
|
{
|
|
this.ship.alertCondition = 3;
|
|
if (this.ship.equipmentStatus("EQ_ECM") == "EQUIPMENT_OK")
|
|
{
|
|
this.ship.fireECM();
|
|
this.ship.addDefenseTarget(missile);
|
|
this.ship.addDefenseTarget(whom);
|
|
// but don't reconsider immediately
|
|
}
|
|
else
|
|
{
|
|
this.ship.addDefenseTarget(missile);
|
|
this.ship.addDefenseTarget(whom);
|
|
var tmp = this.ship.target;
|
|
this.ship.target = whom;
|
|
this.ship.requestHelpFromGroup();
|
|
this.ship.target = tmp;
|
|
this.reconsiderNow();
|
|
}
|
|
};
|
|
|
|
handlers.shipBeingAttacked = function(whom)
|
|
{
|
|
if (!whom)
|
|
{
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
if (whom.target != this.ship && whom != player.ship)
|
|
{
|
|
// was accidental
|
|
if (this.allied(whom,this.ship))
|
|
{
|
|
this.communicate("oolite_friendlyFire",this.entityCommsParams(whom),4);
|
|
// ignore it
|
|
return;
|
|
}
|
|
if (Math.random() > 0.1)
|
|
{
|
|
// usually ignore it anyway
|
|
return;
|
|
}
|
|
}
|
|
this.ship.alertCondition = 3;
|
|
if (this.ship.defenseTargets.indexOf(whom) < 0)
|
|
{
|
|
this.ship.addDefenseTarget(whom);
|
|
this.reconsiderNow();
|
|
}
|
|
else
|
|
{
|
|
// else we know about this attacker already
|
|
if (this.ship.energy * 4 < this.ship.maxEnergy)
|
|
{
|
|
// but at low energy still reconsider
|
|
this.reconsiderNow();
|
|
this.ship.requestHelpFromGroup();
|
|
}
|
|
}
|
|
if (this.ship.hasHostileTarget)
|
|
{
|
|
if (!this.isAggressive(this.ship.target))
|
|
{
|
|
// if our current target is running away, switch targets
|
|
this.ship.target = whom;
|
|
}
|
|
else if (this.ship.target.target != this.ship)
|
|
{
|
|
// if our current target isn't aiming at us
|
|
if (Math.random() < 0.2)
|
|
{
|
|
// occasionally switch
|
|
this.ship.target = whom;
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
handlers.shipTargetLost = function(target)
|
|
{
|
|
this.reconsiderNow();
|
|
};
|
|
|
|
handlers.helpRequestReceived = function(ally, enemy)
|
|
{
|
|
this.ship.addDefenseTarget(enemy);
|
|
if (!this.ship.alertCondition == 3)
|
|
{
|
|
this.ship.target = enemy;
|
|
this.reconsiderNow();
|
|
return; // not in a combat mode
|
|
}
|
|
this.ship.target = enemy;
|
|
}
|
|
|
|
handlers.distressMessageReceived = function(aggressor, sender)
|
|
{
|
|
if (this.getParameter("oolite_flag_listenForDistressCall") != true)
|
|
{
|
|
return;
|
|
}
|
|
this.setParameter("oolite_distressAggressor",aggressor);
|
|
this.setParameter("oolite_distressSender",sender);
|
|
this.setParameter("oolite_distressTimestamp",clock.adjustedSeconds);
|
|
this.reconsiderNow();
|
|
}
|
|
handlers.offenceCommittedNearby = function(attacker, victim)
|
|
{
|
|
if (this.getParameter("oolite_flag_markOffenders"))
|
|
{
|
|
attacker.setBounty(attacker.bounty | 7,"seen by police");
|
|
this.ship.addDefenseTarget(attacker);
|
|
if (this.ship.alertCondition < 3)
|
|
{
|
|
this.ship.alertCondition = 3;
|
|
this.ship.target = attacker;
|
|
}
|
|
this.reconsiderNow();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
AILib.prototype.responsesAddMissile = function(handlers) {
|
|
handlers.commsMessageReceived = function(message)
|
|
{
|
|
this.noteCommsHeard();
|
|
}
|
|
|
|
handlers.shipHitByECM = function()
|
|
{
|
|
if (this.ship.scriptInfo.oolite_missile_ecmResponse)
|
|
{
|
|
var fn = this.ship.scriptInfo.oolite_missile_ecmResponse;
|
|
if (this.ship.AIScript[fn])
|
|
{
|
|
this.ship.AIScript[fn]();
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
if (this.ship.script[fn])
|
|
{
|
|
this.ship.script[fn]();
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* This section for the hardheads should be an ECM
|
|
* response function, and that is used in the default
|
|
* shipdata.plist, but for compatibility with older OXPs
|
|
* it's also hardcoded here for now.
|
|
*
|
|
* OXPs wanting to overrule this for hardheads can set a
|
|
* response function to do so.
|
|
*/
|
|
if (this.ship.primaryRole == "EQ_HARDENED_MISSILE")
|
|
{
|
|
if (Math.random() < 0.1) //10% chance per pulse
|
|
{
|
|
if (Math.random() < 0.5)
|
|
{
|
|
// 50% chance responds by detonation
|
|
this.ship.AIScript.shipAchievedDesiredRange();
|
|
return;
|
|
}
|
|
// otherwise explode as normal below
|
|
}
|
|
else // 90% chance unaffected
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.ship.explode();
|
|
}
|
|
handlers.shipTargetCloaked = function()
|
|
{
|
|
this.setParameter("oolite_interceptCoordinates",this.ship.target.position);
|
|
this.setParameter("oolite_interceptTarget",this.ship.target);
|
|
// stops performIntercept sending AchievedDesiredRange
|
|
this.ship.performIdle();
|
|
}
|
|
handlers.shipTargetLost = function()
|
|
{
|
|
this.reconsiderNow();
|
|
}
|
|
handlers.shipAchievedDesiredRange = function()
|
|
{
|
|
if (this.ship.scriptInfo.oolite_missile_detonation)
|
|
{
|
|
var fn = this.ship.scriptInfo.oolite_missile_detonation;
|
|
if (this.ship.AIScript[fn])
|
|
{
|
|
this.ship.AIScript[fn]();
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
if (this.ship.script[fn])
|
|
{
|
|
this.ship.script[fn]();
|
|
this.reconsiderNow();
|
|
return;
|
|
}
|
|
}
|
|
/* Defaults to standard missile settings, in case they're
|
|
* not specified in scriptInfo */
|
|
var blastpower = 170;
|
|
var blastradius = 32.5;
|
|
var blastshaping = 0.25;
|
|
if (this.ship.scriptInfo.oolite_missile_blastPower)
|
|
{
|
|
blastpower = this.ship.scriptInfo.oolite_missile_blastPower;
|
|
}
|
|
if (this.ship.scriptInfo.oolite_missile_blastRadius)
|
|
{
|
|
blastradius = this.ship.scriptInfo.oolite_missile_blastRadius;
|
|
}
|
|
if (this.ship.scriptInfo.oolite_missile_blastShaping)
|
|
{
|
|
blastshaping = this.ship.scriptInfo.oolite_missile_blastShaping;
|
|
}
|
|
this.ship.dealEnergyDamage(blastpower,blastradius,blastshaping);
|
|
this.ship.explode();
|
|
}
|
|
}
|
|
|
|
|
|
/* ******************* Waypoint generators *********************** */
|
|
|
|
/* Waypoint generators. When these are called, they should set up
|
|
* the next waypoint for the ship. Ideally ships should either
|
|
* reach that waypoint or formally give up on it before asking for
|
|
* the next one, but the generator shouldn't assume that unless
|
|
* it's one written specifically for a particular AI. */
|
|
|
|
AILib.prototype.waypointsSpacelanePatrol = function()
|
|
{
|
|
var p = this.ship.position;
|
|
var choice = "";
|
|
if (p.magnitude() < 10000)
|
|
{
|
|
// near witchpoint
|
|
if (Math.random() < 0.9)
|
|
{
|
|
// mostly return to planet
|
|
choice = "PLANET";
|
|
}
|
|
else
|
|
{
|
|
choice = "SUN";
|
|
}
|
|
}
|
|
else if (p.distanceTo(system.mainPlanet) < system.mainPlanet.radius * 2)
|
|
{
|
|
// near planet
|
|
if (Math.random() < 0.75)
|
|
{
|
|
// mostly go to witchpoint
|
|
choice = "WITCHPOINT";
|
|
}
|
|
else
|
|
{
|
|
choice = "SUN";
|
|
}
|
|
}
|
|
else if (p.distanceTo(system.sun) < system.sun.radius * 3)
|
|
{
|
|
// near sun
|
|
if (Math.random() < 0.9)
|
|
{
|
|
// mostly return to planet
|
|
choice = "PLANET";
|
|
}
|
|
else
|
|
{
|
|
choice = "SUN";
|
|
}
|
|
}
|
|
else if (p.z < system.mainPlanet.position.z && ((p.x * p.x) + (p.y * p.y)) < this.ship.scannerRange * this.ship.scannerRange * 4)
|
|
{
|
|
// on lane 1
|
|
if (Math.random() < 0.5)
|
|
{
|
|
choice = "PLANET";
|
|
}
|
|
else
|
|
{
|
|
choice = "WITCHPOINT";
|
|
}
|
|
}
|
|
else if (p.subtract(system.mainPlanet).dot(p.subtract(system.sun)) < -0.9)
|
|
{
|
|
// on lane 2
|
|
if (Math.random() < 0.5)
|
|
{
|
|
choice = "PLANET";
|
|
}
|
|
else
|
|
{
|
|
choice = "SUN";
|
|
}
|
|
}
|
|
else if (p.dot(system.sun.position) > 0.9)
|
|
{
|
|
// on lane 3
|
|
if (Math.random() < 0.5)
|
|
{
|
|
choice = "WITCHPOINT";
|
|
}
|
|
else
|
|
{
|
|
choice = "SUN";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we're not on any lane. Return to the planet
|
|
choice = "PLANET";
|
|
}
|
|
// having chosen, now set up the next stop on the patrol
|
|
switch (choice) {
|
|
case "WITCHPOINT":
|
|
this.setParameter("oolite_waypoint",new Vector3D(0,0,0));
|
|
this.setParameter("oolite_waypointRange",7500);
|
|
break;
|
|
case "PLANET":
|
|
this.setParameter("oolite_waypoint",system.mainPlanet.position);
|
|
this.setParameter("oolite_waypointRange",system.mainPlanet.radius*2);
|
|
break;
|
|
case "SUN":
|
|
this.setParameter("oolite_waypoint",system.sun.position);
|
|
this.setParameter("oolite_waypointRange",system.sun.radius*2.5);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
AILib.prototype.waypointsStationPatrol = function()
|
|
{
|
|
var station = null;
|
|
if (this.ship.group)
|
|
{
|
|
station = this.ship.group.leader;
|
|
}
|
|
if (!station)
|
|
{
|
|
station = system.mainStation;
|
|
if (!station)
|
|
{
|
|
this.setParameter("oolite_waypoint",new Vector3D(0,0,0));
|
|
this.setParameter("oolite_waypointRange",7500);
|
|
return;
|
|
}
|
|
}
|
|
var z = station.vectorForward;
|
|
var tmp = new Vector3D(0,1,0);
|
|
if (system.sun)
|
|
{
|
|
tmp = z.cross(system.sun.position.direction());
|
|
}
|
|
var x = z.cross(tmp);
|
|
var y = z.cross(x);
|
|
// x and y now consistent vectors relative to a rotating station
|
|
|
|
var waypoints = [
|
|
station.position.add(x.multiply(25000)),
|
|
station.position.add(y.multiply(25000)),
|
|
station.position.add(x.multiply(-25000)),
|
|
station.position.add(y.multiply(-25000))
|
|
];
|
|
|
|
var waypoint = waypoints[0];
|
|
for (var i=0;i<=3;i++)
|
|
{
|
|
if (this.ship.position.distanceTo(waypoints[i]) < 500)
|
|
{
|
|
waypoint = waypoints[(i+1)%4];
|
|
break;
|
|
}
|
|
}
|
|
this.setParameter("oolite_waypoint",waypoint);
|
|
this.setParameter("oolite_waypointRange",100);
|
|
|
|
}
|
|
|
|
/* ********** Communications data ****************/
|
|
|
|
/* Warning: OXPs should only interact with this through the provided
|
|
* API functions. The internals of data storage may be changed at any
|
|
* time. This data is global. */
|
|
|
|
this.startUp = function()
|
|
{
|
|
// initial definition is just essential communications for now
|
|
this.$commsSettings = {
|
|
generic: {
|
|
generic: {
|
|
oolite_makePirateDemand: "[oolite-comms-makePirateDemand]",
|
|
oolite_acceptPirateDemand: "[oolite-comms-acceptPirateDemand]",
|
|
oolite_makeDistressCall: "[oolite-comms-makeDistressCall]"
|
|
}
|
|
},
|
|
police: {
|
|
generic: {
|
|
oolite_markForFines: "[oolite-comms-markForFines]",
|
|
oolite_distressResponseAggressor: "[oolite-comms-distressResponseAggressor]",
|
|
oolite_offenceDetected: "[oolite-comms-offenceDetected]",
|
|
}
|
|
}
|
|
};
|
|
|
|
/* These are temporary for testing. Remove before release... */
|
|
this.$commsSettings.generic.generic.oolite_continuingAttack = "I've got the [oolite_entityClass]";
|
|
this.$commsSettings.generic.generic.oolite_beginningAttack = "Die, [oolite_entityClass]!";
|
|
|
|
}
|
|
|
|
|
|
/* Set a communication for the specified role, personality and comms
|
|
* key. "generic" is used as a fallback role and personality. */
|
|
this._setCommunication = function(role, personality, key, value)
|
|
{
|
|
if (!this.$commsSettings[role])
|
|
{
|
|
this.$commsSettings[role] = {};
|
|
}
|
|
if (!this.$commsSettings[role][personality])
|
|
{
|
|
this.$commsSettings[role][personality] = {};
|
|
}
|
|
this.$commsSettings[role][personality][key] = value;
|
|
}
|
|
|
|
|
|
/* Search through communications from most specific to least specific.
|
|
* role+personality
|
|
* "generic"+personality
|
|
* role+"generic"
|
|
* "generic"+"generic"
|
|
* A return value of "" means no communication is set.
|
|
*/
|
|
this._getCommunication = function(role, personality, key)
|
|
{
|
|
if (this.$commsSettings[role] && this.$commsSettings[role][personality] && this.$commsSettings[role][personality][key] && this.$commsSettings[role][personality][key] != "")
|
|
{
|
|
return this.$commsSettings[role][personality][key];
|
|
}
|
|
if (this.$commsSettings["generic"] && this.$commsSettings["generic"][personality] && this.$commsSettings["generic"][personality][key] && this.$commsSettings["generic"][personality][key] != "")
|
|
{
|
|
return this.$commsSettings["generic"][personality][key];
|
|
}
|
|
if (this.$commsSettings[role] && this.$commsSettings[role]["generic"] && this.$commsSettings[role]["generic"][key] && this.$commsSettings[role]["generic"][key] != "")
|
|
{
|
|
return this.$commsSettings[role]["generic"][key];
|
|
}
|
|
if (this.$commsSettings["generic"] && this.$commsSettings["generic"]["generic"] && this.$commsSettings["generic"]["generic"][key] && this.$commsSettings["generic"]["generic"][key] != "")
|
|
{
|
|
return this.$commsSettings["generic"]["generic"][key];
|
|
}
|
|
return "";
|
|
} |