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