Difference between revisions of "Oolite PriorityAI Tutorial"

From Elite Wiki
(Added Eric Walch's tip)
(Added AI checking section)
Line 568: Line 568:
 
*Simpler intro to console: [[Debug OXP]]
 
*Simpler intro to console: [[Debug OXP]]
 
*More complex on console: [[Oolite debug console TCP protocol]]
 
*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 [http://www.aegidian.org/bb/viewtopic.php?p=291162#p291162 here] for how to enable this (several posts - 2023)
 +
*Graphical Output (not so good for javascript AIs) - see [http://www.aegidian.org/bb/viewtopic.php?f=2&t=21443 here] for more detail (2023)
  
 
== Links ==
 
== Links ==

Revision as of 17:48, 12 September 2023

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.

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!

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