diff --git a/Resources/AIs/bountyHunterAI.js b/Resources/AIs/bountyHunterAI.js index be008432..e8197a8f 100644 --- a/Resources/AIs/bountyHunterAI.js +++ b/Resources/AIs/bountyHunterAI.js @@ -30,7 +30,7 @@ this.name = "Oolite Bounty Hunter AI"; this.version = "1.79"; this.aiStarted = function() { - this.ai = new worldScripts["oolite-libPriorityAI"].AILib(this.ship); + var ai = new worldScripts["oolite-libPriorityAI"].AILib(this.ship); ai.setParameter("oolite_flag_listenForDistressCall",true); diff --git a/Resources/AIs/escortAI.js b/Resources/AIs/escortAI.js new file mode 100644 index 00000000..f0e46a28 --- /dev/null +++ b/Resources/AIs/escortAI.js @@ -0,0 +1,115 @@ +/* + +escortAI.js + +Priority-based AI for escorts + +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 Escort AI"; +this.version = "1.79"; + +this.aiStarted = function() { + var ai = new worldScripts["oolite-libPriorityAI"].AILib(this.ship); + + ai.setPriorities([ + { + condition: ai.conditionLosingCombat, + behaviour: ai.behaviourFleeCombat, + reconsider: 5 + }, + { + condition: ai.conditionMothershipInCombat, + truebranch: [ + { + condition: ai.conditionMothershipUnderAttack, + configuration: ai.configurationAcquireDefensiveEscortTarget, + behaviour: ai.behaviourRepelCurrentTarget, + reconsider: 5 + }, + { + condition: ai.conditionMothershipIsAttacking, + configuration: ai.configurationAcquireOffensiveEscortTarget, + behaviour: ai.behaviourDestroyCurrentTarget, + reconsider: 5 + }, + { + behaviour: ai.behaviourRejoinMothership, + reconsider: 5 + } + ] + }, + { + // if we're in combat but mothership isn't, then we need + // to finish this fight off and get back to them + condition: ai.conditionInCombat, + configuration: ai.configurationAcquireCombatTarget, + behaviour: ai.behaviourRepelCurrentTarget, + reconsider: 5 + }, + { + condition: ai.conditionHasMothership, + behaviour: ai.behaviourEscortMothership, + reconsider: 60 + }, + /* Don't have a mothership */ + { + condition: ai.conditionWitchspaceEntryRequested, + behaviour: ai.behaviourEnterWitchspace, + reconsider: 15 + }, + /* And it's not because they've recently jumped out */ + { + condition: ai.conditionFriendlyStationNearby, + configuration: ai.configurationSetNearbyFriendlyStationForDocking, + behaviour: ai.behaviourDockWithStation, + reconsider: 30 + }, + /* And it's not because they just docked either */ + { + preconfiguration: ai.configurationCheckScanner, + condition: ai.conditionScannerContainsShipNeedingEscort, + behaviour: ai.behaviourOfferToEscort, + reconsider: 15 + }, + { + condition: ai.conditionFriendlyStationExists, + configuration: ai.configurationSetDestinationToNearestFriendlyStation, + behaviour: ai.behaviourApproachDestination, + reconsider: 30 + }, + /* No friendly stations and no nearby ships needing escort */ + { + condition: ai.conditionCanWitchspaceOut, + configuration: ai.configurationSelectWitchspaceDestination, + behaviour: ai.behaviourEnterWitchspace, + reconsider: 20 + }, + /* And we're stuck here */ + { + configuration: ai.configurationSetDestinationToWitchpoint, + behaviour: ai.approachDestination, + reconsider: 30 + } + ]); +} \ No newline at end of file diff --git a/Resources/Scripts/oolite-ailib.js b/Resources/Scripts/oolite-ailib.js index 811e8711..4caffbbe 100644 --- a/Resources/Scripts/oolite-ailib.js +++ b/Resources/Scripts/oolite-ailib.js @@ -230,6 +230,37 @@ this.AILib = function(ship) } } + this.friendlyStation = function(station) + { + return (station.target != this.ship || !station.hasHostileTarget); + } + + this.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 (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 (cruise > this.ship.escortGroup.ships[i].maxSpeed) + { + cruise = this.ship.escortGroup.ships[i].maxSpeed; + } + } + } + return cruise; + } + /* ****************** Condition functions ************** */ @@ -324,6 +355,82 @@ this.AILib = function(ship) return false; } + this.conditionHasMothership = function() + { + return (this.ship.group && this.ship.group.leader != this.ship && this.ship.group.leader.escortGroup.containsShip(this.ship)); + } + + this.conditionMothershipInCombat = function() + { + if (this.ship.group && this.ship.group.leader != this.ship && this.ship.group.leader.escortGroup.containsShip(this.ship)) + { + var leader = this.ship.group.leader; + if (leader.position.distanceTo(this.ship) > this.ship.scannerRange) + { + return false; // can't tell + } + if (leader.hasHostileTarget) + { + 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; + } + } + + this.conditionMothershipUnderAttack = function() + { + if (this.ship.group && this.ship.group.leader != this.ship && this.ship.group.leader.escortGroup.containsShip(this.ship)) + { + var leader = this.ship.group.leader; + if (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; + } + } + + this.conditionMothershipIsAttacking = function() + { + if (this.ship.group && this.ship.group.leader != this.ship && this.ship.group.leader.escortGroup.containsShip(this.ship)) + { + var leader = this.ship.group.leader; + if (leader.target && leader.hasHostileTarget && leader.target.position.distanceTo(this.ship) < this.ship.scannerRange) + { + return true; + } + } + return false; + } this.conditionNearDestination = function() { @@ -348,7 +455,7 @@ this.AILib = function(ship) this.conditionScannerContainsSalvage = function() { - if (this.ship.cargoSpaceAvailable == 0) + if (this.ship.cargoSpaceAvailable == 0 || this.ship.equipmentStatus("EQ_FUEL_SCOOPS") != "EQUIPMENT_OK") { return false; } @@ -381,6 +488,71 @@ this.AILib = function(ship) return system.isInterstellarSpace; } + this.conditionWitchspaceEntryRequested = function() + { + return (this.getParameter("oolite_witchspaceWormhole") != null); + } + + this.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; + } + + this.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; + } + + this.conditionScannerContainsShipNeedingEscort = function() + { + if (this.ship.bounty == 0) + { + return this.checkScannerWithPredicate(function(s) { + return s.bounty == 0 && (!s.escortGroup || s.escortGroup.count <= s.maxEscorts); + }); + } + else + { + return this.checkScannerWithPredicate(function(s) { + return s.bounty > 0 && (!s.escortGroup || s.escortGroup.count <= s.maxEscorts); + }); + } + } + + this.conditionCanWitchspaceOut = function() + { + if (!this.ship.hasHyperspaceMotor) + { + return false; + } + return (system.info.systemsInRange(this.ship.fuel).length > 0); + } + /* ****************** Behaviour functions ************** */ /* Behaviours. Behaviours are effectively a state definition, @@ -444,6 +616,30 @@ this.AILib = function(ship) } + this.behaviourRepelCurrentTarget = function() + { + var handlers = {}; + this.responsesAddStandard(handlers); + this.setUpHandlers(handlers); + if (this.ship.target.isFleeing || this.ship.target.isDerelict) + { + // repelling succeeded + this.ship.removeDefenseTarget(this.ship.target); + this.ship.target = null; + this.reconsiderNow(); + } + else + { + if (!this.ship.hasHostileTarget) + { + // entering attack mode + this.communicate("oolite_beginningAttack",this.ship.target.displayName); + } + this.ship.performAttack(); + } + } + + this.behaviourCollectSalvage = function() { var handlers = {}; @@ -609,6 +805,15 @@ this.AILib = function(ship) // the wormhole we were trying for has expired this.setParameter("oolite_witchspaceWormhole",null); } + else if (wormhole) + { + this.ship.destination = wormhole.position; + this.ship.desiredRange = 0; + this.ship.desiredSpeed = this.ship.maxSpeed; + this.ship.performFlyToRangeFromDestination(); + this.setUpHandlers(handlers); + return; + } var destID = this.getParameter("oolite_witchspaceDestination"); if (destID == null) @@ -645,7 +850,7 @@ this.AILib = function(ship) { this.ship.setDestination = blocker.position; this.ship.setDesiredRange = 30000; - this.ship.setDesiredSpeed = this.ship.maxSpeed; + this.ship.setDesiredSpeed = this.cruiseSpeed(); this.ship.performFlyToRangeFromDestination(); // no reconsidering yet } @@ -661,6 +866,56 @@ this.AILib = function(ship) } } + + this.behaviourEscortMothership = function() + { + var handlers = {}; + this.responsesAddStandard(handlers); + this.responsesAddEscort(handlers); + this.setUpHandlers(handlers); + this.ship.desiredRange = 0; + this.ship.performEscort(); + } + + + // Separate behaviour just in case we want to change it later + // This is the one to catch up with a distant mothership + this.behaviourRejoinMothership = function() + { + var handlers = {}; + this.responsesAddStandard(handlers); + this.responsesAddEscort(handlers); + this.setUpHandlers(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(); + } + + + this.behaviourOfferToEscort = function() + { + var handlers = {}; + this.responsesAddStandard(handlers); + this.setUpHandlers(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 + } + } + /* ****************** Configuration functions ************** */ /* Configurations. Configurations are set up actions for a behaviour @@ -676,10 +931,13 @@ this.AILib = function(ship) return; } var dts = this.ship.defenseTargets - if (dts.length > 0) + for (var i = 0; i < dts.length ; i++) { - this.ship.target = dts[0]; - return; + if (dts[i].position.distanceTo(this.ship) < this.ship.scannerRange) + { + this.ship.target = dts[0]; + return; + } } if (this.ship.group != null) { @@ -687,7 +945,7 @@ this.AILib = function(ship) { if (this.ship.group.ships[i] != this.ship) { - if (this.ship.group.ships[i].target && this.ship.group.ships[i].hasHostileTarget) + if (this.ship.group.ships[i].target && this.ship.group.ships[i].hasHostileTarget && this.ship.group.ships[i].target.position.distanceTo(this.ship) < this.ship.scannerRange) { this.ship.target = this.ship.group.ships[i].target; return; @@ -701,7 +959,7 @@ this.AILib = function(ship) { if (this.ship.escortGroup.ships[i] != this.ship) { - if (this.ship.escortGroup.ships[i].target && this.ship.escortGroup.ships[i].hasHostileTarget) + if (this.ship.escortGroup.ships[i].target && this.ship.escortGroup.ships[i].hasHostileTarget && this.ship.escortGroup.ships[i].target.position.distanceTo(this.ship) < this.ship.scannerRange) { this.ship.target = this.ship.escortGroup.ships[i].target; return; @@ -711,6 +969,53 @@ this.AILib = function(ship) } } + this.configurationAcquireOffensiveEscortTarget = function() + { + if (this.ship.group && this.ship.group.leader && this.ship.group.leader.hasHostileTarget) + { + if (this.ship.distanceTo(this.ship.group.leader.target) < this.ship.scannerRange) + { + this.ship.target = this.ship.group.leader.target; + this.ship.addDefenseTarget(this.ship.target); + } + } + } + + this.configurationAcquireDefensiveEscortTarget = function() + { + if (this.ship.group && this.ship.group.leader) + { + var leader = this.ship.group.leader; + if (leader.target.target == leader && leader.hasHostileTarget && 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 && dts[i].hasHostileTarget && !dts[i].isFleeing && dts[i].position.distanceTo(this.ship) < this.ship.scannerRange) + { + this.ship.target = dts[i]; + } + } + } + } + } + + this.configurationAcquireOffensiveEscortTarget = function() + { + if (this.ship.group && this.ship.group.leader) + { + var leader = this.ship.group.leader; + if (leader.hasHostileTarget) + { + this.ship.target = leader.target; + } + } + } + this.configurationCheckScanner = function() { this.setParameter("oolite_scanResults",this.ship.checkScanner()); @@ -722,11 +1027,49 @@ this.AILib = function(ship) this.ship.target = this.getParameter("oolite_scanResultSpecific"); } + this.configurationSetDestinationToWitchpoint = function() + { + this.ship.destination = new Vector3D(0,0,0); + this.ship.desiredRange = 10000; + this.ship.desiredSpeed = this.cruiseSpeed(); + } + + this.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(); + } + } + this.configurationSetDestinationFromPatrolRoute = function() { this.ship.destination = this.getParameter("oolite_patrolRoute"); this.ship.desiredRange = this.getParameter("oolite_patrolRouteRange"); - this.ship.desiredSpeed = this.ship.maxSpeed; + this.ship.desiredSpeed = this.cruiseSpeed(); } this.configurationMakeSpacelanePatrolRoute = function() @@ -845,6 +1188,26 @@ this.AILib = function(ship) this.setParameter("oolite_witchspaceDestination",possible[Math.floor(Math.random()*possible.length)].systemID); } + this.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; + } + } + } + } + + /* ****************** Response definition functions ************** */ /* Standard state-machine responses. These set up a set of standard @@ -853,7 +1216,8 @@ this.AILib = function(ship) * priorities. Many behaviours will need to supplement the standard * responses with additional definitions. */ - this.responsesAddStandard = function(handlers) { + this.responsesAddStandard = function(handlers) + { handlers.cascadeWeaponDetected = function(weapon) { this.ship.clearDefenseTargets(); @@ -957,6 +1321,15 @@ this.AILib = function(ship) { this.reconsiderNow(); } + handlers.shipLaunchedFromStation = function() + { + this.reconsiderNow(); + } + handlers.shipExitedWormhole = function() + { + this.reconsiderNow(); + } + handlers.distressMessageReceived = function(aggressor, sender) { if (this.getParameter("oolite_flag_listenForDistressCall") != true) @@ -967,10 +1340,25 @@ this.AILib = function(ship) this.setParameter("oolite_distressSender",sender); this.reconsiderNow(); } + handlers.playerWillEnterWitchspace = function() + { + var wormhole = this.getParameter("oolite_witchspaceWormhole"); + if (wormhole != null) + { + this.ship.enterWormhole(wormhole); + } + } + handlers.wormholeSuggested = function(hole) + { + this.setParameter("oolite_witchspaceWormhole",hole); + this.reconsiderNow(); + } // TODO: more event handlers } - this.responsesAddDocking = function(handlers) { + /* Additional handlers for use while docking */ + this.responsesAddDocking = function(handlers) + { handlers.stationWithdrewDockingClearance = function() { this.setParameter("oolite_dockingStation",null); @@ -985,10 +1373,49 @@ this.AILib = function(ship) this.reconsiderNow(); } }; + } + + /* Override of standard handlers for use while escorting */ + this.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(); + } } + }; // end object constructor diff --git a/src/Core/Entities/ShipEntity.m b/src/Core/Entities/ShipEntity.m index c0abccff..e4328e0f 100644 --- a/src/Core/Entities/ShipEntity.m +++ b/src/Core/Entities/ShipEntity.m @@ -2199,6 +2199,8 @@ ShipEntity* doOctreesCollide(ShipEntity* prime, ShipEntity* other) { StationEntity *stationLaunchedFrom = [UNIVERSE nearestEntityMatchingPredicate:IsStationPredicate parameter:NULL relativeToEntity:self]; [self setStatus:STATUS_IN_FLIGHT]; + // awaken JS-based AIs + [self doScriptEvent:OOJSID("aiStarted")]; [self doScriptEvent:OOJSID("shipLaunchedFromStation") withArgument:stationLaunchedFrom]; [shipAI reactToMessage:@"LAUNCHED OKAY" context:@"launched"]; } @@ -12178,10 +12180,15 @@ Vector positionOffsetForShipInRotationToAlignment(ShipEntity* ship, Quaternion q idleEscorts = [NSMutableSet set]; for (escortEnum = [self escortEnumerator]; (escort = [escortEnum nextObject]); ) { - if (![[[escort getAI] name] isEqualToString:@"interceptAI.plist"]) + if (![[[escort getAI] name] isEqualToString:@"interceptAI.plist"] && ![[[escort getAI] name] isEqualToString:@"nullAI.plist"]) { [idleEscorts addObject:escort]; } + else if ([[[escort getAI] name] isEqualToString:@"nullAI.plist"]) + { + // JS-based escorts get a help request + [escort doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:[self primaryTarget]]; + } } escortCount = [idleEscorts count]; @@ -12225,7 +12232,10 @@ Vector positionOffsetForShipInRotationToAlignment(ShipEntity* ship, Quaternion q if ([escort owner] == self) [escort setOwner:escort]; if(target && [target isStation]) [escort setTargetStation:target]; // JSAI: needs slightly different implementation of delay - [escort setAITo:@"dockingAI.plist"]; + if (![[[escort getAI] name] isEqualToString:@"nullAI.plist"]) + { + [escort setAITo:@"dockingAI.plist"]; + } [ai setState:@"ABORT" afterDelay:delay + 0.25]; [escort doScriptEvent:OOJSID("escortDock") withArgument:[NSNumber numberWithFloat:delay]]; } diff --git a/src/Core/Entities/WormholeEntity.m b/src/Core/Entities/WormholeEntity.m index 925234b4..c0452b68 100644 --- a/src/Core/Entities/WormholeEntity.m +++ b/src/Core/Entities/WormholeEntity.m @@ -433,8 +433,11 @@ static void DrawWormholeCorona(GLfloat inner_radius, GLfloat outer_radius, int s } [ship setSpeed:[self exitSpeed]]; // all ships from this wormhole have same velocity - - // Should probably pass the wormhole, but they have no JS representation + // awaken JS-based AIs + [ship doScriptEvent:OOJSID("aiStarted")]; + + // Wormholes now have a JS representation, so we could provide it + // but is it worth it for the exit wormhole? [ship doScriptEvent:OOJSID("shipExitedWormhole") andReactToAIMessage:@"EXITED WITCHSPACE"]; // update the ships's position diff --git a/src/Core/Scripting/OOJSSystem.m b/src/Core/Scripting/OOJSSystem.m index 5e98867b..9381cfae 100644 --- a/src/Core/Scripting/OOJSSystem.m +++ b/src/Core/Scripting/OOJSSystem.m @@ -130,6 +130,7 @@ enum kSystem_pseudoRandom256, // constant-per-system pseudorandom number in [0..256), integer, read-only kSystem_pseudoRandomNumber, // constant-per-system pseudorandom number in [0..1), double, read-only kSystem_sun, // system's sun, Planet, read-only + kSystem_stations, // list of dockable entities, read-only kSystem_techLevel, // tech level ID, integer, read/write }; @@ -159,6 +160,7 @@ static JSPropertySpec sSystemProperties[] = { "pseudoRandom100", kSystem_pseudoRandom100, OOJS_PROP_READONLY_CB }, { "pseudoRandom256", kSystem_pseudoRandom256, OOJS_PROP_READONLY_CB }, { "pseudoRandomNumber", kSystem_pseudoRandomNumber, OOJS_PROP_READONLY_CB }, + { "stations", kSystem_stations, OOJS_PROP_READONLY_CB }, { "sun", kSystem_sun, OOJS_PROP_READONLY_CB }, { "techLevel", kSystem_techLevel, OOJS_PROP_READWRITE_CB }, { 0 } @@ -259,6 +261,16 @@ static JSBool SystemGetProperty(JSContext *context, JSObject *this, jsid propID, handled = YES; break; + case kSystem_stations: + /* Optimise? This may be called enough times that it's worth + * having a list of stations cached and edited on station + * creation/destruction, as we do for planets - CIM 13/7/2013 */ + OOJS_BEGIN_FULL_NATIVE(context) + result = [UNIVERSE findShipsMatchingPredicate:IsStationPredicate parameter:NULL inRange:-1 ofEntity:nil]; + OOJS_END_FULL_NATIVE + handled = YES; + break; + case kSystem_allShips: OOJS_BEGIN_FULL_NATIVE(context) result = [UNIVERSE findShipsMatchingPredicate:JSEntityIsJavaScriptSearchablePredicate parameter:NULL inRange:-1 ofEntity:nil];