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