Line data Source code
1 0 : /*
2 :
3 : StationEntity.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 "StationEntity.h"
26 : #import "DockEntity.h"
27 : #import "ShipEntityAI.h"
28 : #import "OOCollectionExtractors.h"
29 : #import "OOStringParsing.h"
30 : #import "OOFilteringEnumerator.h"
31 :
32 : #import "Universe.h"
33 : #import "GameController.h"
34 : #import "HeadUpDisplay.h"
35 : #import "OOConstToString.h"
36 :
37 : #import "PlayerEntityLegacyScriptEngine.h"
38 : #import "OOLegacyScriptWhitelist.h"
39 : #import "OOPlanetEntity.h"
40 : #import "OOShipGroup.h"
41 : #import "OOQuiriumCascadeEntity.h"
42 :
43 : #import "AI.h"
44 : #import "OOCharacter.h"
45 :
46 : #import "OOJSScript.h"
47 : #import "OODebugGLDrawing.h"
48 : #import "OODebugFlags.h"
49 : #import "OODebugStandards.h"
50 : #import "OOWeakSet.h"
51 :
52 :
53 : @interface StationEntity (OOPrivate)
54 :
55 0 : - (void) pullInShipIfPermitted:(ShipEntity *)ship;
56 0 : - (void) addShipToStationCount:(ShipEntity *)ship;
57 :
58 0 : - (void) addShipToLaunchQueue:(ShipEntity *)ship withPriority:(BOOL)priority;
59 0 : - (unsigned) countOfShipsInLaunchQueueWithPrimaryRole:(NSString *)role;
60 :
61 0 : - (NSDictionary *) holdPositionInstructionForShip:(ShipEntity *)ship;
62 :
63 : @end
64 :
65 :
66 : #ifndef NDEBUG
67 : @interface StationEntity (mwDebug)
68 :
69 0 : - (NSArray *) dbgGetShipsOnApproach;
70 0 : - (NSArray *) dbgGetIdLocks;
71 0 : - (NSString *) dbgDumpIdLocks;
72 : @end
73 : #endif
74 :
75 :
76 : @implementation StationEntity
77 :
78 : /* Override ShipEntity: stations of CLASS_ROCK or CLASS_CARGO are not automatically unpiloted. */
79 0 : - (BOOL)isUnpiloted
80 : {
81 : return [self isExplicitlyUnpiloted] || [self isHulk];
82 : }
83 :
84 :
85 : - (OOTechLevelID) equivalentTechLevel
86 : {
87 : if (equivalentTechLevel == NSNotFound)
88 : {
89 : return [[UNIVERSE currentSystemData] oo_intForKey:KEY_TECHLEVEL];
90 : }
91 : else
92 : {
93 : return equivalentTechLevel;
94 : }
95 : }
96 :
97 :
98 : - (void) setEquivalentTechLevel:(OOTechLevelID) value
99 : {
100 : equivalentTechLevel = value;
101 : }
102 :
103 :
104 : - (Vector) virtualPortDimensions
105 : {
106 : return port_dimensions;
107 : }
108 :
109 :
110 : - (DockEntity*) playerReservedDock
111 : {
112 : return player_reserved_dock;
113 : }
114 :
115 :
116 : - (HPVector) beaconPosition
117 : {
118 : double buoy_distance = 10000.0; // distance from station entrance
119 : Vector v_f = vector_forward_from_quaternion([self orientation]);
120 : HPVector result = HPvector_add([self position], vectorToHPVector(vector_multiply_scalar(v_f, buoy_distance)));
121 :
122 : return result;
123 : }
124 :
125 :
126 : - (float) equipmentPriceFactor
127 : {
128 : return equipmentPriceFactor;
129 : }
130 :
131 :
132 : - (OOCargoQuantity) marketCapacity
133 : {
134 : return marketCapacity;
135 : }
136 :
137 :
138 : - (NSArray *) marketDefinition
139 : {
140 : return marketDefinition;
141 : }
142 :
143 :
144 : - (NSString *) marketScriptName
145 : {
146 : return marketScriptName;
147 : }
148 :
149 :
150 : - (BOOL) marketMonitored
151 : {
152 : if (self == [UNIVERSE station])
153 : {
154 : return YES;
155 : }
156 : return marketMonitored;
157 : }
158 :
159 :
160 : - (BOOL) marketBroadcast
161 : {
162 : if (self == [UNIVERSE station])
163 : {
164 : return YES;
165 : }
166 : return marketBroadcast;
167 : }
168 :
169 :
170 : - (OOCreditsQuantity) legalStatusOfManifest:(OOCommodityMarket *)manifest export:(BOOL)export
171 : {
172 : OOCreditsQuantity penalty, status = 0;
173 : OOCommodityMarket *market = [self localMarket];
174 : OOCommodityType good = nil;
175 : foreach (good, [market goods])
176 : {
177 : if (export)
178 : {
179 : penalty = [market exportLegalityForGood:good];
180 : }
181 : else
182 : {
183 : penalty = [market importLegalityForGood:good];
184 : }
185 : status += penalty * [manifest quantityForGood:good];
186 : }
187 : return status;
188 : }
189 :
190 :
191 : - (OOCommodityMarket *) localMarket
192 : {
193 : if (self == [UNIVERSE station])
194 : {
195 : // main stations use the system market
196 : // just return a reference
197 : return [UNIVERSE commodityMarket];
198 : }
199 : if (!localMarket)
200 : {
201 : [self initialiseLocalMarket];
202 : }
203 : return localMarket;
204 : }
205 :
206 :
207 : - (void) setLocalMarket:(NSArray *) some_market
208 : {
209 : [[self localMarket] loadStationAmounts:some_market];
210 : }
211 :
212 :
213 : - (NSDictionary *) localMarketForScripting
214 : {
215 : return [[self localMarket] dictionaryForScripting];
216 : }
217 :
218 :
219 : - (void) setPrice:(OOCreditsQuantity)price forCommodity:(OOCommodityType)commodity
220 : {
221 : [[self localMarket] setPrice:price forGood:commodity];
222 : }
223 :
224 :
225 : - (void) setQuantity:(OOCargoQuantity)quantity forCommodity:(OOCommodityType)commodity
226 : {
227 : [[self localMarket] setQuantity:quantity forGood:commodity];
228 : }
229 :
230 :
231 : - (NSMutableArray *) localShipyard
232 : {
233 : return localShipyard;
234 : }
235 :
236 :
237 : - (void) setLocalShipyard:(NSArray *) some_market
238 : {
239 : if (localShipyard)
240 : [localShipyard release];
241 : localShipyard = [[NSMutableArray alloc] initWithArray:some_market];
242 : }
243 :
244 :
245 : - (NSMutableDictionary *) localInterfaces
246 : {
247 : return localInterfaces;
248 : }
249 :
250 :
251 : - (void) setInterfaceDefinition:(OOJSInterfaceDefinition *)definition forKey:(NSString *)key
252 : {
253 : if (definition == nil)
254 : {
255 : [localInterfaces removeObjectForKey:key];
256 : }
257 : else
258 : {
259 : [localInterfaces setObject:definition forKey:key];
260 : }
261 : }
262 :
263 :
264 : - (OOCommodityMarket *) initialiseLocalMarket
265 : {
266 : DESTROY(localMarket);
267 : localMarket = [[[UNIVERSE commodities] generateMarketForStation:self] retain];
268 : return localMarket;
269 : }
270 :
271 :
272 : - (void) setPlanet:(OOPlanetEntity *)planet_entity
273 : {
274 : if (planet_entity)
275 : planet = [planet_entity universalID];
276 : else
277 : planet = NO_TARGET;
278 : }
279 :
280 :
281 : - (OOPlanetEntity *) planet
282 : {
283 : return [UNIVERSE entityForUniversalID:planet];
284 : }
285 :
286 :
287 : - (unsigned) countOfDockedContractors
288 : {
289 : return max_scavengers > scavengers_launched ? max_scavengers - scavengers_launched : 0;
290 : }
291 :
292 :
293 : - (unsigned) countOfDockedPolice
294 : {
295 : return max_police > defenders_launched ? max_police - defenders_launched : 0;
296 : }
297 :
298 :
299 : - (unsigned) countOfDockedDefenders
300 : {
301 : return max_defense_ships > defenders_launched ? max_defense_ships - defenders_launched : 0;
302 : }
303 :
304 :
305 : - (NSEnumerator *)dockSubEntityEnumerator
306 : {
307 : return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isDock)];
308 : }
309 :
310 :
311 : - (void) sanityCheckShipsOnApproach
312 : {
313 :
314 : NSEnumerator *subEnum = nil;
315 : DockEntity* sub = nil;
316 : unsigned soa = 0;
317 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
318 : {
319 : soa += [sub pruneAndCountShipsOnApproach];
320 : }
321 :
322 : if (soa == 0)
323 : {
324 : // if all docks have no ships on approach
325 : [shipAI message:@"DOCKING_COMPLETE"];
326 : [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")];
327 : }
328 : }
329 :
330 :
331 : // only used by player - everything else ends up in a Dock's launch queue
332 : - (void) launchShip:(ShipEntity *)ship
333 : {
334 : NSEnumerator *subEnum = nil;
335 : DockEntity *sub = nil;
336 :
337 : // try to find an unused dock first
338 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
339 : {
340 : if ([sub allowsLaunching] && [sub countOfShipsInLaunchQueue] == 0)
341 : {
342 : [sub launchShip:ship];
343 : return;
344 : }
345 : }
346 : // otherwise any launchable dock will do
347 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
348 : {
349 : if ([sub allowsLaunching])
350 : {
351 : [sub launchShip:ship];
352 : return;
353 : }
354 : }
355 :
356 : // ship has no launch docks specified; just use the last one
357 : if (sub != nil)
358 : {
359 : [sub launchShip:ship];
360 : return;
361 : }
362 : // guaranteed to always be a dock as virtual dock will suffice
363 : }
364 :
365 :
366 : // Exposed to AI
367 : - (void) abortAllDockings
368 : {
369 : NSEnumerator *subEnum = nil;
370 : DockEntity *sub = nil;
371 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
372 : {
373 : [sub abortAllDockings];
374 : }
375 :
376 : [_shipsOnHold makeObjectsPerformSelector:@selector(sendAIMessage:) withObject:@"DOCKING_ABORTED"];
377 : NSEnumerator *holdEnum = nil;
378 : ShipEntity *hold = nil;
379 : for (holdEnum = [_shipsOnHold objectEnumerator]; (hold = [holdEnum nextObject]); )
380 : {
381 : [hold doScriptEvent:OOJSID("stationWithdrewDockingClearance")];
382 : }
383 :
384 : PlayerEntity *player = PLAYER;
385 :
386 : if ([player getTargetDockStation] == self && [player getDockingClearanceStatus] >= DOCKING_CLEARANCE_STATUS_REQUESTED)
387 : {
388 : // then docking clearance is requested but hasn't been cancelled
389 : // yet by a DockEntity
390 : [self sendExpandedMessage:@"[station-docking-clearance-abort-cancelled]" toShip:player];
391 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
392 : [player doScriptEvent:OOJSID("stationWithdrewDockingClearance")];
393 : }
394 :
395 : [_shipsOnHold removeAllObjects];
396 :
397 : [shipAI message:@"DOCKING_COMPLETE"];
398 : [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")];
399 :
400 : }
401 :
402 :
403 0 : - (void) autoDockShipsOnHold
404 : {
405 : NSEnumerator *onHoldEnum = [_shipsOnHold objectEnumerator];
406 : ShipEntity *ship = nil;
407 : while ((ship = [onHoldEnum nextObject]))
408 : {
409 : [self pullInShipIfPermitted:ship];
410 : }
411 :
412 : [_shipsOnHold removeAllObjects];
413 : }
414 :
415 :
416 : - (void) autoDockShipsOnApproach
417 : {
418 : NSEnumerator *subEnum = nil;
419 : DockEntity *sub = nil;
420 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
421 : {
422 : [sub autoDockShipsOnApproach];
423 : }
424 :
425 : [self autoDockShipsOnHold];
426 :
427 : [shipAI message:@"DOCKING_COMPLETE"];
428 : [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")];
429 :
430 : }
431 :
432 :
433 : - (Vector) portUpVectorForShip:(ShipEntity*) ship
434 : {
435 : NSEnumerator *subEnum = nil;
436 : DockEntity *sub = nil;
437 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
438 : {
439 : if ([sub shipIsInDockingQueue:ship])
440 : {
441 : return [sub portUpVectorForShipsBoundingBox:[ship totalBoundingBox]];
442 : }
443 : }
444 : return kZeroVector;
445 : }
446 :
447 :
448 0 : NSDictionary *OOMakeDockingInstructions(StationEntity *station, HPVector coords, float speed, float range, NSString *ai_message, NSString *comms_message, BOOL match_rotation, int docking_stage)
449 : {
450 : NSMutableDictionary *acc = [NSMutableDictionary dictionaryWithCapacity:8];
451 : [acc oo_setHPVector:coords forKey:@"destination"];
452 : [acc oo_setFloat:speed forKey:@"speed"];
453 : [acc oo_setFloat:range forKey:@"range"];
454 : [acc setObject:[[station weakRetain] autorelease] forKey:@"station"];
455 : [acc oo_setBool:match_rotation forKey:@"match_rotation"];
456 : [acc oo_setInteger:docking_stage forKey:@"docking_stage"];
457 : if (ai_message)
458 : {
459 : [acc setObject:ai_message forKey:@"ai_message"];
460 : }
461 : if (comms_message)
462 : {
463 : [acc setObject:comms_message forKey:@"comms_message"];
464 : }
465 : return [NSDictionary dictionaryWithDictionary:acc];
466 : }
467 :
468 :
469 : // this method does initial traffic control, before passing the ship
470 : // to an appropriate dock for docking coordinates and instructions.
471 : // used for NPCs, and the player when they use the docking computer
472 : - (NSDictionary *) dockingInstructionsForShip:(ShipEntity *) ship
473 : {
474 : if (ship == nil) return nil;
475 :
476 : [self doScriptEvent:OOJSID("stationReceivedDockingRequest") withArgument:ship];
477 :
478 : if ([ship isPlayer])
479 : {
480 : player_reserved_dock = nil; // clear any dock reservation for manual docking
481 : }
482 :
483 : if ([ship isPlayer] && [ship legalStatus] > 50) // note: non-player fugitives dock as normal
484 : {
485 : // refuse docking to the fugitive player
486 : return OOMakeDockingInstructions(self, [ship position], 0, 100, @"DOCKING_REFUSED", @"[station-docking-refused-to-fugitive]", NO, -1);
487 : }
488 :
489 : if (magnitude2(velocity) > 1.0 ||
490 : fabs(flightPitch) > 0.01 ||
491 : fabs(flightYaw) > 0.01)
492 : {
493 : // no docking while station is moving, pitching or yawing
494 : return [self holdPositionInstructionForShip:ship];
495 : }
496 : PlayerEntity *player = PLAYER;
497 : BOOL player_is_ahead = (![ship isPlayer] && [player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_REQUESTED && (self == [player getTargetDockStation]));
498 :
499 : NSEnumerator *subEnum = nil;
500 : DockEntity *chosenDock = nil;
501 : NSString *docking = nil;
502 : DockEntity *sub = nil;
503 : NSUInteger queue = 100;
504 :
505 : BOOL alldockstoosmall = YES;
506 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
507 : {
508 : if ([sub shipIsInDockingQueue:ship])
509 : {
510 : // if already claimed a docking queue, use that one
511 : chosenDock = sub;
512 : alldockstoosmall = NO;
513 : break;
514 : }
515 : if (player_is_ahead) {
516 : // can't allocate a new queue while player is manually docking
517 : continue;
518 : }
519 : if (sub != player_reserved_dock || [ship isPlayer])
520 : {
521 : docking = [sub canAcceptShipForDocking:ship];
522 : if ([docking isEqualToString:@"DOCK_CLOSED"])
523 : {
524 : JSContext *context = OOJSAcquireContext();
525 : jsval rval = JSVAL_VOID;
526 : jsval args[] = { OOJSValueFromNativeObject(context, sub),
527 : OOJSValueFromNativeObject(context, ship) };
528 : JSBool tempreject = NO;
529 :
530 : BOOL OK = [[self script] callMethod:OOJSID("willOpenDockingPortFor") inContext:context withArguments:args count:2 result:&rval];
531 : if (OK) OK = JS_ValueToBoolean(context, rval, &tempreject);
532 : if (!OK) tempreject = NO; // default to permreject
533 : if (tempreject)
534 : {
535 : docking = @"TRY_AGAIN_LATER";
536 : }
537 : else
538 : {
539 : docking = @"TOO_BIG_TO_DOCK";
540 : }
541 :
542 : OOJSRelinquishContext(context);
543 : }
544 :
545 : if ([docking isEqualToString:@"DOCKING_POSSIBLE"] && [sub countOfShipsInDockingQueue] < queue) {
546 : // try to select the dock with the fewest ships already enqueued
547 : chosenDock = sub;
548 : queue = [sub countOfShipsInDockingQueue];
549 : alldockstoosmall = NO;
550 : }
551 : else if (![docking isEqualToString:@"TOO_BIG_TO_DOCK"])
552 : {
553 : alldockstoosmall = NO;
554 : }
555 : }
556 : else
557 : {
558 : alldockstoosmall = NO;
559 : }
560 : }
561 : if (chosenDock == nil)
562 : {
563 : if (player_is_ahead || ([docking isEqualToString:@"TOO_BIG_TO_DOCK"] && !alldockstoosmall) || docking == nil)
564 : {
565 : // either player is manually docking and we can't allocate new docks,
566 : // or the last dock was too small, and there may be an acceptable one
567 : // not tested yet or returning TRY_AGAIN_LATER
568 : docking = @"TRY_AGAIN_LATER";
569 : }
570 : // no docks accept this ship (or the player is blocking them)
571 : return OOMakeDockingInstructions(self, [ship position], 200, 100, docking, nil, NO, -1);
572 : }
573 :
574 :
575 : // rolling is okay for some
576 : if (fabs(flightRoll) > 0.01 && [chosenDock isOffCentre])
577 : {
578 : return [self holdPositionInstructionForShip:ship];
579 : }
580 :
581 : // we made it through holding!
582 : [_shipsOnHold removeObject:ship];
583 :
584 : [shipAI reactToMessage:@"DOCKING_REQUESTED" context:@"requestDockingCoordinates"]; // react to the request
585 : [self doScriptEvent:OOJSID("stationAcceptedDockingRequest") withArgument:ship];
586 :
587 : return [chosenDock dockingInstructionsForShip:ship];
588 : }
589 :
590 :
591 0 : - (NSDictionary *)holdPositionInstructionForShip:(ShipEntity *)ship
592 : {
593 : if (![_shipsOnHold containsObject:ship])
594 : {
595 : [self sendExpandedMessage:@"[station-acknowledges-hold-position]" toShip:ship];
596 : [_shipsOnHold addObject:ship];
597 : }
598 :
599 : return OOMakeDockingInstructions(self, [ship position], 0, 100, @"HOLD_POSITION", nil, NO, -1);
600 : }
601 :
602 :
603 : - (void) abortDockingForShip:(ShipEntity *) ship
604 : {
605 : [ship sendAIMessage:@"DOCKING_ABORTED"];
606 : [ship doScriptEvent:OOJSID("stationWithdrewDockingClearance")];
607 :
608 : [_shipsOnHold removeObject:ship];
609 :
610 : NSEnumerator *subEnum = nil;
611 : DockEntity *sub = nil;
612 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
613 : {
614 : [sub abortDockingForShip:ship];
615 : }
616 :
617 : if ([ship isPlayer])
618 : {
619 : player_reserved_dock = nil;
620 : }
621 :
622 : [self sanityCheckShipsOnApproach];
623 : }
624 :
625 :
626 : //////////////////////////////////////////////// from superclass
627 :
628 0 : - (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict
629 : {
630 : OOJS_PROFILE_ENTER
631 :
632 : self = [super initWithKey:key definition:dict];
633 : if (self != nil)
634 : {
635 : isStation = YES;
636 : _shipsOnHold = [[OOWeakSet alloc] init];
637 : hasBreakPattern = YES;
638 : localInterfaces = [[NSMutableDictionary alloc] init];
639 : }
640 : return self;
641 :
642 : OOJS_PROFILE_EXIT
643 : }
644 :
645 :
646 0 : - (void) dealloc
647 : {
648 : DESTROY(_shipsOnHold);
649 : DESTROY(marketDefinition);
650 : DESTROY(marketScriptName);
651 : DESTROY(localMarket);
652 : DESTROY(allegiance);
653 : // DESTROY(localPassengers);
654 : // DESTROY(localContracts);
655 : DESTROY(localShipyard);
656 : DESTROY(localInterfaces);
657 :
658 : [super dealloc];
659 : }
660 :
661 :
662 0 : - (BOOL) setUpShipFromDictionary:(NSDictionary *) dict
663 : {
664 : OOJS_PROFILE_ENTER
665 :
666 : isShip = YES;
667 : isStation = YES;
668 : alertLevel = STATION_ALERT_LEVEL_GREEN;
669 :
670 : port_radius = [dict oo_nonNegativeDoubleForKey:@"port_radius" defaultValue:500.0];
671 :
672 : // port_dimensions is deprecated
673 : port_dimensions = make_vector(69, 69, 250);
674 : NSString *portDimensionsStr = [dict oo_stringForKey:@"port_dimensions"];
675 : if (portDimensionsStr != nil)
676 : {
677 : OOStandardsDeprecated(@"The port_dimensions key is deprecated");
678 : if (!OOEnforceStandards())
679 : {
680 : NSArray* tokens = [portDimensionsStr componentsSeparatedByString:@"x"];
681 : if ([tokens count] == 3)
682 : {
683 : port_dimensions = make_vector([[tokens objectAtIndex:0] floatValue],
684 : [[tokens objectAtIndex:1] floatValue],
685 : [[tokens objectAtIndex:2] floatValue]);
686 : }
687 : }
688 : }
689 :
690 : if (![super setUpShipFromDictionary:dict]) return NO;
691 :
692 : equivalentTechLevel = [dict oo_unsignedIntegerForKey:@"equivalent_tech_level" defaultValue:NSNotFound];
693 : max_scavengers = [dict oo_unsignedIntForKey:@"max_scavengers" defaultValue:3];
694 : max_defense_ships = [dict oo_unsignedIntForKey:@"max_defense_ships" defaultValue:3];
695 : max_police = [dict oo_unsignedIntForKey:@"max_police" defaultValue:STATION_MAX_POLICE];
696 : equipmentPriceFactor = [dict oo_nonNegativeFloatForKey:@"equipment_price_factor" defaultValue:1.0];
697 : equipmentPriceFactor = fmax(equipmentPriceFactor, 0.5f);
698 : hasNPCTraffic = [dict oo_fuzzyBooleanForKey:@"has_npc_traffic" defaultValue:(maxFlightSpeed == 0)]; // carriers default to NO
699 : hasPatrolShips = [dict oo_fuzzyBooleanForKey:@"has_patrol_ships" defaultValue:NO];
700 : suppress_arrival_reports = [dict oo_boolForKey:@"suppress_arrival_reports" defaultValue:NO];
701 : [self setAllegiance:[dict oo_stringForKey:@"allegiance"]];
702 :
703 : marketCapacity = [dict oo_unsignedIntForKey:@"market_capacity" defaultValue:MAIN_SYSTEM_MARKET_LIMIT];
704 : marketDefinition = [[dict oo_arrayForKey:@"market_definition" defaultValue:nil] retain];
705 : marketScriptName = [[dict oo_stringForKey:@"market_script" defaultValue:nil] retain];
706 : marketMonitored = [dict oo_boolForKey:@"market_monitored" defaultValue:NO];
707 : marketBroadcast = [dict oo_boolForKey:@"market_broadcast" defaultValue:YES];
708 :
709 : // Non main stations may have requiresDockingClearance set to yes as a result of the code below,
710 : // but this variable should be irrelevant for them, as they do not make use of it anyway.
711 : requiresDockingClearance = [dict oo_boolForKey:@"requires_docking_clearance" defaultValue:[UNIVERSE dockingClearanceProtocolActive]];
712 :
713 : allowsFastDocking = [dict oo_boolForKey:@"allows_fast_docking" defaultValue:NO];
714 :
715 : allowsAutoDocking = [dict oo_boolForKey:@"allows_auto_docking" defaultValue:YES];
716 :
717 : allowsSaving = [UNIVERSE deterministicPopulation];
718 :
719 : interstellarUndockingAllowed = [dict oo_boolForKey:@"interstellar_undocking" defaultValue:NO];
720 :
721 : double unitime = [UNIVERSE getTime];
722 :
723 : if ([self hasNPCTraffic]) // removed the 'isRotatingStation' restriction.
724 : {
725 : docked_shuttles = ranrot_rand() & 3; // 0..3;
726 : shuttle_launch_interval = 15.0 * 60.0; // every 15 minutes
727 : last_shuttle_launch_time = unitime - (ranrot_rand() & 63) * shuttle_launch_interval / 60.0;
728 :
729 : docked_traders = 3 + (ranrot_rand() & 7); // 1..3;
730 : trader_launch_interval = 3600.0 / docked_traders; // every few minutes
731 : last_trader_launch_time = unitime + 60.0 - trader_launch_interval; // in one minute's time
732 : }
733 : else
734 : {
735 : docked_shuttles = 0;
736 : docked_traders = 0; // 1..3;
737 : }
738 :
739 : patrol_launch_interval = 300.0; // 5 minutes
740 : last_patrol_report_time = unitime - patrol_launch_interval;
741 :
742 : if ([self crew] == nil)
743 : {
744 : [self setSingleCrewWithRole:@"police"];
745 : }
746 :
747 : if ([self group] == nil)
748 : {
749 : [self setGroup:[self stationGroup]];
750 : }
751 : return YES;
752 :
753 : OOJS_PROFILE_EXIT
754 : }
755 :
756 :
757 : // used to set up a virtual dock if necessary
758 0 : - (BOOL) setUpSubEntities
759 : {
760 : if (![super setUpSubEntities])
761 : {
762 : return NO;
763 : }
764 :
765 : NSEnumerator *subEnum = nil;
766 :
767 : #ifndef NDEBUG
768 : ShipEntity *subEntity = nil;
769 : for (subEnum = [self shipSubEntityEnumerator]; (subEntity = [subEnum nextObject]); )
770 : {
771 : if ([subEntity isStation])
772 : {
773 : OOLog(@"setup.ship.badType.subentities",@"Subentity %@ (%@) of station %@ is itself a StationEntity. This is an internal error - please report it. ",subEntity,[subEntity shipDataKey],[self displayName]);
774 : }
775 : }
776 : #endif
777 :
778 : // and now check for docks
779 : DockEntity *sub = nil;
780 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
781 : {
782 : return YES;
783 : }
784 :
785 : OOStandardsDeprecated([NSString stringWithFormat:@"No docks set up for %@",self]);
786 : OOLog(@"ship.setup.docks",@"No docks set up for %@, making virtual dock",self);
787 :
788 : // no real docks, make a virtual one
789 : NSMutableDictionary *virtualDockDict = [NSMutableDictionary dictionaryWithCapacity:10];
790 : [virtualDockDict setObject:@"standard" forKey:@"type"];
791 : [virtualDockDict setObject:@"oolite-dock-virtual" forKey:@"subentity_key"];
792 : [virtualDockDict oo_setVector:make_vector(0,0,port_radius) forKey:@"position"];
793 : [virtualDockDict oo_setQuaternion:kIdentityQuaternion forKey:@"orientation"];
794 : [virtualDockDict oo_setBool:YES forKey:@"is_dock"];
795 : [virtualDockDict setObject:@"the docking bay" forKey:@"dock_label"];
796 : [virtualDockDict oo_setBool:YES forKey:@"allow_docking"];
797 : [virtualDockDict oo_setBool:NO forKey:@"disallowed_docking_collides"];
798 : [virtualDockDict oo_setBool:YES forKey:@"allow_launching"];
799 : [virtualDockDict oo_setBool:YES forKey:@"_is_virtual_dock"];
800 :
801 : if (![self setUpOneStandardSubentity:virtualDockDict asTurret:NO])
802 : {
803 : return NO;
804 : }
805 : return YES;
806 : }
807 :
808 :
809 :
810 :
811 : - (BOOL) shipIsInDockingCorridor:(ShipEntity *)ship
812 : {
813 : if (![ship isShip]) return NO;
814 : if ([ship isPlayer] && [ship status] == STATUS_DEAD) return NO;
815 :
816 : NSEnumerator *subEnum = nil;
817 : DockEntity* sub = nil;
818 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
819 : {
820 : if ([sub shipIsInDockingCorridor:ship])
821 : {
822 : return YES;
823 : }
824 : }
825 : return NO;
826 : }
827 :
828 :
829 :
830 :
831 0 : - (void) pullInShipIfPermitted:(ShipEntity *)ship
832 : {
833 : [ship enterDock:self]; // dock performs permitted checks
834 : }
835 :
836 :
837 : - (BOOL) dockingCorridorIsEmpty
838 : {
839 : if (!UNIVERSE)
840 : return NO;
841 :
842 : NSEnumerator *subEnum = nil;
843 : DockEntity* sub = nil;
844 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
845 : {
846 : if ([sub dockingCorridorIsEmpty])
847 : {
848 : return YES; // if any are
849 : }
850 : }
851 : return NO;
852 : }
853 :
854 :
855 : - (void) clearDockingCorridor
856 : {
857 : if (!UNIVERSE)
858 : return;
859 :
860 : NSEnumerator *subEnum = nil;
861 : DockEntity* sub = nil;
862 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
863 : {
864 : [sub clearDockingCorridor];
865 : }
866 :
867 : return;
868 : }
869 :
870 :
871 0 : - (void) update:(OOTimeDelta) delta_t
872 : {
873 : BOOL isRockHermit = (scanClass == CLASS_ROCK);
874 : BOOL isMainStation = (self == [UNIVERSE station]);
875 :
876 : double unitime = [UNIVERSE getTime];
877 :
878 : if (!isMainStation && localMarket == nil)
879 : {
880 : [self initialiseLocalMarket];
881 : }
882 :
883 : [super update:delta_t];
884 :
885 : PlayerEntity *player = PLAYER;
886 :
887 : BOOL isDockingStation = (self == [player getTargetDockStation]);
888 : if (isDockingStation && [player status] == STATUS_IN_FLIGHT)
889 : {
890 : if ([player getDockingClearanceStatus] >= DOCKING_CLEARANCE_STATUS_GRANTED)
891 : {
892 : if (last_launch_time-30 < unitime && [player getDockingClearanceStatus] != DOCKING_CLEARANCE_STATUS_TIMING_OUT)
893 : {
894 : [self sendExpandedMessage:@"[station-docking-clearance-about-to-expire]" toShip:player];
895 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_TIMING_OUT];
896 : }
897 : else if (last_launch_time < unitime)
898 : {
899 : [self sendExpandedMessage:@"[station-docking-clearance-expired]" toShip:player];
900 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE]; // Docking clearance for player has expired.
901 : [player doScriptEvent:OOJSID("playerDockingClearanceExpired")];
902 : if ([self currentlyInDockingQueues] == 0)
903 : {
904 : [[self getAI] message:@"DOCKING_COMPLETE"];
905 : [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")];
906 : }
907 : player_reserved_dock = nil;
908 : }
909 : }
910 :
911 : else if ([player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_NOT_REQUIRED)
912 : {
913 : if (last_launch_time < unitime)
914 : {
915 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
916 : if ([self currentlyInDockingQueues] == 0)
917 : {
918 : [[self getAI] message:@"DOCKING_COMPLETE"];
919 : [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")];
920 : }
921 : }
922 : }
923 :
924 : else if ([player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_REQUESTED &&
925 : [self hasClearDock])
926 : {
927 : DockEntity *dock = [self selectDockForDocking];
928 : last_launch_time = unitime + DOCKING_CLEARANCE_WINDOW;
929 : if ([self hasMultipleDocks])
930 : {
931 : [self sendExpandedMessage:[NSString stringWithFormat:
932 : DESC(@"station-docking-clearance-granted-in-@-until-@"),
933 : [dock displayName],
934 : ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)]
935 : toShip:player];
936 : }
937 : else
938 : {
939 : [self sendExpandedMessage:[NSString stringWithFormat:
940 : DESC(@"station-docking-clearance-granted-until-@"),
941 : ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)]
942 : toShip:player];
943 : }
944 : player_reserved_dock = dock;
945 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED];
946 : [player doScriptEvent:OOJSID("playerDockingClearanceGranted")];
947 :
948 : }
949 : }
950 :
951 :
952 : if (approach_spacing > 0.0)
953 : {
954 : approach_spacing -= delta_t * 10.0; // reduce by 10 m/s
955 : if (approach_spacing < 0.0) approach_spacing = 0.0;
956 : }
957 :
958 : /* JSAI: JS-based AIs handle their own traffic either alone or
959 : * in conjunction with the system repopulator */
960 : if (![self hasNewAI])
961 : {
962 : // begin launch of shuttles, traders, patrols
963 : if ((docked_shuttles > 0)&&(!isRockHermit))
964 : {
965 : if (unitime > last_shuttle_launch_time + shuttle_launch_interval)
966 : {
967 : if (([self hasNPCTraffic])&&(aegis_status != AEGIS_NONE))
968 : {
969 : [self launchShuttle];
970 : }
971 : last_shuttle_launch_time = unitime;
972 : }
973 : }
974 :
975 : if ((docked_traders > 0)&&(!isRockHermit))
976 : {
977 : if (unitime > last_trader_launch_time + trader_launch_interval)
978 : {
979 : if ([self hasNPCTraffic])
980 : {
981 : [self launchIndependentShip:@"trader"];
982 : docked_traders--;
983 : }
984 : last_trader_launch_time = unitime;
985 : }
986 : }
987 :
988 : // testing patrols
989 : if (unitime > (last_patrol_report_time + patrol_launch_interval))
990 : {
991 : if (!((isMainStation && [self hasNPCTraffic]) || hasPatrolShips) || [self launchPatrol] != nil)
992 : last_patrol_report_time = unitime;
993 : }
994 :
995 : }
996 : }
997 :
998 :
999 : - (void) clear
1000 : {
1001 : NSEnumerator *subEnum = nil;
1002 : DockEntity* sub = nil;
1003 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1004 : {
1005 : [sub clear];
1006 : }
1007 :
1008 : [_shipsOnHold removeAllObjects];
1009 : }
1010 :
1011 :
1012 : - (BOOL) hasMultipleDocks
1013 : {
1014 : NSEnumerator *subEnum = nil;
1015 : DockEntity* sub = nil;
1016 : unsigned docks = 0;
1017 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1018 : {
1019 : docks++;
1020 : if (docks > 1) {
1021 : return YES;
1022 : }
1023 : }
1024 : return NO;
1025 : }
1026 :
1027 :
1028 : // is there a dock free for the player to dock manually?
1029 : // not used for NPCs
1030 : - (BOOL) hasClearDock
1031 : {
1032 : NSEnumerator *subEnum = nil;
1033 : DockEntity* sub = nil;
1034 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1035 : {
1036 : if ([sub allowsDocking] && [sub countOfShipsInLaunchQueue] == 0 && [sub countOfShipsInDockingQueue] == 0)
1037 : {
1038 : if ([[sub canAcceptShipForDocking:PLAYER] isEqualToString:@"DOCKING_POSSIBLE"])
1039 : {
1040 : return YES;
1041 : }
1042 : }
1043 : }
1044 : return NO;
1045 : }
1046 :
1047 :
1048 0 : - (BOOL) hasEligibleDock
1049 : {
1050 : NSEnumerator *subEnum = nil;
1051 : DockEntity* sub = nil;
1052 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1053 : {
1054 : // TRY_AGAIN_LATER in this context means "ships launching now"
1055 : if ([sub allowsDocking] && ([[sub canAcceptShipForDocking:PLAYER] isEqualToString:@"DOCKING_POSSIBLE"] || [[sub canAcceptShipForDocking:PLAYER] isEqualToString:@"TRY_AGAIN_LATER"]))
1056 : {
1057 : return YES;
1058 : }
1059 : }
1060 : return NO;
1061 : }
1062 :
1063 :
1064 : // is there any dock which may launch ships?
1065 : - (BOOL) hasLaunchDock
1066 : {
1067 : NSEnumerator *subEnum = nil;
1068 : DockEntity* sub = nil;
1069 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1070 : {
1071 : if ([sub allowsLaunching])
1072 : {
1073 : return YES;
1074 : }
1075 : }
1076 : return NO;
1077 : }
1078 :
1079 : // only used to pick a dock for the player
1080 : - (DockEntity *) selectDockForDocking
1081 : {
1082 : NSEnumerator *subEnum = nil;
1083 : DockEntity* sub = nil;
1084 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1085 : {
1086 : if ([sub allowsDocking] && [sub countOfShipsInLaunchQueue] == 0 && [sub countOfShipsInDockingQueue] == 0)
1087 : {
1088 : return sub;
1089 : }
1090 : }
1091 : return nil;
1092 : }
1093 :
1094 :
1095 0 : - (void) addShipToLaunchQueue:(ShipEntity *)ship withPriority:(BOOL)priority
1096 : {
1097 : NSEnumerator *subEnum = nil;
1098 : DockEntity *sub = nil;
1099 : unsigned threshold = 0;
1100 :
1101 : // quickest launch if we assign ships to those bays with no incoming ships
1102 : // and spread the ships evenly around those bays
1103 : // much easier if the station has at least one launch-only dock
1104 : while (threshold < 16)
1105 : {
1106 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1107 : {
1108 : if (sub != player_reserved_dock)
1109 : {
1110 : if ([sub countOfShipsInDockingQueue] == 0)
1111 : {
1112 : if ([sub allowsLaunching] && [sub countOfShipsInLaunchQueue] <= threshold)
1113 : {
1114 : if ([sub allowsLaunchingOf:ship])
1115 : {
1116 : [sub addShipToLaunchQueue:ship withPriority:priority];
1117 : return;
1118 : }
1119 : }
1120 : }
1121 : }
1122 : }
1123 : threshold++;
1124 : }
1125 : // if we get this far, all docks have at least some incoming traffic.
1126 : // usually most efficient (since launching is far faster than docking)
1127 : // to assign all ships to the *same* dock with the smallest incoming queue
1128 : // rather than to try spreading them out across several queues
1129 : // also stops escorts being launched before their mothership
1130 : threshold = 0;
1131 : while (threshold < 16)
1132 : {
1133 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1134 : {
1135 : /* so this time as long as it allows launching only check
1136 : * the docking queue size so long as enumerator order is
1137 : * deterministic, this will assign every launch this
1138 : * update to the same dock (edge case where new docking
1139 : * ship appears in the middle, probably not a problem) */
1140 : if ([sub allowsLaunching] && [sub countOfShipsInDockingQueue] <= threshold)
1141 : {
1142 : if ([sub allowsLaunchingOf:ship])
1143 : {
1144 : [sub addShipToLaunchQueue:ship withPriority:priority];
1145 : return;
1146 : }
1147 : }
1148 :
1149 : }
1150 : threshold++;
1151 : }
1152 :
1153 : OOLog(@"station.launchShip.failed", @"Cancelled launch for a %@ with role %@, as the %@ has too many ships in its launch queue(s) or no suitable launch docks.",
1154 : [ship displayName], [ship primaryRole], [self displayName]);
1155 : }
1156 :
1157 :
1158 0 : - (unsigned) countOfShipsInLaunchQueueWithPrimaryRole:(NSString *)role
1159 : {
1160 : unsigned result = 0;
1161 : NSEnumerator *subEnum = nil;
1162 : DockEntity* sub = nil;
1163 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1164 : {
1165 : result += [sub countOfShipsInLaunchQueueWithPrimaryRole:role];
1166 : }
1167 : return result;
1168 : }
1169 :
1170 : - (BOOL) fitsInDock:(ShipEntity *)ship
1171 : {
1172 : return [self fitsInDock:ship andLogNoFit:YES];
1173 : }
1174 :
1175 :
1176 : - (BOOL) fitsInDock:(ShipEntity *)ship andLogNoFit:(BOOL)logNoFit
1177 : {
1178 : if (![ship isShip]) return NO;
1179 :
1180 : NSEnumerator *subEnum = nil;
1181 : DockEntity* sub = nil;
1182 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1183 : {
1184 : if ([sub allowsLaunchingOf:ship])
1185 : {
1186 : return YES;
1187 : }
1188 : }
1189 :
1190 : if (logNoFit) OOLog(@"station.launchShip.failed", @"Cancelled launch for a %@ with role %@, as it is too large for the docking port of the %@.",
1191 : [ship displayName], [ship primaryRole], self);
1192 : return NO;
1193 : }
1194 :
1195 :
1196 : - (void) noteDockedShip:(ShipEntity *) ship
1197 : {
1198 : if (ship == nil) return;
1199 :
1200 : PlayerEntity *player = PLAYER;
1201 : // set last launch time to avoid clashes with outgoing ships
1202 : if ([player getDockingClearanceStatus] != DOCKING_CLEARANCE_STATUS_GRANTED)
1203 : {
1204 : // avoid interfering with docking clearance on another bay
1205 : last_launch_time = [UNIVERSE getTime];
1206 : }
1207 : [self addShipToStationCount: ship];
1208 :
1209 : NSEnumerator *subEnum = nil;
1210 : DockEntity* sub = nil;
1211 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
1212 : {
1213 : [sub noteDockingForShip:ship];
1214 : }
1215 : [self sanityCheckShipsOnApproach];
1216 :
1217 : [self doScriptEvent:OOJSID("otherShipDocked") withArgument:ship];
1218 :
1219 : BOOL isDockingStation = (self == [player getTargetDockStation]);
1220 : if (isDockingStation && [player status] == STATUS_IN_FLIGHT &&
1221 : [player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_REQUESTED)
1222 : {
1223 : if (![self hasClearDock])
1224 : {
1225 : // then say why
1226 : if ([self currentlyInDockingQueues])
1227 : {
1228 : [self sendExpandedMessage:[NSString stringWithFormat:
1229 : DESC(@"station-docking-clearance-holding-d-ships-approaching"),
1230 : [self currentlyInDockingQueues]+1] toShip:player];
1231 : }
1232 : else if([self currentlyInLaunchingQueues])
1233 : {
1234 : [self sendExpandedMessage:[NSString stringWithFormat:
1235 : DESC(@"station-docking-clearance-holding-d-ships-departing"),
1236 : [self currentlyInLaunchingQueues]+1] toShip:player];
1237 : }
1238 : }
1239 : }
1240 :
1241 :
1242 : if ([ship isPlayer])
1243 : {
1244 : player_reserved_dock = nil;
1245 : }
1246 : }
1247 :
1248 0 : - (void) addShipToStationCount:(ShipEntity *) ship
1249 : {
1250 : if ([ship isShuttle]) docked_shuttles++;
1251 : else if ([ship isTrader] && ![ship isPlayer]) docked_traders++;
1252 : else if (([ship isPolice] && ![ship isEscort]) || [ship hasPrimaryRole:@"defense_ship"])
1253 : {
1254 : if (0 < defenders_launched) defenders_launched--;
1255 : }
1256 : else if ([ship hasPrimaryRole:@"scavenger"] || [ship hasPrimaryRole:@"miner"]) // treat miners and scavengers alike!
1257 : {
1258 : if (0 < scavengers_launched) scavengers_launched--;
1259 : }
1260 : }
1261 :
1262 :
1263 : - (BOOL) interstellarUndockingAllowed
1264 : {
1265 : return interstellarUndockingAllowed;
1266 : }
1267 :
1268 :
1269 : - (BOOL)hasNPCTraffic
1270 : {
1271 : return hasNPCTraffic;
1272 : }
1273 :
1274 :
1275 : - (void)setHasNPCTraffic:(BOOL)flag
1276 : {
1277 : hasNPCTraffic = flag != NO;
1278 : }
1279 :
1280 :
1281 0 : - (BOOL) collideWithShip:(ShipEntity *)other
1282 : {
1283 : /*
1284 : There used to be a [self abortAllDockings] here. Removed as there
1285 : doesn't appear to be a good reason for it and it interferes with
1286 : docking clearance.
1287 : -- Micha 2010-06-10
1288 : Reformatted, Ahruman 2012-08-26
1289 : */
1290 : return [super collideWithShip:other];
1291 : }
1292 :
1293 :
1294 0 : - (BOOL) hasHostileTarget
1295 : {
1296 : return [super hasHostileTarget] || ([self primaryTarget] != nil && ((alertLevel == STATION_ALERT_LEVEL_YELLOW) || (alertLevel == STATION_ALERT_LEVEL_RED)));
1297 : }
1298 :
1299 0 : - (void) takeEnergyDamage:(double)amount from:(Entity *)ent becauseOf:(Entity *)other weaponIdentifier:(NSString *)weaponIdentifier
1300 : {
1301 : // stations must ignore friendly fire, otherwise the defenders' AI gets stuck.
1302 : BOOL isFriend = NO;
1303 : OOShipGroup *group = [self group];
1304 :
1305 : if ([other isShip] && group != nil)
1306 : {
1307 : OOShipGroup *otherGroup = [(ShipEntity *)other group];
1308 : isFriend = otherGroup == group || [otherGroup leader] == self;
1309 : }
1310 :
1311 : // If this is the system's main station...
1312 : if (self == [UNIVERSE station] && !isFriend)
1313 : {
1314 : //...get angry
1315 : BOOL isEnergyMine = [ent isCascadeWeapon];
1316 :
1317 : // JSAIs might ignore friendly fire from conventional weapons
1318 : if ([self hasNewAI] || isEnergyMine)
1319 : {
1320 : unsigned b=isEnergyMine ? 96 : 64;
1321 : if ([(ShipEntity*)other bounty] >= b) //already a hardened criminal?
1322 : {
1323 : b *= 1.5; //bigger bounty!
1324 : }
1325 : [(ShipEntity*)other markAsOffender:b withReason:kOOLegalStatusReasonAttackedMainStation];
1326 : [self setPrimaryAggressor:other];
1327 : [self setFoundTarget:other];
1328 : [self launchPolice];
1329 : }
1330 :
1331 : if (isEnergyMine) //don't blow up!
1332 : {
1333 : [self increaseAlertLevel];
1334 : [self respondToAttackFrom:ent becauseOf:other];
1335 : return;
1336 : }
1337 : }
1338 : // Stop damage if main station & close to death!
1339 : if (!isFriend && (self != [UNIVERSE station] || amount < energy) )
1340 : {
1341 : // Handle damage like a ship.
1342 : [super takeEnergyDamage:amount from:ent becauseOf:other weaponIdentifier:weaponIdentifier];
1343 : }
1344 : }
1345 :
1346 0 : - (void) adjustVelocity:(Vector) xVel
1347 : {
1348 : if (self != [UNIVERSE station]) [super adjustVelocity:xVel]; //dont get moved
1349 : }
1350 :
1351 0 : - (void)takeScrapeDamage:(double)amount from:(Entity *)ent
1352 : {
1353 : // Stop damage if main station
1354 : if (self != [UNIVERSE station]) [super takeScrapeDamage:amount from:ent];
1355 : }
1356 :
1357 :
1358 0 : - (void) takeHeatDamage:(double)amount
1359 : {
1360 : // Stop damage if main station
1361 : if (self != [UNIVERSE station]) [super takeHeatDamage:amount];
1362 : }
1363 :
1364 :
1365 : - (NSString *) allegiance
1366 : {
1367 : return allegiance;
1368 : }
1369 :
1370 :
1371 : - (void) setAllegiance:(NSString *)newAllegiance
1372 : {
1373 : [allegiance release];
1374 : allegiance = [newAllegiance copy];
1375 : }
1376 :
1377 :
1378 : - (OOStationAlertLevel) alertLevel
1379 : {
1380 : return alertLevel;
1381 : }
1382 :
1383 :
1384 : - (void) setAlertLevel:(OOStationAlertLevel)level signallingScript:(BOOL)signallingScript
1385 : {
1386 : if (level < STATION_ALERT_LEVEL_GREEN) level = STATION_ALERT_LEVEL_GREEN;
1387 : if (level > STATION_ALERT_LEVEL_RED) level = STATION_ALERT_LEVEL_RED;
1388 :
1389 : if (alertLevel != level)
1390 : {
1391 : OOStationAlertLevel oldLevel = alertLevel;
1392 : alertLevel = level;
1393 : if (signallingScript)
1394 : {
1395 : ShipScriptEventNoCx(self, "alertConditionChanged", INT_TO_JSVAL(level), INT_TO_JSVAL(oldLevel));
1396 : }
1397 : switch (level)
1398 : {
1399 : case STATION_ALERT_LEVEL_GREEN:
1400 : [shipAI reactToMessage:@"GREEN_ALERT" context:nil];
1401 : break;
1402 :
1403 : case STATION_ALERT_LEVEL_YELLOW:
1404 : [shipAI reactToMessage:@"YELLOW_ALERT" context:nil];
1405 : break;
1406 :
1407 : case STATION_ALERT_LEVEL_RED:
1408 : [shipAI reactToMessage:@"RED_ALERT" context:nil];
1409 : break;
1410 : }
1411 : }
1412 : }
1413 :
1414 :
1415 : // Exposed to AI
1416 : - (ShipEntity *) launchIndependentShip:(NSString*) role
1417 : {
1418 : if (![self hasLaunchDock])
1419 : {
1420 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a ship with role %@, as the %@ has no launch docks.",
1421 : role, [self displayName]);
1422 : return nil;
1423 : }
1424 :
1425 : BOOL trader = [role isEqualToString:@"trader"];
1426 : BOOL sunskimmer = ([role isEqualToString:@"sunskim-trader"]);
1427 : ShipEntity *ship = nil;
1428 :
1429 : if((trader && (randf() < 0.1)) || sunskimmer)
1430 : {
1431 : ship = [UNIVERSE newShipWithRole:@"sunskim-trader"];
1432 : sunskimmer = true;
1433 : trader = true;
1434 : role = @"trader"; // make sure also sunskimmers get trader role.
1435 : }
1436 : else
1437 : {
1438 : ship = [UNIVERSE newShipWithRole:role];
1439 : }
1440 :
1441 : if (![self fitsInDock:ship])
1442 : {
1443 : [ship release];
1444 : return nil;
1445 : }
1446 :
1447 : if (ship)
1448 : {
1449 : if (![ship crew])
1450 : {
1451 : [ship setSingleCrewWithRole:role];
1452 : }
1453 : [ship setPrimaryRole:role];
1454 :
1455 : if(trader || ship->scanClass == CLASS_NOT_SET) [ship setScanClass: CLASS_NEUTRAL]; // keep defined scanclasses for non-traders.
1456 :
1457 : if (trader)
1458 : {
1459 : [ship setBounty:0 withReason:kOOLegalStatusReasonSetup];
1460 : [ship setCargoFlag:CARGO_FLAG_FULL_PLENTIFUL];
1461 : if (sunskimmer)
1462 : {
1463 : [ship setFuel:(Ranrot()&31)];
1464 : [UNIVERSE makeSunSkimmer:ship andSetAI:YES];
1465 : }
1466 : else
1467 : {
1468 : // JSAI: not needed - oolite-traderAI.js handles exiting if full fuel and plentiful cargo
1469 : // [ship switchAITo:@"exitingTraderAI.plist"];
1470 : if([ship fuel] == 0) [ship setFuel:70];
1471 : // if ([ship hasRole:@"sunskim-trader"]) [UNIVERSE makeSunSkimmer:ship andSetAI:NO];
1472 : }
1473 : }
1474 :
1475 : [self addShipToLaunchQueue:ship withPriority:NO];
1476 :
1477 : OOShipGroup *escortGroup = [ship escortGroup];
1478 : if ([ship group] == nil) [ship setGroup:escortGroup];
1479 : // Eric: Escorts are defined both as _group and as _escortGroup because friendly attacks are only handled within _group.
1480 : [escortGroup setLeader:ship];
1481 :
1482 : // add escorts to the trader
1483 : unsigned escorts = [ship pendingEscortCount];
1484 : if(escorts > 0)
1485 : {
1486 : [ship setOwner:self]; // makes escorts get added to station launch queue
1487 : [ship setUpEscorts];
1488 : [ship setOwner:ship];
1489 : }
1490 :
1491 : [ship setPendingEscortCount:0];
1492 : [ship autorelease];
1493 : }
1494 : return ship;
1495 : }
1496 :
1497 :
1498 : //////////////////////////////////////////////// extra AI routines
1499 :
1500 :
1501 : // Exposed to AI
1502 : - (void) increaseAlertLevel
1503 : {
1504 : [self setAlertLevel:[self alertLevel] + 1 signallingScript:YES];
1505 : }
1506 :
1507 :
1508 : // Exposed to AI
1509 : - (void) decreaseAlertLevel
1510 : {
1511 : [self setAlertLevel:[self alertLevel] - 1 signallingScript:YES];
1512 : }
1513 :
1514 :
1515 : // Exposed to AI
1516 : - (NSArray *) launchPolice
1517 : {
1518 : if (![self hasLaunchDock])
1519 : {
1520 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a police ship, as the %@ has no launch docks.",
1521 : [self displayName]);
1522 : return [NSArray array];
1523 : }
1524 :
1525 : OOUniversalID police_target = [[self primaryTarget] universalID];
1526 : unsigned i;
1527 : NSMutableArray *result = nil;
1528 : OOTechLevelID techlevel = [self equivalentTechLevel];
1529 : if (techlevel == NSNotFound) techlevel = 6;
1530 :
1531 : result = [NSMutableArray arrayWithCapacity:4];
1532 :
1533 : for (i = 0; (i < 4)&&(defenders_launched < max_police) ; i++)
1534 : {
1535 : ShipEntity *police_ship = nil;
1536 : if (![UNIVERSE entityForUniversalID:police_target])
1537 : {
1538 : [self noteLostTarget];
1539 : return [NSArray array];
1540 : }
1541 : /* this is more likely to give interceptors than the
1542 : * equivalent populator function: save them for defense
1543 : * ships */
1544 : if ((Ranrot() & 3) + 9 < techlevel)
1545 : {
1546 : police_ship = [UNIVERSE newShipWithRole:@"interceptor"]; // retain count = 1
1547 : }
1548 : else
1549 : {
1550 : police_ship = [UNIVERSE newShipWithRole:@"police"]; // retain count = 1
1551 : }
1552 :
1553 : if (police_ship && [self fitsInDock:police_ship])
1554 : {
1555 : if (![police_ship crew])
1556 : {
1557 : [police_ship setSingleCrewWithRole:@"police"];
1558 : }
1559 :
1560 : [police_ship setGroup:[self stationGroup]]; // who's your Daddy
1561 : [police_ship setPrimaryRole:@"police"];
1562 : [police_ship addTarget:[UNIVERSE entityForUniversalID:police_target]];
1563 : if ([police_ship scanClass] == CLASS_NOT_SET)
1564 : [police_ship setScanClass: CLASS_POLICE];
1565 : [police_ship setBounty:0 withReason:kOOLegalStatusReasonSetup];
1566 : if ([police_ship heatInsulation] < [self heatInsulation])
1567 : [police_ship setHeatInsulation:[self heatInsulation]];
1568 : [police_ship switchAITo:@"oolite-defenseShipAI.js"];
1569 : [self addShipToLaunchQueue:police_ship withPriority:YES];
1570 : defenders_launched++;
1571 : [result addObject:police_ship];
1572 : }
1573 : [police_ship autorelease];
1574 : }
1575 : [self abortAllDockings];
1576 : return result;
1577 : }
1578 :
1579 :
1580 : // Exposed to AI
1581 : - (ShipEntity *) launchDefenseShip
1582 : {
1583 : if (![self hasLaunchDock])
1584 : {
1585 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a defense ship, as the %@ has no launch docks.",
1586 : [self displayName]);
1587 : return nil;
1588 : }
1589 :
1590 : OOUniversalID defense_target = [[self primaryTarget] universalID];
1591 : ShipEntity *defense_ship = nil;
1592 : NSString *defense_ship_key = nil,
1593 : *defense_ship_role = nil,
1594 : *default_defense_ship_role = nil;
1595 : NSString *defense_ship_ai = @"oolite-defenseShipAI.js";
1596 :
1597 : OOTechLevelID techlevel;
1598 :
1599 : techlevel = [self equivalentTechLevel];
1600 : if (techlevel == NSNotFound) techlevel = 6;
1601 : if ((Ranrot() & 7) + 6 <= techlevel)
1602 : default_defense_ship_role = @"interceptor";
1603 : else
1604 : default_defense_ship_role = @"police";
1605 :
1606 : if (scanClass == CLASS_ROCK)
1607 : default_defense_ship_role = @"hermit-ship";
1608 :
1609 : if (defenders_launched >= max_defense_ships) // shuttles are to rockhermits what police ships are to stations
1610 : return nil;
1611 :
1612 : if (![UNIVERSE entityForUniversalID:defense_target])
1613 : {
1614 : [self noteLostTarget];
1615 : return nil;
1616 : }
1617 :
1618 : defense_ship_key = [shipinfoDictionary oo_stringForKey:@"defense_ship"];
1619 : if (defense_ship_key != nil)
1620 : {
1621 : defense_ship = [UNIVERSE newShipWithName:defense_ship_key];
1622 : }
1623 : if (!defense_ship)
1624 : {
1625 : defense_ship_role = [shipinfoDictionary oo_stringForKey:@"defense_ship_role" defaultValue:default_defense_ship_role];
1626 : defense_ship = [UNIVERSE newShipWithRole:defense_ship_role];
1627 : }
1628 :
1629 : if (!defense_ship && default_defense_ship_role != defense_ship_role)
1630 : defense_ship = [UNIVERSE newShipWithRole:default_defense_ship_role];
1631 :
1632 : if (!defense_ship || ![self fitsInDock:defense_ship])
1633 : {
1634 : [defense_ship release];
1635 : return nil;
1636 : }
1637 :
1638 : if ([defense_ship isPolice] || [defense_ship hasPrimaryRole:@"hermit-ship"])
1639 : {
1640 : [defense_ship switchAITo:defense_ship_ai];
1641 : }
1642 :
1643 : [defense_ship setPrimaryRole:@"defense_ship"];
1644 :
1645 : defenders_launched++;
1646 :
1647 : if (![defense_ship crew])
1648 : {
1649 : if ([defense_ship isPolice])
1650 : {
1651 : [defense_ship setSingleCrewWithRole:@"police"];
1652 : }
1653 : else
1654 : {
1655 : [defense_ship setSingleCrewWithRole:@"hunter"];
1656 : }
1657 : }
1658 :
1659 : [defense_ship setOwner: self];
1660 : if ([self group] == nil)
1661 : {
1662 : [self setGroup:[self stationGroup]];
1663 : }
1664 : [defense_ship setGroup:[self stationGroup]]; // who's your Daddy
1665 :
1666 : [defense_ship addTarget:[UNIVERSE entityForUniversalID:defense_target]];
1667 :
1668 : if ((scanClass != CLASS_ROCK)&&(scanClass != CLASS_STATION))
1669 : {
1670 : [defense_ship setScanClass: scanClass]; // same as self
1671 : }
1672 : else if ([defense_ship scanClass] == CLASS_NOT_SET)
1673 : {
1674 : [defense_ship setScanClass: CLASS_NEUTRAL];
1675 : }
1676 :
1677 : if ([defense_ship heatInsulation] < [self heatInsulation])
1678 : {
1679 : [defense_ship setHeatInsulation:[self heatInsulation]];
1680 : }
1681 :
1682 : [self addShipToLaunchQueue:defense_ship withPriority:YES];
1683 : [defense_ship autorelease];
1684 : [self abortAllDockings];
1685 :
1686 : return defense_ship;
1687 : }
1688 :
1689 :
1690 : // Exposed to AI
1691 : - (ShipEntity *) launchScavenger
1692 : {
1693 : if (![self hasLaunchDock])
1694 : {
1695 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a scavenger ship, as the %@ has no launch docks.",
1696 : [self displayName]);
1697 : return nil;
1698 : }
1699 :
1700 : ShipEntity *scavenger_ship;
1701 :
1702 : unsigned scavs = [UNIVERSE countShipsWithPrimaryRole:@"scavenger" inRange:SCANNER_MAX_RANGE ofEntity:self] + [self countOfShipsInLaunchQueueWithPrimaryRole:@"scavenger"];
1703 :
1704 : if (scavs >= max_scavengers) return nil;
1705 : if (scavengers_launched >= max_scavengers) return nil;
1706 :
1707 : scavenger_ship = [UNIVERSE newShipWithRole:@"scavenger"]; // retain count = 1
1708 :
1709 : if (![self fitsInDock:scavenger_ship])
1710 : {
1711 : [scavenger_ship release];
1712 : return nil;
1713 : }
1714 :
1715 : if (scavenger_ship)
1716 : {
1717 : if (![scavenger_ship crew])
1718 : {
1719 : [scavenger_ship setSingleCrewWithRole:@"miner"];
1720 : }
1721 :
1722 : scavengers_launched++;
1723 : [scavenger_ship setScanClass: CLASS_NEUTRAL];
1724 : if ([scavenger_ship heatInsulation] < [self heatInsulation])
1725 : [scavenger_ship setHeatInsulation:[self heatInsulation]];
1726 : [scavenger_ship setGroup:[self stationGroup]]; // who's your Daddy -- FIXME: should we have a separate group for non-escort auxiliaires?
1727 : [scavenger_ship switchAITo:@"oolite-scavengerAI.js"];
1728 : [self addShipToLaunchQueue:scavenger_ship withPriority:NO];
1729 : [scavenger_ship autorelease];
1730 : }
1731 : return scavenger_ship;
1732 : }
1733 :
1734 :
1735 : // Exposed to AI
1736 : - (ShipEntity *) launchMiner
1737 : {
1738 : if (![self hasLaunchDock])
1739 : {
1740 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a miner ship, as the %@ has no launch docks.",
1741 : [self displayName]);
1742 : return nil;
1743 : }
1744 :
1745 : ShipEntity *miner_ship;
1746 :
1747 : int n_miners = [UNIVERSE countShipsWithPrimaryRole:@"miner" inRange:SCANNER_MAX_RANGE ofEntity:self] + [self countOfShipsInLaunchQueueWithPrimaryRole:@"miner"];
1748 :
1749 : if (n_miners >= 1) // just the one
1750 : return nil;
1751 :
1752 : // count miners as scavengers...
1753 : if (scavengers_launched >= max_scavengers) return nil;
1754 :
1755 : miner_ship = [UNIVERSE newShipWithRole:@"miner"]; // retain count = 1
1756 :
1757 : if (![self fitsInDock:miner_ship])
1758 : {
1759 : [miner_ship release];
1760 : return nil;
1761 : }
1762 :
1763 : if (miner_ship)
1764 : {
1765 : if (![miner_ship crew])
1766 : {
1767 : [miner_ship setSingleCrewWithRole:@"miner"];
1768 : }
1769 :
1770 : scavengers_launched++;
1771 : [miner_ship setScanClass:CLASS_NEUTRAL];
1772 : if ([miner_ship heatInsulation] < [self heatInsulation])
1773 : [miner_ship setHeatInsulation:[self heatInsulation]];
1774 : [miner_ship setGroup:[self stationGroup]]; // who's your Daddy -- FIXME: should we have a separate group for non-escort auxiliaires?
1775 : [miner_ship switchAITo:@"oolite-scavengerAI.js"];
1776 : [self addShipToLaunchQueue:miner_ship withPriority:NO];
1777 : [miner_ship autorelease];
1778 : }
1779 : return miner_ship;
1780 : }
1781 :
1782 : /**Lazygun** added the following method. A complete rip-off of launchDefenseShip.
1783 : */
1784 : // Exposed to AI
1785 : - (ShipEntity *) launchPirateShip
1786 : {
1787 : if ([self hasLaunchDock])
1788 : {
1789 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a pirate ship, as the %@ has no launch docks.",
1790 : [self displayName]);
1791 : return nil;
1792 : }
1793 : //Pirate ships are launched from the same pool as defence ships.
1794 : OOUniversalID defense_target = [[self primaryTarget] universalID];
1795 : ShipEntity *pirate_ship = nil;
1796 :
1797 : if (defenders_launched >= max_defense_ships) return nil; // shuttles are to rockhermits what police ships are to stations
1798 :
1799 : if (![UNIVERSE entityForUniversalID:defense_target])
1800 : {
1801 : [self noteLostTarget];
1802 : return nil;
1803 : }
1804 :
1805 : // Yep! The standard hermit defence ships, even if they're the aggressor.
1806 : pirate_ship = [UNIVERSE newShipWithRole:@"pirate"]; // retain count = 1
1807 : // Nope, use standard pirates in a generic method.
1808 :
1809 : if (![self fitsInDock:pirate_ship])
1810 : {
1811 : [pirate_ship release];
1812 : return nil;
1813 : }
1814 :
1815 : if (pirate_ship)
1816 : {
1817 : if (![pirate_ship crew])
1818 : {
1819 : [pirate_ship setSingleCrewWithRole:@"pirate"];
1820 : }
1821 :
1822 : defenders_launched++;
1823 :
1824 : // set the owner of the ship to the station so that it can check back for docking later
1825 : [pirate_ship setOwner:self];
1826 : [pirate_ship setGroup:[self stationGroup]]; // who's your Daddy
1827 : [pirate_ship setPrimaryRole:@"defense_ship"];
1828 : [pirate_ship addTarget:[UNIVERSE entityForUniversalID:defense_target]];
1829 : [pirate_ship setScanClass: CLASS_NEUTRAL];
1830 : if ([pirate_ship heatInsulation] < [self heatInsulation])
1831 : [pirate_ship setHeatInsulation:[self heatInsulation]];
1832 : //**Lazygun** added 30 Nov 04 to put a bounty on those pirates' heads.
1833 : [pirate_ship setBounty: 10 + floor(randf() * 20) withReason:kOOLegalStatusReasonSetup]; // modified for variety
1834 :
1835 : [self addShipToLaunchQueue:pirate_ship withPriority:NO];
1836 : [pirate_ship autorelease];
1837 : [self abortAllDockings];
1838 : }
1839 : return pirate_ship;
1840 : }
1841 :
1842 :
1843 : // Exposed to AI
1844 : - (ShipEntity *) launchShuttle
1845 : {
1846 : if (![self hasLaunchDock])
1847 : {
1848 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a shuttle ship, as the %@ has no launch docks.",
1849 : [self displayName]);
1850 : return nil;
1851 : }
1852 : ShipEntity *shuttle_ship;
1853 :
1854 : shuttle_ship = [UNIVERSE newShipWithRole:@"shuttle"]; // retain count = 1
1855 :
1856 : if (![self fitsInDock:shuttle_ship])
1857 : {
1858 : [shuttle_ship release];
1859 : return nil;
1860 : }
1861 :
1862 : if (shuttle_ship)
1863 : {
1864 : if (![shuttle_ship crew])
1865 : {
1866 : [shuttle_ship setSingleCrewWithRole:@"trader"];
1867 : }
1868 :
1869 : docked_shuttles--;
1870 : [shuttle_ship setScanClass: CLASS_NEUTRAL];
1871 : [shuttle_ship setCargoFlag:CARGO_FLAG_FULL_SCARCE];
1872 : [shuttle_ship switchAITo:@"oolite-shuttleAI.js"];
1873 : [self addShipToLaunchQueue:shuttle_ship withPriority:NO];
1874 :
1875 : [shuttle_ship autorelease];
1876 : }
1877 : return shuttle_ship;
1878 : }
1879 :
1880 :
1881 : // Exposed to AI
1882 : - (ShipEntity *) launchEscort
1883 : {
1884 : if (![self hasLaunchDock])
1885 : {
1886 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for an escort ship, as the %@ has no launch docks.",
1887 : [self displayName]);
1888 : return nil;
1889 : }
1890 : ShipEntity *escort_ship;
1891 :
1892 : escort_ship = [UNIVERSE newShipWithRole:@"escort"]; // retain count = 1
1893 :
1894 : if (escort_ship && [self fitsInDock:escort_ship])
1895 : {
1896 : if (![escort_ship crew])
1897 : {
1898 : [escort_ship setSingleCrewWithRole:@"hunter"];
1899 : }
1900 :
1901 : [escort_ship setScanClass: CLASS_NEUTRAL];
1902 : [escort_ship setCargoFlag: CARGO_FLAG_FULL_PLENTIFUL];
1903 : [escort_ship switchAITo:@"oolite-escortAI.js"];
1904 : [self addShipToLaunchQueue:escort_ship withPriority:NO];
1905 :
1906 : }
1907 : [escort_ship release];
1908 : return escort_ship;
1909 : }
1910 :
1911 :
1912 : // Exposed to AI
1913 : - (ShipEntity *) launchPatrol
1914 : {
1915 : if (![self hasLaunchDock])
1916 : {
1917 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a patrol ship, as the %@ has no launch docks.",
1918 : [self displayName]);
1919 : return nil;
1920 : }
1921 : if (defenders_launched < max_police)
1922 : {
1923 : ShipEntity *patrol_ship = nil;
1924 : OOTechLevelID techlevel;
1925 :
1926 : techlevel = [self equivalentTechLevel];
1927 : if (techlevel == NSNotFound)
1928 : techlevel = 6;
1929 :
1930 : if ((Ranrot() & 7) + 6 <= techlevel)
1931 : patrol_ship = [UNIVERSE newShipWithRole:@"interceptor"]; // retain count = 1
1932 : else
1933 : patrol_ship = [UNIVERSE newShipWithRole:@"police"]; // retain count = 1
1934 :
1935 : if (![self fitsInDock:patrol_ship])
1936 : {
1937 : [patrol_ship release];
1938 : return nil;
1939 : }
1940 :
1941 : if (patrol_ship)
1942 : {
1943 : if (![patrol_ship crew])
1944 : {
1945 : [patrol_ship setSingleCrewWithRole:@"police"];
1946 : }
1947 :
1948 : defenders_launched++;
1949 : [patrol_ship switchLightsOff];
1950 : if ([patrol_ship scanClass] == CLASS_NOT_SET)
1951 : [patrol_ship setScanClass: CLASS_POLICE];
1952 : if ([patrol_ship heatInsulation] < [self heatInsulation])
1953 : [patrol_ship setHeatInsulation:[self heatInsulation]];
1954 : [patrol_ship setPrimaryRole:@"police-station-patrol"];
1955 : [patrol_ship setBounty:0 withReason:kOOLegalStatusReasonSetup];
1956 : [patrol_ship setGroup:[self stationGroup]]; // who's your Daddy
1957 : [patrol_ship switchAITo:@"oolite-policeAI.js"];
1958 : [self addShipToLaunchQueue:patrol_ship withPriority:NO];
1959 : [self acceptPatrolReportFrom:patrol_ship];
1960 : [patrol_ship autorelease];
1961 : return patrol_ship;
1962 : }
1963 : }
1964 : return nil;
1965 : }
1966 :
1967 :
1968 : // Exposed to AI
1969 : - (void) launchShipWithRole:(NSString*) role
1970 : {
1971 : if (![self hasLaunchDock])
1972 : {
1973 : OOLog(@"station.launchShip.impossible", @"Cancelled launch for a ship with role %@, as the %@ has no launch docks.",
1974 : role, [self displayName]);
1975 : return;
1976 : }
1977 : ShipEntity *ship = [UNIVERSE newShipWithRole: role]; // retain count = 1
1978 : if (ship && [self fitsInDock:ship])
1979 : {
1980 : if (![ship crew])
1981 : {
1982 : [ship setSingleCrewWithRole:role];
1983 : }
1984 : if (ship->scanClass == CLASS_NOT_SET) [ship setScanClass: CLASS_NEUTRAL];
1985 : [ship setPrimaryRole:role];
1986 : [ship setGroup:[self stationGroup]]; // who's your Daddy
1987 : [self addShipToLaunchQueue:ship withPriority:NO];
1988 : }
1989 : [ship release];
1990 : }
1991 :
1992 :
1993 : // Exposed to AI
1994 0 : - (void) becomeExplosion
1995 : {
1996 : if (self == [UNIVERSE station]) return;
1997 :
1998 : // launch docked ships if possible
1999 : PlayerEntity* player = PLAYER;
2000 : if ((player)&&([player status] == STATUS_DOCKED || [player status] == STATUS_DOCKING)&&([player dockedStation] == self))
2001 : {
2002 : // undock the player!
2003 : [player leaveDock:self];
2004 : [UNIVERSE setViewDirection:VIEW_FORWARD];
2005 : [[UNIVERSE gameController] setMouseInteractionModeForFlight];
2006 : [player warnAboutHostiles]; // sound a klaxon
2007 : }
2008 :
2009 : if (scanClass == CLASS_ROCK) // ie we're a rock hermit or similar
2010 : {
2011 : // set the role so that we break up into rocks!
2012 : [self setPrimaryRole:@"asteroid"];
2013 : being_mined = YES;
2014 : }
2015 :
2016 : // finally bite the bullet
2017 : [super becomeExplosion];
2018 : }
2019 :
2020 :
2021 : // Exposed to AI
2022 0 : - (void) becomeEnergyBlast
2023 : {
2024 : if (self == [UNIVERSE station]) return;
2025 : [super becomeEnergyBlast];
2026 : }
2027 :
2028 :
2029 0 : - (void) becomeLargeExplosion:(double) factor
2030 : {
2031 : if (self == [UNIVERSE station]) return;
2032 : [super becomeLargeExplosion:factor];
2033 : }
2034 :
2035 :
2036 : - (void) acceptPatrolReportFrom:(ShipEntity*) patrol_ship
2037 : {
2038 : last_patrol_report_time = [UNIVERSE getTime];
2039 : }
2040 :
2041 :
2042 : // used by player - "other" should always be a reference to the player
2043 : // there are some checks in the function from possibly when this wasn't true?
2044 : - (NSString *) acceptDockingClearanceRequestFrom:(ShipEntity *)other
2045 : {
2046 : NSString *result = nil;
2047 : double timeNow = [UNIVERSE getTime];
2048 : PlayerEntity *player = PLAYER;
2049 :
2050 : [self doScriptEvent:OOJSID("stationReceivedDockingRequest") withArgument:other];
2051 :
2052 :
2053 : [UNIVERSE clearPreviousMessage];
2054 :
2055 : [self sanityCheckShipsOnApproach];
2056 :
2057 : // Docking clearance not required - clear it just in case it's been
2058 : // set for another nearby station.
2059 : if (![self requiresDockingClearance])
2060 : {
2061 : // TODO: We're potentially cancelling docking at another station, so
2062 : // ensure we clear the timer to allow NPC traffic. If we
2063 : // don't, normal traffic will resume once the timer runs out.
2064 : // No clearance is needed, but don't send friendly messages to hostile ships!
2065 : if (!(([other isPlayer] && [other hasHostileTarget]) || (self == [UNIVERSE station] && [other bounty] > 50)))
2066 : {
2067 : [self sendExpandedMessage:@"[station-docking-clearance-not-required]" toShip:other];
2068 : }
2069 : if ([other isPlayer])
2070 : {
2071 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NOT_REQUIRED];
2072 : }
2073 : [shipAI reactToMessage:@"DOCKING_REQUESTED" context:nil]; // react to the request
2074 : [self doScriptEvent:OOJSID("stationAcceptedDockingRequest") withArgument:other];
2075 :
2076 : last_launch_time = timeNow + DOCKING_CLEARANCE_WINDOW;
2077 : result = @"DOCKING_CLEARANCE_NOT_REQUIRED";
2078 : }
2079 :
2080 : // Docking clearance already granted for this station - check for
2081 : // time-out or cancellation (but only for the Player).
2082 : if( result == nil && [other isPlayer] && self == [player getTargetDockStation])
2083 : {
2084 : switch( [player getDockingClearanceStatus] )
2085 : {
2086 : case DOCKING_CLEARANCE_STATUS_TIMING_OUT:
2087 : if (!no_docking_while_launching)
2088 : {
2089 : last_launch_time = timeNow + DOCKING_CLEARANCE_WINDOW;
2090 : [self sendExpandedMessage:[NSString stringWithFormat:
2091 : DESC(@"station-docking-clearance-extended-until-@"),
2092 : ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)]
2093 : toShip:other];
2094 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED];
2095 : result = @"DOCKING_CLEARANCE_EXTENDED";
2096 : break;
2097 : }
2098 : // else, continue with canceling.
2099 : case DOCKING_CLEARANCE_STATUS_REQUESTED:
2100 : case DOCKING_CLEARANCE_STATUS_GRANTED:
2101 : last_launch_time = timeNow;
2102 : [self sendExpandedMessage:@"[station-docking-clearance-cancelled]" toShip:other];
2103 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
2104 : result = @"DOCKING_CLEARANCE_CANCELLED";
2105 : player_reserved_dock = nil;
2106 : if ([self currentlyInDockingQueues] == 0)
2107 : {
2108 : [shipAI message:@"DOCKING_COMPLETE"];
2109 : [self doScriptEvent:OOJSID("stationDockingQueuesAreEmpty")];
2110 : }
2111 : break;
2112 : case DOCKING_CLEARANCE_STATUS_NONE:
2113 : case DOCKING_CLEARANCE_STATUS_NOT_REQUIRED:
2114 : break;
2115 : }
2116 : }
2117 :
2118 : // First we must set the status to REQUESTED to avoid problems when
2119 : // switching docking targets - even if we later set it back to NONE.
2120 : if (result == nil && [other isPlayer] && self != [player getTargetDockStation])
2121 : {
2122 : player_reserved_dock = nil; // and clear any previously reserved dock
2123 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_REQUESTED];
2124 : }
2125 :
2126 : // Deny docking for fugitives at the main station
2127 : // TODO: Should this be another key in shipdata.plist and/or should this
2128 : // apply to all stations?
2129 : if (result == nil && self == [UNIVERSE station] && [other bounty] > 50) // do not grant docking clearance to fugitives
2130 : {
2131 : [self sendExpandedMessage:@"[station-docking-clearance-H-clearance-refused]" toShip:other];
2132 : if ([other isPlayer])
2133 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
2134 : result = @"DOCKING_CLEARANCE_DENIED_SHIP_FUGITIVE";
2135 : }
2136 :
2137 : if (result == nil && [other hasHostileTarget]) // do not grant docking clearance to hostile ships.
2138 : {
2139 : [self sendExpandedMessage:@"[station-docking-clearance-denied]" toShip:other];
2140 : if ([other isPlayer])
2141 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
2142 : result = @"DOCKING_CLEARANCE_DENIED_SHIP_HOSTILE";
2143 : }
2144 :
2145 : if (![self hasEligibleDock]) // make sure at least one dock could plausibly accept the player
2146 : {
2147 : if ([other isPlayer])
2148 : {
2149 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
2150 : }
2151 : [self sendExpandedMessage:@"[station-docking-clearance-denied-no-docks]" toShip:other];
2152 :
2153 : result = @"DOCKING_CLEARANCE_DENIED_NO_DOCKS";
2154 : }
2155 : else if (![self hasClearDock]) // skip check if at least one dock clear
2156 : {
2157 : // Put ship in queue if we've got incoming or outgoing traffic or
2158 : // if the player is waiting for manual clearance and we are not
2159 : // the player
2160 : if (result == nil && (([self currentlyInDockingQueues] && last_launch_time < timeNow) || (![other isPlayer] && [player getDockingClearanceStatus] == DOCKING_CLEARANCE_STATUS_REQUESTED)))
2161 : {
2162 : [self sendExpandedMessage:[NSString stringWithFormat:
2163 : DESC(@"station-docking-clearance-acknowledged-d-ships-approaching"),
2164 : [self currentlyInDockingQueues]+1] toShip:other];
2165 : // No need to set status to REQUESTED as we've already done that earlier.
2166 : result = @"DOCKING_CLEARANCE_DENIED_TRAFFIC_INBOUND";
2167 : }
2168 :
2169 : if (result == nil && [self currentlyInLaunchingQueues])
2170 : {
2171 : [self sendExpandedMessage:[NSString stringWithFormat:
2172 : DESC(@"station-docking-clearance-acknowledged-d-ships-departing"),
2173 : [self currentlyInLaunchingQueues]+1] toShip:other];
2174 : // No need to set status to REQUESTED as we've already done that earlier.
2175 : result = @"DOCKING_CLEARANCE_DENIED_TRAFFIC_OUTBOUND";
2176 : }
2177 : if (result == nil)
2178 : {
2179 : // if this happens, the station has no docks which allow
2180 : // docking, so deny clearance
2181 : if ([other isPlayer])
2182 : {
2183 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
2184 : }
2185 : result = @"DOCKING_CLEARANCE_DENIED_NO_DOCKS";
2186 : // but can check to see if we'll open some for later.
2187 : NSEnumerator *subEnum = nil;
2188 : DockEntity* sub = nil;
2189 : BOOL openLater = NO;
2190 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
2191 : {
2192 : NSString *docking = [sub canAcceptShipForDocking:other];
2193 : if ([docking isEqualToString:@"DOCK_CLOSED"])
2194 : {
2195 : JSContext *context = OOJSAcquireContext();
2196 : jsval rval = JSVAL_VOID;
2197 : jsval args[] = { OOJSValueFromNativeObject(context, sub),
2198 : OOJSValueFromNativeObject(context, other) };
2199 : JSBool tempreject = NO;
2200 :
2201 : BOOL OK = [[self script] callMethod:OOJSID("willOpenDockingPortFor") inContext:context withArguments:args count:2 result:&rval];
2202 : if (OK) OK = JS_ValueToBoolean(context, rval, &tempreject);
2203 : if (!OK) tempreject = NO; // default to permreject
2204 : if (tempreject)
2205 : {
2206 : openLater = YES;
2207 : }
2208 : OOJSRelinquishContext(context);
2209 : }
2210 : if (openLater) break;
2211 : }
2212 :
2213 : if (openLater)
2214 : {
2215 : [self sendExpandedMessage:@"[station-docking-clearance-denied-no-docks-yet]" toShip:other];
2216 : }
2217 : else
2218 : {
2219 : [self sendExpandedMessage:@"[station-docking-clearance-denied-no-docks]" toShip:other];
2220 : }
2221 :
2222 : }
2223 : }
2224 :
2225 : // Ship has passed all checks - grant docking!
2226 : if (result == nil)
2227 : {
2228 : last_launch_time = timeNow + DOCKING_CLEARANCE_WINDOW;
2229 : if ([other isPlayer])
2230 : {
2231 : [player setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED];
2232 : player_reserved_dock = [self selectDockForDocking];
2233 : }
2234 :
2235 : if ([self hasMultipleDocks] && [other isPlayer])
2236 : {
2237 : [self sendExpandedMessage:[NSString stringWithFormat:
2238 : DESC(@"station-docking-clearance-granted-in-@-until-@"),
2239 : [player_reserved_dock displayName],
2240 : ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)]
2241 : toShip:other];
2242 : }
2243 : else
2244 : {
2245 : [self sendExpandedMessage:[NSString stringWithFormat:
2246 : DESC(@"station-docking-clearance-granted-until-@"),
2247 : ClockToString([player clockTime] + DOCKING_CLEARANCE_WINDOW, NO)]
2248 : toShip:other];
2249 : }
2250 :
2251 : result = @"DOCKING_CLEARANCE_GRANTED";
2252 : [shipAI reactToMessage:@"DOCKING_REQUESTED" context:nil]; // react to the request
2253 : [self doScriptEvent:OOJSID("stationAcceptedDockingRequest") withArgument:other];
2254 : }
2255 : return result;
2256 : }
2257 :
2258 :
2259 : - (unsigned) currentlyInDockingQueues
2260 : {
2261 : NSEnumerator *subEnum = nil;
2262 : DockEntity* sub = nil;
2263 : unsigned soa = 0;
2264 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
2265 : {
2266 : soa += [sub countOfShipsInDockingQueue];
2267 : }
2268 : soa += [_shipsOnHold count];
2269 : return soa;
2270 : }
2271 :
2272 :
2273 : - (unsigned) currentlyInLaunchingQueues
2274 : {
2275 : NSEnumerator *subEnum = nil;
2276 : DockEntity* sub = nil;
2277 : unsigned soa = 0;
2278 : for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
2279 : {
2280 : soa += [sub countOfShipsInLaunchQueue];
2281 : }
2282 : return soa;
2283 : }
2284 :
2285 :
2286 : - (BOOL) requiresDockingClearance
2287 : {
2288 : return requiresDockingClearance;
2289 : }
2290 :
2291 :
2292 : - (void) setRequiresDockingClearance:(BOOL)newValue
2293 : {
2294 : requiresDockingClearance = !!newValue; // Ensure yes or no
2295 : }
2296 :
2297 :
2298 : - (BOOL) allowsFastDocking
2299 : {
2300 : return allowsFastDocking;
2301 : }
2302 :
2303 :
2304 : - (void) setAllowsFastDocking:(BOOL)newValue
2305 : {
2306 : allowsFastDocking = !!newValue; // Ensure yes or no
2307 : }
2308 :
2309 :
2310 : - (BOOL) allowsAutoDocking
2311 : {
2312 : return allowsAutoDocking;
2313 : }
2314 :
2315 :
2316 : - (void) setAllowsAutoDocking:(BOOL)newValue
2317 : {
2318 : allowsAutoDocking = !!newValue; // Ensure yes or no
2319 : }
2320 :
2321 :
2322 : - (BOOL) allowsSaving
2323 : {
2324 : // fixed stations only, not carriers!
2325 : return allowsSaving && ([self maxFlightSpeed] == 0);
2326 : }
2327 :
2328 :
2329 : - (BOOL) isRotatingStation
2330 : {
2331 : if ([shipinfoDictionary oo_boolForKey:@"rotating" defaultValue:NO]) return YES;
2332 : return [[shipinfoDictionary objectForKey:@"roles"] rangeOfString:@"rotating-station"].location != NSNotFound; // legacy
2333 : }
2334 :
2335 :
2336 : - (NSString *) marketOverrideName
2337 : {
2338 : // 2010.06.14 - Micha - we can't default to the primary role as otherwise the logic
2339 : // generating the market in [Universe commodityDataForEconomy:] doesn't
2340 : // work properly with the various overrides. The primary role will get
2341 : // used if either there is no market override, or the market wasn't
2342 : // defined.
2343 : return [shipinfoDictionary oo_stringForKey:@"market"];
2344 : }
2345 :
2346 :
2347 : - (BOOL) hasShipyard
2348 : {
2349 : if ([UNIVERSE station] == self)
2350 : return YES;
2351 : id determinant = [shipinfoDictionary objectForKey:@"has_shipyard"];
2352 :
2353 : if (!determinant)
2354 : determinant = [shipinfoDictionary objectForKey:@"hasShipyard"];
2355 :
2356 : // NOTE: non-standard capitalization is documented and entrenched.
2357 : if (determinant)
2358 : {
2359 : if ([determinant isKindOfClass:[NSArray class]])
2360 : {
2361 : return [PLAYER scriptTestConditions:OOSanitizeLegacyScriptConditions(determinant, nil)];
2362 : }
2363 : else
2364 : {
2365 : return OOFuzzyBooleanFromObject(determinant, 0.0f);
2366 : }
2367 : }
2368 : else
2369 : {
2370 : return NO;
2371 : }
2372 : }
2373 :
2374 :
2375 : - (void) generateShipyard
2376 : {
2377 : [self generateShipyard:[self equivalentTechLevel]];
2378 : }
2379 :
2380 :
2381 : - (void) generateShipyard:(OOTechLevelID)stationTechLevel
2382 : {
2383 : unsigned i;
2384 :
2385 : if ([self localShipyard] == nil)
2386 : {
2387 : [self setLocalShipyard:[UNIVERSE shipsForSaleForSystem:[UNIVERSE currentSystemID] withTL:stationTechLevel atTime:[PLAYER clockTime]]];
2388 : }
2389 :
2390 : NSMutableArray *shipyard = [self localShipyard];
2391 :
2392 : // remove ships that the player has already bought
2393 : for (i = 0; i < [shipyard count]; i++)
2394 : {
2395 : NSString *shipID = [[shipyard oo_dictionaryAtIndex:i] oo_stringForKey:SHIPYARD_KEY_ID];
2396 : if ([[PLAYER shipyardRecord] objectForKey:shipID])
2397 : {
2398 : [shipyard removeObjectAtIndex:i--];
2399 : }
2400 : }
2401 : }
2402 :
2403 :
2404 : - (BOOL) suppressArrivalReports
2405 : {
2406 : return suppress_arrival_reports;
2407 : }
2408 :
2409 :
2410 : - (void) setSuppressArrivalReports:(BOOL)newValue
2411 : {
2412 : suppress_arrival_reports = !!newValue; // ensure YES or NO
2413 : }
2414 :
2415 :
2416 : - (BOOL) hasBreakPattern
2417 : {
2418 : return hasBreakPattern;
2419 : }
2420 :
2421 :
2422 : - (void) setHasBreakPattern:(BOOL)newValue
2423 : {
2424 : hasBreakPattern = !!newValue;
2425 : }
2426 :
2427 :
2428 0 : - (NSString *) descriptionComponents
2429 : {
2430 : return [NSString stringWithFormat:@"\"%@\" %@", name, [super descriptionComponents]];
2431 : }
2432 :
2433 :
2434 0 : - (void)dumpSelfState
2435 : {
2436 : NSMutableArray *flags = nil;
2437 : NSString *flagsString = nil;
2438 : NSString *alertString = @"*** ERROR: UNKNOWN ALERT LEVEL ***";
2439 :
2440 : [super dumpSelfState];
2441 :
2442 : switch (alertLevel)
2443 : {
2444 : case STATION_ALERT_LEVEL_GREEN:
2445 : alertString = @"green";
2446 : break;
2447 :
2448 : case STATION_ALERT_LEVEL_YELLOW:
2449 : alertString = @"yellow";
2450 : break;
2451 :
2452 : case STATION_ALERT_LEVEL_RED:
2453 : alertString = @"red";
2454 : break;
2455 : }
2456 :
2457 : OOLog(@"dumpState.stationEntity", @"Alert level: %@", alertString);
2458 : OOLog(@"dumpState.stationEntity", @"Max police: %u", max_police);
2459 : OOLog(@"dumpState.stationEntity", @"Max defense ships: %u", max_defense_ships);
2460 : OOLog(@"dumpState.stationEntity", @"Defenders launched: %u", defenders_launched);
2461 : OOLog(@"dumpState.stationEntity", @"Max scavengers: %u", max_scavengers);
2462 : OOLog(@"dumpState.stationEntity", @"Scavengers launched: %u", scavengers_launched);
2463 : OOLog(@"dumpState.stationEntity", @"Docked shuttles: %u", docked_shuttles);
2464 : OOLog(@"dumpState.stationEntity", @"Docked traders: %u", docked_traders);
2465 : OOLog(@"dumpState.stationEntity", @"Equivalent tech level: %li", equivalentTechLevel);
2466 : OOLog(@"dumpState.stationEntity", @"Equipment price factor: %g", equipmentPriceFactor);
2467 :
2468 : flags = [NSMutableArray array];
2469 0 : #define ADD_FLAG_IF_SET(x) if (x) { [flags addObject:@#x]; }
2470 : ADD_FLAG_IF_SET(no_docking_while_launching);
2471 : if ([self isRotatingStation]) { [flags addObject:@"rotatingStation"]; }
2472 : if (![self dockingCorridorIsEmpty]) { [flags addObject:@"dockingCorridorIsBusy"]; }
2473 : flagsString = [flags count] ? [flags componentsJoinedByString:@", "] : (NSString *)@"none";
2474 : OOLog(@"dumpState.stationEntity", @"Flags: %@", flagsString);
2475 :
2476 : // approach and hold lists.
2477 :
2478 : // Ships on hold list, only used with moving stations (= carriers)
2479 : if([_shipsOnHold count] > 0)
2480 : {
2481 : OOLog(@"dumpState.stationEntity", @"%li Ships on hold (unsorted):", [_shipsOnHold count]);
2482 :
2483 : OOLogIndent();
2484 : NSEnumerator *onHoldEnum = [_shipsOnHold objectEnumerator];
2485 : ShipEntity *ship = nil;
2486 : unsigned i = 1;
2487 : while ((ship = [onHoldEnum nextObject]))
2488 : {
2489 : OOLog(@"dumpState.stationEntity", @"Nr %i: %@ at distance %g with role: %@", i++, [ship displayName], HPdistance([self position], [ship position]), [ship primaryRole]);
2490 : }
2491 : OOLogOutdent();
2492 : }
2493 : }
2494 :
2495 : @end
|