LCOV - code coverage report
Current view: top level - Core/Entities - ShipEntityAI.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 116 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          Line data    Source code
       1           0 : /*
       2             :  
       3             :  ShipEntityAI.m
       4             :  
       5             :  Oolite
       6             :  Copyright (C) 2004-2013 Giles C Williams and contributors
       7             :  
       8             :  This program is free software; you can redistribute it and/or
       9             :  modify it under the terms of the GNU General Public License
      10             :  as published by the Free Software Foundation; either version 2
      11             :  of the License, or (at your option) any later version.
      12             :  
      13             :  This program is distributed in the hope that it will be useful,
      14             :  but WITHOUT ANY WARRANTY; without even the implied warranty of
      15             :  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16             :  GNU General Public License for more details.
      17             :  
      18             :  You should have received a copy of the GNU General Public License
      19             :  along with this program; if not, write to the Free Software
      20             :  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
      21             :  MA 02110-1301, USA.
      22             :  
      23             :  */
      24             : 
      25             : #import "ShipEntityAI.h"
      26             : #import "OOMaths.h"
      27             : #import "Universe.h"
      28             : #import "AI.h"
      29             : 
      30             : #import "StationEntity.h"
      31             : #import "OOSunEntity.h"
      32             : #import "OOPlanetEntity.h"
      33             : #import "WormholeEntity.h"
      34             : #import "PlayerEntity.h"
      35             : #import "PlayerEntityLegacyScriptEngine.h"
      36             : #import "OOJavaScriptEngine.h"
      37             : #import "OOJSFunction.h"
      38             : #import "OOShipGroup.h"
      39             : 
      40             : #import "OOStringExpander.h"
      41             : #import "OOStringParsing.h"
      42             : #import "OOEntityFilterPredicate.h"
      43             : #import "OOConstToString.h"
      44             : #import "OOConstToJSString.h"
      45             : #import "OOCollectionExtractors.h"
      46             : #import "ResourceManager.h"
      47             : 
      48             : 
      49             : 
      50             : @interface ShipEntity (OOAIPrivate)
      51             : 
      52           0 : - (void) checkFoundTarget;
      53             : 
      54           0 : - (BOOL)performHyperSpaceExitReplace:(BOOL)replace;
      55           0 : - (BOOL)performHyperSpaceExitReplace:(BOOL)replace toSystem:(OOSystemID)systemID;
      56             : 
      57           0 : - (void)scanForNearestShipWithPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter;
      58           0 : - (void)scanForNearestShipWithNegatedPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter;
      59             : 
      60           0 : - (void) acceptDistressMessageFrom:(ShipEntity *)other;
      61             : 
      62             : @end
      63             : 
      64             : 
      65             : @interface StationEntity (OOAIPrivate)
      66             : 
      67           0 : - (void) acceptDistressMessageFrom:(ShipEntity *)other;
      68             : 
      69             : @end
      70             : 
      71             : 
      72             : @interface ShipEntity (PureAI)
      73             : 
      74             : // Methods used only by AI.
      75             : 
      76           0 : - (void) setStateTo:(NSString *)state;
      77             : 
      78           0 : - (void) pauseAI:(NSString *)intervalString;
      79             : 
      80           0 : - (void) randomPauseAI:(NSString *)intervalString;
      81             : 
      82           0 : - (void) dropMessages:(NSString *)messageString;
      83             : 
      84           0 : - (void) debugDumpPendingMessages;
      85             : 
      86           0 : - (void) setDestinationToCurrentLocation;
      87             : 
      88           0 : - (void) setDesiredRangeTo:(NSString *)rangeString;
      89             : 
      90           0 : - (void) setDesiredRangeForWaypoint;
      91             : 
      92           0 : - (void) setSpeedTo:(NSString *)speedString;
      93             : 
      94           0 : - (void) setSpeedFactorTo:(NSString *)speedString;
      95             : 
      96           0 : - (void) setSpeedToCruiseSpeed;
      97             : 
      98           0 : - (void) setThrustFactorTo:(NSString *)thrustFactorString;
      99             : 
     100             : 
     101           0 : - (void) setTargetToPrimaryAggressor;
     102             : 
     103           0 : - (void) scanForNearestMerchantman;
     104           0 : - (void) scanForRandomMerchantman;
     105             : 
     106           0 : - (void) scanForLoot;
     107             : 
     108           0 : - (void) scanForRandomLoot;
     109             : 
     110           0 : - (void) setTargetToFoundTarget;
     111             : 
     112           0 : - (void) checkForFullHold;
     113             : 
     114           0 : - (void) getWitchspaceEntryCoordinates;
     115             : 
     116           0 : - (void) setDestinationFromCoordinates;
     117           0 : - (void) setCoordinatesFromPosition;
     118             : 
     119           0 : - (void) fightOrFleeMissile;
     120             : 
     121           0 : - (void) setCourseToPlanet;
     122           0 : - (void) setTakeOffFromPlanet;
     123           0 : - (void) landOnPlanet;
     124             : 
     125           0 : - (void) checkTargetLegalStatus;
     126           0 : - (void) checkOwnLegalStatus;
     127             : 
     128           0 : - (void) exitAIWithMessage:(NSString *)message;
     129             : 
     130           0 : - (void) setDestinationToTarget;
     131           0 : - (void) setDestinationWithinTarget;
     132             : 
     133           0 : - (void) checkCourseToDestination;
     134             : 
     135           0 : - (void) checkAegis;
     136             : 
     137           0 : - (void) checkEnergy;
     138           0 : - (void) checkHeatInsulation;
     139             : 
     140           0 : - (void) scanForOffenders;
     141             : 
     142           0 : - (void) setCourseToWitchpoint;
     143             : 
     144           0 : - (void) setDestinationToWitchpoint;
     145           0 : - (void) setDestinationToStationBeacon;
     146             : 
     147           0 : - (void) performHyperSpaceExit;
     148           0 : - (void) performHyperSpaceExitWithoutReplacing;
     149           0 : - (void) wormholeGroup;
     150             : 
     151           0 : - (void) commsMessage:(NSString *)valueString;
     152           0 : - (void) commsMessageByUnpiloted:(NSString *)valueString;
     153             : 
     154           0 : - (void) ejectCargo;
     155             : 
     156           0 : - (void) scanForThargoid;
     157           0 : - (void) scanForNonThargoid;
     158           0 : - (void) thargonCheckMother;
     159           0 : - (void) becomeUncontrolledThargon;
     160             : 
     161           0 : - (void) checkDistanceTravelled;
     162             : 
     163           0 : - (void) fightOrFleeHostiles;
     164             : 
     165           0 : - (void) suggestEscort;
     166             : 
     167           0 : - (void) escortCheckMother;
     168             : 
     169           0 : - (void) checkGroupOddsVersusTarget;
     170             : 
     171           0 : - (void) scanForFormationLeader;
     172             : 
     173           0 : - (void) messageMother:(NSString *)msgString;
     174             : 
     175           0 : - (void) setPlanetPatrolCoordinates;
     176             : 
     177           0 : - (void) setSunSkimStartCoordinates;
     178             : 
     179           0 : - (void) setSunSkimEndCoordinates;
     180             : 
     181           0 : - (void) setSunSkimExitCoordinates;
     182             : 
     183           0 : - (void) patrolReportIn;
     184             : 
     185           0 : - (void) checkForMotherStation;
     186             : 
     187           0 : - (void) sendTargetCommsMessage:(NSString *)message;
     188             : 
     189           0 : - (void) markTargetForFines;
     190             : 
     191           0 : - (void) markTargetForOffence:(NSString *)valueString;
     192             : 
     193           0 : - (void) storeTarget;
     194           0 : - (void) recallStoredTarget;
     195             : 
     196           0 : - (void) scanForRocks;
     197             : 
     198           0 : - (void) setDestinationToDockingAbort;
     199             : 
     200           0 : - (void) requestNewTarget;
     201             : 
     202           0 : - (void) rollD:(NSString *)die_number;
     203             : 
     204           0 : - (void) scanForNearestShipWithPrimaryRole:(NSString *)scanRole;
     205           0 : - (void) scanForNearestShipHavingRole:(NSString *)scanRole;
     206           0 : - (void) scanForNearestShipWithAnyPrimaryRole:(NSString *)scanRoles;
     207           0 : - (void) scanForNearestShipHavingAnyRole:(NSString *)scanRoles;
     208           0 : - (void) scanForNearestShipWithScanClass:(NSString *)scanScanClass;
     209             : 
     210           0 : - (void) scanForNearestShipWithoutPrimaryRole:(NSString *)scanRole;
     211           0 : - (void) scanForNearestShipNotHavingRole:(NSString *)scanRole;
     212           0 : - (void) scanForNearestShipWithoutAnyPrimaryRole:(NSString *)scanRoles;
     213           0 : - (void) scanForNearestShipNotHavingAnyRole:(NSString *)scanRoles;
     214           0 : - (void) scanForNearestShipWithoutScanClass:(NSString *)scanScanClass;
     215             : 
     216           0 : - (void) setCoordinates:(NSString *)system_x_y_z;
     217             : 
     218           0 : - (void) checkForNormalSpace;
     219             : 
     220           0 : - (void) setTargetToRandomStation;
     221           0 : - (void) setTargetToLastStation;
     222             : 
     223           0 : - (void) addFuel:(NSString *) fuel_number;
     224             : 
     225           0 : - (void) scriptActionOnTarget:(NSString *) action;
     226             : 
     227           0 : - (void) sendScriptMessage:(NSString *)message;
     228             : 
     229           0 : - (void) ai_throwSparks;
     230             : 
     231           0 : - (void) explodeSelf;
     232             : 
     233           0 : - (void) ai_debugMessage:(NSString *)message;
     234             : 
     235             : // racing code.
     236           0 : - (void) targetFirstBeaconWithCode:(NSString *) code;
     237           0 : - (void) targetNextBeaconWithCode:(NSString *) code;
     238           0 : - (void) setRacepointsFromTarget;
     239           0 : - (void) performFlyRacepoints;
     240             : 
     241             : // defense targets 
     242           0 : - (void) addPrimaryAggressorAsDefenseTarget;
     243           0 : - (void) addFoundTargetAsDefenseTarget;
     244           0 : - (void) findNewDefenseTarget;
     245             : 
     246             : @end
     247             : 
     248             : 
     249             : @implementation ShipEntity (AI)
     250             : 
     251             : 
     252             : - (void) setAITo:(NSString *)aiString
     253             : {
     254             :         // don't try to load real AIs if the game hasn't started yet
     255             :         if (![PLAYER scriptsLoaded])
     256             :         {
     257             :                 aiString = @"oolite-nullAI.js";
     258             :         }
     259             :         if ([aiString hasSuffix:@".plist"])
     260             :         {
     261             :                 [[self getAI] setStateMachine:aiString withJSScript:@"oolite-nullAI.js"];
     262             :                 [self setAIScript:@"oolite-nullAI.js"];
     263             :         }
     264             :         else if ([aiString hasSuffix:@".js"])
     265             :         {
     266             :                 [[self getAI] setStateMachine:@"nullAI.plist" withJSScript:aiString];
     267             :                 [self setAIScript:aiString];
     268             :         }
     269             :         else
     270             :         {
     271             :                 NSString *path = [ResourceManager pathForFileNamed:[aiString stringByAppendingString:@".js"] inFolder:@"AIs"];
     272             :                 if (path == nil) // no js, use plist
     273             :                 {
     274             :                         [self setAITo:[aiString stringByAppendingString:@".plist"]];
     275             :                 }
     276             :                 else
     277             :                 {
     278             :                         [self setAITo:[aiString stringByAppendingString:@".js"]];
     279             :                 }
     280             :         }
     281             : }
     282             : 
     283             : 
     284             : - (void) setAIScript:(NSString *)aiString
     285             : {
     286             :         NSMutableDictionary             *properties = nil;
     287             :         
     288             :         properties = [NSMutableDictionary dictionary];
     289             :         [properties setObject:self forKey:@"ship"];
     290             :         
     291             :         [aiScript autorelease];
     292             :         aiScript = [OOScript jsAIScriptFromFileNamed:aiString properties:properties];
     293             :         if (aiScript == nil)
     294             :         {
     295             :                 OOLog(@"ai.load.failed.unknownAI",@"Unable to load JS AI %@ for ship %@ (%@ for role %@)",aiString,self,[self shipDataKey],[self primaryRole]);
     296             :                 aiScript = [OOScript jsAIScriptFromFileNamed:@"oolite-nullAI.js" properties:properties];
     297             :         }
     298             :         else
     299             :         {
     300             :                 aiScriptWakeTime = 0;
     301             :                 haveStartedJSAI = NO;
     302             :         }
     303             :         [aiScript retain];
     304             : }
     305             : 
     306             : 
     307             : - (void) switchAITo:(NSString *)aiString
     308             : {
     309             :         [self setAITo:aiString];
     310             :         [[self getAI] clearStack];
     311             : }
     312             : 
     313             : 
     314             : - (void) scanForHostiles
     315             : {
     316             :         /*-- Locates all the ships in range targeting the receiver and chooses the nearest --*/
     317             :         DESTROY(_foundTarget);
     318             :         
     319             :         [self checkScanner];
     320             :         unsigned i;
     321             :         GLfloat found_d2 = scannerRange * scannerRange;
     322             :         for (i = 0; i < n_scanned_ships ; i++)
     323             :         {
     324             :                 ShipEntity *thing = scanned_ships[i];
     325             :                 GLfloat d2 = distance2_scanned_ships[i];
     326             :                 if ((d2 < found_d2) 
     327             :                         && ([thing isThargoid] || (([thing primaryTarget] == self) && [thing hasHostileTarget]) || [thing isDefenseTarget:self])
     328             :                         && ![thing isCloaked])
     329             :                 {
     330             :                         [self setFoundTarget:thing];
     331             :                         found_d2 = d2;
     332             :                 }
     333             :         }
     334             :         
     335             :         [self checkFoundTarget];
     336             : }
     337             : 
     338             : 
     339             : - (void) groupAttackTarget
     340             : {
     341             :         NSEnumerator            *shipEnum = nil;
     342             :         ShipEntity                      *target = nil, *ship = nil;
     343             : 
     344             :         target = [self primaryTarget];
     345             :         
     346             :         if (target == nil) return;
     347             :         
     348             :         if ([self group] == nil)                // ship is alone!
     349             :         {
     350             :                 [self setFoundTarget:target];
     351             :                 [shipAI reactToMessage:@"GROUP_ATTACK_TARGET" context:@"groupAttackTarget"];
     352             :                 [self doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:target];
     353             :                 return;
     354             :         }
     355             :         
     356             :         for (shipEnum = [[self group] mutationSafeEnumerator]; (ship = [shipEnum nextObject]); )
     357             :         {
     358             :                 [ship setFoundTarget:target];
     359             :                 [ship reactToAIMessage:@"GROUP_ATTACK_TARGET" context:@"groupAttackTarget"];
     360             :                 [ship doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:target];
     361             : 
     362             :                 if ([ship escortGroup] != [ship group] && [[ship escortGroup] count] > 1) // Ship has a seperate escort group.
     363             :                 {
     364             :                         ShipEntity              *escort = nil;
     365             :                         NSEnumerator    *shipEnum = nil;
     366             :                         NSArray                 *escortMembers = [[ship escortGroup] memberArrayExcludingLeader];
     367             :                         for (shipEnum = [escortMembers objectEnumerator]; (escort = [shipEnum nextObject]); )
     368             :                         {
     369             :                                 [escort setFoundTarget:target];
     370             :                                 [escort reactToAIMessage:@"GROUP_ATTACK_TARGET" context:@"groupAttackTarget"];
     371             :                                 [escort doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:target];
     372             :                         }
     373             :                 }
     374             :         }
     375             : }
     376             : 
     377             : 
     378             : - (void) performAttack
     379             : {
     380             :         if (behaviour != BEHAVIOUR_EVASIVE_ACTION)
     381             :         {
     382             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
     383             :                 desired_range = 1250 * randf() + 750; // 750 til 2000
     384             :                 frustration = 0.0;      
     385             :         }
     386             : }
     387             : 
     388             : 
     389             : - (void) performCollect
     390             : {
     391             :         behaviour = BEHAVIOUR_COLLECT_TARGET;
     392             :         frustration = 0.0;
     393             : }
     394             : 
     395             : 
     396             : - (void) performEscort
     397             : {
     398             :         if(behaviour != BEHAVIOUR_FORMATION_FORM_UP) 
     399             :         {
     400             :                 behaviour = BEHAVIOUR_FORMATION_FORM_UP;
     401             :                 frustration = 0.0; // behavior changed, reset frustration.
     402             :         }
     403             : }
     404             : 
     405             : 
     406             : - (void) performFaceDestination
     407             : {
     408             :         behaviour = BEHAVIOUR_FACE_DESTINATION;
     409             :         frustration = 0.0;
     410             : }
     411             : 
     412             : 
     413             : - (void) performFlee
     414             : {
     415             :         if (behaviour != BEHAVIOUR_FLEE_EVASIVE_ACTION)
     416             :         {
     417             :                 behaviour = BEHAVIOUR_FLEE_TARGET;
     418             :                 [self setEvasiveJink:400.0];
     419             :                 frustration = 0.0;
     420             :                 if (accuracy > COMBAT_AI_ISNT_AWFUL)
     421             :                 {
     422             :                         // alert! they've got us in their sights! react!!
     423             :                         if ([self approachAspectToPrimaryTarget] > 0.9995)
     424             :                         {
     425             :                                 behaviour = randf() < 0.15 ? BEHAVIOUR_EVASIVE_ACTION : BEHAVIOUR_FLEE_EVASIVE_ACTION;
     426             :                         }
     427             :                 }
     428             :         }
     429             : }
     430             : 
     431             : 
     432             : - (void) performFlyToRangeFromDestination
     433             : {
     434             :         behaviour = BEHAVIOUR_FLY_RANGE_FROM_DESTINATION;
     435             :         frustration = 0.0;
     436             : }
     437             : 
     438             : 
     439             : - (void) performHold
     440             : {
     441             :         desired_speed = 0.0;
     442             :         behaviour = BEHAVIOUR_TRACK_TARGET;
     443             :         frustration = 0.0;
     444             : }
     445             : 
     446             : 
     447             : - (void) performIdle
     448             : {
     449             :         behaviour = BEHAVIOUR_IDLE;
     450             :         frustration = 0.0;
     451             : }
     452             : 
     453             : 
     454             : - (void) performIntercept
     455             : {
     456             :         behaviour = BEHAVIOUR_INTERCEPT_TARGET;
     457             :         frustration = 0.0;
     458             : }
     459             : 
     460             : 
     461             : - (void) performLandOnPlanet
     462             : {
     463             :         OOPlanetEntity  *nearest = [self findNearestPlanet];
     464             :         if (isNearPlanetSurface)
     465             :         {
     466             :                 _destination = [nearest position];
     467             :                 behaviour = BEHAVIOUR_LAND_ON_PLANET;
     468             :                 planetForLanding = [nearest universalID];
     469             :         }
     470             :         else
     471             :         {
     472             :                 behaviour = BEHAVIOUR_IDLE;
     473             :                 [shipAI message:@"NO_PLANET_NEARBY"];
     474             :         }
     475             :         
     476             :         frustration = 0.0;
     477             : }
     478             : 
     479             : 
     480             : - (void) performMining
     481             : {
     482             :         Entity *target = [self primaryTarget];
     483             :         // mining is not seen as hostile behaviour, so ensure it is only used against rocks.
     484             :         if (target &&  [target scanClass] == CLASS_ROCK)
     485             :         {
     486             :                 behaviour = BEHAVIOUR_ATTACK_MINING_TARGET;
     487             :                 frustration = 0.0;
     488             :         }
     489             :         else
     490             :         {       
     491             :                 [self noteLostTargetAndGoIdle];
     492             :         }
     493             : }
     494             : 
     495             : 
     496             : - (void) performScriptedAI
     497             : {
     498             :         behaviour = BEHAVIOUR_SCRIPTED_AI;
     499             :         frustration = 0.0;
     500             : }
     501             : 
     502             : 
     503             : - (void) performScriptedAttackAI
     504             : {
     505             :         behaviour = BEHAVIOUR_SCRIPTED_ATTACK_AI;
     506             :         frustration = 0.0;
     507             : }
     508             : 
     509             : 
     510           0 : - (void) performBuoyTumble
     511             : {
     512             :         stick_roll = 0.10;
     513             :         stick_pitch = 0.15;
     514             :         behaviour = BEHAVIOUR_TUMBLE;
     515             :         frustration = 0.0;
     516             : }
     517             : 
     518             : 
     519             : - (void) performStop
     520             : {
     521             :         behaviour = BEHAVIOUR_STOP_STILL;
     522             :         desired_speed = 0.0;
     523             :         frustration = 0.0;
     524             : }
     525             : 
     526             : 
     527             : - (void) performTumble
     528             : {
     529             :         stick_roll = max_flight_roll*2.0*(randf() - 0.5);
     530             :         stick_pitch = max_flight_pitch*2.0*(randf() - 0.5);
     531             :         behaviour = BEHAVIOUR_TUMBLE;
     532             :         frustration = 0.0;
     533             : }
     534             : 
     535             : 
     536             : - (BOOL) performHyperSpaceToSpecificSystem:(OOSystemID)systemID
     537             : {
     538             :         return [self performHyperSpaceExitReplace:NO toSystem:systemID];
     539             : }
     540             : 
     541             : 
     542             : - (void) requestDockingCoordinates
     543             : {
     544             :         /*-     requests coordinates from the target station
     545             :          if the target station can't be found
     546             :          then use the nearest it can find (which may be a rock hermit) -*/
     547             :         
     548             :         StationEntity   *station =  nil;
     549             :         Entity                  *targStation = nil;
     550             :         NSString                *message = nil;
     551             :         double          distanceToStation2 = 0.0;
     552             :         
     553             :         targStation = [self targetStation];
     554             :         if ([targStation isStation])
     555             :         {
     556             :                 station = (StationEntity*)targStation;
     557             :         }
     558             :         else
     559             :         {
     560             :                 station = [UNIVERSE nearestShipMatchingPredicate:IsStationPredicate
     561             :                                                                                            parameter:nil
     562             :                                                                                 relativeToEntity:self];
     563             :         }
     564             :         
     565             :         distanceToStation2 = HPdistance2([station position], [self position]);
     566             :         
     567             :         // Player check for being inside the aegis already exists in PlayerEntityControls. We just
     568             :         // check here that distance to station is less than 2.5 times scanner range to avoid problems with
     569             :         // NPC ships getting stuck with a dockingAI while just outside the aegis - Nikos 20090630, as proposed by Eric
     570             :         // On very busy systems (> 50 docking ships) docking ships can be sent to a hold position outside the range, 
     571             :         // so also test for presence of dockingInstructions. - Eric 20091130
     572             :         if (station != nil && (distanceToStation2 < SCANNER_MAX_RANGE2 * 6.25 || dockingInstructions != nil))
     573             :         {
     574             :                 // remember the instructions
     575             :                 [dockingInstructions release];
     576             :                 dockingInstructions = [[station dockingInstructionsForShip:self] retain];
     577             :                 if (dockingInstructions != nil)
     578             :                 {
     579             :                         [self recallDockingInstructions];
     580             :                         
     581             :                         message = [dockingInstructions objectForKey:@"ai_message"];
     582             :                         if (message != nil)  [shipAI message:message];
     583             :                         message = [dockingInstructions objectForKey:@"comms_message"];
     584             :                         if (message != nil)  [station sendExpandedMessage:message toShip:self];
     585             :                 }
     586             :         }
     587             :         else
     588             :         {
     589             :                 DESTROY(dockingInstructions);
     590             :         }
     591             :         
     592             :         if (dockingInstructions == nil)
     593             :         {
     594             :                 [shipAI message:@"NO_STATION_FOUND"];
     595             :         }
     596             : }
     597             : 
     598             : 
     599             : - (void) recallDockingInstructions
     600             : {
     601             :         if (dockingInstructions != nil)
     602             :         {
     603             :                 _destination = [dockingInstructions oo_hpvectorForKey:@"destination"];
     604             :                 desired_speed = fmin([dockingInstructions oo_floatForKey:@"speed"], maxFlightSpeed);
     605             :                 desired_range = [dockingInstructions oo_floatForKey:@"range"];
     606             :                 if ([dockingInstructions objectForKey:@"station"])
     607             :                 {
     608             :                         StationEntity *targetStation = [[dockingInstructions objectForKey:@"station"] weakRefUnderlyingObject];
     609             :                         if (targetStation != nil)
     610             :                         {
     611             :                                 [self addTarget:targetStation];
     612             :                                 [self setTargetStation:targetStation];
     613             :                         }
     614             :                         else 
     615             :                         {
     616             :                                 [self removeTarget:[self primaryTarget]];
     617             :                         }
     618             :                 }
     619             :                 docking_match_rotation = [dockingInstructions oo_boolForKey:@"match_rotation"];
     620             :         }
     621             : }
     622             : 
     623             : 
     624             : - (void) scanForNearestIncomingMissile
     625             : {
     626             :         BinaryOperationPredicateParameter param =
     627             :         {
     628             :                 HasScanClassPredicate, [NSNumber numberWithInt:CLASS_MISSILE],
     629             :                 IsHostileAgainstTargetPredicate, self
     630             :         };
     631             :         [self scanForNearestShipWithPredicate:ANDPredicate parameter:&param];
     632             : }
     633             : 
     634             : - (void) enterPlayerWormhole
     635             : {
     636             :         [self enterWormhole:[PLAYER wormhole] replacing:NO];
     637             : }
     638             : 
     639             : - (void) enterTargetWormhole
     640             : {
     641             :         WormholeEntity *whole = nil;
     642             :         ShipEntity              *targEnt = [self primaryTarget];
     643             :         double found_d2 = scannerRange * scannerRange;
     644             :         
     645             :         if (targEnt && (HPdistance2(position, [targEnt position]) < found_d2))
     646             :         {
     647             :                 if ([targEnt isWormhole])
     648             :                         whole = (WormholeEntity *)targEnt;
     649             :                 else if ([targEnt isPlayer])
     650             :                         whole = [PLAYER wormhole];
     651             :         }
     652             : 
     653             :         if (!whole)
     654             :         {
     655             :                 // locate nearest wormhole
     656             :                 int                             ent_count =             UNIVERSE->n_entities;
     657             :                 Entity**                uni_entities =  UNIVERSE->sortedEntities;    // grab the public sorted list
     658             :                 WormholeEntity* wormholes[ent_count];
     659             :                 int i;
     660             :                 int wh_count = 0;
     661             :                 for (i = 0; i < ent_count; i++)
     662             :                         if (uni_entities[i]->isWormhole)
     663             :                                 wormholes[wh_count++] = [(WormholeEntity *)uni_entities[i] retain];
     664             :                 //
     665             :                 //double found_d2 = scannerRange * scannerRange;
     666             :                 for (i = 0; i < wh_count ; i++)
     667             :                 {
     668             :                         WormholeEntity *wh = wormholes[i];
     669             :                         double d2 = HPdistance2(position, wh->position);
     670             :                         if (d2 < found_d2)
     671             :                         {
     672             :                                 whole = wh;
     673             :                                 found_d2 = d2;
     674             :                         }
     675             :                         [wh release];
     676             :                 }
     677             :         }
     678             :         
     679             :         [self enterWormhole:whole replacing:NO];
     680             : }
     681             : 
     682             : 
     683             : // FIXME: resolve this stuff.
     684             : - (void) wormholeEscorts
     685             : {
     686             :         NSEnumerator            *shipEnum = nil;
     687             :         ShipEntity                      *ship = nil;
     688             :         NSString                        *context = nil;
     689             :         WormholeEntity          *whole = nil;
     690             :         
     691             :         whole = [self primaryTarget];
     692             :         if (![whole isWormhole])  return;
     693             :         
     694             : #ifndef NDEBUG
     695             :         context = [NSString stringWithFormat:@"%@ wormholeEscorts", [self shortDescription]];
     696             : #endif
     697             :         
     698             :         for (shipEnum = [self escortEnumerator]; (ship = [shipEnum nextObject]); )
     699             :         {
     700             :                 [ship addTarget:whole];
     701             :                 [ship reactToAIMessage:@"ENTER WORMHOLE" context:context];
     702             :                 [ship doScriptEvent:OOJSID("wormholeSuggested") withArgument:whole];
     703             :         }
     704             :         
     705             :         // We now have no escorts..
     706             : 
     707             :         [_escortGroup release];
     708             :         _escortGroup = nil;
     709             : 
     710             : }
     711             : 
     712             : 
     713             : - (void) wormholeEntireGroup
     714             : {
     715             :         [self wormholeGroup];
     716             :         [self wormholeEscorts];
     717             : }
     718             : 
     719             : 
     720             : - (BOOL) suggestEscortTo:(ShipEntity *)mother
     721             : {
     722             :         if (mother)
     723             :         {
     724             : #ifndef NDEBUG
     725             :                 if (reportAIMessages)
     726             :                 {
     727             :                         OOLog(@"ai.suggestEscort", @"DEBUG: %@ suggests escorting %@", self, mother);
     728             :                 }
     729             : #endif
     730             :                 
     731             :                 if ([mother acceptAsEscort:self])
     732             :                 {
     733             :                         // copy legal status across
     734             :                         if (([mother legalStatus] > 0)&&(bounty <= 0))
     735             :                         {
     736             :                                 int extra = 1 | (ranrot_rand() & 15);
     737             : //                              [mother setBounty: [mother legalStatus] + extra withReason:kOOLegalStatusReasonAssistingOffender];
     738             :                                 [self markAsOffender:extra withReason:kOOLegalStatusReasonAssistingOffender];
     739             :                                 //                              bounty += extra;        // obviously we're dodgier than we thought!
     740             :                         }
     741             :                         
     742             :                         [self setOwner:mother];
     743             :                         [self setGroup:[mother escortGroup]];
     744             :                         [shipAI message:@"ESCORTING"];
     745             :                         return YES;
     746             :                 }
     747             :                 
     748             : #ifndef NDEBUG
     749             :                 if (reportAIMessages)
     750             :                 {
     751             :                         OOLog(@"ai.suggestEscort.refused", @"DEBUG: %@ refused by %@", self, mother);
     752             :                 }
     753             : #endif
     754             :                 
     755             :         }
     756             :         [self setOwner:self];
     757             :         [shipAI message:@"NOT_ESCORTING"];
     758             :         [self doScriptEvent:OOJSID("escortRejected") withArgument:mother];
     759             :         return NO;
     760             : }
     761             : 
     762             : 
     763             : - (void) broadcastDistressMessage
     764             : {
     765             :         /*-- Locates all the stations, bounty hunters and police ships in range and tells them that you are under attack --*/
     766             :         [self broadcastDistressMessageWithDumping:YES];
     767             : }       
     768             : 
     769             : - (void) broadcastDistressMessageWithDumping:(BOOL)dumpCargo
     770             : {
     771             :         [self checkScannerIgnoringUnpowered];
     772             :         DESTROY(_foundTarget);
     773             :         
     774             :         ShipEntity      *aggressor_ship = (ShipEntity*)[self primaryAggressor];
     775             :         if (aggressor_ship == nil)  return;
     776             :         
     777             :         // don't send too many distress messages at once, space them out semi-randomly
     778             :         if (messageTime > 2.0 * randf())  return;
     779             :         
     780             :         NSString        *distress_message = nil;
     781             :         BOOL            is_buoy = (scanClass == CLASS_BUOY);
     782             :         if (is_buoy)  distress_message = @"[buoy-distress-call]";
     783             :         else  distress_message = @"[distress-call]";
     784             :         
     785             :         unsigned i;
     786             :         for (i = 0; i < n_scanned_ships; i++)
     787             :         {
     788             :                 ShipEntity*     ship = scanned_ships[i];
     789             : 
     790             :     // dump cargo if energy is low
     791             :                 if (dumpCargo && !is_buoy && [self primaryAggressor] == ship && energy < 0.375 * maxEnergy)
     792             :                 {
     793             :                         [self ejectCargo];
     794             :                         [self performFlee];
     795             :                 }
     796             :                 
     797             :                 // tell it! (only plist AIs send comms here; JS AIs are
     798             :                 // expected to handle their own)
     799             :                 if (ship->isPlayer && ![self hasNewAI])
     800             :                 {
     801             :                         [ship doScriptEvent:OOJSID("distressMessageReceived") withArgument:aggressor_ship andArgument:self];
     802             : 
     803             :                         if (!is_buoy && [self primaryAggressor] == ship && energy < 0.375 * maxEnergy)
     804             :                         {
     805             :                                 [self sendExpandedMessage:@"[beg-for-mercy]" toShip:ship];
     806             :                         }
     807             :                         else if ([self bounty] == 0)
     808             :                         {
     809             :                                 // only send distress message to player if plausibly sending
     810             :                                 // one more generally
     811             :                                 [self sendExpandedMessage:distress_message toShip:ship];
     812             :                         }
     813             :                         
     814             :                         // reset the thanked_ship_id
     815             :                         DESTROY(_thankedShip);
     816             :                 }
     817             :                 else if ([self bounty] == 0 && [ship crew]) // Only clean ships can have their distress calls accepted
     818             :                 {
     819             :                         [ship doScriptEvent:OOJSID("distressMessageReceived") withArgument:aggressor_ship andArgument:self];
     820             :                         
     821             :                         // we only can send distressMessages to ships that are known to have a "ACCEPT_DISTRESS_CALL" reaction
     822             :                         // in their AI, or they might react wrong on the added found_target.
     823             : 
     824             :                         // ship must have a plist AI for this next bit. JS AIs
     825             :                         // should already have done something sensible on
     826             :                         // distressMessageReceived
     827             :                         if (![self hasNewAI])
     828             :                         {
     829             :                                 // FIXME: this test only works with core AIs
     830             :                                 if (ship->isStation || [ship hasPrimaryRole:@"police"] || [ship hasPrimaryRole:@"hunter"])
     831             :                                 {
     832             :                                         [ship acceptDistressMessageFrom:self];
     833             :                                 }
     834             :                         }
     835             :                 }
     836             :         }
     837             : }
     838             : 
     839             : 
     840             : @end
     841             : 
     842             : 
     843             : @implementation ShipEntity (PureAI)
     844             : 
     845             : - (void) setStateTo:(NSString *)state
     846             : {
     847             :         [[self getAI] setState:state];
     848             : }
     849             : 
     850             : 
     851             : - (void) pauseAI:(NSString *)intervalString
     852             : {
     853             :         [shipAI setNextThinkTime:[UNIVERSE getTime] + [intervalString doubleValue]];
     854             : }
     855             : 
     856             : 
     857             : - (void) randomPauseAI:(NSString *)intervalString
     858             : {
     859             :         NSArray*        tokens = ScanTokensFromString(intervalString);
     860             :         double start, end;
     861             :         
     862             :         if ([tokens count] != 2)
     863             :         {
     864             :                 OOLog(@"ai.syntax.randomPauseAI", @"***** ERROR: cannot read min and max value for randomPauseAI:, needs 2 values: '%@'.", intervalString);
     865             :                 return;
     866             :         }
     867             :         
     868             :         start = [tokens oo_doubleAtIndex:0];
     869             :         end   = [tokens oo_doubleAtIndex:1];
     870             :         
     871             :         [shipAI setNextThinkTime:[UNIVERSE getTime] + (start + (end - start)*randf())];
     872             : }
     873             : 
     874             : 
     875             : - (void) dropMessages:(NSString *)messageString
     876             : {
     877             :         NSArray                         *messages = nil;
     878             :         NSEnumerator            *messageEnum = nil;
     879             :         NSString                        *message = nil;
     880             :         NSCharacterSet          *whiteSpace = [NSCharacterSet whitespaceCharacterSet];
     881             :         
     882             :         messages = [messageString componentsSeparatedByString:@","];
     883             :         for (messageEnum = [messages objectEnumerator]; (message = [messageEnum nextObject]); )
     884             :         {
     885             :                 [shipAI dropMessage:[message stringByTrimmingCharactersInSet:whiteSpace]];
     886             :         }
     887             : }
     888             : 
     889             : 
     890             : - (void) debugDumpPendingMessages
     891             : {
     892             :         [shipAI debugDumpPendingMessages];
     893             : }
     894             : 
     895             : 
     896             : - (void) setDestinationToCurrentLocation
     897             : {
     898             :         // randomly add a .5m variance
     899             :         _destination = HPvector_add(position, OOHPVectorRandomSpatial(0.5));
     900             : }
     901             : 
     902             : 
     903           0 : - (void) setDestinationToJinkPosition
     904             : {
     905             :         Vector front = vector_multiply_scalar([self forwardVector], flightSpeed / max_flight_pitch * 2);
     906             :         _destination = HPvector_add(position, vectorToHPVector(vector_add(front, OOVectorRandomSpatial(100))));
     907             :         pitching_over = YES; // don't complete roll first, but immediately start with pitching. 
     908             : }
     909             : 
     910             : 
     911             : - (void) setDesiredRangeTo:(NSString *)rangeString
     912             : {
     913             :         desired_range = [rangeString doubleValue];
     914             : }
     915             : 
     916             : - (void) setDesiredRangeForWaypoint
     917             : {
     918             :         desired_range = fmax(maxFlightSpeed / max_flight_pitch / 6, 50.0); // some ships need a longer range to reach a waypoint.
     919             : }
     920             : 
     921             : - (void) setSpeedTo:(NSString *)speedString
     922             : {
     923             :         desired_speed = [speedString doubleValue];
     924             : }
     925             : 
     926             : 
     927             : - (void) setSpeedFactorTo:(NSString *)speedString
     928             : {
     929             :         desired_speed = maxFlightSpeed * [speedString doubleValue];
     930             : }
     931             : 
     932             : - (void) setSpeedToCruiseSpeed
     933             : {
     934             :         desired_speed = cruiseSpeed;
     935             : }
     936             : 
     937             : - (void) setThrustFactorTo:(NSString *)thrustFactorString
     938             : {
     939             :         thrust = OOClamp_0_1_f([thrustFactorString doubleValue]) * max_thrust;
     940             : }
     941             : 
     942             : 
     943             : - (void) setTargetToPrimaryAggressor
     944             : {
     945             :         Entity *primeAggressor = [self primaryAggressor];
     946             :         if (!primeAggressor)
     947             :                 return;
     948             :         if ([self primaryTarget] == primeAggressor)
     949             :                 return;
     950             :         
     951             :         // a more considered approach here:
     952             :         // if we're already busy attacking a target we don't necessarily want to break off
     953             :         //
     954             :         if ([self hasHostileTarget] && randf() < 0.75)       // if I'm attacking, ignore 75% of new aggressor's attacks
     955             :         {
     956             :                                 // but add them as a secondary target anyway
     957             :                 [self addDefenseTarget:(ShipEntity*)primeAggressor];
     958             :                 return;
     959             :         }
     960             :         // react only if the primary aggressor is not a friendly ship, else ignore it
     961             :         if ([primeAggressor isShip] && ![(ShipEntity *)primeAggressor isFriendlyTo:self])
     962             :         {
     963             :                 // inform our old target of our new target
     964             :                 //
     965             :                 Entity *primeTarget = [self primaryTarget];
     966             :                 if ((primeTarget)&&(primeTarget->isShip))
     967             :                 {
     968             :                         ShipEntity *currentShip = [self primaryTarget];
     969             :                         [[currentShip getAI] message:[NSString stringWithFormat:@"%@ %d %d", AIMS_AGGRESSOR_SWITCHED_TARGET, universalID, [[self primaryAggressor] universalID]]];
     970             :                         [currentShip doScriptEvent:OOJSID("shipAttackerDistracted") withArgument:[self primaryAggressor]];
     971             :                 }
     972             :                 
     973             :                 // okay, so let's now target the aggressor
     974             :                 [self addTarget:[self primaryAggressor]];
     975             :         }
     976             : }
     977             : 
     978             : 
     979             : - (void) addPrimaryAggressorAsDefenseTarget
     980             : {
     981             :         Entity *primeAggressor = [self primaryAggressor];
     982             :         if (!primeAggressor)
     983             :                 return;
     984             :         if ([self isDefenseTarget:primeAggressor])
     985             :                 return;
     986             :         
     987             :         if ([primeAggressor isShip] && ![(ShipEntity*)primeAggressor isFriendlyTo:self])
     988             :         {
     989             :                 [self addDefenseTarget:primeAggressor];
     990             :         }
     991             : }
     992             : 
     993             : 
     994             : - (void) scanForNearestMerchantman
     995             : {
     996             :         float                           d2, found_d2;
     997             :         unsigned                        i;
     998             :         ShipEntity                      *ship = nil;
     999             :         
    1000             :         //-- Locates the nearest merchantman in range.
    1001             :         [self checkScannerIgnoringUnpowered];
    1002             :         
    1003             :         found_d2 = scannerRange * scannerRange;
    1004             :         DESTROY(_foundTarget);
    1005             :         
    1006             :         for (i = 0; i < n_scanned_ships ; i++)
    1007             :         {
    1008             :                 ship = scanned_ships[i];
    1009             :                 if ([ship isPirateVictim] && ([ship status] != STATUS_DEAD) && ([ship status] != STATUS_DOCKED) && ![ship isCloaked])
    1010             :                 {
    1011             :                         d2 = distance2_scanned_ships[i];
    1012             :                         if (PIRATES_PREFER_PLAYER && (d2 < desired_range * desired_range) && ship->isPlayer && [self isPirate])
    1013             :                         {
    1014             :                                 d2 = 0.0;
    1015             :                         }
    1016             :                         else d2 = distance2_scanned_ships[i];
    1017             :                         if (d2 < found_d2)
    1018             :                         {
    1019             :                                 found_d2 = d2;
    1020             :                                 [self setFoundTarget:ship];
    1021             :                         }
    1022             :                 }
    1023             :         }
    1024             :         [self checkFoundTarget];
    1025             : }
    1026             : 
    1027             : 
    1028             : - (void) scanForRandomMerchantman
    1029             : {
    1030             :         unsigned                        n_found, i;
    1031             :         
    1032             :         //-- Locates one of the merchantman in range.
    1033             :         [self checkScannerIgnoringUnpowered];
    1034             :         ShipEntity*             ids_found[n_scanned_ships];
    1035             :         
    1036             :         n_found = 0;
    1037             :         DESTROY(_foundTarget);
    1038             :         for (i = 0; i < n_scanned_ships ; i++)
    1039             :         {
    1040             :                 ShipEntity *ship = scanned_ships[i];
    1041             :                 if (([ship status] != STATUS_DEAD) && ([ship status] != STATUS_DOCKED) && [ship isPirateVictim] && ![ship isCloaked])
    1042             :                         ids_found[n_found++] = ship;
    1043             :         }
    1044             :         if (n_found == 0)
    1045             :         {
    1046             :                 [shipAI message:@"NOTHING_FOUND"];
    1047             :         }
    1048             :         else
    1049             :         {
    1050             :                 i = ranrot_rand() % n_found;    // pick a number from 0 -> (n_found - 1)
    1051             :                 [self setFoundTarget:ids_found[i]];
    1052             :                 [shipAI message:@"TARGET_FOUND"];
    1053             :         }
    1054             : }
    1055             : 
    1056             : 
    1057             : - (void) scanForLoot
    1058             : {
    1059             :         /*-- Locates the nearest debris in range --*/
    1060             :         if (!isStation)
    1061             :         {
    1062             :                 if (![self hasCargoScoop])
    1063             :                 {
    1064             :                         [shipAI message:@"NOTHING_FOUND"];            //can't collect loot if you have no scoop!
    1065             :                         return;
    1066             :                 }
    1067             :                 if ([cargo count] >= [self maxAvailableCargoSpace])
    1068             :                 {
    1069             :                         if (max_cargo)  [shipAI message:@"HOLD_FULL"];        //can't collect loot if holds are full!
    1070             :                         [shipAI message:@"NOTHING_FOUND"];            //can't collect loot if holds are full!
    1071             :                         return;
    1072             :                 }
    1073             :         }
    1074             :         else
    1075             :         {
    1076             :                 if (magnitude2([self velocity]))
    1077             :                 {
    1078             :                         [shipAI message:@"NOTHING_FOUND"];            //can't collect loot if you're a moving station
    1079             :                         return;
    1080             :                 }
    1081             :         }
    1082             :         
    1083             :         [self checkScanner];
    1084             :         
    1085             :         double found_d2 = scannerRange * scannerRange;
    1086             :         DESTROY(_foundTarget);
    1087             :         unsigned i;
    1088             :         for (i = 0; i < n_scanned_ships; i++)
    1089             :         {
    1090             :                 ShipEntity *other = (ShipEntity *)scanned_ships[i];
    1091             :                 if ([other scanClass] == CLASS_CARGO && [other cargoType] != CARGO_NOT_CARGO && [other status] != STATUS_BEING_SCOOPED)
    1092             :                 {
    1093             :                         if ((![self isPolice]) || ([[other commodityType] isEqualToString:@"slaves"])) // police only rescue lifepods and slaves
    1094             :                         {
    1095             :                                 GLfloat d2 = distance2_scanned_ships[i];
    1096             :                                 if (d2 < found_d2)
    1097             :                                 {
    1098             :                                         found_d2 = d2;
    1099             :                                         [self setFoundTarget:other];
    1100             :                                 }
    1101             :                         }
    1102             :                 }
    1103             :         }
    1104             :         [self checkFoundTarget];
    1105             : }
    1106             : 
    1107             : 
    1108             : - (void) scanForRandomLoot
    1109             : {
    1110             :         /*-- Locates the all debris in range and chooses a piece at random from the first sixteen found --*/
    1111             :         if (![self isStation] && ![self hasCargoScoop])
    1112             :         {
    1113             :                 [shipAI message:@"NOTHING_FOUND"];            //can't collect loot if you have no scoop!
    1114             :                 return;
    1115             :         }
    1116             :         //
    1117             :         [self checkScanner];
    1118             :         //
    1119             :         ShipEntity* thing_uids_found[16];
    1120             :         unsigned things_found = 0;
    1121             :         DESTROY(_foundTarget);
    1122             :         unsigned i;
    1123             :         for (i = 0; (i < n_scanned_ships)&&(things_found < 16) ; i++)
    1124             :         {
    1125             :                 ShipEntity *other = scanned_ships[i];
    1126             :                 if ([other scanClass] == CLASS_CARGO && [other cargoType] != CARGO_NOT_CARGO && [other status] != STATUS_BEING_SCOOPED)
    1127             :                 {
    1128             :                         thing_uids_found[things_found++] = other;
    1129             :                 }
    1130             :         }
    1131             :         
    1132             :         if (things_found != 0)
    1133             :         {
    1134             :                 [self setFoundTarget:thing_uids_found[ranrot_rand() % things_found]];
    1135             :                 [shipAI message:@"TARGET_FOUND"];
    1136             :         }
    1137             :         else
    1138             :                 [shipAI message:@"NOTHING_FOUND"];
    1139             : }
    1140             : 
    1141             : 
    1142             : - (void) setTargetToFoundTarget
    1143             : {
    1144             :         if ([self foundTarget] != nil)
    1145             :         {
    1146             :                 [self addTarget:[self foundTarget]];
    1147             :         }
    1148             :         else
    1149             :         {
    1150             :                 [shipAI message:@"TARGET_LOST"]; // to prevent the ship going for a wrong, previous target. Should not be a reactToMessage.
    1151             :         }
    1152             : }
    1153             : 
    1154             : 
    1155             : - (void) addFoundTargetAsDefenseTarget
    1156             : {
    1157             :         Entity* fTarget = [self foundTarget];
    1158             :         if (fTarget != nil)
    1159             :         {
    1160             :                 if ([fTarget isShip] && ![(ShipEntity *)fTarget isFriendlyTo:self])
    1161             :                 {
    1162             :                         [self addDefenseTarget:fTarget];
    1163             :                 }
    1164             :         }
    1165             : }
    1166             : 
    1167             : - (void) checkForFullHold
    1168             : {
    1169             :         if (!max_cargo)
    1170             :         {
    1171             :                 [shipAI message:@"NO_CARGO_BAY"];
    1172             :         }
    1173             :         else if ([cargo count] >= [self maxAvailableCargoSpace])
    1174             :         {
    1175             :                 [shipAI message:@"HOLD_FULL"];
    1176             :         }
    1177             :         else
    1178             :         {
    1179             :                 [shipAI message:@"HOLD_NOT_FULL"];
    1180             :         }
    1181             : }
    1182             : 
    1183             : 
    1184             : 
    1185             : 
    1186             : 
    1187             : - (void) getWitchspaceEntryCoordinates
    1188             : {
    1189             :         /*- calculates coordinates from the nearest station it can find, or just fly 10s forward -*/
    1190             :         if (!UNIVERSE)
    1191             :         {
    1192             :                 Vector  vr = vector_multiply_scalar(v_forward, maxFlightSpeed * 10.0);  // 10 second flying away
    1193             :                 coordinates = HPvector_add(position, vectorToHPVector(vr));
    1194             :                 return;
    1195             :         }
    1196             :         //
    1197             :         // find the nearest station...
    1198             :         //
    1199             :         // we don't use "checkScanner" because we must rely on finding a present station.
    1200             :         //
    1201             :         StationEntity   *station =  nil;
    1202             :         station = [UNIVERSE nearestShipMatchingPredicate:IsStationPredicate
    1203             :                                                                                    parameter:nil
    1204             :                                                                         relativeToEntity:self];
    1205             :         
    1206             :         if (station && HPdistance2([station position], position) < SCANNER_MAX_RANGE2) // there is a station in range.
    1207             :         {
    1208             :                 Vector  vr = vector_multiply_scalar([station rightVector], 10000);  // 10km from station
    1209             :                 coordinates = HPvector_add([station position], vectorToHPVector(vr));
    1210             :         }
    1211             :         else
    1212             :         {
    1213             :                 Vector  vr = vector_multiply_scalar(v_forward, maxFlightSpeed * 10.0);  // 10 second flying away
    1214             :                 coordinates = HPvector_add(position, vectorToHPVector(vr));
    1215             :         }
    1216             : }
    1217             : 
    1218             : 
    1219             : - (void) setDestinationFromCoordinates
    1220             : {
    1221             :         _destination = coordinates;
    1222             : }
    1223             : 
    1224             : 
    1225             : - (void) setCoordinatesFromPosition
    1226             : {
    1227             :         coordinates = position;
    1228             : }
    1229             : 
    1230             : 
    1231             : - (void) fightOrFleeMissile
    1232             : {
    1233             :         // find an incoming missile...
    1234             :         //
    1235             :         ShipEntity                      *missile =  nil;
    1236             :         unsigned                        i;
    1237             :         NSEnumerator            *escortEnum = nil;
    1238             :         ShipEntity                      *escort = nil;
    1239             :         ShipEntity                      *target = nil;
    1240             :         
    1241             :         [self checkScannerIgnoringUnpowered];
    1242             :         for (i = 0; (i < n_scanned_ships)&&(missile == nil); i++)
    1243             :         {
    1244             :                 ShipEntity *thing = scanned_ships[i];
    1245             :                 if (thing->scanClass == CLASS_MISSILE)
    1246             :                 {
    1247             :                         target = [thing primaryTarget];
    1248             :                         
    1249             :                         if (target == self)
    1250             :                         {
    1251             :                                 missile = thing;
    1252             :                         }
    1253             :                         else
    1254             :                         {
    1255             :                                 for (escortEnum = [self escortEnumerator]; (escort = [escortEnum nextObject]); )
    1256             :                                 {
    1257             :                                         if (target == escort)
    1258             :                                         {
    1259             :                                                 missile = thing;
    1260             :                                         }
    1261             :                                 }
    1262             :                         }
    1263             :                 }
    1264             :         }
    1265             :         
    1266             :         if (missile == nil)  return;
    1267             :         
    1268             :         [self addTarget:missile];
    1269             :         [self addDefenseTarget:missile];
    1270             :         
    1271             :         // Notify own ship script that we are being attacked.   
    1272             :         ShipEntity *hunter = [missile owner];
    1273             :         [self doScriptEvent:OOJSID("shipBeingAttacked") withArgument:hunter];
    1274             :         [hunter doScriptEvent:OOJSID("shipAttackedOther") withArgument:self];
    1275             :         
    1276             :         if ([self isPolice])
    1277             :         {
    1278             :                 // Notify other police in group of attacker.
    1279             :                 // Note: prior to 1.73 this was done only if we had ECM.
    1280             :                 NSEnumerator    *policeEnum = nil;
    1281             :                 ShipEntity              *police = nil;
    1282             :                 
    1283             :                 for (policeEnum = [[self group] mutationSafeEnumerator]; (police = [policeEnum nextObject]); )
    1284             :                 {
    1285             :                         [police setFoundTarget:hunter];
    1286             :                         [police setPrimaryAggressor:hunter];
    1287             :                 }
    1288             :         }
    1289             :         
    1290             :         // if I'm a copper and you're not, then mark the other as an offender!
    1291             :         if ([self isPolice] && ![hunter isPolice])  [hunter markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice];
    1292             : 
    1293             :         if ([self hasECM])
    1294             :         {
    1295             :                 // use the ECM and battle on
    1296             :                 
    1297             :                 [self setPrimaryAggressor:hunter];      // lets get them now for that!
    1298             :                 [self setFoundTarget:hunter];
    1299             :                 
    1300             :                 [self fireECM];
    1301             :                 return;
    1302             :         }
    1303             :         
    1304             :         // RUN AWAY !!
    1305             :         desired_range = 10000;
    1306             :         [self performFlee];
    1307             :         [shipAI message:@"FLEEING"];
    1308             : }
    1309             : 
    1310             : 
    1311             : - (void) setCourseToPlanet
    1312             : {
    1313             :         /*- selects the nearest planet it can find -*/
    1314             :         OOPlanetEntity  *the_planet =  [self findNearestPlanetExcludingMoons];
    1315             :         if (the_planet)
    1316             :         {
    1317             :                 double variation = (aegis_status == AEGIS_NONE ? 0.5 : 0.2); // more random deviation when far from planet.
    1318             :                 HPVector p_pos = the_planet->position;
    1319             :                 double p_cr = the_planet->collision_radius;          // the surface
    1320             :                 HPVector p1 = HPvector_between(p_pos, position);
    1321             :                 p1 = HPvector_normal(p1);                       // vector towards ship
    1322             :                 p1.x += variation * (randf() - variation);
    1323             :                 p1.y += variation * (randf() - variation);
    1324             :                 p1.z += variation * (randf() - variation);
    1325             :                 p1 = HPvector_normal(p1); 
    1326             :                 _destination = HPvector_add(p_pos, HPvector_multiply_scalar(p1, p_cr)); // on surface
    1327             :                 desired_range = collision_radius + 100.0;       // +100m from the destination
    1328             :         }
    1329             :         else
    1330             :         {
    1331             :                 [shipAI message:@"NO_PLANET_FOUND"];
    1332             :         }
    1333             : }
    1334             : 
    1335             : 
    1336             : - (void) setTakeOffFromPlanet
    1337             : {
    1338             :         /*- selects the nearest planet it can find -*/
    1339             :         OOPlanetEntity  *the_planet =  [self findNearestPlanet];
    1340             :         if (the_planet)
    1341             :         {
    1342             :                 _destination = HPvector_add([the_planet position], HPvector_multiply_scalar(
    1343             :                                                                                                                                                            HPvector_normal(HPvector_subtract([the_planet position],position)),-10000.0-the_planet->collision_radius));// 10km straight up
    1344             :                 desired_range = 50.0;
    1345             :         }
    1346             :         else
    1347             :         {
    1348             :                 OOLog(@"ai.setTakeOffFromPlanet.noPlanet", @"%@", @"***** Error. Planet not found during take off!");
    1349             :         }
    1350             : }
    1351             : 
    1352             : 
    1353             : - (void) landOnPlanet
    1354             : {
    1355             :         // Selects the nearest planet it can find.
    1356             :         [self landOnPlanet:[self findNearestPlanet]];
    1357             : }
    1358             : 
    1359             : 
    1360             : - (void) checkTargetLegalStatus
    1361             : {
    1362             :         ShipEntity  *other_ship = [self primaryTarget];
    1363             :         if (!other_ship)
    1364             :         {
    1365             :                 [shipAI message:@"NO_TARGET"];
    1366             :                 return;
    1367             :         }
    1368             :         else
    1369             :         {
    1370             :                 int ls = [other_ship legalStatus];
    1371             :                 if (ls > 50)
    1372             :                 {
    1373             :                         [shipAI message:@"TARGET_FUGITIVE"];
    1374             :                         return;
    1375             :                 }
    1376             :                 if (ls > 20)
    1377             :                 {
    1378             :                         [shipAI message:@"TARGET_OFFENDER"];
    1379             :                         return;
    1380             :                 }
    1381             :                 if (ls > 0)
    1382             :                 {
    1383             :                         [shipAI message:@"TARGET_MINOR_OFFENDER"];
    1384             :                         return;
    1385             :                 }
    1386             :                 [shipAI message:@"TARGET_CLEAN"];
    1387             :         }
    1388             : }
    1389             : 
    1390             : 
    1391             : - (void) checkOwnLegalStatus
    1392             : {
    1393             :         if (scanClass == CLASS_THARGOID)
    1394             :         {
    1395             :                 [shipAI message:@"SELF_THARGOID"];
    1396             :                 return;
    1397             :         }
    1398             :         int ls = [self legalStatus];
    1399             :         if (ls > 50)
    1400             :         {
    1401             :                 [shipAI message:@"SELF_FUGITIVE"];
    1402             :                 return;
    1403             :         }
    1404             :         if (ls > 20)
    1405             :         {
    1406             :                 [shipAI message:@"SELF_OFFENDER"];
    1407             :                 return;
    1408             :         }
    1409             :         if (ls > 0)
    1410             :         {
    1411             :                 [shipAI message:@"SELF_MINOR_OFFENDER"];
    1412             :                 return;
    1413             :         }
    1414             :         [shipAI message:@"SELF_CLEAN"];
    1415             : }
    1416             : 
    1417             : 
    1418             : - (void) exitAIWithMessage:(NSString *)message
    1419             : {
    1420             :         if ([message length] == 0)  message = @"RESTARTED";
    1421             :         [shipAI exitStateMachineWithMessage:message];
    1422             : }
    1423             : 
    1424             : 
    1425             : - (void) setDestinationToTarget
    1426             : {
    1427             :         Entity *the_target = [self primaryTarget];
    1428             :         if (the_target)
    1429             :                 _destination = the_target->position;
    1430             : }
    1431             : 
    1432             : 
    1433             : - (void) setDestinationWithinTarget
    1434             : {
    1435             :         Entity *the_target = [self primaryTarget];
    1436             :         if (the_target)
    1437             :         {
    1438             :                 HPVector pos = the_target->position;
    1439             :                 Quaternion q;   quaternion_set_random(&q);
    1440             :                 Vector v = vector_forward_from_quaternion(q);
    1441             :                 GLfloat d = (randf() - randf()) * the_target->collision_radius;
    1442             :                 _destination = make_HPvector(pos.x + d * v.x, pos.y + d * v.y, pos.z + d * v.z);
    1443             :         }
    1444             : }
    1445             : 
    1446             : 
    1447             : - (void) checkCourseToDestination
    1448             : {
    1449             :         Entity *hazard = [UNIVERSE hazardOnRouteFromEntity: self toDistance: desired_range fromPoint: _destination];
    1450             :         
    1451             :         if (hazard == nil || ([hazard isShip] && HPdistance(position, [hazard position]) > scannerRange) || ([hazard isPlanet] && aegis_status == AEGIS_NONE)) 
    1452             :                 [shipAI message:@"COURSE_OK"]; // Avoid going into a waypoint.plist for far away objects, it cripples the main AI a bit in its funtionality.
    1453             :         else
    1454             :         {
    1455             :                 if ([hazard isShip] && (weapon_damage * 24.0 > [hazard energy]))
    1456             :                 {
    1457             :                         [shipAI reactToMessage:@"HAZARD_CAN_BE_DESTROYED" context:@"checkCourseToDestination"];
    1458             :                 }
    1459             :                 
    1460             :                 _destination = [UNIVERSE getSafeVectorFromEntity:self toDistance:desired_range fromPoint:_destination];
    1461             :                 [shipAI message:@"WAYPOINT_SET"];
    1462             :         }
    1463             : }
    1464             : 
    1465             : 
    1466             : - (void) checkAegis
    1467             : {
    1468             :         switch (aegis_status)
    1469             :         {
    1470             :                 case AEGIS_CLOSE_TO_MAIN_PLANET: 
    1471             :                         [shipAI message:@"AEGIS_CLOSE_TO_MAIN_PLANET"];
    1472             :                         // It's been a few years since 1.71 - it should be safe enough to comment out the line below for 1.77/1.78 -- Kaks 20120917
    1473             :                         //[shipAI message:@"AEGIS_CLOSE_TO_PLANET"];       // fires only for main planets, kept for compatibility with pre-1.72 AI plists.
    1474             :                         return;
    1475             :                 case AEGIS_CLOSE_TO_ANY_PLANET:
    1476             :                 {
    1477             :                         Entity<OOStellarBody> *nearest = [self findNearestStellarBody];
    1478             :                         
    1479             :                         if([nearest isSun])
    1480             :                         {
    1481             :                                 [shipAI message:@"CLOSE_TO_SUN"];
    1482             :                         }
    1483             :                         else
    1484             :                         {
    1485             :                                 [shipAI message:@"CLOSE_TO_PLANET"];
    1486             :                                 if ([nearest planetType] == STELLAR_TYPE_MOON)
    1487             :                                 {
    1488             :                                         [shipAI message:@"CLOSE_TO_MOON"];
    1489             :                                 }
    1490             :                                 else
    1491             :                                 {
    1492             :                                         [shipAI message:@"CLOSE_TO_SECONDARY_PLANET"];
    1493             :                                 }
    1494             :                         }
    1495             :                         return;
    1496             :                 }
    1497             :                 case AEGIS_IN_DOCKING_RANGE:
    1498             :                         [shipAI message:@"AEGIS_IN_DOCKING_RANGE"];
    1499             :                         return;
    1500             :                 case AEGIS_NONE:
    1501             :                         [shipAI message:@"AEGIS_NONE"];
    1502             :                         return;
    1503             :         }
    1504             :         
    1505             :         NSLog(@"Aegis status for %@ has taken on invalid value %i. This is an internal error, please report it.", self, aegis_status);
    1506             :         aegis_status = AEGIS_NONE;
    1507             :         [shipAI message:@"AEGIS_NONE"];
    1508             : }
    1509             : 
    1510             : 
    1511             : - (void) checkEnergy
    1512             : {
    1513             :         if (energy == maxEnergy)
    1514             :         {
    1515             :                 [shipAI message:@"ENERGY_FULL"];
    1516             :                 return;
    1517             :         }
    1518             :         if (energy >= maxEnergy * 0.75)
    1519             :         {
    1520             :                 [shipAI message:@"ENERGY_HIGH"];
    1521             :                 return;
    1522             :         }
    1523             :         if (energy <= maxEnergy * 0.25)
    1524             :         {
    1525             :                 [shipAI message:@"ENERGY_LOW"];
    1526             :                 return;
    1527             :         }
    1528             :         [shipAI message:@"ENERGY_MEDIUM"];
    1529             : }
    1530             : 
    1531             : - (void) checkHeatInsulation
    1532             : {
    1533             :         float minInsulation = 1000 / [self maxFlightSpeed] + 1;
    1534             :         
    1535             :         if ([self heatInsulation] < minInsulation)
    1536             :         {
    1537             :                 [shipAI message:@"INSULATION_POOR"];
    1538             :                 return;
    1539             :         }
    1540             :         [shipAI message:@"INSULATION_OK"];
    1541             : }
    1542             : 
    1543             : 
    1544             : - (void) findNewDefenseTarget
    1545             : {
    1546             :         [self checkScanner];
    1547             :         unsigned i;
    1548             :         for (i = 0; i < n_scanned_ships ; i++)
    1549             :         {
    1550             :                 ShipEntity *ship = scanned_ships[i];
    1551             :                 if (![ship isCloaked] && (([ship primaryTarget] == self && [ship hasHostileTarget]) || [ship isMine] || ([ship isThargoid] != [self isThargoid])))
    1552             :                 {
    1553             :                         if (![self isDefenseTarget:ship])
    1554             :                         {
    1555             :                                 [self addDefenseTarget:ship];
    1556             :                                 return;
    1557             :                         }
    1558             :                 }
    1559             :         }
    1560             : }
    1561             : 
    1562             : 
    1563             : - (void) scanForOffenders
    1564             : {
    1565             :         /*-- Locates all the ships in range and compares their legal status or bounty against ranrot_rand() & 255 - chooses the worst offender --*/
    1566             :         NSDictionary            *systeminfo = [UNIVERSE currentSystemData];
    1567             :         float gov_factor =      0.4 * [(NSNumber *)[systeminfo objectForKey:KEY_GOVERNMENT] intValue]; // 0 .. 7 (0 anarchic .. 7 most stable) --> [0.0, 0.4, 0.8, 1.2, 1.6, 2.0, 2.4, 2.8]
    1568             :         //
    1569             :         if ([UNIVERSE sun] == nil)
    1570             :                 gov_factor = 1.0;
    1571             :         //
    1572             :         DESTROY(_foundTarget);
    1573             :         
    1574             :         // find the worst offender on the scanner
    1575             :         //
    1576             :         [self checkScanner];
    1577             :         unsigned i;
    1578             :         float   worst_legal_factor = 0;
    1579             :         GLfloat found_d2 = scannerRange * scannerRange;
    1580             :         OOShipGroup *group = [self group];
    1581             :         for (i = 0; i < n_scanned_ships ; i++)
    1582             :         {
    1583             :                 ShipEntity *ship = scanned_ships[i];
    1584             :                 if ((ship->scanClass != CLASS_CARGO)&&([ship status] != STATUS_DEAD)&&([ship status] != STATUS_DOCKED)&& ![ship isCloaked])
    1585             :                 {
    1586             :                         GLfloat d2 = distance2_scanned_ships[i];
    1587             :                         float   legal_factor = [ship legalStatus] * gov_factor;
    1588             :                         int random_factor = ranrot_rand() & 255;   // 25% chance of spotting a fugitive in 15s
    1589             :                         if ((d2 < found_d2)&&(random_factor < legal_factor)&&(legal_factor > worst_legal_factor))
    1590             :                         {
    1591             :                                 if (group == nil || group != [ship group])  // fellows with bounty can't be offenders
    1592             :                                 {
    1593             :                                         [self setFoundTarget:ship];
    1594             :                                         worst_legal_factor = legal_factor;
    1595             :                                 }
    1596             :                         }
    1597             :                 }
    1598             :         }
    1599             :         
    1600             :         [self checkFoundTarget];
    1601             : }
    1602             : 
    1603             : 
    1604             : - (void) setCourseToWitchpoint
    1605             : {
    1606             :         if (UNIVERSE)
    1607             :         {
    1608             :                 _destination = [UNIVERSE getWitchspaceExitPosition];
    1609             :                 desired_range = 10000.0;   // 10km away
    1610             :         }
    1611             : }
    1612             : 
    1613             : 
    1614             : - (void) setDestinationToWitchpoint
    1615             : {
    1616             :         _destination = [UNIVERSE getWitchspaceExitPosition];
    1617             : }
    1618             : 
    1619             : 
    1620             : - (void) setDestinationToStationBeacon
    1621             : {
    1622             :         if ([UNIVERSE station])
    1623             :         {
    1624             :                 _destination = [[UNIVERSE station] beaconPosition];
    1625             :         }
    1626             : }
    1627             : 
    1628             : 
    1629             : - (void) performHyperSpaceExit
    1630             : {
    1631             :         [self performHyperSpaceExitReplace:YES];
    1632             : }
    1633             : 
    1634             : 
    1635             : - (void) performHyperSpaceExitWithoutReplacing
    1636             : {
    1637             :         [self performHyperSpaceExitReplace:NO];
    1638             : }
    1639             : 
    1640             : 
    1641           0 : - (void) disengageAutopilot
    1642             : {
    1643             :         OOLogERR(@"ai.invalid.notPlayer", @"Error in %@:%@, AI method endAutoPilot is only applicable to the player.", [shipAI name], [shipAI state]);
    1644             : }
    1645             : 
    1646             : 
    1647             : - (void) wormholeGroup
    1648             : {
    1649             :         NSEnumerator            *shipEnum = nil;
    1650             :         ShipEntity                      *ship = nil;
    1651             :         WormholeEntity          *whole = nil;
    1652             :         
    1653             :         whole = [self primaryTarget];
    1654             :         if (![whole isWormhole])  return;
    1655             :         
    1656             :         for (shipEnum = [[self group] mutationSafeEnumerator]; (ship = [shipEnum nextObject]); )
    1657             :         {
    1658             :                 [ship addTarget:whole];
    1659             :                 [ship reactToAIMessage:@"ENTER WORMHOLE" context:@"wormholeGroup"];
    1660             :                 [ship doScriptEvent:OOJSID("wormholeSuggested") withArgument:whole];
    1661             :         }
    1662             : }
    1663             : 
    1664             : 
    1665             : - (void) commsMessage:(NSString *)valueString
    1666             : {
    1667             :         [self commsMessage:valueString withUnpilotedOverride:NO];
    1668             : }
    1669             : 
    1670             : 
    1671             : - (void) commsMessageByUnpiloted:(NSString *)valueString
    1672             : {
    1673             :         [self commsMessage:valueString withUnpilotedOverride:YES];
    1674             : }
    1675             : 
    1676             : 
    1677             : - (void) ejectCargo
    1678             : {
    1679             :         OOCargoQuantity i, cargo_to_go = 0.1 * [self maxAvailableCargoSpace];
    1680             :         while (cargo_to_go > 15)
    1681             :         {
    1682             :                 cargo_to_go = ranrot_rand() % cargo_to_go;
    1683             :         }
    1684             :         [self dumpCargo];
    1685             :         for (i = 1; i < cargo_to_go; i++)
    1686             :         {
    1687             :                 [self performSelector:@selector(dumpCargo) withObject:nil afterDelay:0.75 * i]; // drop 3 canisters per 2 seconds
    1688             :         }
    1689             : }
    1690             : 
    1691             : 
    1692             : - (void) scanForThargoid
    1693             : {
    1694             :         return [self scanForNearestShipWithPrimaryRole:@"thargoid"];
    1695             : }
    1696             : 
    1697             : 
    1698             : - (void) scanForNonThargoid
    1699             : {
    1700             :         /*-- Locates all the non thargoid ships in range and chooses the nearest --*/
    1701             :         DESTROY(_foundTarget);
    1702             :         
    1703             :         [self checkScanner];
    1704             :         unsigned i;
    1705             :         GLfloat found_d2 = scannerRange * scannerRange;
    1706             :         for (i = 0; i < n_scanned_ships ; i++)
    1707             :         {
    1708             :                 ShipEntity *thing = scanned_ships[i];
    1709             :                 GLfloat d2 = distance2_scanned_ships[i];
    1710             :                 if (([thing scanClass] != CLASS_CARGO) && ([thing status] != STATUS_DOCKED) && ![thing isThargoid] && ![thing isCloaked] && (d2 < found_d2))
    1711             :                 {
    1712             :                         [self setFoundTarget:thing];
    1713             :                         if ([thing isPlayer]) d2 = 0.0;   // prefer the player
    1714             :                         found_d2 = d2;
    1715             :                 }
    1716             :         }
    1717             : 
    1718             :         [self checkFoundTarget];
    1719             : }
    1720             : 
    1721             : 
    1722             : - (void) thargonCheckMother
    1723             : {
    1724             :         ShipEntity   *mother = [self owner];
    1725             :         if (mother == nil && [self group])  mother = [[self group] leader];
    1726             :         
    1727             :         double  maxRange2 = scannerRange * scannerRange;
    1728             :         
    1729             :         if (mother && mother != self && HPdistance2(mother->position, position) < maxRange2)
    1730             :         {
    1731             :                 [shipAI message:@"TARGET_FOUND"]; // no need for scanning, we still have our mother.
    1732             :         }
    1733             :         else
    1734             :         {
    1735             :                 // we lost the old mother, search for a new one
    1736             :                 [self scanForNearestShipHavingRole:@"thargoid-mothership"]; // the scan will send further AI messages.
    1737             :                 if ([self foundTarget] != nil)
    1738             :                 {
    1739             :                         mother = (ShipEntity*)[self foundTarget];
    1740             :                         [self setOwner:mother];
    1741             :                         if ([mother group] != [mother escortGroup]) // avoid adding thargon to an escort group.
    1742             :                         {
    1743             :                                 [self setGroup:[mother group]];
    1744             :                         }
    1745             :                 };
    1746             :         }
    1747             : }
    1748             : 
    1749             : 
    1750             : - (void) becomeUncontrolledThargon
    1751             : {
    1752             :         int                     ent_count =             UNIVERSE->n_entities;
    1753             :         Entity**        uni_entities =  UNIVERSE->sortedEntities;    // grab the public sorted list
    1754             :         int i;
    1755             :         for (i = 0; i < ent_count; i++) if (uni_entities[i]->isShip)
    1756             :         {
    1757             :                 ShipEntity *other = (ShipEntity*)uni_entities[i];
    1758             :                 if ([other primaryTarget] == self)
    1759             :                 {
    1760             :                         [other removeTarget:self];
    1761             :                 }
    1762             :                 if ([other isDefenseTarget:self])
    1763             :                 {
    1764             :                         [other removeDefenseTarget:self];
    1765             :                 }
    1766             :         }
    1767             :         // now we're just a bunch of alien artefacts!
    1768             :         scanClass = CLASS_CARGO;
    1769             :         reportAIMessages = NO;
    1770             :         [self setAITo:@"dumbAI.plist"];
    1771             :         DESTROY(_primaryTarget);
    1772             :         [self setSpeed: 0.0];
    1773             :         [self setGroup:nil];
    1774             : }
    1775             : 
    1776             : 
    1777             : - (void) checkDistanceTravelled
    1778             : {
    1779             :         if (distanceTravelled > desired_range)
    1780             :                 [shipAI message:@"GONE_BEYOND_RANGE"];
    1781             : }
    1782             : 
    1783             : 
    1784             : - (void) fightOrFleeHostiles
    1785             : {
    1786             :         [self addDefenseTarget:[self foundTarget]];
    1787             :         
    1788             :         if ([self hasEscorts])
    1789             :         {
    1790             :                 Entity *leTarget = [self lastEscortTarget];
    1791             :                 if (leTarget != nil)
    1792             :                 {
    1793             :                         [self setFoundTarget:leTarget];
    1794             :                         [shipAI message:@"FLEEING"];
    1795             :                         return;
    1796             :                 }
    1797             :                 
    1798             :                 [self setPrimaryAggressor:[self foundTarget]];
    1799             :                 [self addTarget:[self foundTarget]];
    1800             :                 [self deployEscorts];
    1801             :                 [shipAI message:@"DEPLOYING_ESCORTS"];
    1802             :                 [shipAI message:@"FLEEING"];
    1803             :                 return;
    1804             :         }
    1805             :         
    1806             :         // consider launching a missile
    1807             :         if (missiles > 2)   // keep a reserve
    1808             :         {
    1809             :                 if (randf() < 0.50)
    1810             :                 {
    1811             :                         [self setPrimaryAggressor:[self foundTarget]];
    1812             :                         [self addTarget:[self foundTarget]];
    1813             :                         [self fireMissile];
    1814             :                         [shipAI message:@"FLEEING"];
    1815             :                         return;
    1816             :                 }
    1817             :         }
    1818             :         
    1819             :         // consider fighting
    1820             :         if (energy > maxEnergy * 0.80)
    1821             :         {
    1822             :                 [self setPrimaryAggressor:[self foundTarget]];
    1823             :                 //[self performAttack];
    1824             :                 [shipAI message:@"FIGHTING"];
    1825             :                 return;
    1826             :         }
    1827             :         
    1828             :         [shipAI message:@"FLEEING"];
    1829             : }
    1830             : 
    1831             : 
    1832             : - (void) suggestEscort
    1833             : {
    1834             :         ShipEntity   *mother = [self primaryTarget];
    1835             :         [self suggestEscortTo:mother];
    1836             : }
    1837             : 
    1838             : 
    1839             : 
    1840             : - (void) escortCheckMother
    1841             : {
    1842             :         ShipEntity   *mother = [self owner];
    1843             :         
    1844             :         if ([mother acceptAsEscort:self])
    1845             :         {
    1846             :                 [self setOwner:mother];
    1847             :                 [self setGroup:[mother escortGroup]];
    1848             :                 [shipAI message:@"ESCORTING"];
    1849             :         }
    1850             :         else
    1851             :         {
    1852             :                 [self setOwner:self];
    1853             :                 if ([self group] == [mother escortGroup])  [self setGroup:nil];
    1854             :                 [shipAI message:@"NOT_ESCORTING"];
    1855             :         }
    1856             : }
    1857             : 
    1858             : 
    1859             : - (void) checkGroupOddsVersusTarget
    1860             : {
    1861             :         NSUInteger ownGroupCount = [[self group] count] + (ranrot_rand() & 3);                      // add a random fudge factor
    1862             :         NSUInteger targetGroupCount = [[[self primaryTarget] group] count] + (ranrot_rand() & 3);   // add a random fudge factor
    1863             :         
    1864             :         if (ownGroupCount == targetGroupCount)
    1865             :         {
    1866             :                 [shipAI message:@"ODDS_LEVEL"];
    1867             :         }
    1868             :         else if (ownGroupCount > targetGroupCount)
    1869             :         {
    1870             :                 [shipAI message:@"ODDS_GOOD"];
    1871             :         }
    1872             :         else
    1873             :         {
    1874             :                 [shipAI message:@"ODDS_BAD"];
    1875             :         }
    1876             : }
    1877             : 
    1878             : 
    1879             : 
    1880             : 
    1881             : - (void) scanForFormationLeader
    1882             : {
    1883             :         //-- Locates the nearest suitable formation leader in range --//
    1884             :         DESTROY(_foundTarget);
    1885             :         [self checkScannerIgnoringUnpowered];
    1886             :         unsigned i;
    1887             :         GLfloat found_d2 = scannerRange * scannerRange;
    1888             :         for (i = 0; i < n_scanned_ships; i++)
    1889             :         {
    1890             :                 ShipEntity *ship = scanned_ships[i];
    1891             :                 if ((ship != self) && (!ship->isPlayer) && (ship->scanClass == scanClass) && [ship primaryTarget] != self && ![ship isCloaked])   // look for alike
    1892             :                 {
    1893             :                         GLfloat d2 = distance2_scanned_ships[i];
    1894             :                         if ((d2 < found_d2) && [ship canAcceptEscort:self])
    1895             :                         {
    1896             :                                 found_d2 = d2;
    1897             :                                 [self setFoundTarget:ship];
    1898             :                         }
    1899             :                 }
    1900             :         }
    1901             :         
    1902             :         if ([self foundTarget] != nil)  [shipAI message:@"TARGET_FOUND"];
    1903             :         else
    1904             :         {
    1905             :                 [shipAI message:@"NOTHING_FOUND"];
    1906             :                 if ([self hasPrimaryRole:@"wingman"])
    1907             :                 {
    1908             :                         // become free-lance police :)
    1909             :                         [self setAITo:@"route1patrolAI.plist"];       // use this to avoid referencing a released AI
    1910             :                         [self setPrimaryRole:@"police"]; // other wingman can now select this ship as leader.
    1911             :                 }
    1912             :         }
    1913             :         
    1914             : }
    1915             : 
    1916             : 
    1917             : - (void) messageMother:(NSString *)msgString
    1918             : {
    1919             :         ShipEntity *mother = [self owner];
    1920             :         if (mother != nil && mother != self)
    1921             :         {
    1922             :                 NSString *context = nil;
    1923             : #ifndef NDEBUG
    1924             :                 context = [NSString stringWithFormat:@"%@ messageMother", [self shortDescription]];
    1925             : #endif
    1926             :                 [mother reactToAIMessage:msgString context:context];
    1927             :         }
    1928             : }
    1929             : 
    1930             : 
    1931           0 : - (void) messageSelf:(NSString *)msgString
    1932             : {
    1933             :         [self sendAIMessage:msgString];
    1934             : }
    1935             : 
    1936             : 
    1937             : - (void) setPlanetPatrolCoordinates
    1938             : {
    1939             :         // check we've arrived near the last given coordinates
    1940             :         HPVector r_pos = HPvector_subtract(position, coordinates);
    1941             :         if (HPmagnitude2(r_pos) < 1000000 || patrol_counter == 0)
    1942             :         {
    1943             :                 Entity *the_sun = [UNIVERSE sun];
    1944             :                 ShipEntity *the_station = [[self group] leader];
    1945             :                 if(!the_station || ![the_station isStation]) the_station = [UNIVERSE station];
    1946             :                 if ((!the_sun)||(!the_station))
    1947             :                         return;
    1948             :                 HPVector sun_pos = the_sun->position;
    1949             :                 HPVector stn_pos = the_station->position;
    1950             :                 HPVector sun_dir = HPvector_subtract(sun_pos,stn_pos);
    1951             :                 Vector vSun = make_vector(0, 0, 1);
    1952             :                 if (sun_dir.x||sun_dir.y||sun_dir.z)
    1953             :                         vSun = HPVectorToVector(HPvector_normal(sun_dir));
    1954             :                 Vector v0 = [the_station forwardVector];
    1955             :                 Vector v1 = cross_product(v0, vSun);
    1956             :                 Vector v2 = cross_product(v0, v1);
    1957             :                 switch (patrol_counter)
    1958             :                 {
    1959             :                         case 0:         // first go to 5km ahead of the station
    1960             :                                 coordinates = make_HPvector(stn_pos.x + 5000 * v0.x, stn_pos.y + 5000 * v0.y, stn_pos.z + 5000 * v0.z);
    1961             :                                 desired_range = 250.0;
    1962             :                                 break;
    1963             :                         case 1:         // go to 25km N of the station
    1964             :                                 coordinates = make_HPvector(stn_pos.x + 25000 * v1.x, stn_pos.y + 25000 * v1.y, stn_pos.z + 25000 * v1.z);
    1965             :                                 desired_range = 250.0;
    1966             :                                 break;
    1967             :                         case 2:         // go to 25km E of the station
    1968             :                                 coordinates = make_HPvector(stn_pos.x + 25000 * v2.x, stn_pos.y + 25000 * v2.y, stn_pos.z + 25000 * v2.z);
    1969             :                                 desired_range = 250.0;
    1970             :                                 break;
    1971             :                         case 3:         // go to 25km S of the station
    1972             :                                 coordinates = make_HPvector(stn_pos.x - 25000 * v1.x, stn_pos.y - 25000 * v1.y, stn_pos.z - 25000 * v1.z);
    1973             :                                 desired_range = 250.0;
    1974             :                                 break;
    1975             :                         case 4:         // go to 25km W of the station
    1976             :                                 coordinates = make_HPvector(stn_pos.x - 25000 * v2.x, stn_pos.y - 25000 * v2.y, stn_pos.z - 25000 * v2.z);
    1977             :                                 desired_range = 250.0;
    1978             :                                 break;
    1979             :                         default:        // We should never come here
    1980             :                                 coordinates = make_HPvector(stn_pos.x + 5000 * v0.x, stn_pos.y + 5000 * v0.y, stn_pos.z + 5000 * v0.z);
    1981             :                                 desired_range = 250.0;
    1982             :                                 break;
    1983             :                 }
    1984             :                 patrol_counter++;
    1985             :                 if (patrol_counter > 4)
    1986             :                 {
    1987             :                         if (randf() < .25)
    1988             :                         {
    1989             :                                 // consider docking
    1990             :                                 [self setTargetStation:the_station];
    1991             :                                 [self setAITo:@"dockingAI.plist"];
    1992             :                                 return;
    1993             :                         }
    1994             :                         else
    1995             :                         {
    1996             :                                 // go around again
    1997             :                                 patrol_counter = 1;
    1998             :                         }
    1999             :                 }
    2000             :         }
    2001             :         [shipAI message:@"APPROACH_COORDINATES"];
    2002             : }
    2003             : 
    2004             : 
    2005             : - (void) setSunSkimStartCoordinates
    2006             : {
    2007             :         if ([UNIVERSE sun] == nil)
    2008             :         {
    2009             :                 [shipAI message:@"NO_SUN_FOUND"];
    2010             :                 return;
    2011             :         }
    2012             :         
    2013             :         HPVector v0 = [UNIVERSE getSunSkimStartPositionForShip:self];
    2014             :         
    2015             :         if (!HPvector_equal(v0, kZeroHPVector))
    2016             :         {
    2017             :                 coordinates = v0;
    2018             :                 [shipAI message:@"APPROACH_COORDINATES"];
    2019             :         }
    2020             :         else
    2021             :         {
    2022             :                 [shipAI message:@"WAIT_FOR_SUN"];
    2023             :         }
    2024             : }
    2025             : 
    2026             : 
    2027             : - (void) setSunSkimEndCoordinates
    2028             : {
    2029             :         if ([UNIVERSE sun] == nil)
    2030             :         {
    2031             :                 [shipAI message:@"NO_SUN_FOUND"];
    2032             :                 return;
    2033             :         }
    2034             :         
    2035             :         coordinates = [UNIVERSE getSunSkimEndPositionForShip:self];
    2036             :         [shipAI message:@"APPROACH_COORDINATES"];
    2037             : }
    2038             : 
    2039             : 
    2040             : - (void) setSunSkimExitCoordinates
    2041             : {
    2042             :         Entity *the_sun = [UNIVERSE sun];
    2043             :         if (the_sun == nil)  return;
    2044             :         HPVector v1 = [UNIVERSE getSunSkimEndPositionForShip:self];
    2045             :         HPVector vs = the_sun->position;
    2046             :         HPVector vout = HPvector_subtract(v1,vs);
    2047             :         if (vout.x||vout.y||vout.z)
    2048             :                 vout = HPvector_normal(vout);
    2049             :         else
    2050             :                 vout.z = 1.0;
    2051             :         v1.x += 10000 * vout.x; v1.y += 10000 * vout.y; v1.z += 10000 * vout.z;
    2052             :         coordinates = v1;
    2053             :         [shipAI message:@"APPROACH_COORDINATES"];
    2054             : }
    2055             : 
    2056             : 
    2057             : - (void) patrolReportIn
    2058             : {
    2059             :         // Set a report time in the patrolled station to delay a new launch.
    2060             :         ShipEntity *the_station = [[self group] leader];
    2061             :         if(!the_station || ![the_station isStation]) the_station = [UNIVERSE station];
    2062             :         [(StationEntity*)the_station acceptPatrolReportFrom:self];
    2063             : }
    2064             : 
    2065             : 
    2066             : - (void) checkForMotherStation
    2067             : {
    2068             :         ShipEntity *motherStation = [[self group] leader];
    2069             :         if ((!motherStation) || (!(motherStation->isStation)))
    2070             :         {
    2071             :                 [shipAI message:@"NOTHING_FOUND"];
    2072             :                 return;
    2073             :         }
    2074             :         double found_d2 = scannerRange * scannerRange;
    2075             :         HPVector v0 = motherStation->position;
    2076             :         if (HPdistance2(v0,position) > found_d2)
    2077             :         {
    2078             :                 [shipAI message:@"NOTHING_FOUND"];
    2079             :                 return;
    2080             :         }
    2081             :         [shipAI message:@"STATION_FOUND"];            
    2082             : }
    2083             : 
    2084             : 
    2085             : - (void) sendTargetCommsMessage:(NSString*) message
    2086             : {
    2087             :         ShipEntity *ship = [self primaryTarget];
    2088             :         if ((ship == nil) || ([ship status] == STATUS_DEAD) || ([ship status] == STATUS_DOCKED))
    2089             :         {
    2090             :                 [self noteLostTarget];
    2091             :                 return;
    2092             :         }
    2093             :         [self sendExpandedMessage:message toShip:[self primaryTarget]];
    2094             : }
    2095             : 
    2096             : 
    2097             : - (void) markTargetForFines
    2098             : {
    2099             :         ShipEntity *ship = [self primaryTarget];
    2100             :         if ((ship == nil) || ([ship status] == STATUS_DEAD) || ([ship status] == STATUS_DOCKED))
    2101             :         {
    2102             :                 [self noteLostTarget];
    2103             :                 return;
    2104             :         }
    2105             :         if ([ship markForFines])  [shipAI message:@"TARGET_MARKED"];
    2106             : }
    2107             : 
    2108             : 
    2109             : - (void) markTargetForOffence:(NSString *)valueString
    2110             : {
    2111             :         if ((isStation)||(scanClass == CLASS_POLICE))
    2112             :         {
    2113             :                 ShipEntity *ship = [self primaryTarget];
    2114             :                 if ((ship == nil) || ([ship status] == STATUS_DEAD) || ([ship status] == STATUS_DOCKED))
    2115             :                 {
    2116             :                         [self noteLostTarget];
    2117             :                         return;
    2118             :                 }
    2119             :                 NSString *finalValue = OOExpand(valueString);   // expand values
    2120             :                 [ship markAsOffender:[finalValue intValue] withReason:kOOLegalStatusReasonSeenByPolice];
    2121             :         }
    2122             : }
    2123             : 
    2124             : 
    2125             : - (void) storeTarget
    2126             : {
    2127             :         Entity  *target = [self primaryTarget];
    2128             :         
    2129             :         if (target)
    2130             :         {
    2131             :                 [self setRememberedShip:target];
    2132             :         }
    2133             :         else
    2134             :         {
    2135             :                 DESTROY(_rememberedShip);
    2136             :         }
    2137             :         
    2138             : }
    2139             : 
    2140             : - (void) recallStoredTarget
    2141             : {
    2142             :         ShipEntity      *oldTarget = (ShipEntity*)[self rememberedShip];
    2143             :         BOOL    found = NO;
    2144             :         
    2145             :         if (oldTarget && ![oldTarget isCloaked])
    2146             :         {
    2147             :                 GLfloat range2 = HPdistance2([oldTarget position], position);
    2148             :                 if (range2 <= scannerRange * scannerRange && range2 <= SCANNER_MAX_RANGE2)
    2149             :                 {
    2150             :                         found = YES;
    2151             :                 }
    2152             :         }
    2153             :         
    2154             :         if (found)
    2155             :         {
    2156             :                 [self setFoundTarget:oldTarget];
    2157             :                 [shipAI message:@"TARGET_FOUND"];
    2158             :         }
    2159             :         else
    2160             :         {
    2161             :                 if (oldTarget == nil) DESTROY(_rememberedShip); // ship no longer exists
    2162             :                 [shipAI message:@"NOTHING_FOUND"];
    2163             :         }
    2164             :         
    2165             : }
    2166             : 
    2167             : - (void) scanForRocks
    2168             : {
    2169             :         /*-- Locates the all boulders and asteroids in range and selects nearest --*/
    2170             :         
    2171             :         // find boulders then asteroids within range
    2172             :         //
    2173             :         DESTROY(_foundTarget);
    2174             :         [self checkScanner];
    2175             :         unsigned i;
    2176             :         GLfloat found_d2 = scannerRange * scannerRange;
    2177             :         for (i = 0; i < n_scanned_ships; i++)
    2178             :         {
    2179             :                 ShipEntity *thing = scanned_ships[i];
    2180             :                 if ([thing isBoulder])
    2181             :                 {
    2182             :                         GLfloat d2 = distance2_scanned_ships[i];
    2183             :                         if (d2 < found_d2)
    2184             :                         {
    2185             :                                 [self setFoundTarget:thing];
    2186             :                                 found_d2 = d2;
    2187             :                         }
    2188             :                 }
    2189             :         }
    2190             :         if ([self foundTarget] == nil)
    2191             :         {
    2192             :                 for (i = 0; i < n_scanned_ships; i++)
    2193             :                 {
    2194             :                         ShipEntity *thing = scanned_ships[i];
    2195             :                         if ([thing hasRole:@"asteroid"])
    2196             :                         {
    2197             :                                 GLfloat d2 = distance2_scanned_ships[i];
    2198             :                                 if (d2 < found_d2)
    2199             :                                 {
    2200             :                                         [self setFoundTarget:thing];
    2201             :                                         found_d2 = d2;
    2202             :                                 }
    2203             :                         }
    2204             :                 }
    2205             :         }
    2206             :         
    2207             :         [self checkFoundTarget];
    2208             : }
    2209             : 
    2210             : 
    2211             : - (void) setDestinationToDockingAbort
    2212             : {
    2213             :         Entity *the_target = [self targetStation];
    2214             :         if (!the_target) {
    2215             :                 /* Probably the player trying to dock with docking computer
    2216             :                  * from out of scanner range */
    2217             :                 the_target = [UNIVERSE station];
    2218             :         }
    2219             :         double bo_distance = 8000; //   8km back off
    2220             :         HPVector v0 = position;
    2221             :         HPVector d0 = (the_target) ? the_target->position : kZeroHPVector;
    2222             :         v0.x += (randf() - 0.5)*collision_radius;       v0.y += (randf() - 0.5)*collision_radius;       v0.z += (randf() - 0.5)*collision_radius;
    2223             :         v0.x -= d0.x;   v0.y -= d0.y;   v0.z -= d0.z;
    2224             :         v0 = HPvector_normal_or_fallback(v0, make_HPvector(0, 0, -1));
    2225             :         
    2226             :         v0.x *= bo_distance;    v0.y *= bo_distance;    v0.z *= bo_distance;
    2227             :         v0.x += d0.x;   v0.y += d0.y;   v0.z += d0.z;
    2228             :         coordinates = v0;
    2229             :         _destination = v0;
    2230             : }
    2231             : 
    2232             : 
    2233             : - (void) requestNewTarget
    2234             : {
    2235             :         ShipEntity *mother = [[self group] leader];
    2236             :         if (mother == nil)
    2237             :         {
    2238             :                 [shipAI message:@"MOTHER_LOST"];
    2239             :                 return;
    2240             :         }
    2241             :         
    2242             :         /*-- Locates all the ships in range targeting the mother ship and chooses the nearest/biggest --*/
    2243             :         DESTROY(_foundTarget);
    2244             :         [self checkScanner];
    2245             :         unsigned i;
    2246             :         GLfloat found_d2 = scannerRange * scannerRange;
    2247             :         GLfloat max_e = 0;
    2248             :         for (i = 0; i < n_scanned_ships ; i++)
    2249             :         {
    2250             :                 ShipEntity *thing = scanned_ships[i];
    2251             :                 GLfloat d2 = distance2_scanned_ships[i];
    2252             :                 GLfloat e1 = [thing energy];
    2253             :                 if ((d2 < found_d2) && ![thing isCloaked] && (([thing isThargoid] && ![mother isThargoid]) || (([thing primaryTarget] == mother) && [thing hasHostileTarget])))
    2254             :                 {
    2255             :                         if (e1 > max_e)
    2256             :                         {
    2257             :                                 [self setFoundTarget:thing];
    2258             :                                 max_e = e1;
    2259             :                         }
    2260             :                 }
    2261             :         }
    2262             :         
    2263             :         [self checkFoundTarget];
    2264             : }
    2265             : 
    2266             : 
    2267             : - (void) rollD:(NSString *)die_number
    2268             : {
    2269             :         int die_sides = [die_number intValue];
    2270             :         if (die_sides > 0)
    2271             :         {
    2272             :                 int die_roll = 1 + (ranrot_rand() % die_sides);
    2273             :                 NSString* result = [NSString stringWithFormat:@"ROLL_%d", die_roll];
    2274             :                 [shipAI reactToMessage:result context:@"rollD:"];
    2275             :         }
    2276             :         else
    2277             :         {
    2278             :                 OOLog(@"ai.rollD.invalidValue", @"***** ERROR: invalid value supplied to rollD: '%@'.", die_number);
    2279             :         }
    2280             : }
    2281             : 
    2282             : 
    2283             : - (void) scanForNearestShipWithPrimaryRole:(NSString *)scanRole
    2284             : {
    2285             :         [self scanForNearestShipWithPredicate:HasPrimaryRolePredicate parameter:scanRole];
    2286             : }
    2287             : 
    2288             : 
    2289             : - (void) scanForNearestShipHavingRole:(NSString *)scanRole
    2290             : {
    2291             :         [self scanForNearestShipWithPredicate:HasRolePredicate parameter:scanRole];
    2292             : }
    2293             : 
    2294             : 
    2295             : - (void) scanForNearestShipWithAnyPrimaryRole:(NSString *)scanRoles
    2296             : {
    2297             :         NSSet *set = [NSSet setWithArray:ScanTokensFromString(scanRoles)];
    2298             :         [self scanForNearestShipWithPredicate:HasPrimaryRoleInSetPredicate parameter:set];
    2299             : }
    2300             : 
    2301             : 
    2302             : - (void) scanForNearestShipHavingAnyRole:(NSString *)scanRoles
    2303             : {
    2304             :         NSSet *set = [NSSet setWithArray:ScanTokensFromString(scanRoles)];
    2305             :         [self scanForNearestShipWithPredicate:HasRoleInSetPredicate parameter:set];
    2306             : }
    2307             : 
    2308             : 
    2309             : - (void) scanForNearestShipWithScanClass:(NSString *)scanScanClass
    2310             : {
    2311             :         NSNumber *parameter = [NSNumber numberWithInt:OOScanClassFromString(scanScanClass)];
    2312             :         [self scanForNearestShipWithPredicate:HasScanClassPredicate parameter:parameter];
    2313             : }
    2314             : 
    2315             : 
    2316             : - (void) scanForNearestShipWithoutPrimaryRole:(NSString *)scanRole
    2317             : {
    2318             :         [self scanForNearestShipWithNegatedPredicate:HasPrimaryRolePredicate parameter:scanRole];
    2319             : }
    2320             : 
    2321             : 
    2322             : - (void) scanForNearestShipNotHavingRole:(NSString *)scanRole
    2323             : {
    2324             :         [self scanForNearestShipWithNegatedPredicate:HasRolePredicate parameter:scanRole];
    2325             : }
    2326             : 
    2327             : 
    2328             : - (void) scanForNearestShipWithoutAnyPrimaryRole:(NSString *)scanRoles
    2329             : {
    2330             :         NSSet *set = [NSSet setWithArray:ScanTokensFromString(scanRoles)];
    2331             :         [self scanForNearestShipWithNegatedPredicate:HasPrimaryRoleInSetPredicate parameter:set];
    2332             : }
    2333             : 
    2334             : 
    2335             : - (void) scanForNearestShipNotHavingAnyRole:(NSString *)scanRoles
    2336             : {
    2337             :         NSSet *set = [NSSet setWithArray:ScanTokensFromString(scanRoles)];
    2338             :         [self scanForNearestShipWithNegatedPredicate:HasRoleInSetPredicate parameter:set];
    2339             : }
    2340             : 
    2341             : 
    2342             : - (void) scanForNearestShipWithoutScanClass:(NSString *)scanScanClass
    2343             : {
    2344             :         NSNumber *parameter = [NSNumber numberWithInt:OOScanClassFromString(scanScanClass)];
    2345             :         [self scanForNearestShipWithNegatedPredicate:HasScanClassPredicate parameter:parameter];
    2346             : }
    2347             : 
    2348             : 
    2349           0 : - (void) scanForNearestShipMatchingPredicate:(NSString *)predicateExpression
    2350             : {
    2351             :         /*      Takes a boolean-valued JS expression where "ship" is the ship being
    2352             :          evaluated and "this" is our ship's ship script. the expression is
    2353             :          turned into a JS function of the form:
    2354             :          
    2355             :          function _oo_AIScanPredicate(ship)
    2356             :          {
    2357             :          return $expression;
    2358             :          }
    2359             :          
    2360             :          Examples of expressions:
    2361             :          ship.isWeapon
    2362             :          this.someComplicatedPredicate(ship)
    2363             :          function (ship) { ...do something complicated... } ()
    2364             :          */
    2365             :         
    2366             :         static NSMutableDictionary      *scriptCache = nil;
    2367             :         NSString                                        *aiName = nil;
    2368             :         NSString                                        *key = nil;
    2369             :         OOJSFunction                            *function = nil;
    2370             :         JSContext                                       *context = NULL;
    2371             :         
    2372             :         context = OOJSAcquireContext();
    2373             :         
    2374             :         if (predicateExpression == nil)  predicateExpression = @"false";
    2375             :         
    2376             :         aiName = [[self getAI] name];
    2377             : #ifndef NDEBUG
    2378             :         /*      In debug/test release builds, scripts are cached per AI in order to be
    2379             :          able to report errors correctly. For end-user releases, we only cache
    2380             :          one copy of each predicate, potentially leading to error messages for
    2381             :          the wrong AI.
    2382             :          */
    2383             :         key = [NSString stringWithFormat:@"%@\n%@", aiName, predicateExpression];
    2384             : #else
    2385             :         key = predicateExpression;
    2386             : #endif
    2387             :         
    2388             :         // Look for cached function
    2389             :         function = [scriptCache objectForKey:key];
    2390             :         if (function == nil)
    2391             :         {
    2392             :                 NSString                                        *predicateCode = nil;
    2393             :                 const char                                      *argNames[] = { "ship" };
    2394             :                 
    2395             :                 // Stuff expression in a function.
    2396             :                 predicateCode = [NSString stringWithFormat:@"return %@;", predicateExpression];
    2397             :                 function = [[OOJSFunction alloc] initWithName:@"_oo_AIScanPredicate"
    2398             :                                                                                                 scope:NULL
    2399             :                                                                                                  code:predicateCode
    2400             :                                                                                 argumentCount:1
    2401             :                                                                                 argumentNames:argNames
    2402             :                                                                                          fileName:aiName
    2403             :                                                                                    lineNumber:0
    2404             :                                                                                           context:context];
    2405             :                 [function autorelease];
    2406             :                 
    2407             :                 // Cache function.
    2408             :                 if (function != nil)
    2409             :                 {
    2410             :                         if (scriptCache == nil)  scriptCache = [[NSMutableDictionary alloc] init];
    2411             :                         [scriptCache setObject:function forKey:key];
    2412             :                 }
    2413             :         }
    2414             :         
    2415             :         if (function != nil)
    2416             :         {
    2417             :                 JSFunctionPredicateParameter param =
    2418             :                 {
    2419             :                         .context = context,
    2420             :                         .function = [function functionValue],
    2421             :                         .jsThis = OOJSObjectFromNativeObject(context, self)
    2422             :                 };
    2423             :                 [self scanForNearestShipWithPredicate:JSFunctionPredicate parameter:&param];
    2424             :         }
    2425             :         else
    2426             :         {
    2427             :                 // Report error (once per occurrence)
    2428             :                 static NSMutableSet                     *errorCache = nil;
    2429             :                 
    2430             :                 if (![errorCache containsObject:key])
    2431             :                 {
    2432             :                         OOLog(@"ai.scanForNearestShipMatchingPredicate.compile.failed", @"Could not compile JavaScript predicate \"%@\" for AI %@.", predicateExpression, [[self getAI] name]);
    2433             :                         if (errorCache == nil)  errorCache = [[NSMutableSet alloc] init];
    2434             :                         [errorCache addObject:key];
    2435             :                 }
    2436             :                 
    2437             :                 // Select nothing
    2438             :                 DESTROY(_foundTarget);
    2439             :                 [[self getAI] message:@"NOTHING_FOUND"];
    2440             :         }
    2441             :         
    2442             :         JS_ReportPendingException(context);
    2443             :         OOJSRelinquishContext(context);
    2444             : }
    2445             : 
    2446             : 
    2447             : - (void) setCoordinates:(NSString *)system_x_y_z
    2448             : {
    2449             :         NSArray*        tokens = ScanTokensFromString(system_x_y_z);
    2450             :         NSString*       systemString = nil;
    2451             :         NSString*       xString = nil;
    2452             :         NSString*       yString = nil;
    2453             :         NSString*       zString = nil;
    2454             :         
    2455             :         if ([tokens count] != 4)
    2456             :         {
    2457             :                 OOLog(@"ai.syntax.setCoordinates", @"***** ERROR: cannot setCoordinates: '%@'.",system_x_y_z);
    2458             :                 return;
    2459             :         }
    2460             :         
    2461             :         systemString = (NSString *)[tokens objectAtIndex:0];
    2462             :         xString = (NSString *)[tokens objectAtIndex:1];
    2463             :         if ([xString hasPrefix:@"rand:"])
    2464             :                 xString = [NSString stringWithFormat:@"%.3f", bellf([(NSString*)[[xString componentsSeparatedByString:@":"] objectAtIndex:1] intValue])];
    2465             :         yString = (NSString *)[tokens objectAtIndex:2];
    2466             :         if ([yString hasPrefix:@"rand:"])
    2467             :                 yString = [NSString stringWithFormat:@"%.3f", bellf([(NSString*)[[yString componentsSeparatedByString:@":"] objectAtIndex:1] intValue])];
    2468             :         zString = (NSString *)[tokens objectAtIndex:3];
    2469             :         if ([zString hasPrefix:@"rand:"])
    2470             :                 zString = [NSString stringWithFormat:@"%.3f", bellf([(NSString*)[[zString componentsSeparatedByString:@":"] objectAtIndex:1] intValue])];
    2471             :         
    2472             :         HPVector posn = make_HPvector([xString floatValue], [yString floatValue], [zString floatValue]);
    2473             :         GLfloat scalar = 1.0;
    2474             :         
    2475             :         coordinates = [UNIVERSE coordinatesForPosition:posn withCoordinateSystem:systemString returningScalar:&scalar];
    2476             :         
    2477             :         [shipAI message:@"APPROACH_COORDINATES"];
    2478             : }
    2479             : 
    2480             : 
    2481             : - (void) checkForNormalSpace
    2482             : {
    2483             :         if ([UNIVERSE sun] && [UNIVERSE planet])
    2484             :                 [shipAI message:@"NORMAL_SPACE"];
    2485             :         else
    2486             :                 [shipAI message:@"INTERSTELLAR_SPACE"];
    2487             : }
    2488             : 
    2489             : 
    2490             : - (void) setTargetToRandomStation
    2491             : {
    2492             :         /*- selects the nearest station it can find -*/
    2493             :         int                             ent_count = UNIVERSE->n_entities;
    2494             :         Entity                  **uni_entities = UNIVERSE->sortedEntities;   // grab the public sorted list
    2495             :         Entity                  *my_entities[ent_count];
    2496             :         StationEntity   *station = nil, *my_station = nil;
    2497             :         double                  maxRange2 = desired_range * desired_range;
    2498             :         int                             i;
    2499             :         int                             station_count = 0;
    2500             :         
    2501             :         for (i = 0; i < ent_count; i++)
    2502             :         {
    2503             :                 // find stations within range but exclude carriers.
    2504             :                 if (uni_entities[i]->isStation)
    2505             :                 {
    2506             :                         my_station = (StationEntity*)uni_entities[i];
    2507             :                         if ([my_station maxFlightSpeed] == 0 && [my_station hasNPCTraffic] && HPdistance2(position, [my_station position]) < maxRange2)
    2508             :                         {
    2509             :                                 my_entities[station_count++] = [uni_entities[i] retain];                //      retained
    2510             :                         }
    2511             :                 }
    2512             :         }
    2513             :         
    2514             :         if (station_count != 0)
    2515             :         {
    2516             :                 // select a random station
    2517             :                 station = (StationEntity *)my_entities[ranrot_rand() % station_count];
    2518             :                 // if more than one candidate do not select main station
    2519             :                 if (station == [UNIVERSE station] && station_count > 1)
    2520             :                 {
    2521             :                         while (station == [UNIVERSE station])
    2522             :                         {
    2523             :                                 station = (StationEntity *)my_entities[ranrot_rand() % station_count];
    2524             :                         }
    2525             :                 }
    2526             :         }
    2527             :         
    2528             :         for (i = 0; i < station_count; i++)
    2529             :                 [my_entities[i] release];               //      released
    2530             :         //
    2531             :         if (station)
    2532             :         {
    2533             :                 [self addTarget:station];
    2534             :                 [self setTargetStation:station];
    2535             :                 [shipAI message:@"STATION_FOUND"];
    2536             :         }
    2537             :         else
    2538             :         {
    2539             :                 [shipAI message:@"NO_STATION_IN_RANGE"];
    2540             :         }
    2541             : }
    2542             : 
    2543             : - (void) setTargetToLastStation
    2544             : {
    2545             :         Entity  *station = [self targetStation];
    2546             :         
    2547             :         if (station != nil && [station isStation])
    2548             :         {
    2549             :                 [self addTarget:station];
    2550             :         }
    2551             :         else
    2552             :         {
    2553             :                 [shipAI message:@"NO_STATION_FOUND"];
    2554             :                 [self setTargetStation:nil];
    2555             :         }
    2556             :         
    2557             : }
    2558             : 
    2559             : 
    2560             : - (void) addFuel:(NSString*) fuel_number
    2561             : {
    2562             :         [self setFuel:[self fuel] + [fuel_number intValue] * 10];
    2563             : }
    2564             : 
    2565             : 
    2566             : 
    2567             : - (void) scriptActionOnTarget:(NSString *)action
    2568             : {
    2569             :         PlayerEntity    *player = PLAYER;
    2570             :         ShipEntity              *targEnt = [self primaryTarget];
    2571             :         ShipEntity              *oldTarget = nil;
    2572             :         
    2573             : #ifndef NDEBUG
    2574             :         static BOOL             deprecationWarning = NO;
    2575             :         
    2576             :         if (!deprecationWarning)
    2577             :         {
    2578             :                 deprecationWarning = YES;
    2579             :                 OOLog(@"script.deprecated.scriptActionOnTarget", @"----- WARNING in AI %@: the AI method scriptActionOnTarget: is deprecated and should not be used. It is slow and has unpredictable side effects. The recommended alternative is to use sendScriptMessage: to call a function in a ship's JavaScript ship script instead. scriptActionOnTarget: should not be used at all from scripts. An alternative is safeScriptActionOnTarget:, which is similar to scriptActionOnTarget: but has less side effects.", [AI currentlyRunningAIDescription]);
    2580             :         }
    2581             :         else
    2582             :         {
    2583             :                 OOLog(@"script.deprecated.scriptActionOnTarget.repeat", @"----- WARNING in AI %@: the AI method scriptActionOnTarget: is deprecated and should not be used.", [AI currentlyRunningAIDescription]);
    2584             :         }
    2585             : #endif
    2586             :         
    2587             :         if ([targEnt isShip])
    2588             :         {
    2589             :                 oldTarget = [player scriptTarget];
    2590             :                 [player setScriptTarget:(ShipEntity*)targEnt];
    2591             :                 [player runUnsanitizedScriptActions:[NSArray arrayWithObject:action]
    2592             :                                                   allowingAIMethods:YES
    2593             :                                                         withContextName:[NSString stringWithFormat:@"<AI \"%@\" state %@ - scriptActionOnTarget:>", [[self getAI] name], [[self getAI] state]]
    2594             :                                                                   forTarget:targEnt];
    2595             :                 [player checkScript];   // react immediately to any changes this makes
    2596             :                 [player setScriptTarget:oldTarget];
    2597             :         }
    2598             : }
    2599             : 
    2600             : 
    2601           0 : - (void) safeScriptActionOnTarget:(NSString *)action
    2602             : {
    2603             :         PlayerEntity    *player = PLAYER;
    2604             :         ShipEntity              *targEnt = [self primaryTarget];
    2605             :         ShipEntity              *oldTarget = nil;
    2606             :         
    2607             :         if ([targEnt isShip])
    2608             :         {
    2609             :                 oldTarget = [player scriptTarget];
    2610             :                 [player setScriptTarget:(ShipEntity*)targEnt];
    2611             :                 [player runUnsanitizedScriptActions:[NSArray arrayWithObject:action]
    2612             :                                                   allowingAIMethods:YES
    2613             :                                                         withContextName:[NSString stringWithFormat:@"<AI \"%@\" state %@ - safeScriptActionOnTarget:>", [[self getAI] name], [[self getAI] state]]
    2614             :                                                                   forTarget:targEnt];
    2615             :                 [player setScriptTarget:oldTarget];
    2616             :         }
    2617             : }
    2618             : 
    2619             : 
    2620             : // Send own ship script a message.
    2621             : - (void) sendScriptMessage:(NSString *)message
    2622             : {
    2623             :         NSArray *components = ScanTokensFromString(message);
    2624             :         
    2625             :         if ([components count] == 1)
    2626             :         {
    2627             :                 [self doScriptEvent:OOJSIDFromString(message)];
    2628             :         }
    2629             :         else
    2630             :         {
    2631             :                 NSString *function = [components objectAtIndex:0];
    2632             :                 components = [components subarrayWithRange:NSMakeRange(1, [components count] - 1)];
    2633             :                 [self doScriptEvent:OOJSIDFromString(function) withArgument:components];
    2634             :         }
    2635             : }
    2636             : 
    2637             : 
    2638             : - (void) ai_throwSparks
    2639             : {
    2640             :         [self setThrowSparks:YES];
    2641             : }
    2642             : 
    2643             : 
    2644             : - (void) explodeSelf
    2645             : {
    2646             :         [self getDestroyedBy:nil damageType:kOODamageTypeEnergy];
    2647             : }
    2648             : 
    2649             : 
    2650             : - (void) ai_debugMessage:(NSString *)message
    2651             : {
    2652             :         NSString *desc = [NSString stringWithFormat:@"%@ %d", [self name], [self universalID]];
    2653             :         if ([self isPlayer])  desc = @"player autopilot";
    2654             :         OOLog(@"ai.takeAction.debugMessage", @"DEBUG: AI MESSAGE from %@: %@", desc, message);
    2655             : }
    2656             : 
    2657             : 
    2658             : 
    2659             : // racing code TODO
    2660             : - (void) targetFirstBeaconWithCode:(NSString*) code
    2661             : {
    2662             :         NSArray                 *all_beacons = [UNIVERSE listBeaconsWithCode: code];
    2663             :         if ([all_beacons count])
    2664             :         {
    2665             :                 [self addTarget:(ShipEntity*)[all_beacons objectAtIndex:0]];
    2666             :                 [shipAI message:@"TARGET_FOUND"];
    2667             :         }
    2668             :         else
    2669             :                 [shipAI message:@"NOTHING_FOUND"];
    2670             : }
    2671             : 
    2672             : 
    2673             : - (void) targetNextBeaconWithCode:(NSString*) code
    2674             : {
    2675             :         NSArray                 *all_beacons = [UNIVERSE listBeaconsWithCode: code];
    2676             :         ShipEntity              *current_beacon = [self primaryTarget];
    2677             :         
    2678             :         if ((!current_beacon)||(![current_beacon isBeacon]))
    2679             :         {
    2680             :                 [shipAI message:@"NO_CURRENT_BEACON"];
    2681             :                 [shipAI message:@"NOTHING_FOUND"];
    2682             :                 return;
    2683             :         }
    2684             :         
    2685             :         // find the current beacon in the list..
    2686             :         NSUInteger i = [all_beacons indexOfObject:current_beacon];
    2687             :         
    2688             :         if (i == NSNotFound)
    2689             :         {
    2690             :                 [shipAI message:@"NOTHING_FOUND"];
    2691             :                 return;
    2692             :         }
    2693             :         
    2694             :         i++;    // next index
    2695             :         
    2696             :         if (i < [all_beacons count])
    2697             :         {
    2698             :                 // locate current target in list
    2699             :                 [self addTarget:(ShipEntity*)[all_beacons objectAtIndex:i]];
    2700             :                 [shipAI message:@"TARGET_FOUND"];
    2701             :         }
    2702             :         else
    2703             :         {
    2704             :                 [shipAI message:@"LAST_BEACON"];
    2705             :                 [shipAI message:@"NOTHING_FOUND"];
    2706             :         }
    2707             : }
    2708             : 
    2709             : 
    2710             : - (void) setRacepointsFromTarget
    2711             : {
    2712             :         // two point - one at z - cr one at z + cr
    2713             :         ShipEntity *ship = [self primaryTarget];
    2714             :         if (ship == nil)
    2715             :         {
    2716             :                 [shipAI message:@"NOTHING_FOUND"];
    2717             :                 return;
    2718             :         }
    2719             :         Vector k = ship->v_forward;
    2720             :         GLfloat c = ship->collision_radius;
    2721             :         HPVector o = ship->position;
    2722             :         navpoints[0] = make_HPvector(o.x - c * k.x, o.y - c * k.y, o.z - c * k.z);
    2723             :         navpoints[1] = make_HPvector(o.x + c * k.x, o.y + c * k.y, o.z + c * k.z);
    2724             :         navpoints[2] = make_HPvector(o.x + 2.0 * c * k.x, o.y + 2.0 * c * k.y, o.z + 2.0 * c * k.z);
    2725             :         number_of_navpoints = 2;
    2726             :         next_navpoint_index = 0;
    2727             :         _destination = navpoints[0];
    2728             :         [shipAI message:@"RACEPOINTS_SET"];
    2729             : }
    2730             : 
    2731             : 
    2732             : - (void) performFlyRacepoints
    2733             : {
    2734             :         next_navpoint_index = 0;
    2735             :         desired_range = collision_radius;
    2736             :         behaviour = BEHAVIOUR_FLY_THRU_NAVPOINTS;
    2737             : }
    2738             : 
    2739             : @end
    2740             : 
    2741             : 
    2742             : @implementation ShipEntity (OOAIPrivate)
    2743             : 
    2744             : 
    2745             : - (void) checkFoundTarget
    2746             : {
    2747             :         if ([self foundTarget] != nil) 
    2748             :         {
    2749             :                 [shipAI message:@"TARGET_FOUND"];
    2750             :         }
    2751             :         else
    2752             :         {
    2753             :                 [shipAI message:@"NOTHING_FOUND"];
    2754             :         }
    2755             : }
    2756             : 
    2757             : 
    2758             : - (BOOL) performHyperSpaceExitReplace:(BOOL)replace
    2759             : {
    2760             :         return [self performHyperSpaceExitReplace:replace toSystem:-1];
    2761             : }
    2762             : 
    2763             : 
    2764             : - (BOOL) performHyperSpaceExitReplace:(BOOL)replace toSystem:(OOSystemID)systemID
    2765             : {
    2766             :         if(![self hasHyperspaceMotor])
    2767             :         {
    2768             :                 [shipAI reactToMessage:@"WITCHSPACE UNAVAILABLE" context:@"performHyperSpaceExit"];
    2769             :                 return NO;
    2770             :         }
    2771             :         if([self status] == STATUS_ENTERING_WITCHSPACE)
    2772             :         {
    2773             : // already in a wormhole
    2774             :                 return NO;
    2775             :         }
    2776             :         
    2777             :         NSArray                 *sDests = nil;
    2778             :         OOSystemID              targetSystem;
    2779             :         NSUInteger              i = 0;
    2780             :         
    2781             :         // get a list of destinations within range
    2782             :         sDests = [UNIVERSE nearbyDestinationsWithinRange: 0.1f * fuel];
    2783             :         NSUInteger n_dests = [sDests count];
    2784             :         
    2785             :         // if none available report to the AI and exit
    2786             :         if (n_dests == 0)
    2787             :         {
    2788             :                 [shipAI reactToMessage:@"WITCHSPACE UNAVAILABLE" context:@"performHyperSpaceExit"];
    2789             :                 
    2790             :                 // If no systems exist near us, the AI is switched to a different state, so we do not need
    2791             :                 // the nearby destinations array anymore.
    2792             :                 return NO;
    2793             :         }
    2794             :         
    2795             :         // check if we're clear of nearby masses
    2796             :         ShipEntity *blocker = [UNIVERSE entityForUniversalID:[self checkShipsInVicinityForWitchJumpExit]];
    2797             :         if (blocker)
    2798             :         {
    2799             :                 [self setFoundTarget:blocker];
    2800             :                 [shipAI reactToMessage:@"WITCHSPACE BLOCKED" context:@"performHyperSpaceExit"];
    2801             :                 [self doScriptEvent:OOJSID("shipWitchspaceBlocked") withArgument:blocker];
    2802             : 
    2803             :                 return NO;
    2804             :         }
    2805             :         
    2806             :         if (systemID == -1)
    2807             :         {
    2808             :                 // select one at random
    2809             :                 if (n_dests > 1)
    2810             :                 {
    2811             :                         i = ranrot_rand() % n_dests;
    2812             :                 }
    2813             :                 
    2814             :                 
    2815             :                 targetSystem = [[sDests oo_dictionaryAtIndex:i] oo_intForKey:@"sysID"];
    2816             :         }
    2817             :         else
    2818             :         {
    2819             :                 targetSystem = systemID;
    2820             :                 
    2821             :                 for (i = 0; i < n_dests; i++)
    2822             :                 {
    2823             :                         if (systemID == [[sDests oo_dictionaryAtIndex:i] oo_intForKey:@"sysID"]) break;
    2824             :                 }
    2825             :                 
    2826             :                 if (i == n_dests)       // no match found
    2827             :                 {
    2828             :                         return NO;
    2829             :                 }
    2830             :         }
    2831             :         float dist = [[sDests oo_dictionaryAtIndex:i] oo_floatForKey:@"distance"];
    2832             :         if (dist > [self maxHyperspaceDistance] || dist > fuel/10.0f) 
    2833             :         {
    2834             :                 OOLogWARN(@"script.debug", @"DEBUG: %@ Jumping %f which is further than allowed.  I have %d fuel", self, dist, fuel);
    2835             :         }
    2836             :         fuel -= 10 * dist;
    2837             :         
    2838             :         // create wormhole
    2839             :         WormholeEntity  *whole = [[[WormholeEntity alloc] initWormholeTo: targetSystem fromShip:self] autorelease];
    2840             :         [UNIVERSE addEntity: whole];
    2841             :         
    2842             :         [self enterWormhole:whole replacing:replace];
    2843             :         
    2844             :         // we've no need for the destinations array anymore.
    2845             :         return YES;
    2846             : }
    2847             : 
    2848             : 
    2849             : - (void) scanForNearestShipWithPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter
    2850             : {
    2851             :         // Locates all the ships in range for which predicate returns YES, and chooses the nearest.
    2852             :         unsigned                i;
    2853             :         ShipEntity              *candidate;
    2854             :         float                   d2, found_d2 = scannerRange * scannerRange;
    2855             :         
    2856             :         DESTROY(_foundTarget);
    2857             :         [self checkScanner];
    2858             :         
    2859             :         if (predicate == NULL)  return;
    2860             :         
    2861             :         for (i = 0; i < n_scanned_ships ; i++)
    2862             :         {
    2863             :                 candidate = scanned_ships[i];
    2864             :                 d2 = distance2_scanned_ships[i];
    2865             :                 if ((d2 < found_d2) && (candidate->scanClass != CLASS_CARGO) && ([candidate status] != STATUS_DOCKED) 
    2866             :                                         && predicate(candidate, parameter) && ![candidate isCloaked])
    2867             :                 {
    2868             :                         [self setFoundTarget:candidate];
    2869             :                         found_d2 = d2;
    2870             :                 }
    2871             :         }
    2872             :         
    2873             :         [self checkFoundTarget];
    2874             : }
    2875             : 
    2876             : 
    2877             : - (void) scanForNearestShipWithNegatedPredicate:(EntityFilterPredicate)predicate parameter:(void *)parameter
    2878             : {
    2879             :         ChainedEntityPredicateParameter param = { predicate, parameter };
    2880             :         [self scanForNearestShipWithPredicate:NOTPredicate parameter:&param];
    2881             : }
    2882             : 
    2883             : 
    2884             : - (void) acceptDistressMessageFrom:(ShipEntity *)other
    2885             : {
    2886             :         [self setFoundTarget:[other primaryTarget]];
    2887             :         if ([self isPolice])
    2888             :         {
    2889             :                 [(ShipEntity*)[self foundTarget] markAsOffender:8 withReason:kOOLegalStatusReasonDistressCall];  // you have been warned!!
    2890             :         }
    2891             :         
    2892             :         NSString *context = nil;
    2893             : #ifndef NDEBUG
    2894             :         context = [NSString stringWithFormat:@"%@ broadcastDistressMessage", [other shortDescription]];
    2895             : #endif
    2896             :         [shipAI reactToMessage:@"ACCEPT_DISTRESS_CALL" context:context];
    2897             :         
    2898             : }
    2899             : 
    2900             : 
    2901             : 
    2902             : 
    2903             : @end
    2904             : 
    2905             : 
    2906             : @implementation StationEntity (OOAIPrivate)
    2907             : 
    2908             : - (void) acceptDistressMessageFrom:(ShipEntity *)other
    2909             : {
    2910             :         if (self != [UNIVERSE station])  return;
    2911             :         
    2912             :         OOWeakReference *old_target = _primaryTarget;
    2913             :         _primaryTarget = [[[other primaryTarget] weakRetain] autorelease];
    2914             :         [(ShipEntity *)[other primaryTarget] markAsOffender:8 withReason:kOOLegalStatusReasonDistressCall];     // mark their card
    2915             :         [self launchDefenseShip];
    2916             :         _primaryTarget = old_target;
    2917             :         
    2918             : }
    2919             : 
    2920             : @end
    2921             : 
    2922             : 
    2923             : @implementation ShipEntity (OOAIStationStubs)
    2924             : 
    2925             : // AI methods for stations, have no effect on normal ships.
    2926             : 
    2927           0 : #define STATION_STUB_BASE(PROTO, NAME)  PROTO { OOLog(@"ai.invalid.notAStation", @"Attempt to use station AI method \"%s\" on non-station %@.", NAME, self); }
    2928           0 : #define STATION_STUB_NOARG(NAME)        STATION_STUB_BASE(- (void) NAME, #NAME)
    2929           0 : #define STATION_STUB_ARG(NAME)          STATION_STUB_BASE(- (void) NAME (NSString *)param, #NAME)
    2930             : 
    2931             : STATION_STUB_NOARG(increaseAlertLevel)
    2932             : STATION_STUB_NOARG(decreaseAlertLevel)
    2933             : STATION_STUB_NOARG(launchPolice)
    2934             : STATION_STUB_NOARG(launchDefenseShip)
    2935             : STATION_STUB_NOARG(launchScavenger)
    2936             : STATION_STUB_NOARG(launchMiner)
    2937             : STATION_STUB_NOARG(launchPirateShip)
    2938             : STATION_STUB_NOARG(launchShuttle)
    2939             : STATION_STUB_NOARG(launchTrader)
    2940             : STATION_STUB_NOARG(launchEscort)
    2941           0 : - (BOOL) launchPatrol
    2942             : {
    2943             :         OOLog(@"ai.invalid.notAStation", @"Attempt to use station AI method \"%s\" on non-station %@.", "launchPatrol", self);
    2944             :         return NO;
    2945             : }
    2946             : STATION_STUB_ARG(launchShipWithRole:)
    2947             : STATION_STUB_NOARG(abortAllDockings)
    2948             : 
    2949             : @end
    2950             : 

Generated by: LCOV version 1.14