Oolite 1.91.0.7644-241112-7f5034b
Loading...
Searching...
No Matches
StationEntity.m
Go to the documentation of this file.
1/*
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"
29#import "OOStringParsing.h"
31
32#import "Universe.h"
33#import "GameController.h"
34#import "HeadUpDisplay.h"
35#import "OOConstToString.h"
36
39#import "OOPlanetEntity.h"
40#import "OOShipGroup.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- (void) pullInShipIfPermitted:(ShipEntity *)ship;
56- (void) addShipToStationCount:(ShipEntity *)ship;
57
58- (void) addShipToLaunchQueue:(ShipEntity *)ship withPriority:(BOOL)priority;
59- (unsigned) countOfShipsInLaunchQueueWithPrimaryRole:(NSString *)role;
60
61- (NSDictionary *) holdPositionInstructionForShip:(ShipEntity *)ship;
62
63@end
64
65
66#ifndef NDEBUG
67@interface StationEntity (mwDebug)
68
69- (NSArray *) dbgGetShipsOnApproach;
70- (NSArray *) dbgGetIdLocks;
71- (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- (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- (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 {
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
448NSDictionary *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- (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
627
628- (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict
629{
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
643 }
644
645
646- (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- (BOOL) setUpShipFromDictionary:(NSDictionary *) dict
663{
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
754}
755
756
757// used to set up a virtual dock if necessary
758- (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- (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 {
865 }
866
867 return;
868}
869
870
871- (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- (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- (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- (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- (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- (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- (BOOL) hasHostileTarget
1295{
1296 return [super hasHostileTarget] || ([self primaryTarget] != nil && ((alertLevel == STATION_ALERT_LEVEL_YELLOW) || (alertLevel == STATION_ALERT_LEVEL_RED)));
1297}
1298
1299- (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- (void) adjustVelocity:(Vector) xVel
1347{
1348 if (self != [UNIVERSE station]) [super adjustVelocity:xVel]; //dont get moved
1349}
1350
1351- (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- (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{
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 {
1400 [shipAI reactToMessage:@"GREEN_ALERT" context:nil];
1401 break;
1402
1404 [shipAI reactToMessage:@"YELLOW_ALERT" context:nil];
1405 break;
1406
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
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
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- (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- (void) becomeEnergyBlast
2023{
2024 if (self == [UNIVERSE station]) return;
2025 [super becomeEnergyBlast];
2026}
2027
2028
2029- (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 {
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.
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;
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- (NSString *) descriptionComponents
2429{
2430 return [NSString stringWithFormat:@"\"%@\" %@", name, [super descriptionComponents]];
2431}
2432
2433
2434- (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 {
2445 alertString = @"green";
2446 break;
2447
2449 alertString = @"yellow";
2450 break;
2451
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 #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
#define ADD_FLAG_IF_SET(x)
#define DESTROY(x)
Definition OOCocoa.h:77
BOOL OOFuzzyBooleanFromObject(id object, float defaultValue)
void OOStandardsDeprecated(NSString *message)
BOOL OOEnforceStandards(void)
#define OOJS_PROFILE_EXIT
#define OOJS_PROFILE_ENTER
OOINLINE jsval OOJSValueFromNativeObject(JSContext *context, id object)
OOINLINE JSContext * OOJSAcquireContext(void)
OOINLINE void OOJSRelinquishContext(JSContext *context)
void OOLogOutdent(void)
Definition OOLogging.m:376
#define OOLog(class, format,...)
Definition OOLogging.h:88
void OOLogIndent(void)
Definition OOLogging.m:366
return self
unsigned count
return nil
Vector vector_forward_from_quaternion(Quaternion quat)
NSString * OOCommodityType
Definition OOTypes.h:106
@ AEGIS_NONE
Definition OOTypes.h:61
uint64_t OOCreditsQuantity
Definition OOTypes.h:182
uint16_t OOUniversalID
Definition OOTypes.h:189
NSUInteger OOTechLevelID
Definition OOTypes.h:204
uint32_t OOCargoQuantity
Definition OOTypes.h:176
double OOTimeDelta
Definition OOTypes.h:224
@ DOCKING_CLEARANCE_STATUS_TIMING_OUT
Definition OOTypes.h:172
@ DOCKING_CLEARANCE_STATUS_NOT_REQUIRED
Definition OOTypes.h:169
@ DOCKING_CLEARANCE_STATUS_GRANTED
Definition OOTypes.h:171
@ DOCKING_CLEARANCE_STATUS_NONE
Definition OOTypes.h:168
@ DOCKING_CLEARANCE_STATUS_REQUESTED
Definition OOTypes.h:170
@ NO_TARGET
Definition OOTypes.h:194
const Vector kZeroVector
Definition OOVector.m:28
#define PLAYER
#define ShipScriptEventNoCx(ship, event,...)
NSDictionary * OOMakeDockingInstructions(StationEntity *station, HPVector coords, float speed, float range, NSString *ai_message, NSString *comms_message, BOOL match_rotation, int docking_stage)
OOStationAlertLevel
@ STATION_ALERT_LEVEL_RED
@ STATION_ALERT_LEVEL_YELLOW
@ STATION_ALERT_LEVEL_GREEN
#define DOCKING_CLEARANCE_WINDOW
#define UNIVERSE
Definition Universe.h:833
void abortAllDockings()
Definition DockEntity.m:96
void launchShip:(ShipEntity *ship)
Definition DockEntity.m:888
Vector portUpVectorForShipsBoundingBox:(BoundingBox bb)
Definition DockEntity.m:624
NSDictionary * dockingInstructionsForShip:(ShipEntity *ship)
Definition DockEntity.m:282
NSUInteger countOfShipsInDockingQueue()
Definition DockEntity.m:660
NSString * canAcceptShipForDocking:(ShipEntity *ship)
Definition DockEntity.m:222
void autoDockShipsOnApproach()
Definition DockEntity.m:166
void abortDockingForShip:(ShipEntity *ship)
Definition DockEntity.m:589
void noteDockingForShip:(ShipEntity *ship)
Definition DockEntity.m:579
void clearDockingCorridor()
void addShipToLaunchQueue:withPriority:(ShipEntity *ship,[withPriority] BOOL priority)
Definition DockEntity.m:865
unsigned isPlayer
Definition Entity.h:93
NSUInteger exportLegalityForGood:(OOCommodityType good)
NSUInteger importLegalityForGood:(OOCommodityType good)
void setLeader:(ShipEntity *leader)
ShipEntity * leader()
OODockingClearanceStatus getDockingClearanceStatus()
StationEntity * getTargetDockStation()
void setDockingClearanceStatus:(OODockingClearanceStatus newValue)
void warnAboutHostiles()
void leaveDock:(StationEntity *station)
BOOL isExplicitlyUnpiloted()
void doScriptEvent:(jsid message)
unsigned isHulk
Definition ShipEntity.h:261
void sendAIMessage:(NSString *message)
NSString * displayName
Definition ShipEntity.h:330
void enterDock:(StationEntity *station)
float randf(void)
unsigned Ranrot(void)
#define ranrot_rand()