LCOV - code coverage report
Current view: top level - Core/Entities - StationEntity.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 35 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          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

Generated by: LCOV version 1.14