Difference between revisions of "Oolite PriorityAI Tutorial"
(Basic tutorial for priority AI) |
(Updating BB links) |
||
(10 intermediate revisions by one other user not shown) | |||
Line 126: | Line 126: | ||
Have a look through the [[Oolite_PriorityAI_Documentation|Priority AI documentation]] and the AIs in the 1.79 core game for more examples and ideas. | Have a look through the [[Oolite_PriorityAI_Documentation|Priority AI documentation]] and the AIs in the 1.79 core game for more examples and ideas. | ||
+ | |||
+ | == Examples == | ||
+ | These are just three of the two dozen examples in the Vanilla game | ||
+ | === Rock Hermit AI === | ||
+ | <div class="mw-collapsible mw-collapsed" data-expandtext="Show code" data-collapsetext="Hide code" style="overflow:auto;"> | ||
+ | /* | ||
+ | |||
+ | rockHermitAI.js | ||
+ | |||
+ | Priority-based AI for rock hermits | ||
+ | |||
+ | 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"; | ||
+ | |||
+ | this.name = "Oolite Rock Hermit AI"; | ||
+ | |||
+ | this.aiStarted = function() { | ||
+ | var ai = new worldScripts["oolite-libPriorityAI"].PriorityAIController(this.ship); | ||
+ | |||
+ | if (worldScripts["oolite-libPriorityAI"]._getCommunicationPersonalities("hermit").length > 0) | ||
+ | { | ||
+ | ai.setCommunicationsRole("hermit"); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | ai.setCommunicationsRole("station"); | ||
+ | } | ||
+ | |||
+ | ai.setParameter("oolite_friendlyRoles",["oolite-scavenger"]); | ||
+ | |||
+ | ai.setPriorities([ | ||
+ | /* Fight */ | ||
+ | { | ||
+ | preconfiguration: ai.configurationStationValidateTarget, | ||
+ | condition: ai.conditionInCombat, | ||
+ | behaviour: ai.behaviourStationLaunchDefenseShips, | ||
+ | reconsider: 5 | ||
+ | }, | ||
+ | /* Scan */ | ||
+ | { | ||
+ | preconfiguration: ai.configurationCheckScanner, | ||
+ | condition: ai.conditionScannerContainsRocks, | ||
+ | behaviour: ai.behaviourStationLaunchMiner, | ||
+ | reconsider: 60 | ||
+ | }, | ||
+ | { | ||
+ | configuration: ai.configurationStationReduceAlertLevel, | ||
+ | behaviour: ai.behaviourStationManageTraffic, | ||
+ | reconsider: 60 | ||
+ | } | ||
+ | ]); | ||
+ | } | ||
+ | |||
+ | </div> | ||
+ | |||
+ | |||
+ | === Pirate AI === | ||
+ | <div class="mw-collapsible mw-collapsed" data-expandtext="Show code" data-collapsetext="Hide code" style="overflow:auto;"> | ||
+ | |||
+ | /* | ||
+ | |||
+ | pirateAI.js | ||
+ | |||
+ | Priority-based AI for pirates | ||
+ | |||
+ | 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"; | ||
+ | |||
+ | this.name = "Oolite Pirate AI"; | ||
+ | |||
+ | this.aiStarted = function() { | ||
+ | this.ai = new worldScripts["oolite-libPriorityAI"].PriorityAIController(this.ship); | ||
+ | |||
+ | ai.setParameter("oolite_flag_watchForCargo",true); | ||
+ | |||
+ | ai.setParameter("oolite_personalityMatchesLeader",0.9); | ||
+ | ai.setCommunicationsRole("pirate"); | ||
+ | |||
+ | ai.setParameter("oolite_friendlyRoles",["oolite-pirate"]); | ||
+ | |||
+ | ai.setPriorities([ | ||
+ | /* Combat */ | ||
+ | { | ||
+ | condition: ai.conditionLosingCombat, | ||
+ | behaviour: ai.behaviourFleeCombat, | ||
+ | reconsider: 5 | ||
+ | }, | ||
+ | { | ||
+ | label: "Cargo demands met?", | ||
+ | condition: ai.conditionCargoDemandsMet, | ||
+ | /* Let them go if they've dropped enough cargo and stop firing back */ | ||
+ | truebranch: [ | ||
+ | { | ||
+ | condition: ai.conditionInCombatWithHostiles, | ||
+ | configuration: ai.configurationAcquireHostileCombatTarget, | ||
+ | behaviour: ai.behaviourRepelCurrentTarget, | ||
+ | reconsider: 5 | ||
+ | } | ||
+ | ], | ||
+ | falsebranch: [ | ||
+ | { | ||
+ | condition: ai.conditionInCombat, | ||
+ | configuration: ai.configurationAcquireCombatTarget, | ||
+ | behaviour: ai.behaviourDestroyCurrentTarget, | ||
+ | reconsider: 5 | ||
+ | } | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | preconfiguration: ai.configurationCheckScanner, | ||
+ | condition: ai.conditionScannerContainsSalvageForGroup, | ||
+ | truebranch: [ | ||
+ | { | ||
+ | condition: ai.conditionScannerContainsSalvageForMe, | ||
+ | configuration: ai.configurationAcquireScannedTarget, | ||
+ | behaviour: ai.behaviourCollectSalvage, | ||
+ | reconsider: 20 | ||
+ | }, | ||
+ | // if can't scoop, hang around waiting for the others, | ||
+ | // unless the entire group has enough cargo | ||
+ | { | ||
+ | notcondition: ai.conditionGroupHasEnoughLoot, | ||
+ | configuration: ai.configurationSetDestinationToGroupLeader, | ||
+ | behaviour: ai.behaviourApproachDestination, | ||
+ | reconsider: 15 | ||
+ | } | ||
+ | ] | ||
+ | }, | ||
+ | /* Stay out of the way of hunters */ | ||
+ | { | ||
+ | condition: ai.conditionHostileStationNearby, | ||
+ | configuration: ai.configurationSetDestinationToNearestStation, | ||
+ | behaviour: ai.behaviourLeaveVicinityOfDestination, | ||
+ | reconsider: 20 | ||
+ | }, | ||
+ | { | ||
+ | condition: ai.conditionScannerContainsHunters, | ||
+ | configuration: ai.configurationAcquireScannedTarget, | ||
+ | behaviour: ai.behaviourLeaveVicinityOfTarget, | ||
+ | reconsider: 20 | ||
+ | }, | ||
+ | /* Regroup if necessary */ | ||
+ | { | ||
+ | preconfiguration: ai.configurationAppointGroupLeader, | ||
+ | condition: ai.conditionGroupIsSeparated, | ||
+ | configuration: ai.configurationSetDestinationToGroupLeader, | ||
+ | behaviour: ai.behaviourApproachDestination, | ||
+ | reconsider: 15 | ||
+ | }, | ||
+ | { | ||
+ | label: "Enough loot?", | ||
+ | condition: ai.conditionGroupHasEnoughLoot, | ||
+ | /* Find a station to dock at */ | ||
+ | truebranch: [ | ||
+ | { | ||
+ | condition: ai.conditionIsGroupLeader, | ||
+ | truebranch: ai.templateReturnToBaseOrPlanet() | ||
+ | }, | ||
+ | /* Once the group leader has docked or landed, another one gets | ||
+ | * appointed, and they can decide what to do next */ | ||
+ | { | ||
+ | behaviour: ai.behaviourFollowGroupLeader, | ||
+ | reconsider: 15 | ||
+ | } | ||
+ | |||
+ | ], | ||
+ | /* Look for more loot */ | ||
+ | falsebranch: [ | ||
+ | { | ||
+ | condition: ai.conditionIsGroupLeader, | ||
+ | truebranch: ai.templateLeadPirateMission() | ||
+ | }, | ||
+ | { | ||
+ | behaviour: ai.behaviourFollowGroupLeader, | ||
+ | reconsider: 15 | ||
+ | } | ||
+ | ] | ||
+ | } | ||
+ | ].concat(ai.templateWitchspaceJumpAnywhere())); | ||
+ | |||
+ | } | ||
+ | </div> | ||
+ | |||
+ | |||
+ | |||
+ | === Police AI === | ||
+ | <div class="mw-collapsible mw-collapsed" data-expandtext="Show code" data-collapsetext="Hide code" style="overflow:auto;"> | ||
+ | /* | ||
+ | |||
+ | policeAI.js | ||
+ | |||
+ | Priority-based AI for police | ||
+ | |||
+ | 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"; | ||
+ | |||
+ | this.name = "Oolite Police AI"; | ||
+ | |||
+ | this.aiStarted = function() { | ||
+ | var ai = new worldScripts["oolite-libPriorityAI"].PriorityAIController(this.ship); | ||
+ | |||
+ | ai.setParameter("oolite_flag_listenForDistressCall",true); | ||
+ | ai.setParameter("oolite_flag_markOffenders",true); | ||
+ | ai.setParameter("oolite_flag_fightsNearHostileStations",true); | ||
+ | ai.setParameter("oolite_flag_selfDestructAbandonedShip",true); | ||
+ | |||
+ | if (this.ship.primaryRole == "police-station-patrol") | ||
+ | { | ||
+ | ai.setParameter("oolite_leaderRole","police-station-patrol"); | ||
+ | ai.setWaypointGenerator(ai.waypointsStationPatrol); | ||
+ | ai.setParameter("oolite_flag_patrolStation",true); | ||
+ | } | ||
+ | else if (this.ship.primaryRole == "police-witchpoint-patrol") | ||
+ | { | ||
+ | ai.setParameter("oolite_leaderRole","police-witchpoint-patrol"); | ||
+ | ai.setWaypointGenerator(ai.waypointsWitchpointPatrol); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | // chasing a bandit well off the spacelane is almost as good | ||
+ | // as destroying them | ||
+ | ai.setParameter("oolite_leaderRole","police"); | ||
+ | ai.setWaypointGenerator(ai.waypointsSpacelanePatrol); | ||
+ | } | ||
+ | |||
+ | ai.setParameter("oolite_escortRole","wingman"); | ||
+ | |||
+ | ai.setParameter("oolite_friendlyRoles",["oolite-trader","oolite-bounty-hunter","oolite-scavenger","oolite-shuttle"]); | ||
+ | |||
+ | ai.setParameter("oolite_personalityMatchesLeader",0.5); | ||
+ | ai.setCommunicationsRole("police"); | ||
+ | |||
+ | ai.setPriorities([ | ||
+ | /* Fight */ | ||
+ | { | ||
+ | preconfiguration: ai.configurationLightsOn, | ||
+ | condition: ai.conditionLosingCombat, | ||
+ | behaviour: ai.behaviourFleeCombat, | ||
+ | reconsider: 5 | ||
+ | }, | ||
+ | { | ||
+ | condition: ai.conditionInCombat, | ||
+ | configuration: ai.configurationAcquireCombatTarget, | ||
+ | behaviour: ai.behaviourDestroyCurrentTarget, | ||
+ | reconsider: 5 | ||
+ | }, | ||
+ | /* Check for distress calls */ | ||
+ | { | ||
+ | condition: ai.conditionHasReceivedDistressCall, | ||
+ | behaviour: ai.behaviourRespondToDistressCall, | ||
+ | reconsider: 20 | ||
+ | }, | ||
+ | /* Check for offenders */ | ||
+ | { | ||
+ | preconfiguration: ai.configurationCheckScanner, | ||
+ | condition: ai.conditionScannerContainsFugitive, | ||
+ | configuration: ai.configurationAcquireScannedTarget, | ||
+ | behaviour: ai.behaviourCommenceAttackOnCurrentTarget, | ||
+ | reconsider: 1 | ||
+ | }, | ||
+ | { | ||
+ | condition: ai.conditionScannerContainsSeriousOffender, | ||
+ | configuration: ai.configurationAcquireScannedTarget, | ||
+ | behaviour: ai.behaviourCommenceAttackOnCurrentTarget, | ||
+ | reconsider: 1 | ||
+ | }, | ||
+ | { | ||
+ | preconfiguration: ai.configurationLightsOff, | ||
+ | condition: ai.conditionScannerContainsFineableOffender, | ||
+ | configuration: ai.configurationAcquireScannedTarget, | ||
+ | behaviour: ai.behaviourFineCurrentTarget, | ||
+ | reconsider: 10 | ||
+ | }, | ||
+ | /* What about escape pods? */ | ||
+ | { | ||
+ | condition: ai.conditionScannerContainsEscapePods, | ||
+ | configuration: ai.configurationAcquireScannedTarget, | ||
+ | behaviour: ai.behaviourCollectSalvage, | ||
+ | reconsider: 20 | ||
+ | }, | ||
+ | /* Regroup if necessary */ | ||
+ | { | ||
+ | preconfiguration: ai.configurationAppointGroupLeader, | ||
+ | condition: ai.conditionGroupIsSeparated, | ||
+ | configuration: ai.configurationSetDestinationToGroupLeader, | ||
+ | behaviour: ai.behaviourApproachDestination, | ||
+ | reconsider: 15 | ||
+ | }, | ||
+ | { | ||
+ | condition: ai.conditionGroupLeaderIsStation, | ||
+ | /* Group leader is the station: a short-range patrol or | ||
+ | * defense ship */ | ||
+ | truebranch: [ | ||
+ | { | ||
+ | condition: ai.conditionHasWaypoint, | ||
+ | configuration: ai.configurationSetDestinationToWaypoint, | ||
+ | behaviour: ai.behaviourApproachDestination, | ||
+ | reconsider: 30 | ||
+ | }, | ||
+ | { | ||
+ | condition: ai.conditionPatrolIsOver, | ||
+ | truebranch: ai.templateReturnToBase() | ||
+ | }, | ||
+ | /* No patrol route set up. Make one */ | ||
+ | { | ||
+ | configuration: ai.configurationSetWaypoint, | ||
+ | behaviour: ai.behaviourApproachDestination, | ||
+ | reconsider: 30 | ||
+ | } | ||
+ | ], | ||
+ | /* Group leader is not station: i.e. this is a long-range | ||
+ | * patrol unit */ | ||
+ | falsebranch: [ | ||
+ | { | ||
+ | /* The group leader leads the patrol */ | ||
+ | condition: ai.conditionIsGroupLeader, | ||
+ | truebranch: [ | ||
+ | { | ||
+ | /* Sometimes follow, sometimes not */ | ||
+ | label: "Consider following suspicious?", | ||
+ | condition: ai.conditionCoinFlip, | ||
+ | truebranch: [ | ||
+ | /* Suspicious characters */ | ||
+ | { | ||
+ | condition: ai.conditionScannerContainsSuspiciousShip, | ||
+ | configuration: ai.configurationSetDestinationToScannedTarget, | ||
+ | behaviour: ai.behaviourApproachDestination, | ||
+ | reconsider: 20 | ||
+ | } | ||
+ | ] | ||
+ | }, | ||
+ | /* Nothing interesting here. Patrol for a bit */ | ||
+ | { | ||
+ | condition: ai.conditionHasWaypoint, | ||
+ | configuration: ai.configurationSetDestinationToWaypoint, | ||
+ | behaviour: ai.behaviourApproachDestination, | ||
+ | reconsider: 30 | ||
+ | }, | ||
+ | { | ||
+ | condition: ai.conditionPatrolIsOver, | ||
+ | truebranch: [ | ||
+ | { | ||
+ | condition: ai.conditionMainPlanetNearby, | ||
+ | truebranch: ai.templateReturnToBase() | ||
+ | } | ||
+ | ] | ||
+ | }, | ||
+ | /* No patrol route set up. Make one */ | ||
+ | { | ||
+ | configuration: ai.configurationSetWaypoint, | ||
+ | behaviour: ai.behaviourApproachDestination, | ||
+ | reconsider: 30 | ||
+ | } | ||
+ | ], | ||
+ | /* Other ships in the group will set themselves up | ||
+ | * as escorts if possible, or looser followers if | ||
+ | * not */ | ||
+ | falsebranch: [ | ||
+ | { | ||
+ | preconfiguration: ai.configurationEscortGroupLeader, | ||
+ | condition: ai.conditionIsEscorting, | ||
+ | behaviour: ai.behaviourEscortMothership, | ||
+ | reconsider: 30 | ||
+ | }, | ||
+ | /* if we can't set up as an escort */ | ||
+ | { | ||
+ | behaviour: ai.behaviourFollowGroupLeader, | ||
+ | reconsider: 15 | ||
+ | } | ||
+ | ] | ||
+ | } | ||
+ | ] | ||
+ | } | ||
+ | ]); | ||
+ | |||
+ | } | ||
+ | </div> | ||
+ | |||
+ | == Tip == | ||
+ | When you are familiar with JS, the AI scripting might give you biggest headache. It takes some time to fully understand the detailed working. Especially for complex scripts, the code can blow up in your face. | ||
+ | |||
+ | After you read the relevant bits in the wiki, you probably should analyse the execution of known scripts. This is done with the console. Target a ship and set the AI logging for that ship on by typing "player.ship.target.reportAIMessages = true" (see: reportAIMessages. All the executed AI commands and their responses are now written to your log. I always found this helpful when ships behaved different than expected. (I assume you are on windows as the mac version of the console has some extra features that are very helpful in following running AI scripts.) [https://bb.oolite.space/viewtopic.php?p=117255#p117255 Eric Walch (2010)] - note this comment predates Cim's major reworking of AI which this page describes! | ||
+ | |||
+ | *Simpler intro to console: [[Debug OXP]] | ||
+ | *More complex on console: [[Oolite debug console TCP protocol]] | ||
+ | |||
+ | == Understanding what is happening == | ||
+ | There are a number of ways of accessing the AI decision-making process: | ||
+ | *Outputting in [[Latest.log]] - see [https://bb.oolite.space/viewtopic.php?p=291162#p291162 here] for how to enable this (several posts - 2023) | ||
+ | *Graphical Output (not so good for javascript AIs) - see [https://bb.oolite.space/viewtopic.php?f=2&t=21443 here] for more detail (2023) | ||
+ | |||
+ | == Links == | ||
+ | [[OXP howto]] | ||
+ | [[OXP howto AI]] (.plist AI's) | ||
+ | [[Methods]] | ||
+ | [[AI]] (.plist AI's) | ||
+ | [[AI_methods]] (.plist AI's) | ||
+ | |||
+ | [[Oolite Javascript Reference: PriorityAI Documentation]] | ||
+ | |||
+ | [[Role]] | ||
+ | |||
+ | *[https://bb.oolite.space/viewtopic.php?f=4&t=18891 Debugging AI - and issues with unresponsive Thargoids] (2017) | ||
+ | *[https://bb.oolite.space/viewtopic.php?f=4&t=18889 Getting a ship to fire ECM on behalf of another ship] (2017) | ||
+ | *[https://bb.oolite.space/viewtopic.php?f=4&t=18518 AI for NPC delivering fuel to another NPC liner] (2017) | ||
+ | *[https://bb.oolite.space/viewtopic.php?f=4&t=18973 Create AIs without own ships?] (2017) Tutorial in creating an AI to run a companion ship | ||
[[Category:Oolite]] | [[Category:Oolite]] | ||
[[Category:Oolite scripting]] | [[Category:Oolite scripting]] |
Latest revision as of 02:20, 29 February 2024
The Priority-based AI is a library for writing Javascript-based AIs in Oolite 1.79 or later, designed to allow sophisticated behaviour in relatively few lines of code, and to ensure that AI behaviour remains sensible after changes in the core game.
You don't need to use Priority-based AI to write Javascript AIs if you don't want to, but for many cases it can be the simplest option. OXPers may of course write their own AIs, or even their own AI construction libraries.
This tutorial goes through the steps to set up a basic player hunting AI that you might use in a mission.
Contents
Basic setup
As usual for an AI, the file needs to be in the AIs
folder of your OXP, and referenced from shipdata.plist (or set by script or autoAI, but let's assume that's not happening here)
ai_type = "tutorial_playerHunterAI.js";
The Javascript file then contains
// ship script event handler triggered when a new AI is loaded this.aiStarted = function() { // create a new AI controller and assign it this ship var ai = new worldScripts["oolite-libPriorityAI"].PriorityAIController(this.ship); // set up the priority list var priorities = [ /* priorities go here */ ]; // apply the priority list to the AI ai.setPriorities(priorities); }
That's generally all you need in the file ... except to define the priorities.
Priority 1: survival
The priority list is a list of Javascript objects, each usually containing a condition, and something to do if that condition is true, though more complex options are possible.
The first thing to do, since this ship is going to be fighting, is to make sure it behaves sensibly in the fight.
var priorities = [ { condition: ai.conditionLosingCombat, behaviour: ai.behaviourFleeCombat, reconsider: 20 }, { condition: ai.conditionInCombat, configuration: ai.configurationAcquireCombatTarget, behaviour: ai.behaviourDestroyCurrentTarget, reconsider: 5 } ];
So, firstly the ship checks if it is losing combat. If it is, it starts fleeing combat, and won't check again for another 20 seconds (or until something interrupts it and forces it to reconsider - for example, being shot again)
If it's not losing combat, but it is in combat, then it will acquire a combat target using configurationAcquireCombatTarget, and then it will try to destroy that target (rechecking every five seconds in this case, to make sure it hasn't started losing).
We add the requirement to acquire a combat target because the ship might be fighting multiple targets - this lets it pick up a new one if its current target is destroyed.
So far, so good. The ship will defend itself if attacked. However, it won't attack the player first, so the only way it's going to destroy them is if they make the mistake of shooting it first.
Priority 2: Start a fight with the player
The next thing we add is a search for the player.
var priorities = [ /* ... as above */ { condition: ai.conditionPlayerNearby, configuration: ai.configurationAcquirePlayerAsTarget, behaviour: ai.behaviourDestroyCurrentTarget, reconsider: 5 } ];
This now checks if the player is nearby (i.e. in scanner range). If they are, the AI acquires them as a target, and then proceeds to try to destroy that target as above. Assuming the player doesn't flee out of scanner range in the first five seconds, when the AI comes to reconsider, it will now be in combat, and the losing-or-winning priorities added above will be checked first.
If the player has brought friends, the ship might end up fighting them as well, and only go back to fighting the player afterwards. (Or it might need to fight those ships next, but the player probably won't be in a position to notice that)
Priority 3: Hunt the player
So far, so good, but we're still relying on the player actually coming within scanner range of this ship. It would be better if the ship could hunt the player down.
Of course, if the player isn't within scanner range - and we don't cheat - we're going to have to guess where the player is. Sooner or later they're going to be at the system witchpoint, so if the ship can't see the player, send it there.
var priorities = [ /* ... as above */ { configuration: ai.configurationSetDestinationToWitchpoint, behaviour: ai.behaviourApproachDestination, reconsider: 20 } ];
This priority doesn't have a condition, so the AI will always do it if it gets this far down the priority list. The last priority of any AI must always be unconditional.
behaviourApproachDestination is a quite sophisticated behaviour, which will fly to the appropriate destination, avoiding obstacles on the way. The configuration
entry sets up the destination, desired range, and desired speed for this trip.
The result
So, the full AI script is
// ship script event handler triggered when a new AI is loaded this.aiStarted = function() { // create a new AI controller and assign it this ship var ai = new worldScripts["oolite-libPriorityAI"].PriorityAIController(this.ship); // set up the priority list var priorities = [ { condition: ai.conditionLosingCombat, behaviour: ai.behaviourFleeCombat, reconsider: 20 }, { condition: ai.conditionInCombat, configuration: ai.configurationAcquireCombatTarget, behaviour: ai.behaviourDestroyCurrentTarget, reconsider: 5 }, { condition: ai.conditionPlayerNearby, configuration: ai.configurationAcquirePlayerAsTarget, behaviour: ai.behaviourDestroyCurrentTarget, reconsider: 5 }, { configuration: ai.configurationSetDestinationToWitchpoint, behaviour: ai.behaviourApproachDestination, reconsider: 20 } ]; // apply the priority list to the AI ai.setPriorities(priorities); }
That's it - the ship is ready to hunt down the player!
Obviously there are more sophisticated things the ship could do - it could patrol the spacelane looking for the player, it could only attack the player when there are no witnesses and follow them otherwise, or it could decide that if the player makes it flee, it needs to come back with friends, and go to a nearby station to get some. These are mainly just matters of adding extra priorities to the list.
Have a look through the Priority AI documentation and the AIs in the 1.79 core game for more examples and ideas.
Examples
These are just three of the two dozen examples in the Vanilla game
Rock Hermit AI
/*
rockHermitAI.js
Priority-based AI for rock hermits
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"; this.name = "Oolite Rock Hermit AI"; this.aiStarted = function() { var ai = new worldScripts["oolite-libPriorityAI"].PriorityAIController(this.ship); if (worldScripts["oolite-libPriorityAI"]._getCommunicationPersonalities("hermit").length > 0) { ai.setCommunicationsRole("hermit"); } else { ai.setCommunicationsRole("station"); } ai.setParameter("oolite_friendlyRoles",["oolite-scavenger"]); ai.setPriorities([ /* Fight */ { preconfiguration: ai.configurationStationValidateTarget, condition: ai.conditionInCombat, behaviour: ai.behaviourStationLaunchDefenseShips, reconsider: 5 }, /* Scan */ { preconfiguration: ai.configurationCheckScanner, condition: ai.conditionScannerContainsRocks, behaviour: ai.behaviourStationLaunchMiner, reconsider: 60 }, { configuration: ai.configurationStationReduceAlertLevel, behaviour: ai.behaviourStationManageTraffic, reconsider: 60 } ]); }
Pirate AI
/*
pirateAI.js
Priority-based AI for pirates
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"; this.name = "Oolite Pirate AI"; this.aiStarted = function() { this.ai = new worldScripts["oolite-libPriorityAI"].PriorityAIController(this.ship); ai.setParameter("oolite_flag_watchForCargo",true); ai.setParameter("oolite_personalityMatchesLeader",0.9); ai.setCommunicationsRole("pirate"); ai.setParameter("oolite_friendlyRoles",["oolite-pirate"]); ai.setPriorities([ /* Combat */ { condition: ai.conditionLosingCombat, behaviour: ai.behaviourFleeCombat, reconsider: 5 }, { label: "Cargo demands met?", condition: ai.conditionCargoDemandsMet, /* Let them go if they've dropped enough cargo and stop firing back */ truebranch: [ { condition: ai.conditionInCombatWithHostiles, configuration: ai.configurationAcquireHostileCombatTarget, behaviour: ai.behaviourRepelCurrentTarget, reconsider: 5 } ], falsebranch: [ { condition: ai.conditionInCombat, configuration: ai.configurationAcquireCombatTarget, behaviour: ai.behaviourDestroyCurrentTarget, reconsider: 5 } ] }, { preconfiguration: ai.configurationCheckScanner, condition: ai.conditionScannerContainsSalvageForGroup, truebranch: [ { condition: ai.conditionScannerContainsSalvageForMe, configuration: ai.configurationAcquireScannedTarget, behaviour: ai.behaviourCollectSalvage, reconsider: 20 }, // if can't scoop, hang around waiting for the others, // unless the entire group has enough cargo { notcondition: ai.conditionGroupHasEnoughLoot, configuration: ai.configurationSetDestinationToGroupLeader, behaviour: ai.behaviourApproachDestination, reconsider: 15 } ] }, /* Stay out of the way of hunters */ { condition: ai.conditionHostileStationNearby, configuration: ai.configurationSetDestinationToNearestStation, behaviour: ai.behaviourLeaveVicinityOfDestination, reconsider: 20 }, { condition: ai.conditionScannerContainsHunters, configuration: ai.configurationAcquireScannedTarget, behaviour: ai.behaviourLeaveVicinityOfTarget, reconsider: 20 }, /* Regroup if necessary */ { preconfiguration: ai.configurationAppointGroupLeader, condition: ai.conditionGroupIsSeparated, configuration: ai.configurationSetDestinationToGroupLeader, behaviour: ai.behaviourApproachDestination, reconsider: 15 }, { label: "Enough loot?", condition: ai.conditionGroupHasEnoughLoot, /* Find a station to dock at */ truebranch: [ { condition: ai.conditionIsGroupLeader, truebranch: ai.templateReturnToBaseOrPlanet() }, /* Once the group leader has docked or landed, another one gets * appointed, and they can decide what to do next */ { behaviour: ai.behaviourFollowGroupLeader, reconsider: 15 } ], /* Look for more loot */ falsebranch: [ { condition: ai.conditionIsGroupLeader, truebranch: ai.templateLeadPirateMission() }, { behaviour: ai.behaviourFollowGroupLeader, reconsider: 15 } ] } ].concat(ai.templateWitchspaceJumpAnywhere())); }
Police AI
/*
policeAI.js
Priority-based AI for police
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"; this.name = "Oolite Police AI"; this.aiStarted = function() { var ai = new worldScripts["oolite-libPriorityAI"].PriorityAIController(this.ship); ai.setParameter("oolite_flag_listenForDistressCall",true); ai.setParameter("oolite_flag_markOffenders",true); ai.setParameter("oolite_flag_fightsNearHostileStations",true); ai.setParameter("oolite_flag_selfDestructAbandonedShip",true); if (this.ship.primaryRole == "police-station-patrol") { ai.setParameter("oolite_leaderRole","police-station-patrol"); ai.setWaypointGenerator(ai.waypointsStationPatrol); ai.setParameter("oolite_flag_patrolStation",true); } else if (this.ship.primaryRole == "police-witchpoint-patrol") { ai.setParameter("oolite_leaderRole","police-witchpoint-patrol"); ai.setWaypointGenerator(ai.waypointsWitchpointPatrol); } else { // chasing a bandit well off the spacelane is almost as good // as destroying them ai.setParameter("oolite_leaderRole","police"); ai.setWaypointGenerator(ai.waypointsSpacelanePatrol); } ai.setParameter("oolite_escortRole","wingman"); ai.setParameter("oolite_friendlyRoles",["oolite-trader","oolite-bounty-hunter","oolite-scavenger","oolite-shuttle"]); ai.setParameter("oolite_personalityMatchesLeader",0.5); ai.setCommunicationsRole("police"); ai.setPriorities([ /* Fight */ { preconfiguration: ai.configurationLightsOn, condition: ai.conditionLosingCombat, behaviour: ai.behaviourFleeCombat, reconsider: 5 }, { condition: ai.conditionInCombat, configuration: ai.configurationAcquireCombatTarget, behaviour: ai.behaviourDestroyCurrentTarget, reconsider: 5 }, /* Check for distress calls */ { condition: ai.conditionHasReceivedDistressCall, behaviour: ai.behaviourRespondToDistressCall, reconsider: 20 }, /* Check for offenders */ { preconfiguration: ai.configurationCheckScanner, condition: ai.conditionScannerContainsFugitive, configuration: ai.configurationAcquireScannedTarget, behaviour: ai.behaviourCommenceAttackOnCurrentTarget, reconsider: 1 }, { condition: ai.conditionScannerContainsSeriousOffender, configuration: ai.configurationAcquireScannedTarget, behaviour: ai.behaviourCommenceAttackOnCurrentTarget, reconsider: 1 }, { preconfiguration: ai.configurationLightsOff, condition: ai.conditionScannerContainsFineableOffender, configuration: ai.configurationAcquireScannedTarget, behaviour: ai.behaviourFineCurrentTarget, reconsider: 10 }, /* What about escape pods? */ { condition: ai.conditionScannerContainsEscapePods, configuration: ai.configurationAcquireScannedTarget, behaviour: ai.behaviourCollectSalvage, reconsider: 20 }, /* Regroup if necessary */ { preconfiguration: ai.configurationAppointGroupLeader, condition: ai.conditionGroupIsSeparated, configuration: ai.configurationSetDestinationToGroupLeader, behaviour: ai.behaviourApproachDestination, reconsider: 15 }, { condition: ai.conditionGroupLeaderIsStation, /* Group leader is the station: a short-range patrol or * defense ship */ truebranch: [ { condition: ai.conditionHasWaypoint, configuration: ai.configurationSetDestinationToWaypoint, behaviour: ai.behaviourApproachDestination, reconsider: 30 }, { condition: ai.conditionPatrolIsOver, truebranch: ai.templateReturnToBase() }, /* No patrol route set up. Make one */ { configuration: ai.configurationSetWaypoint, behaviour: ai.behaviourApproachDestination, reconsider: 30 } ], /* Group leader is not station: i.e. this is a long-range * patrol unit */ falsebranch: [ { /* The group leader leads the patrol */ condition: ai.conditionIsGroupLeader, truebranch: [ { /* Sometimes follow, sometimes not */ label: "Consider following suspicious?", condition: ai.conditionCoinFlip, truebranch: [ /* Suspicious characters */ { condition: ai.conditionScannerContainsSuspiciousShip, configuration: ai.configurationSetDestinationToScannedTarget, behaviour: ai.behaviourApproachDestination, reconsider: 20 } ] }, /* Nothing interesting here. Patrol for a bit */ { condition: ai.conditionHasWaypoint, configuration: ai.configurationSetDestinationToWaypoint, behaviour: ai.behaviourApproachDestination, reconsider: 30 }, { condition: ai.conditionPatrolIsOver, truebranch: [ { condition: ai.conditionMainPlanetNearby, truebranch: ai.templateReturnToBase() } ] }, /* No patrol route set up. Make one */ { configuration: ai.configurationSetWaypoint, behaviour: ai.behaviourApproachDestination, reconsider: 30 } ], /* Other ships in the group will set themselves up * as escorts if possible, or looser followers if * not */ falsebranch: [ { preconfiguration: ai.configurationEscortGroupLeader, condition: ai.conditionIsEscorting, behaviour: ai.behaviourEscortMothership, reconsider: 30 }, /* if we can't set up as an escort */ { behaviour: ai.behaviourFollowGroupLeader, reconsider: 15 } ] } ] } ]); }
Tip
When you are familiar with JS, the AI scripting might give you biggest headache. It takes some time to fully understand the detailed working. Especially for complex scripts, the code can blow up in your face.
After you read the relevant bits in the wiki, you probably should analyse the execution of known scripts. This is done with the console. Target a ship and set the AI logging for that ship on by typing "player.ship.target.reportAIMessages = true" (see: reportAIMessages. All the executed AI commands and their responses are now written to your log. I always found this helpful when ships behaved different than expected. (I assume you are on windows as the mac version of the console has some extra features that are very helpful in following running AI scripts.) Eric Walch (2010) - note this comment predates Cim's major reworking of AI which this page describes!
- Simpler intro to console: Debug OXP
- More complex on console: Oolite debug console TCP protocol
Understanding what is happening
There are a number of ways of accessing the AI decision-making process:
- Outputting in Latest.log - see here for how to enable this (several posts - 2023)
- Graphical Output (not so good for javascript AIs) - see here for more detail (2023)
Links
OXP howto OXP howto AI (.plist AI's) Methods AI (.plist AI's) AI_methods (.plist AI's) Oolite Javascript Reference: PriorityAI Documentation Role
- Debugging AI - and issues with unresponsive Thargoids (2017)
- Getting a ship to fire ECM on behalf of another ship (2017)
- AI for NPC delivering fuel to another NPC liner (2017)
- Create AIs without own ships? (2017) Tutorial in creating an AI to run a companion ship