Oolite 1.91.0.7658-250404-b1488af
All Classes Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
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
85
87
88- (void) setDesiredRangeTo:(NSString *)rangeString;
89
91
92- (void) setSpeedTo:(NSString *)speedString;
93
94- (void) setSpeedFactorTo:(NSString *)speedString;
95
97
98- (void) setThrustFactorTo:(NSString *)thrustFactorString;
99
100
102
105
106- (void) scanForLoot;
107
108- (void) scanForRandomLoot;
109
111
112- (void) checkForFullHold;
113
115
118
119- (void) fightOrFleeMissile;
120
121- (void) setCourseToPlanet;
122- (void) setTakeOffFromPlanet;
123- (void) landOnPlanet;
124
126- (void) checkOwnLegalStatus;
127
128- (void) exitAIWithMessage:(NSString *)message;
129
132
134
135- (void) checkAegis;
136
137- (void) checkEnergy;
138- (void) checkHeatInsulation;
139
140- (void) scanForOffenders;
141
142- (void) setCourseToWitchpoint;
143
146
147- (void) performHyperSpaceExit;
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;
160
162
163- (void) fightOrFleeHostiles;
164
165- (void) suggestEscort;
166
167- (void) escortCheckMother;
168
170
172
173- (void) messageMother:(NSString *)msgString;
174
176
178
180
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
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
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;
239- (void) performFlyRacepoints;
240
241// defense targets
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
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
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
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
390{
391 behaviour = BEHAVIOUR_COLLECT_TARGET;
392 frustration = 0.0;
393}
394
395
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
407{
408 behaviour = BEHAVIOUR_FACE_DESTINATION;
409 frustration = 0.0;
410}
411
412
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
433{
434 behaviour = BEHAVIOUR_FLY_RANGE_FROM_DESTINATION;
435 frustration = 0.0;
436}
437
438
440{
441 desired_speed = 0.0;
442 behaviour = BEHAVIOUR_TRACK_TARGET;
443 frustration = 0.0;
444}
445
446
448{
449 behaviour = BEHAVIOUR_IDLE;
450 frustration = 0.0;
451}
452
453
455{
456 behaviour = BEHAVIOUR_INTERCEPT_TARGET;
457 frustration = 0.0;
458}
459
460
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
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 {
492 }
493}
494
495
497{
498 behaviour = BEHAVIOUR_SCRIPTED_AI;
499 frustration = 0.0;
500}
501
502
504{
505 behaviour = BEHAVIOUR_SCRIPTED_ATTACK_AI;
506 frustration = 0.0;
507}
508
509
511{
512 stick_roll = 0.10;
513 stick_pitch = 0.15;
514 behaviour = BEHAVIOUR_TUMBLE;
515 frustration = 0.0;
516}
517
518
520{
521 behaviour = BEHAVIOUR_STOP_STILL;
522 desired_speed = 0.0;
523 frustration = 0.0;
524}
525
526
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
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 {
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
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
625{
627 {
628 HasScanClassPredicate, [NSNumber numberWithInt:CLASS_MISSILE],
630 };
631 [self scanForNearestShipWithPredicate:ANDPredicate parameter:&param];
632}
633
635{
636 [self enterWormhole:[PLAYER wormhole] replacing:NO];
637}
638
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.
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
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
764{
765 /*-- Locates all the stations, bounty hunters and police ships in range and tells them that you are under attack --*/
767}
768
769- (void) broadcastDistressMessageWithDumping:(BOOL)dumpCargo
770{
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 {
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
891{
892 [shipAI debugDumpPendingMessages];
893}
894
895
897{
898 // randomly add a .5m variance
899 _destination = HPvector_add(position, OOHPVectorRandomSpatial(0.5));
900}
901
902
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
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
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
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
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
995{
996 float d2, found_d2;
997 unsigned i;
998 ShipEntity *ship = nil;
999
1000 //-- Locates the nearest merchantman in range.
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
1029{
1030 unsigned n_found, i;
1031
1032 //-- Locates one of the merchantman in range.
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
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
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
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
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
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
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
1220{
1221 _destination = coordinates;
1222}
1223
1224
1226{
1227 coordinates = position;
1228}
1229
1230
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
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
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
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
1354{
1355 // Selects the nearest planet it can find.
1356 [self landOnPlanet:[self findNearestPlanet]];
1357}
1358
1359
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
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
1426{
1427 Entity *the_target = [self primaryTarget];
1428 if (the_target)
1429 _destination = the_target->position;
1430}
1431
1432
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
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
1467{
1468 switch (aegis_status)
1469 {
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;
1476 {
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 }
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
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
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
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
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
1605{
1606 if (UNIVERSE)
1607 {
1608 _destination = [UNIVERSE getWitchspaceExitPosition];
1609 desired_range = 10000.0; // 10km away
1610 }
1611}
1612
1613
1615{
1616 _destination = [UNIVERSE getWitchspaceExitPosition];
1617}
1618
1619
1621{
1622 if ([UNIVERSE station])
1623 {
1624 _destination = [[UNIVERSE station] beaconPosition];
1625 }
1626}
1627
1628
1630{
1632}
1633
1634
1639
1640
1642{
1643 OOLogERR(@"ai.invalid.notPlayer", @"Error in %@:%@, AI method endAutoPilot is only applicable to the player.", [shipAI name], [shipAI state]);
1644}
1645
1646
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
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
1693{
1694 return [self scanForNearestShipWithPrimaryRole:@"thargoid"];
1695}
1696
1697
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
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
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 {
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
1778{
1779 if (distanceTravelled > desired_range)
1780 [shipAI message:@"GONE_BEYOND_RANGE"];
1781}
1782
1783
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
1833{
1834 ShipEntity *mother = [self primaryTarget];
1835 [self suggestEscortTo:mother];
1836}
1837
1838
1839
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
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
1882{
1883 //-- Locates the nearest suitable formation leader in range --//
1884 DESTROY(_foundTarget);
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- (void) messageSelf:(NSString *)msgString
1932{
1933 [self sendAIMessage:msgString];
1934}
1935
1936
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
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
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
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
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];
2063}
2064
2065
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
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
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
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
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
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
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- (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 {
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
2482{
2483 if ([UNIVERSE sun] && [UNIVERSE planet])
2484 [shipAI message:@"NORMAL_SPACE"];
2485 else
2486 [shipAI message:@"INTERSTELLAR_SPACE"];
2487}
2488
2489
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
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]
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- (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]
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
2639{
2640 [self setThrowSparks:YES];
2641}
2642
2643
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
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
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
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{
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#define STATION_STUB_BASE(PROTO, NAME) PROTO { OOLog(@"ai.invalid.notAStation", @"Attempt to use station AI method \"%s\" on non-station %@.", NAME, self); }
2928#define STATION_STUB_NOARG(NAME) STATION_STUB_BASE(- (void) NAME, #NAME)
2929#define STATION_STUB_ARG(NAME) STATION_STUB_BASE(- (void) NAME (NSString *)param, #NAME)
2930
2931STATION_STUB_NOARG(increaseAlertLevel)
2932STATION_STUB_NOARG(decreaseAlertLevel)
2933STATION_STUB_NOARG(launchPolice)
2934STATION_STUB_NOARG(launchDefenseShip)
2935STATION_STUB_NOARG(launchScavenger)
2936STATION_STUB_NOARG(launchMiner)
2937STATION_STUB_NOARG(launchPirateShip)
2938STATION_STUB_NOARG(launchShuttle)
2939STATION_STUB_NOARG(launchTrader)
2940STATION_STUB_NOARG(launchEscort)
2942{
2943 OOLog(@"ai.invalid.notAStation", @"Attempt to use station AI method \"%s\" on non-station %@.", "launchPatrol", self);
2944 return NO;
2945}
2946STATION_STUB_ARG(launchShipWithRole:)
2947STATION_STUB_NOARG(abortAllDockings)
2948
2949@end
2950
#define SCANNER_MAX_RANGE2
Definition Entity.h:52
#define DESTROY(x)
Definition OOCocoa.h:77
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
unsigned count
return nil
Vector vector_forward_from_quaternion(Quaternion quat)
void quaternion_set_random(Quaternion *quat)
@ 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:840
BOOL(* EntityFilterPredicate)(Entity *entity, void *parameter)
Definition Universe.h:52
void broadcastDistressMessage()
void performFlyToRangeFromDestination()
void scanForNearestIncomingMissile()
void enterPlayerWormhole()
void performFaceDestination()
void enterTargetWormhole()
void performScriptedAttackAI()
void performLandOnPlanet()
void recallDockingInstructions()
void requestDockingCoordinates()
void wormholeEntireGroup()
void setDesiredRangeForWaypoint()
void performHyperSpaceExitWithoutReplacing()
void addFoundTargetAsDefenseTarget()
void setDestinationToStationBeacon()
void setTargetToPrimaryAggressor()
void setDestinationToJinkPosition()
void setDestinationToCurrentLocation()
void setDestinationToDockingAbort()
void addPrimaryAggressorAsDefenseTarget()
void setDestinationFromCoordinates()
void getWitchspaceEntryCoordinates()
Definition AI.h:38
void setStateMachine:withJSScript:(NSString *smName,[withJSScript] NSString *script)
Definition AI.m:307
void clearStack()
Definition AI.m:685
NSString * name()
Definition AI.m:364
void message:(NSString *ms)
Definition AI.m:600
void setState:(NSString *stateName)
Definition AI.m:334
NSString * state()
Definition AI.m:376
GLfloat collision_radius
Definition Entity.h:111
OOUniversalID universalID
Definition Entity.m:552
void setThrowSparks:(BOOL value)
Definition Entity.m:564
GLfloat energy
Definition Entity.m:817
unsigned isShip
Definition Entity.m:131
unsigned isStation
Definition Entity.m:143
unsigned isPlayer
Definition Entity.m:155
HPVector position
Definition Entity.m:612
id owner()
Definition Entity.m:583
id jsAIScriptFromFileNamed:properties:(NSString *fileName,[properties] NSDictionary *properties)
Definition OOScript.m:221
NSUInteger count()
ShipEntity * leader()
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 getDestroyedBy:damageType:(Entity *whom,[damageType] OOShipDamageType type)
void landOnPlanet:(OOPlanetEntity *planet)
void setTargetStation:(Entity *targetEntity)
void noteLostTarget()
void wormholeGroup()
void checkFoundTarget()
void addTarget:(Entity *targetEntity)
void setPrimaryAggressor:(Entity *targetEntity)
Vector v_forward
Definition ShipEntity.h:200
void scanForNearestShipWithPredicate:parameter:(EntityFilterPredicate predicate,[parameter] void *parameter)
void setFuel:(OOFuelQuantity amount)
void removeTarget:(Entity *targetEntity)
BOOL addDefenseTarget:(Entity *target)
void setAIScript:(NSString *aiString)
void setEvasiveJink:(GLfloat z)
OOShipGroup * escortGroup()
void setSpeed:(double amount)
void scanForNearestShipWithPrimaryRole:(NSString *scanRole)
BOOL suggestEscortTo:(ShipEntity *mother)
int checkShipsInVicinityForWitchJumpExit()
void setGroup:(OOShipGroup *group)
OOShipGroup * group()
void doScriptEvent:(jsid message)
NSString * name
Entity * primaryAggressor()
void setPrimaryRole:(NSString *role)
void deployEscorts()
void broadcastDistressMessageWithDumping:(BOOL dumpCargo)
BOOL performHyperSpaceExitReplace:(BOOL replace)
OOFuelQuantity fuel
OOScanClass scanClass()
void checkScannerIgnoringUnpowered()
StationEntity * targetStation()
ShipEntity * fireMissile()
void removeDefenseTarget:(Entity *target)
OOPlanetEntity * findNearestPlanet()
OOCommodityType dumpCargo()
void setAITo:(NSString *aiString)
void commsMessage:withUnpilotedOverride:(NSString *valueString,[withUnpilotedOverride] BOOL unpilotedOverride)
void performFlee()
void scanForNearestShipHavingRole:(NSString *scanRole)
BOOL performHyperSpaceExitReplace:toSystem:(BOOL replace,[toSystem] OOSystemID systemID)
Entity< OOStellarBody > * findNearestStellarBody()
int legalStatus()
void sendExpandedMessage:toShip:(NSString *message_text,[toShip] ShipEntity *other_ship)
void wormholeEscorts()
void scanForNearestShipWithNegatedPredicate:parameter:(EntityFilterPredicate predicate,[parameter] void *parameter)
void noteLostTargetAndGoIdle()
OOCargoQuantity maxAvailableCargoSpace()
void checkScanner()
void markAsOffender:withReason:(int offence_value,[withReason] OOLegalStatusReason reason)
void doScriptEvent:withArgument:(jsid message,[withArgument] id argument)
void setRememberedShip:(Entity *targetEntity)
void reactToAIMessage:context:(NSString *message,[context] NSString *debugContext)
void setFoundTarget:(Entity *targetEntity)
OOPlanetEntity * findNearestPlanetExcludingMoons()
void sendAIMessage:(NSString *message)
void acceptDistressMessageFrom:(ShipEntity *other)
void enterWormhole:replacing:(WormholeEntity *w_hole,[replacing] BOOL replacing)
GLfloat maxFlightSpeed
void setOwner:(Entity *who_owns_entity)
Entity * lastEscortTarget()
void recallDockingInstructions()
Entity * foundTarget()
void doScriptEvent:withArgument:andArgument:(jsid message,[withArgument] id argument1,[andArgument] id argument2)
void acceptPatrolReportFrom:(ShipEntity *patrol_ship)
ShipEntity * launchDefenseShip()
NSDictionary * dockingInstructionsForShip:(ShipEntity *ship)
float randf(void)
#define ranrot_rand()