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