Proposals for Oolite 1.79/1.80, 3 of 5: Javascript-based AIs
Posted: Fri Jun 14, 2013 2:11 pm
Thanks for your comments on the first two. This one's a little more technical in detail than the first two, but hopefully should make OXP writing easier. Let me know what you think, especially if you've set up complex behaviours with the current AI system. What do you think to the draft syntax? Are there any core behaviours missing?
Javascript-based AIs
An alternative language for writing ship AIs in, similar to the use of Javascript ship/world scripts instead of legacy scripting.
Problem:
The current state machine AI, while capable of creating extremely complex behaviour, takes a large amount of boilerplate lines to set up even simple behaviours such as "fly to destination" or "fight aggressors".
Code re-use between AIs, beyond a few things which can be entirely self-contained (e.g.interceptAI.plist
) is mostly limited to copy/paste. Within an AI script, each state is likely to need a number of standard copy/pasted event responses (e.g. "ATTACKED
", "INCOMING_MISSILE
", etc)
Several temporary variables such as "found target" and "primary aggressor" are needed as the state machine language has no native variable storage. Querying ship state such as energy level requires issuing an AI command, and then waiting to receive an AI event triggered by it.
Proposal
Allow Javascript to be used as an alternative scripting language for AIs. The current state machine language would continue to be supported for compatibility with older OXPs, but eventually discouraged.
Part 1
Implement Javascript properties, methods, and ship script event handlers for all AI commands and events not currently having a JS equivalent. The major ones areperformAttack
and the other commands which set the internal flight behaviour. Some of the additional properties and event handlers will also be useful to other OXP writing.
This will also require entry wormholes to be given a JS representation (only read-only: fuller scripting of wormholes is a separate task, not included here, and maybe not for 1.80 at all)
This will allow JS-based AIs to be implemented in the ship script, but this is probably only of use to a few OXPs, as this then ties the AI to the ship script, making it difficult to switch AIs.
Part 2
Allow JS files to be placed in the AIs directory, and initialised with a setAI command (setAI("aiFile")
would look for the JS version first, then the plist version). The AI script and ship script will then both receive all events, and can respond accordingly.
This allows useful JS-based AIs, but writing them can still involve a lot of boilerplate code, probably copy/pasted from another AI. The ability to have local variables makes certain things simpler than the state machine AI, but the construction of AI is still at quite a low level.
Part 3
Priority over-ride. New behavior dictated. Must break target into component materials.
Create a priority-based AI library. The idea behind this is that rather than (re)defining common behaviours every time, a ship could be given a priority list of behaviours, which call library functions, which insert the appropriate event handlers into the AI script. For exampleroute1traderAI.plist
could be represented (roughly) as:Each of these priorities would have a test to see if it was relevant behaviour, and a library function which set up the appropriate event handlers. Certain event handlers (especially combat-relevant ones) would then cause the priority list to be evaluated again. Most AIs would also have a timer to allow the list to be reevaluated periodically.
- Flee strong attackers
- Fight weak attackers until escape possible
- Dock with nearby station
- Approach station around current planet
- Approach planet with station
- Jump to nearby system
OXP writers could then use the library functions much of the time for common behaviour such as "destroy this ship" or "fly to waypoint/destination", etc. For more specialist behaviour, the OXP would provide a function with the same effect as the library functions.
Use of the library functions would not be required at all, of course - low-level AI writing (or sticking with the state machine AI) would remain possible.
All native AIs would at this point be rewritten to be JS-based using the AI library. Most of them are likely to end up being very short as a result. For example, the trader AI might be (syntax, function names, parameters, etc. for illustration only - actual ones not yet decided, and comments welcome):
This is considerably shorter than the several hundred lines needed forCode: Select all
this.startAI = function() { var ailib = worldScripts.OoliteAI; // world script is easiest but we could make it a property of something else (e.g. global.aiLib) this.$destination = ailib.getFriendlyStation(this.ship); if (!this.$destination) { this.ship.setAI("exitingTraderAI"); return; } ailib.setPriorities(this,[ { condition: ailib.conditionLosingCombat, behaviour: ailib.behaviourFleeCombat, reconsider: 5 }, { condition: ailib.conditionInCombat, behaviour: ailib.behaviourRepelAggressors, reconsider: 5 }, { condition: ailib.conditionNearStation.bind(ailib,this.$destination,25E3), behaviour: ailib.behaviourDockStation.bind(ailib,this.$destination), // no need for time-based reconsideration here; can do it all with events }, { condition: ailib.objectsWithinRange.bind(ailib,system.mainPlanet,this.$destination,system.mainPlanet.radius*3), truebranch: [ { condition: ailib.conditionNearPlanet.bind(ailib,system.mainPlanet), behaviour: ailib.behaviourApproachDestination.bind(ailib,this.$destination.position,15E3) }, { condition: ailib.conditionObjectValid.bind(ailib,this.$destination), behaviour: ailib.behaviourApproachDestination.bind(ailib,system.mainPlanet,system.mainPlanet.radius*4), } ], // priority subtree for station near planet falsebranch: [ { condition: ailib.conditionObjectValid.bind(ailib,this.$destination), behaviour: ailib.behaviourApproachDestination.bind(ailib,this.$destination.position,15E3) } ] // priority subtree for station away from planet }, { // no condition = always true setAI: "route1traderAI.js" // restart AI to get a new station since the current one has vanished } ]); } this.stopAI = function() { worldScripts.OoliteAI.cleanup(this); }
route1TraderAI
,dockingAI
,traderInterceptAI
, and so on. More importantly, a more complex trading routine would not take many more lines in the AI script (though the AI library might be quite complex internally)
Something which will be needed but isn't in the example above:
The search function would select a target based on criteria, and pass that target to the behaviour. If no suitable target was found, it would return false and the behaviour would not be executed (as if a condition had failed)Code: Select all
{ search: searchFunction, behaviour: ... }
The lists below are intended to cover behaviour in the core game. Writing extra behaviours should be fairly straightforward and is expected. Still, if there are obvious cases missing they should be in the core game.
Preliminary list of combat behavioursPreliminary list of non-combat behaviours
- Flee combat: Fly away from all hostile vessels at top speed, dropping cargo if requested and possible.
- Repel attackers: Fire on any attackers (self, then group) until they flee, then ignore them.
- Destroy attackers: Fire on any attackers (self, then group) until they are dead.
- Aggressive Escort: Fire on mothership's target until they are dead.
- Defensive Escort: Fire on ships attacking mothership until they flee.
- Destroy target: Destroy the selected target.
- Rob target: Fire on selected target, requesting cargo dumping, destroying if not complied with (or too little cargo dropped)
- Intercept target: Ram target
- Missile: Behave as missile. Parameters for detonation function and ECM susceptibility.
- Mine: Wait, then detonate. Parameter for detonation function.
- Go to destination: go to specified range from specified coordinates, avoiding obstacles
- Dock: dock with specified station
- Await docking: stop and wait for ships to dock
- Escort: form up in escort formation with mothership / group leader, including following to witchspace
- Guard: loop at a specified distance from an object (e.g. the station patrols)
- Salvage: collect scoopable objects, prioritising escape pods
- Mine: locate asteroids/boulders and break them apart
- Sunskim: head to solar atmosphere, refuel until full
- Wait: stop, maintain position
- Tumble: rotate
- Land: land on selected planet/moon
- Exit system: go to new system