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

          Line data    Source code
       1           0 : /*
       2             : 
       3             : OOShipRegistry.m
       4             : 
       5             : 
       6             : Copyright (C) 2008-2013 Jens Ayton and contributors
       7             : 
       8             : Permission is hereby granted, free of charge, to any person obtaining a copy
       9             : of this software and associated documentation files (the "Software"), to deal
      10             : in the Software without restriction, including without limitation the rights
      11             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      12             : copies of the Software, and to permit persons to whom the Software is
      13             : furnished to do so, subject to the following conditions:
      14             : 
      15             : The above copyright notice and this permission notice shall be included in all
      16             : copies or substantial portions of the Software.
      17             : 
      18             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      19             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      20             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      21             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      22             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      23             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      24             : SOFTWARE.
      25             : 
      26             : */
      27             : 
      28             : #import "OOShipRegistry.h"
      29             : #import "OOCacheManager.h"
      30             : #import "ResourceManager.h"
      31             : #import "OOCollectionExtractors.h"
      32             : #import "NSDictionaryOOExtensions.h"
      33             : #import "OOProbabilitySet.h"
      34             : #import "OORoleSet.h"
      35             : #import "OOStringParsing.h"
      36             : #import "OOMesh.h"
      37             : #import "GameController.h"
      38             : #import "OOLegacyScriptWhitelist.h"
      39             : #import "OODeepCopy.h"
      40             : #import "OOColor.h"
      41             : #import "OOStringExpander.h"
      42             : #import "OOShipLibraryDescriptions.h"
      43             : #import "Universe.h"
      44             : #import "OOJSScript.h"
      45             : 
      46             : #import "OODebugStandards.h"
      47             : 
      48           0 : #define PRELOAD 0
      49             : 
      50             : 
      51             : static void DumpStringAddrs(NSDictionary *dict, NSString *context);
      52             : static NSComparisonResult SortDemoShipsByName (id a, id b, void* context);
      53             : static NSComparisonResult SortDemoCategoriesByName (id a, id b, void* context);
      54             : 
      55             : 
      56           0 : static OOShipRegistry   *sSingleton = nil;
      57             : 
      58             : 
      59           0 : static NSString * const kShipRegistryCacheName = @"ship registry";
      60           0 : static NSString * const kShipDataCacheKey = @"ship data";
      61           0 : static NSString * const kPlayerShipsCacheKey = @"player ships";
      62           0 : static NSString * const kRoleWeightsCacheKey = @"role weights";
      63           0 : static NSString * const kDefaultDemoShip = @"coriolis-station";
      64           0 : static NSString * const kVisualEffectRegistryCacheName = @"visual effect registry";
      65           0 : static NSString * const kVisualEffectDataCacheKey = @"visual effect data";
      66             : 
      67             : 
      68             : @interface OOShipRegistry (OODataLoader)
      69             : 
      70           0 : - (void) loadShipData;
      71           0 : - (void) loadDemoShipConditions;
      72           0 : - (void) loadDemoShips;
      73           0 : - (void) loadCachedRoleProbabilitySets;
      74           0 : - (void) buildRoleProbabilitySets;
      75             : 
      76           0 : - (BOOL) applyLikeShips:(NSMutableDictionary *)ioData withKey:(NSString *)likeKey;
      77           0 : - (BOOL) loadAndMergeShipyard:(NSMutableDictionary *)ioData;
      78           0 : - (BOOL) stripPrivateKeys:(NSMutableDictionary *)ioData;
      79           0 : - (BOOL) makeShipEntriesMutable:(NSMutableDictionary *)ioData;
      80           0 : - (BOOL) loadAndApplyShipDataOverrides:(NSMutableDictionary *)ioData;
      81           0 : - (BOOL) canonicalizeAndTagSubentities:(NSMutableDictionary *)ioData;
      82           0 : - (BOOL) removeUnusableEntries:(NSMutableDictionary *)ioData shipMode:(BOOL)shipMode;
      83           0 : - (BOOL) sanitizeConditions:(NSMutableDictionary *)ioData;
      84             : 
      85             : #if PRELOAD
      86             : - (BOOL) preloadShipMeshes:(NSMutableDictionary *)ioData;
      87             : #endif
      88             : 
      89           0 : - (NSMutableDictionary *) mergeShip:(NSDictionary *)child withParent:(NSDictionary *)parent;
      90           0 : - (void) mergeShipRoles:(NSString *)roles forShipKey:(NSString *)shipKey intoProbabilityMap:(NSMutableDictionary *)probabilitySets;
      91             : 
      92           0 : - (NSDictionary *) canonicalizeSubentityDeclaration:(id)declaration
      93             :                                                                                         forShip:(NSString *)shipKey
      94             :                                                                                    shipData:(NSDictionary *)shipData
      95             :                                                                                  fatalError:(BOOL *)outFatalError;
      96           0 : - (NSDictionary *) translateOldStyleSubentityDeclaration:(NSString *)declaration
      97             :                                                                                                  forShip:(NSString *)shipKey
      98             :                                                                                                 shipData:(NSDictionary *)shipData
      99             :                                                                                           fatalError:(BOOL *)outFatalError;
     100           0 : - (NSDictionary *) translateOldStyleFlasherDeclaration:(NSArray *)tokens
     101             :                                                                                            forShip:(NSString *)shipKey
     102             :                                                                                         fatalError:(BOOL *)outFatalError;
     103           0 : - (NSDictionary *) translateOldStandardBasicSubentityDeclaration:(NSArray *)tokens
     104             :                                                                                                                  forShip:(NSString *)shipKey
     105             :                                                                                                                 shipData:(NSDictionary *)shipData
     106             :                                                                                                           fatalError:(BOOL *)outFatalError;
     107           0 : - (NSDictionary *) validateNewStyleSubentityDeclaration:(NSDictionary *)declaration
     108             :                                                                                                 forShip:(NSString *)shipKey
     109             :                                                                                          fatalError:(BOOL *)outFatalError;
     110           0 : - (NSDictionary *) validateNewStyleFlasherDeclaration:(NSDictionary *)declaration
     111             :                                                                                           forShip:(NSString *)shipKey
     112             :                                                                                    fatalError:(BOOL *)outFatalError;
     113           0 : - (NSDictionary *) validateNewStyleStandardSubentityDeclaration:(NSDictionary *)declaration
     114             :                                                                                                                 forShip:(NSString *)shipKey
     115             :                                                                                                          fatalError:(BOOL *)outFatalError;
     116             : 
     117           0 : - (BOOL) shipIsBallTurretForKey:(NSString *)shipKey inShipData:(NSDictionary *)shipData;
     118             : 
     119             : @end
     120             : 
     121             : 
     122             : @implementation OOShipRegistry
     123             : 
     124             : + (OOShipRegistry *) sharedRegistry
     125             : {
     126             :         if (sSingleton == nil)
     127             :         {
     128             :                 sSingleton = [[self alloc] init];
     129             :         }
     130             :         
     131             :         return sSingleton;
     132             : }
     133             : 
     134             : 
     135             : + (void) reload
     136             : {
     137             :         if (sSingleton != nil)
     138             :         {
     139             :                 /* CIM: 'release' doesn't work - the class definition
     140             :                  * overrides it, so this leaks memory. Needs a proper reset
     141             :                  * method for reloading the ship registry data instead */
     142             :                 [sSingleton release];
     143             :                 sSingleton = nil;
     144             :                 
     145             :                 (void) [self sharedRegistry];
     146             :         }
     147             : }
     148             : 
     149             : 
     150           0 : - (id) init
     151             : {
     152             :         if ((self = [super init]))
     153             :         {
     154             :                 NSAutoreleasePool               *pool = [[NSAutoreleasePool alloc] init];
     155             :                 OOCacheManager                  *cache = [OOCacheManager sharedCache];
     156             :                 
     157             :                 _shipData = [[cache objectForKey:kShipDataCacheKey inCache:kShipRegistryCacheName] retain];
     158             :                 _playerShips = [[cache objectForKey:kPlayerShipsCacheKey inCache:kShipRegistryCacheName] retain];
     159             :                 _effectData = [[cache objectForKey:kVisualEffectDataCacheKey inCache:kVisualEffectRegistryCacheName] retain];
     160             :                 if ([_shipData count] == 0)     // Don't accept nil or empty
     161             :                 {
     162             :                         [self loadShipData];
     163             :                         if ([_shipData count] == 0)
     164             :                         {
     165             :                                 [NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load any ship data."];
     166             :                         }
     167             :                         if ([_playerShips count] == 0)
     168             :                         {
     169             :                                 [NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load any player ships."];
     170             :                         }
     171             :                 }
     172             :                 
     173             :                 [self loadDemoShipConditions];
     174             :                 [self loadDemoShips]; // testing only
     175             :                 if ([_demoShips count] == 0)
     176             :                 {
     177             :                         [NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load or synthesize any demo ships."];
     178             :                 }
     179             :                 
     180             :                 [self loadCachedRoleProbabilitySets];
     181             :                 if (_probabilitySets == nil)
     182             :                 {
     183             :                         [self buildRoleProbabilitySets];
     184             :                         if ([_probabilitySets count] == 0)
     185             :                         {
     186             :                                 [NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load or synthesize role probability sets."];
     187             :                         }
     188             :                 }
     189             :                 
     190             :                 [pool release];
     191             :         }
     192             :         return self;
     193             : }
     194             : 
     195             : 
     196           0 : - (void) dealloc
     197             : {
     198             :         [_shipData release];
     199             :         [_demoShips release];
     200             :         [_playerShips release];
     201             :         [_probabilitySets release];
     202             :         
     203             :         [super dealloc];
     204             : }
     205             : 
     206             : 
     207             : - (NSDictionary *) shipInfoForKey:(NSString *)key
     208             : {
     209             :         return [_shipData objectForKey:key];
     210             : }
     211             : 
     212             : 
     213             : - (void) setShipInfoForKey:(NSString *)key with:(NSDictionary *)newShipData
     214             : {
     215             :         NSMutableDictionary *mutableDict = [NSMutableDictionary dictionaryWithDictionary:_shipData];
     216             :         [mutableDict setObject:OODeepCopy(newShipData) forKey:key];
     217             :         DESTROY(_shipData);
     218             :         _shipData = [[NSDictionary dictionaryWithDictionary:mutableDict] retain];
     219             : }
     220             : 
     221             : 
     222             : - (NSDictionary *) effectInfoForKey:(NSString *)key
     223             : {
     224             :         return [_effectData objectForKey:key];
     225             : }
     226             : 
     227             : 
     228             : - (NSDictionary *) shipyardInfoForKey:(NSString *)key
     229             : {
     230             :         return [[self shipInfoForKey:key] objectForKey:@"_oo_shipyard"];
     231             : }
     232             : 
     233             : 
     234             : - (OOProbabilitySet *) probabilitySetForRole:(NSString *)role
     235             : {
     236             :         if (role == nil)  return nil;
     237             :         return [_probabilitySets objectForKey:role];
     238             : }
     239             : 
     240             : 
     241             : - (NSArray *) demoShipKeys
     242             : {
     243             :         // with condition scripts in use, can't cache this value
     244             :         [self loadDemoShips];
     245             : 
     246             :         return [[_demoShips copy] autorelease]; 
     247             : }
     248             : 
     249             : 
     250             : - (NSArray *) playerShipKeys
     251             : {
     252             :         return _playerShips;
     253             : }
     254             : 
     255             : @end
     256             : 
     257             : 
     258             : @implementation OOShipRegistry (OOConveniences)
     259             : 
     260             : - (NSArray *) shipKeys
     261             : {
     262             :         return [_shipData allKeys];
     263             : }
     264             : 
     265             : - (NSArray *) shipRoles
     266             : {
     267             :         return [_probabilitySets allKeys];
     268             : }
     269             : 
     270             : - (NSArray *) shipKeysWithRole:(NSString *)role
     271             : {
     272             :         return [[self probabilitySetForRole:role] allObjects];
     273             : }
     274             : 
     275             : 
     276             : - (NSString *) randomShipKeyForRole:(NSString *)role
     277             : {
     278             :         return [[self probabilitySetForRole:role] randomObject];
     279             : }
     280             : 
     281             : @end
     282             : 
     283             : 
     284             : @implementation OOShipRegistry (OODataLoader)
     285             : 
     286             : /*      -loadShipData
     287             :         
     288             :         Load the data for all ships. This consists of five stages:
     289             :                 * Load merges shipdata.plist dictionary.
     290             :                 * Apply all like_ship entries.
     291             :                 * Load shipdata-overrides.plist and apply patches.
     292             :                 * Load shipyard.plist, add shipyard data into ship dictionaries, and
     293             :                   create _playerShips array.
     294             :                 * Build role->ship type probability sets.
     295             : */
     296             : - (void) loadShipData
     297             : {
     298             :         NSMutableDictionary             *result = nil;
     299             :         
     300             :         [_shipData release];
     301             :         _shipData = nil;
     302             :         [_playerShips release];
     303             :         _playerShips = nil;
     304             :         
     305             :         // Load shipdata.plist.
     306             :         result = [[[ResourceManager dictionaryFromFilesNamed:@"shipdata.plist"
     307             :                                                                                                 inFolder:@"Config"
     308             :                                                                                            mergeMode:MERGE_BASIC
     309             :                                                                                                    cache:NO] mutableCopy] autorelease];
     310             :         if (result == nil)  return;
     311             :         
     312             :         DumpStringAddrs(result, @"shipdata.plist");
     313             :         
     314             :         // Make each entry mutable to simplify later stages. Also removes any entries that aren't dictionaries.
     315             :         if (![self makeShipEntriesMutable:result])  return;
     316             :         OOLog(@"shipData.load.progress", @"%@", @"Finished initial cleanup...");
     317             :         
     318             :         // Apply patches.
     319             :         if (![self loadAndApplyShipDataOverrides:result])  return;
     320             :         OOLog(@"shipData.load.progress", @"%@", @"Finished applying patches...");
     321             :         
     322             :         // Strip private keys (anything starting with _oo_).
     323             :         if (![self stripPrivateKeys:result])  return;
     324             :         OOLog(@"shipData.load.progress", @"%@", @"Finished stripping private keys...");
     325             :         
     326             :         // Resolve like_ship entries.
     327             :         if (![self applyLikeShips:result withKey:@"like_ship"])  return;
     328             :         OOLog(@"shipData.load.progress", @"%@", @"Finished resolving like_ships...");
     329             :         
     330             :         // Clean up subentity declarations and tag subentities so they won't be pruned.
     331             :         if (![self canonicalizeAndTagSubentities:result])  return;
     332             :         OOLog(@"shipData.load.progress", @"%@", @"Finished cleaning up subentities...");
     333             :         
     334             :         // Clean out templates and invalid entries.
     335             :         if (![self removeUnusableEntries:result shipMode:YES])  return;
     336             :         OOLog(@"shipData.load.progress", @"%@", @"Finished removing invalid entries...");
     337             :         
     338             :         // Add shipyard entries into shipdata entries.
     339             :         if (![self loadAndMergeShipyard:result])  return;
     340             :         OOLog(@"shipData.load.progress", @"%@", @"Finished adding shipyard entries...");
     341             :         
     342             :         // Sanitize conditions.
     343             :         if (![self sanitizeConditions:result])  return;
     344             :         OOLog(@"shipData.load.progress", @"%@", @"Finished validating data...");
     345             :         
     346             : #if PRELOAD
     347             :         // Preload and cache meshes.
     348             :         if (![self preloadShipMeshes:result])  return;
     349             :         OOLog(@"shipData.load.progress", @"%@", @"Finished loading meshes...");
     350             : #endif
     351             :         
     352             :         _shipData = OODeepCopy(result);
     353             :         [[OOCacheManager sharedCache] setObject:_shipData forKey:kShipDataCacheKey inCache:kShipRegistryCacheName];
     354             :         
     355             :         OOLog(@"shipData.load.done", @"%@", @"Ship data loaded.");
     356             : 
     357             :         [_effectData release];
     358             :         _effectData = nil;
     359             : 
     360             :         result = [[[ResourceManager dictionaryFromFilesNamed:@"effectdata.plist"
     361             :                                                                                                 inFolder:@"Config"
     362             :                                                                                            mergeMode:MERGE_BASIC
     363             :                                                                                                    cache:NO] mutableCopy] autorelease];
     364             :         if (result == nil)  return;
     365             : 
     366             :         // Make each entry mutable to simplify later stages. Also removes any entries that aren't dictionaries.
     367             :         if (![self makeShipEntriesMutable:result])  return;
     368             :         OOLog(@"effectData.load.progress", @"%@", @"Finished initial cleanup...");
     369             : 
     370             :         // Strip private keys (anything starting with _oo_).
     371             :         if (![self stripPrivateKeys:result])  return;
     372             :         OOLog(@"effectData.load.progress", @"%@", @"Finished stripping private keys...");
     373             :         
     374             :         // Resolve like_effect entries.
     375             :         if (![self applyLikeShips:result withKey:@"like_effect"])  return;
     376             :         OOLog(@"effectData.load.progress", @"%@", @"Finished resolving like_effects...");
     377             :         
     378             :         // Clean up subentity declarations and tag subentities so they won't be pruned.
     379             :         if (![self canonicalizeAndTagSubentities:result])  return;
     380             :         OOLog(@"effectData.load.progress", @"%@", @"Finished cleaning up subentities...");
     381             :         
     382             :         // Clean out templates and invalid entries.
     383             :         if (![self removeUnusableEntries:result shipMode:NO])  return;
     384             :         OOLog(@"effectData.load.progress", @"%@", @"Finished removing invalid entries...");
     385             :         
     386             :         _effectData = OODeepCopy(result);
     387             :         [[OOCacheManager sharedCache] setObject:_effectData forKey:kVisualEffectDataCacheKey inCache:kVisualEffectRegistryCacheName];
     388             :         
     389             :         OOLog(@"effectData.load.done", @"%@", @"Effect data loaded.");
     390             : }
     391             : 
     392             : 
     393             : - (void) loadDemoShipConditions
     394             : {
     395             :         NSMutableArray *conditionScripts = [[NSMutableArray alloc] init];
     396             :         NSEnumerator                    *enumerator = nil;
     397             :         NSDictionary                    *key = nil;
     398             :         NSArray                                 *initialDemoShips = nil;
     399             : 
     400             :         initialDemoShips = [ResourceManager arrayFromFilesNamed:@"shiplibrary.plist"
     401             :                                                                                                    inFolder:@"Config"
     402             :                                                                                                    andMerge:YES
     403             :                                                                                                           cache:NO];
     404             : 
     405             :         for (enumerator = [initialDemoShips objectEnumerator]; (key = [enumerator nextObject]); )
     406             :         {
     407             :                 NSString *conditions = [key oo_stringForKey:kOODemoShipConditions defaultValue:nil];
     408             :                 if (conditions != nil)
     409             :                 {
     410             :                         [conditionScripts addObject:conditions];
     411             :                 }
     412             :         }
     413             : 
     414             :         [[OOCacheManager sharedCache] setObject:conditionScripts forKey:@"demoship conditions" inCache:@"condition scripts"];
     415             :         [conditionScripts release];
     416             : }
     417             : 
     418             : 
     419             : /*      -loadDemoShips
     420             :         
     421             :         Load demoships.plist, and filter out non-existent ships. If no existing
     422             :         ships remain, try adding coriolis; if this fails, add any ship in
     423             :         shipdata.
     424             : */
     425             : - (void) loadDemoShips
     426             : {
     427             :         NSEnumerator                    *enumerator = nil;
     428             :         NSDictionary                    *key = nil;
     429             :         NSArray                                 *initialDemoShips = nil;
     430             :         NSMutableArray                  *demoShips = nil;
     431             :         
     432             :         DESTROY(_demoShips);
     433             :         
     434             :         initialDemoShips = [ResourceManager arrayFromFilesNamed:@"shiplibrary.plist"
     435             :                                                                                                    inFolder:@"Config"
     436             :                                                                                                    andMerge:YES
     437             :                                                                                                           cache:NO];
     438             :         demoShips = [NSMutableArray arrayWithArray:initialDemoShips];
     439             :         
     440             :         // Note: iterate over initialDemoShips to avoid mutating the collection being enu,erated.
     441             :         for (enumerator = [initialDemoShips objectEnumerator]; (key = [enumerator nextObject]); )
     442             :         {
     443             :                 NSString *shipKey = [key oo_stringForKey:kOODemoShipKey];
     444             :                 if (![key isKindOfClass:[NSDictionary class]] || [self shipInfoForKey:shipKey] == nil)
     445             :                 {
     446             :                         [demoShips removeObject:key];
     447             :                 }
     448             :                 else 
     449             :                 {
     450             :                         NSString *conditions = [key oo_stringForKey:kOODemoShipConditions defaultValue:nil];
     451             :                         if (conditions != nil)
     452             :                         {
     453             :                                 if ([PLAYER status] == STATUS_START_GAME)
     454             :                                 {
     455             :                                         // conditions always false here
     456             :                                         [demoShips removeObject:key];
     457             :                                 }
     458             :                                 else
     459             :                                 {
     460             :                                         OOJSScript *condScript = [UNIVERSE getConditionScript:conditions];
     461             :                                         if (condScript != nil) // should always be non-nil, but just in case
     462             :                                         {
     463             :                                                 JSContext                       *context = OOJSAcquireContext();
     464             :                                                 BOOL OK;
     465             :                                                 JSBool allow_use;
     466             :                                                 jsval result;
     467             :                                                 jsval args[] = { OOJSValueFromNativeObject(context, shipKey) };
     468             :                                                 
     469             :                                                 OK = [condScript callMethod:OOJSID("allowShowLibraryShip")
     470             :                                                                                   inContext:context
     471             :                                                                           withArguments:args count:sizeof args / sizeof *args
     472             :                                                                                          result:&result];
     473             : 
     474             :                                                 if (OK) OK = JS_ValueToBoolean(context, result, &allow_use);
     475             :                         
     476             :                                                 OOJSRelinquishContext(context);
     477             :                                                 if (OK && !allow_use)
     478             :                                                 {
     479             :                                                         /* if the script exists, the function exists, the function
     480             :                                                          * returns a bool, and that bool is false, hide the
     481             :                                                          * ship. Otherwise allow it as default */
     482             :                                                         [demoShips removeObject:key];
     483             :                                                 }
     484             :                                         }
     485             :                                 }
     486             :                         }
     487             :                 }
     488             :         }
     489             :         
     490             :         if ([demoShips count] == 0)
     491             :         {
     492             :                 NSString *shipKey = nil;
     493             :                 if ([self shipInfoForKey:kDefaultDemoShip] != nil) 
     494             :                 {
     495             :                         shipKey = kDefaultDemoShip;
     496             :                 }
     497             :                 else
     498             :                 {
     499             :                         shipKey = [[_shipData allKeys] objectAtIndex:0];
     500             :                 }
     501             :                 [demoShips addObject:[NSDictionary dictionaryWithObject:shipKey forKey:kOODemoShipKey]];
     502             :         }
     503             :         
     504             :         // now separate out the demoships by class, and add some extra keys
     505             :         NSMutableDictionary *demoList = [NSMutableDictionary dictionaryWithCapacity:8];
     506             :         NSMutableArray *demoClass = nil;
     507             :         foreach(key, demoShips)
     508             :         {
     509             :                 NSString *class = [key oo_stringForKey:kOODemoShipClass defaultValue:@"ship"];
     510             :                 if ([OOShipLibraryCategoryPlural(class) length] == 0)
     511             :                 {
     512             :                         OOLog(@"shipdata.load.warning",@"Unexpected class '%@' in shiplibrary.plist for '%@'",class,[key oo_stringForKey:kOODemoShipKey]);
     513             :                         class = @"ship";
     514             :                 }
     515             :                 demoClass = [demoList objectForKey:class];
     516             :                 if (demoClass == nil)
     517             :                 {
     518             :                         [demoList setObject:[NSMutableArray array] forKey:class];
     519             :                         demoClass = [demoList objectForKey:class];
     520             :                 }
     521             :                 NSMutableDictionary *demoEntry = [NSMutableDictionary dictionaryWithDictionary:key];
     522             :                 // add "name" object to dictionary from ship definition
     523             :                 [demoEntry setObject:[[self shipInfoForKey:[demoEntry oo_stringForKey:@"ship"]] oo_stringForKey:kOODemoShipName] forKey:kOODemoShipName];
     524             :                 // set "class" object to standard ship if not otherwise set
     525             :                 if (![[demoEntry oo_stringForKey:kOODemoShipClass defaultValue:nil] isEqualToString:class])
     526             :                 {
     527             :                         [demoEntry setObject:class forKey:kOODemoShipClass];
     528             :                 }
     529             :                 [demoClass addObject:demoEntry];
     530             :         }
     531             :         // sort each ship list by name
     532             :         NSString *demoClassName = nil;
     533             :         foreach(demoClassName, demoList)
     534             :         {
     535             :                 [[demoList objectForKey:demoClassName] sortUsingFunction:SortDemoShipsByName context:NULL];
     536             :         }
     537             : 
     538             :         // and then sort the ship list list by class name
     539             :         _demoShips = [[[demoList allValues] sortedArrayUsingFunction:SortDemoCategoriesByName context:NULL] retain];
     540             : }
     541             : 
     542             : 
     543             : - (void) loadCachedRoleProbabilitySets
     544             : {
     545             :         NSDictionary                    *cachedSets = nil;
     546             :         NSMutableDictionary             *restoredSets = nil;
     547             :         NSEnumerator                    *roleEnum = nil;
     548             :         NSString                                *role = nil;
     549             :         
     550             :         cachedSets = [[OOCacheManager sharedCache] objectForKey:kRoleWeightsCacheKey inCache:kShipRegistryCacheName];
     551             :         if (cachedSets == nil)  return;
     552             :         
     553             :         restoredSets = [NSMutableDictionary dictionaryWithCapacity:[cachedSets count]];
     554             :         for (roleEnum = [cachedSets keyEnumerator]; (role = [roleEnum nextObject]); )
     555             :         {
     556             :                 [restoredSets setObject:[OOProbabilitySet probabilitySetWithPropertyListRepresentation:[cachedSets objectForKey:role]] forKey:role];
     557             :         }
     558             :         
     559             :         _probabilitySets = [restoredSets copy];
     560             : }
     561             : 
     562             : 
     563             : - (void) buildRoleProbabilitySets
     564             : {
     565             :         NSMutableDictionary             *probabilitySets = nil;
     566             :         NSEnumerator                    *shipEnum = nil;
     567             :         NSString                                *shipKey = nil;
     568             :         NSDictionary                    *shipEntry = nil;
     569             :         NSString                                *roles = nil;
     570             :         NSEnumerator                    *roleEnum = nil;
     571             :         NSString                                *role = nil;
     572             :         OOProbabilitySet                *pset = nil;
     573             :         NSMutableDictionary             *cacheEntry = nil;
     574             :         
     575             :         probabilitySets = [NSMutableDictionary dictionary];
     576             :         
     577             :         // Build role sets
     578             :         for (shipEnum = [_shipData keyEnumerator]; (shipKey = [shipEnum nextObject]); )
     579             :         {
     580             :                 shipEntry = [_shipData objectForKey:shipKey];
     581             :                 roles = [shipEntry oo_stringForKey:@"roles"];
     582             :                 [self mergeShipRoles:roles forShipKey:shipKey intoProbabilityMap:probabilitySets];
     583             :         }
     584             :         
     585             :         // Convert role sets to immutable form, and build cache entry.
     586             :         // Note: we iterate over a copy of the keys to avoid mutating while iterating.
     587             :         cacheEntry = [NSMutableDictionary dictionaryWithCapacity:[probabilitySets count]];
     588             :         for (roleEnum = [[probabilitySets allKeys] objectEnumerator]; (role = [roleEnum nextObject]); )
     589             :         {
     590             :                 pset = [probabilitySets objectForKey:role];
     591             :                 pset = [[pset copy] autorelease];
     592             :                 [probabilitySets setObject:pset forKey:role];
     593             :                 [cacheEntry setObject:[pset propertyListRepresentation] forKey:role];
     594             :         }
     595             :         
     596             :         _probabilitySets = [probabilitySets copy];
     597             :         [[OOCacheManager sharedCache] setObject:cacheEntry forKey:kRoleWeightsCacheKey inCache:kShipRegistryCacheName];
     598             : }
     599             : 
     600             : 
     601             : /*      -applyLikeShips:
     602             :         
     603             :         Implement like_ship by copying inherited ship and overwriting with child
     604             :         ship values. Done iteratively to report recursive references of arbitrary
     605             :         depth. Also removes and reports ships whose like_ship entry does not
     606             :         resolve, and handles reference loops by removing all ships involved.
     607             :  
     608             :         We start with a set of keys all ships that have a like_ships entry. In
     609             :         each iteration, every ship whose like_ship entry does not refer to a ship
     610             :         which itself has a like_ship entry is finalized. If the set of pending
     611             :         ships does not shrink in an iteration, the remaining ships cannot be
     612             :         resolved (either their like_ships do not exist, or they form reference
     613             :         cycles) so we stop looping and report it.
     614             : */
     615             : - (BOOL) applyLikeShips:(NSMutableDictionary *)ioData withKey:(NSString *)likeKey
     616             : {
     617             :         NSMutableSet                    *remainingLikeShips = nil;
     618             :         NSEnumerator                    *enumerator = nil;
     619             :         NSString                                *key = nil;
     620             :         NSString                                *parentKey = nil;
     621             :         NSDictionary                    *shipEntry = nil;
     622             :         NSDictionary                    *parentEntry = nil;
     623             :         NSUInteger                              count, lastCount;
     624             :         NSMutableArray                  *reportedBadShips = nil;
     625             :         
     626             :         // Build set of ships with like_ship references
     627             :         remainingLikeShips = [NSMutableSet set];
     628             :         for (enumerator = [ioData keyEnumerator]; (key = [enumerator nextObject]); )
     629             :         {
     630             :                 shipEntry = [ioData objectForKey:key];
     631             :                 if ([shipEntry oo_stringForKey:likeKey] != nil)
     632             :                 {
     633             :                         [remainingLikeShips addObject:key];
     634             :                 }
     635             :         }
     636             :         
     637             :         count = lastCount = [remainingLikeShips count];
     638             :         while (count != 0)
     639             :         {
     640             :                 for (enumerator = [[[remainingLikeShips copy] autorelease] objectEnumerator]; (key = [enumerator nextObject]); )
     641             :                 {
     642             :                         // Look up like_ship entry
     643             :                         shipEntry = [ioData objectForKey:key];
     644             :                         parentKey = [shipEntry objectForKey:likeKey];
     645             :                         if (![remainingLikeShips containsObject:parentKey])
     646             :                         {
     647             :                                 // If parent is fully resolved, we can resolve this child.
     648             :                                 parentEntry = [ioData objectForKey:parentKey];
     649             :                                 shipEntry = [self mergeShip:shipEntry withParent:parentEntry];
     650             :                                 if (shipEntry != nil)
     651             :                                 {
     652             :                                         [remainingLikeShips removeObject:key];
     653             :                                         [ioData setObject:shipEntry forKey:key];
     654             :                                 }
     655             :                         }
     656             :                 }
     657             :                 
     658             :                 count = [remainingLikeShips count];
     659             :                 if (count == lastCount)
     660             :                 {
     661             :                         /*      Fail: we couldn't resolve all like_ship entries.
     662             :                                 Remove unresolved entries, building a list of the ones that
     663             :                                 don't have is_external_dependency set.
     664             :                         */
     665             :                         reportedBadShips = [NSMutableArray array];
     666             :                         for (enumerator = [remainingLikeShips objectEnumerator]; (key = [enumerator nextObject]); )
     667             :                         {
     668             :                                 if (![[ioData oo_dictionaryForKey:key] oo_boolForKey:@"is_external_dependency"])
     669             :                                 {
     670             :                                         [reportedBadShips addObject:key];
     671             :                                 }
     672             :                                 [ioData removeObjectForKey:key];
     673             :                         }
     674             :                         
     675             :                         if ([reportedBadShips count] != 0)
     676             :                         {
     677             :                                 [reportedBadShips sortUsingSelector:@selector(caseInsensitiveCompare:)];
     678             :                                 OOLogERR(@"shipData.merge.failed", @"one or more shipdata.plist entries have %@ references that cannot be resolved: %@", likeKey, [reportedBadShips componentsJoinedByString:@", "]); // FIXME: distinguish shipdata and effectdata
     679             :                                 OOStandardsError(@"Likely missing a dependency in a manifest.plist");
     680             :                         }
     681             :                         break;
     682             :                 }
     683             :                 lastCount = count;
     684             :         }
     685             :         
     686             :         return YES;
     687             : }
     688             : 
     689             : 
     690             : - (NSMutableDictionary *) mergeShip:(NSDictionary *)child withParent:(NSDictionary *)parent
     691             : {
     692             :         NSMutableDictionary *result = [[parent mutableCopy] autorelease];
     693             :         if (result == nil)  return nil;
     694             :         
     695             :         [result addEntriesFromDictionary:child];
     696             :         [result removeObjectForKey:@"like_ship"];
     697             :         
     698             :         // Certain properties cannot be inherited.
     699             :         if ([child oo_stringForKey:@"display_name"] == nil)  [result removeObjectForKey:@"display_name"];
     700             :         if ([child oo_stringForKey:@"is_template"] == nil)  [result removeObjectForKey:@"is_template"];
     701             :         
     702             :         // Since both 'scanClass' and 'scan_class' are accepted as valid keys for the scanClass property,
     703             :         // we may end up with conflicting scanClass and scan_class keys from like_ship relationships getting
     704             :         // merged in the result dictionary. We want to always have the child overriding the parent setting
     705             :         // and we do that by determining which of the two keys belongs to the child dictionary and removing
     706             :         // the other one from the result - Nikos 20100512
     707             :         if ([result oo_stringForKey:@"scan_class"] != nil && [result oo_stringForKey:@"scanClass"] != nil)
     708             :         {
     709             :                 if ([child oo_stringForKey:@"scanClass"] != nil)
     710             :                         [result removeObjectForKey:@"scan_class"];
     711             :                 else
     712             :                         [result removeObjectForKey:@"scanClass"];
     713             :         }
     714             :         // TODO: all normalised/non-normalised value name pairs need to be catered for. - Kaks 2010-05-13
     715             :         if ([result oo_stringForKey:@"escort_role"] != nil && [result oo_stringForKey:@"escort-role"] != nil)
     716             :         {
     717             :                 if ([child oo_stringForKey:@"escort-role"] != nil)
     718             :                         [result removeObjectForKey:@"escort_role"];
     719             :                 else
     720             :                         [result removeObjectForKey:@"escort-role"];
     721             :         }
     722             :         if ([result oo_stringForKey:@"escort_ship"] != nil && [result oo_stringForKey:@"escort-ship"] != nil)
     723             :         {
     724             :                 if ([child oo_stringForKey:@"escort-ship"] != nil)
     725             :                         [result removeObjectForKey:@"escort_ship"];
     726             :                 else
     727             :                         [result removeObjectForKey:@"escort-ship"];
     728             :         }
     729             :         if ([result oo_stringForKey:@"is_carrier"] != nil && [result oo_stringForKey:@"isCarrier"] != nil)
     730             :         {
     731             :                 if ([child oo_stringForKey:@"isCarrier"] != nil)
     732             :                         [result removeObjectForKey:@"is_carrier"];
     733             :                 else
     734             :                         [result removeObjectForKey:@"isCarrier"];
     735             :         }
     736             :         if ([result oo_stringForKey:@"has_shipyard"] != nil && [result oo_stringForKey:@"hasShipyard"] != nil)
     737             :         {
     738             :                 if ([child oo_stringForKey:@"hasShipyard"] != nil)
     739             :                         [result removeObjectForKey:@"has_shipyard"];
     740             :                 else
     741             :                         [result removeObjectForKey:@"hasShipyard"];
     742             :         }       
     743             :         return result;
     744             : }
     745             : 
     746             : 
     747             : - (BOOL) makeShipEntriesMutable:(NSMutableDictionary *)ioData
     748             : {
     749             :         NSEnumerator                    *shipKeyEnum = nil;
     750             :         NSString                                *shipKey = nil;
     751             :         NSDictionary                    *shipEntry = nil;
     752             :         
     753             :         for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
     754             :         {
     755             :                 shipEntry = [ioData objectForKey:shipKey];
     756             :                 if (![shipEntry isKindOfClass:[NSDictionary class]])
     757             :                 {
     758             :                         OOLogERR(@"shipData.load.badEntry", @"the shipdata.plist entry \"%@\" is not a dictionary.", shipKey);
     759             :                         [ioData removeObjectForKey:shipKey];
     760             :                 }
     761             :                 else
     762             :                 {
     763             :                         shipEntry = [shipEntry mutableCopy];
     764             :                         
     765             :                         [ioData setObject:shipEntry forKey:shipKey];
     766             :                         [shipEntry release];
     767             :                 }
     768             :         }
     769             :         
     770             :         return YES;
     771             : }
     772             : 
     773             : 
     774             : - (BOOL) loadAndApplyShipDataOverrides:(NSMutableDictionary *)ioData
     775             : {
     776             :         NSEnumerator                    *shipKeyEnum = nil;
     777             :         NSString                                *shipKey = nil;
     778             :         NSMutableDictionary             *shipEntry = nil;
     779             :         NSDictionary                    *overrides = nil;
     780             :         NSDictionary                    *overridesEntry = nil;
     781             :         
     782             :         overrides = [ResourceManager dictionaryFromFilesNamed:@"shipdata-overrides.plist"
     783             :                                                                                                  inFolder:@"Config"
     784             :                                                                                                 mergeMode:MERGE_SMART
     785             :                                                                                                         cache:NO];
     786             :         
     787             :         for (shipKeyEnum = [overrides keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
     788             :         {
     789             :                 shipEntry = [ioData objectForKey:shipKey];
     790             :                 if (shipEntry != nil)
     791             :                 {
     792             :                         overridesEntry = [overrides objectForKey:shipKey];
     793             :                         if (![overridesEntry isKindOfClass:[NSDictionary class]])
     794             :                         {
     795             :                                 OOLogERR(@"shipData.load.error", @"the shipdata-overrides.plist entry \"%@\" is not a dictionary.", shipKey);
     796             :                         }
     797             :                         else
     798             :                         {
     799             :                                 [shipEntry addEntriesFromDictionary:overridesEntry];
     800             :                         }
     801             :                 }
     802             :         }
     803             :         
     804             :         return YES;
     805             : }
     806             : 
     807             : 
     808             : - (BOOL) stripPrivateKeys:(NSMutableDictionary *)ioData
     809             : {
     810             :         NSEnumerator                    *shipKeyEnum = nil;
     811             :         NSString                                *shipKey = nil;
     812             :         NSMutableDictionary             *shipEntry = nil;
     813             :         NSEnumerator                    *attrKeyEnum = nil;
     814             :         NSString                                *attrKey = nil;
     815             :         
     816             :         for (shipKeyEnum = [ioData keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
     817             :         {
     818             :                 shipEntry = [ioData objectForKey:shipKey];
     819             :                 
     820             :                 for (attrKeyEnum = [shipEntry keyEnumerator]; (attrKey = [attrKeyEnum nextObject]); )
     821             :                 {
     822             :                         if ([attrKey hasPrefix:@"_oo_"])
     823             :                         {
     824             :                                 [shipEntry removeObjectForKey:attrKey];
     825             :                         }
     826             :                 }
     827             :         }
     828             :         
     829             :         return YES;
     830             : }
     831             : 
     832             : 
     833             : /*      -loadAndMergeShipyard:
     834             :         
     835             :         Load shipyard.plist, add its entries to appropriate shipyard entries as
     836             :         a dictionary under the key "shipyard", and build list of player ships.
     837             :         Before that, we strip out any "shipyard" entries already in shipdata, and
     838             :         apply any shipyard-overrides.plist stuff to shipyard.
     839             : */
     840             : - (BOOL) loadAndMergeShipyard:(NSMutableDictionary *)ioData
     841             : {
     842             :         NSEnumerator                    *shipKeyEnum = nil;
     843             :         NSString                                *shipKey = nil;
     844             :         NSMutableDictionary             *shipEntry = nil;
     845             :         NSDictionary                    *shipyard = nil;
     846             :         NSDictionary                    *shipyardOverrides = nil;
     847             :         NSDictionary                    *shipyardEntry = nil;
     848             :         NSDictionary                    *shipyardOverridesEntry = nil;
     849             :         NSMutableArray                  *playerShips = nil;
     850             :         
     851             :         // Strip out any shipyard stuff in shipdata (there shouldn't be any).
     852             :         for (shipKeyEnum = [ioData keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
     853             :         {
     854             :                 shipEntry = [ioData objectForKey:shipKey];
     855             :                 if ([shipEntry objectForKey:@"_oo_shipyard"] != nil)
     856             :                 {
     857             :                         [shipEntry removeObjectForKey:@"_oo_shipyard"];
     858             :                 }
     859             :         }
     860             :         
     861             :         shipyard = [ResourceManager dictionaryFromFilesNamed:@"shipyard.plist"
     862             :                                                                                                 inFolder:@"Config"
     863             :                                                                                            mergeMode:MERGE_BASIC
     864             :                                                                                                    cache:NO];
     865             :         shipyardOverrides = [ResourceManager dictionaryFromFilesNamed:@"shipyard-overrides.plist"
     866             :                                                                                                                  inFolder:@"Config"
     867             :                                                                                                                 mergeMode:MERGE_SMART
     868             :                                                                                                                         cache:NO];
     869             :         
     870             :         playerShips = [NSMutableArray arrayWithCapacity:[shipyard count]];
     871             :         
     872             :         // Insert merged shipyard and shipyardOverrides entries.
     873             :         for (shipKeyEnum = [shipyard keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
     874             :         {
     875             :                 shipEntry = [ioData objectForKey:shipKey];
     876             :                 if (shipEntry != nil)
     877             :                 {
     878             :                         shipyardEntry = [shipyard objectForKey:shipKey];
     879             :                         shipyardOverridesEntry = [shipyardOverrides objectForKey:shipKey];
     880             :                         shipyardEntry = [shipyardEntry dictionaryByAddingEntriesFromDictionary:shipyardOverridesEntry];
     881             :                         
     882             :                         [shipEntry setObject:shipyardEntry forKey:@"_oo_shipyard"];
     883             :                         
     884             :                         [playerShips addObject:shipKey];
     885             :                 }
     886             :                 else
     887             :                 {
     888             :                         OOLogWARN(@"shipData.load.shipyard.unknown", @"the shipyard.plist entry \"%@\" does not have a corresponding shipdata.plist entry, ignoring.", shipKey);
     889             :                 }
     890             :         }
     891             :         
     892             :         _playerShips = [playerShips copy];
     893             :         [[OOCacheManager sharedCache] setObject:_playerShips forKey:kPlayerShipsCacheKey inCache:kShipRegistryCacheName];
     894             :         
     895             :         return YES;
     896             : }
     897             : 
     898             : 
     899             : - (BOOL) canonicalizeAndTagSubentities:(NSMutableDictionary *)ioData
     900             : {
     901             :         NSEnumerator                    *shipKeyEnum = nil;
     902             :         NSString                                *shipKey = nil;
     903             :         NSMutableDictionary             *shipEntry = nil;
     904             :         NSArray                                 *subentityDeclarations = nil;
     905             :         NSEnumerator                    *subentityEnum = nil;
     906             :         id                                              subentityDecl = nil;
     907             :         NSDictionary                    *subentityDict = nil;
     908             :         NSString                                *subentityKey = nil;
     909             :         NSMutableDictionary             *subentityShipEntry = nil;
     910             :         NSMutableSet                    *badSubentities = nil;
     911             :         NSString                                *badSubentitiesList = nil;
     912             :         NSMutableArray                  *okSubentities = nil;
     913             :         BOOL                                    remove, fatal;
     914             :         
     915             :         // Convert all subentity declarations to dictionaries and add
     916             :         // _oo_is_subentity=YES to all entries used as subentities.
     917             :         
     918             :         // Iterate over all ships. (Iterates over a copy of keys since it mutates the dictionary.)
     919             :         for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
     920             :         {
     921             :                 shipEntry = [ioData objectForKey:shipKey];
     922             :                 remove = NO;
     923             :                 badSubentities = nil;
     924             :                 
     925             :                 // Iterate over each subentity declaration of each ship
     926             :                 subentityDeclarations = [shipEntry oo_arrayForKey:@"subentities"];
     927             :                 if (subentityDeclarations != nil)
     928             :                 {
     929             :                         okSubentities = [NSMutableArray arrayWithCapacity:[subentityDeclarations count]];
     930             :                         for (subentityEnum = [subentityDeclarations objectEnumerator]; (subentityDecl = [subentityEnum nextObject]); )
     931             :                         {
     932             :                                 subentityDict = [self canonicalizeSubentityDeclaration:subentityDecl forShip:shipKey shipData:ioData fatalError:&fatal];
     933             :                                 
     934             :                                 // If entry is broken, we need to kill this ship.
     935             :                                 if (fatal)
     936             :                                 {
     937             :                                         OOStandardsError(@"Bad subentity definition found");
     938             :                                         remove = YES;
     939             :                                 }
     940             :                                 else if (subentityDict != nil)
     941             :                                 {
     942             :                                         [okSubentities addObject:subentityDict];
     943             :                                         
     944             :                                         // Tag subentities.
     945             :                                         if (![[subentityDict oo_stringForKey:@"type"] isEqualToString:@"flasher"])
     946             :                                         {
     947             :                                                 subentityKey = [subentityDict oo_stringForKey:@"subentity_key"];
     948             :                                                 subentityShipEntry = [ioData objectForKey:subentityKey];
     949             :                                                 if (subentityKey == nil || subentityShipEntry == nil)
     950             :                                                 {
     951             :                                                         // Oops, reference to non-existent subent.
     952             :                                                         if (badSubentities == nil)  badSubentities = [NSMutableSet set];
     953             :                                                         [badSubentities addObject:subentityKey];
     954             :                                                 }
     955             :                                                 else
     956             :                                                 {
     957             :                                                         // Subent exists, add _oo_is_subentity so roles aren't required.
     958             :                                                         [subentityShipEntry oo_setBool:YES forKey:@"_oo_is_subentity"];
     959             :                                                 }
     960             :                                         }
     961             :                                 }
     962             :                         }
     963             :                         
     964             :                         // Set updated subentity list.
     965             :                         if ([okSubentities count] != 0)
     966             :                         {
     967             :                                 [shipEntry setObject:okSubentities forKey:@"subentities"];
     968             :                         }
     969             :                         else
     970             :                         {
     971             :                                 [shipEntry removeObjectForKey:@"subentities"];
     972             :                         }
     973             :                         
     974             :                         if (badSubentities != nil)
     975             :                         {
     976             :                                 if (![shipEntry oo_boolForKey:@"is_external_dependency"])
     977             :                                 {
     978             :                                         badSubentitiesList = [[[badSubentities allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] componentsJoinedByString:@", "];
     979             :                                         OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" has unresolved subentit%@ %@.", shipKey, ([badSubentities count] == 1) ? @"y" : @"ies", badSubentitiesList);
     980             :                                         OOStandardsError(@"Bad subentity definition found");
     981             :                                 }
     982             :                                 remove = YES;
     983             :                         }
     984             :                         
     985             :                         if (remove)
     986             :                         {
     987             :                                 // Removal is deferred to avoid bogus "entry doesn't exist" errors.
     988             :                                 [shipEntry oo_setBool:YES forKey:@"_oo_deferred_remove"];
     989             :                         }
     990             :                 }
     991             :         }
     992             :         
     993             :         return YES;
     994             : }
     995             : 
     996             : 
     997             : - (BOOL) removeUnusableEntries:(NSMutableDictionary *)ioData shipMode:(BOOL)shipMode
     998             : {
     999             :         NSEnumerator                    *shipKeyEnum = nil;
    1000             :         NSString                                *shipKey = nil;
    1001             :         NSMutableDictionary             *shipEntry = nil;
    1002             :         BOOL                                    remove;
    1003             :         NSString                                *modelName = nil;
    1004             :         
    1005             :         // Clean out invalid entries and templates. (Iterates over a copy of keys since it mutates the dictionary.)
    1006             :         for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
    1007             :         {
    1008             :                 shipEntry = [ioData objectForKey:shipKey];
    1009             :                 remove = NO;
    1010             :                 
    1011             :                 if ([shipEntry oo_boolForKey:@"is_template"] || [shipEntry oo_boolForKey:@"_oo_deferred_remove"])  remove = YES;
    1012             :                 else if (shipMode && [[shipEntry oo_stringForKey:@"roles"] length] == 0 && ![shipEntry oo_boolForKey:@"_oo_is_subentity"] && ![shipEntry oo_boolForKey:@"_oo_is_effect"])
    1013             :                 {
    1014             :                         OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies no %@.", shipKey, @"roles");
    1015             :                         remove = YES;
    1016             :                         OOStandardsError(@"Error in shipdata.plist");
    1017             :                 }
    1018             :                 else
    1019             :                 {
    1020             :                         modelName = [shipEntry oo_stringForKey:@"model"];
    1021             :                         if (shipMode && [modelName length] == 0)
    1022             :                         {
    1023             :                                 OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies no %@.", shipKey, @"model");
    1024             :                                 OOStandardsError(@"Error in shipdata.plist");
    1025             :                                 remove = YES;
    1026             :                         }
    1027             :                         else if ([modelName length] != 0 && [ResourceManager pathForFileNamed:modelName inFolder:@"Models"] == nil)
    1028             :                         {
    1029             :                                 OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies non-existent model \"%@\".", shipKey, modelName);
    1030             :                                 OOStandardsError(@"Error in shipdata.plist");
    1031             :                                 remove = YES;
    1032             :                         }
    1033             :                 }
    1034             :                 if (remove)  [ioData removeObjectForKey:shipKey];
    1035             :         }
    1036             :         
    1037             :         return YES;
    1038             : }
    1039             : 
    1040             : 
    1041             : /*      Transform conditions, determinant (if conditions array) and
    1042             :         shipyard.conditions from hasShipyard to sanitized form.
    1043             :   Also get list of condition_scripts
    1044             : */
    1045             : - (BOOL) sanitizeConditions:(NSMutableDictionary *)ioData
    1046             : {
    1047             :         NSEnumerator                    *shipKeyEnum = nil;
    1048             :         NSString                                *shipKey = nil;
    1049             :         NSMutableDictionary             *shipEntry = nil;
    1050             :         NSMutableDictionary             *mutableShipyard = nil;
    1051             :         NSArray                                 *conditions = nil;
    1052             :         NSArray                                 *hasShipyard = nil;
    1053             :         NSArray                                 *shipyardConditions = nil;
    1054             :         NSString        *condition_script = nil;
    1055             :         NSString        *shipyard_condition_script = nil;
    1056             :         
    1057             :         NSMutableArray *conditionScripts = [[NSMutableArray alloc] init];
    1058             : 
    1059             :         for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
    1060             :         {
    1061             :                 shipEntry = [ioData objectForKey:shipKey];
    1062             :                 conditions = [shipEntry objectForKey:@"conditions"];
    1063             :                 condition_script = [shipEntry oo_stringForKey:@"condition_script"];
    1064             :                 if (condition_script != nil)
    1065             :                 {
    1066             :                         if (![conditionScripts containsObject:condition_script])
    1067             :                         {
    1068             :                                 [conditionScripts addObject:condition_script];
    1069             :                         }
    1070             :                 }
    1071             : 
    1072             :                 hasShipyard = [shipEntry objectForKey:@"has_shipyard"];
    1073             :                 if (![hasShipyard isKindOfClass:[NSArray class]])  hasShipyard = nil;   // May also be fuzzy boolean
    1074             :                 if (hasShipyard == nil)
    1075             :                 {
    1076             :                         hasShipyard = [shipEntry objectForKey:@"hasShipyard"];
    1077             :                         if (![hasShipyard isKindOfClass:[NSArray class]])  hasShipyard = nil;   // May also be fuzzy boolean
    1078             :                 }
    1079             :                 shipyardConditions = [[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] objectForKey:@"conditions"];
    1080             :                 shipyard_condition_script = [[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] oo_stringForKey:@"condition_script"];
    1081             :                 if (shipyard_condition_script != nil)
    1082             :                 {
    1083             :                         if (![conditionScripts containsObject:shipyard_condition_script])
    1084             :                         {
    1085             :                                 [conditionScripts addObject:shipyard_condition_script];
    1086             :                         }
    1087             :                 }
    1088             : 
    1089             :                 
    1090             :                 if (conditions == nil && hasShipyard && shipyardConditions == nil)  continue;
    1091             :                 
    1092             :                 if (conditions != nil)
    1093             :                 {
    1094             :                         OOStandardsDeprecated([NSString stringWithFormat:@"The 'conditions' key is deprecated in shipdata entry %@",shipKey]);
    1095             :                         if (!OOEnforceStandards())
    1096             :                         {
    1097             :                                 if ([conditions isKindOfClass:[NSArray class]])
    1098             :                                 {
    1099             :                                         conditions = OOSanitizeLegacyScriptConditions(conditions, [NSString stringWithFormat:@"<shipdata.plist entry \"%@\">", shipKey]);
    1100             :                                 }
    1101             :                                 else
    1102             :                                 {
    1103             :                                         OOLogWARN(@"shipdata.load.warning", @"conditions for shipdata.plist entry \"%@\" are not an array, ignoring.", shipKey);
    1104             :                                         conditions = nil;
    1105             :                                 }
    1106             :                         
    1107             :                                 if (conditions != nil)
    1108             :                                 {
    1109             :                                         [shipEntry setObject:conditions forKey:@"conditions"];
    1110             :                                 }
    1111             :                                 else
    1112             :                                 {
    1113             :                                         [shipEntry removeObjectForKey:@"conditions"];
    1114             :                                 }
    1115             :                         }
    1116             :                 }
    1117             :                 
    1118             :                 if (hasShipyard != nil)
    1119             :                 {
    1120             :                         hasShipyard = OOSanitizeLegacyScriptConditions(hasShipyard, [NSString stringWithFormat:@"<shipdata.plist entry \"%@\" hasShipyard conditions>", shipKey]);
    1121             :                         OOStandardsDeprecated([NSString stringWithFormat:@"Use of legacy script conditions in the 'has_shipyard' key is deprecated in shipyard entry %@",shipKey]);
    1122             :                         if (!OOEnforceStandards())
    1123             :                         {
    1124             :                                 if (hasShipyard != nil)
    1125             :                                 {
    1126             :                                         [shipEntry setObject:hasShipyard forKey:@"has_shipyard"];
    1127             :                                 }
    1128             :                                 else
    1129             :                                 {
    1130             :                                         [shipEntry removeObjectForKey:@"hasShipyard"];
    1131             :                                         [shipEntry removeObjectForKey:@"has_shipyard"];
    1132             :                                 }
    1133             :                         }
    1134             :                 }
    1135             :                 
    1136             :                 if (shipyardConditions != nil)
    1137             :                 {
    1138             :                         OOStandardsDeprecated([NSString stringWithFormat:@"The 'conditions' key is deprecated in shipyard entry %@",shipKey]);
    1139             :                         if (!OOEnforceStandards())
    1140             :                         {
    1141             :                                 mutableShipyard = [[[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] mutableCopy] autorelease];
    1142             :                         
    1143             :                                 if ([shipyardConditions isKindOfClass:[NSArray class]])
    1144             :                                 {
    1145             :                                         shipyardConditions = OOSanitizeLegacyScriptConditions(shipyardConditions, [NSString stringWithFormat:@"<shipyard.plist entry \"%@\">", shipKey]);
    1146             :                                 }
    1147             :                                 else
    1148             :                                 {
    1149             :                                         OOLogWARN(@"shipdata.load.warning", @"conditions for shipyard.plist entry \"%@\" are not an array, ignoring.", shipKey);
    1150             :                                         shipyardConditions = nil;
    1151             :                                 }
    1152             :                         
    1153             :                                 if (shipyardConditions != nil)
    1154             :                                 {
    1155             :                                         [mutableShipyard setObject:shipyardConditions forKey:@"conditions"];
    1156             :                                 }
    1157             :                                 else
    1158             :                                 {
    1159             :                                         [mutableShipyard removeObjectForKey:@"conditions"];
    1160             :                                 }
    1161             :                         
    1162             :                                 [shipEntry setObject:mutableShipyard forKey:@"_oo_shipyard"];
    1163             :                         }
    1164             :                 }
    1165             :         }
    1166             : 
    1167             :         [[OOCacheManager sharedCache] setObject:conditionScripts forKey:@"ship conditions" inCache:@"condition scripts"];
    1168             :         [conditionScripts release];
    1169             : 
    1170             :         return YES;
    1171             : }
    1172             : 
    1173             : 
    1174             : #if PRELOAD
    1175             : - (BOOL) preloadShipMeshes:(NSMutableDictionary *)ioData
    1176             : {
    1177             :         NSEnumerator                    *shipKeyEnum = nil;
    1178             :         NSString                                *shipKey = nil;
    1179             :         NSMutableDictionary             *shipEntry = nil;
    1180             :         BOOL                                    remove;
    1181             :         NSString                                *modelName = nil;
    1182             :         OOMesh                                  *mesh = nil;
    1183             :         NSAutoreleasePool               *pool = nil;
    1184             :         NSUInteger                              i = 0, count;
    1185             :         
    1186             :         count = [ioData count];
    1187             :         
    1188             :         // Preload ship meshes. (Iterates over a copy of keys since it mutates the dictionary.)
    1189             :         for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
    1190             :         {
    1191             :                 pool = [[NSAutoreleasePool alloc] init];
    1192             :                 
    1193             :                 [[GameController sharedController] setProgressBarValue:(float)i++ / (float)count];
    1194             :                 
    1195             :                 shipEntry = [ioData objectForKey:shipKey];
    1196             :                 remove = NO;
    1197             :                 
    1198             :                 modelName = [shipEntry oo_stringForKey:@"model"];
    1199             :                 mesh = [OOMesh meshWithName:modelName
    1200             :                                  materialDictionary:[shipEntry oo_dictionaryForKey:@"materials"]
    1201             :                                   shadersDictionary:[shipEntry oo_dictionaryForKey:@"shaders"]
    1202             :                                                          smooth:[shipEntry oo_boolForKey:@"smooth"]
    1203             :                                            shaderMacros:nil
    1204             :                                 shaderBindingTarget:nil];
    1205             :                 
    1206             :                 [pool release]; // NOTE: mesh is now invalid, but pointer nil check is OK.
    1207             :                 
    1208             :                 if (mesh == nil)
    1209             :                 {
    1210             :                         // FIXME: what if it's a subentity? Need to rearrange things.
    1211             :                         OOLogERR(@"shipData.load.error", @"model \"%@\" could not be loaded for ship \"%@\", removing.", modelName, shipKey);
    1212             :                         [ioData removeObjectForKey:shipKey];
    1213             :                 }
    1214             :         }
    1215             :         
    1216             :         [[GameController sharedController] setProgressBarValue:-1.0f];
    1217             :         
    1218             :         return YES;
    1219             : }
    1220             : #endif
    1221             : 
    1222             : 
    1223             : - (void) mergeShipRoles:(NSString *)roles
    1224             :                          forShipKey:(NSString *)shipKey
    1225             :          intoProbabilityMap:(NSMutableDictionary *)probabilitySets
    1226             : {
    1227             :         NSDictionary                    *rolesAndWeights = nil;
    1228             :         NSEnumerator                    *roleEnum = nil;
    1229             :         NSString                                *role = nil;
    1230             :         OOMutableProbabilitySet *probSet = nil;
    1231             : 
    1232             :         
    1233             :         /*      probabilitySets is a dictionary whose keys are roles and whose values
    1234             :                 are mutable probability sets, whose values are ship keys.
    1235             :                 
    1236             :                 When creating new ships Oolite looks up this probability map.
    1237             :                 To upgrade all soliton 'thargon' roles to 'EQ_THARGON' we need
    1238             :                 to swap these roles here.
    1239             :         */
    1240             :         
    1241             :         rolesAndWeights = OOParseRolesFromString(roles);
    1242             :   // add default [shipKey] role
    1243             :         NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithDictionary:rolesAndWeights];
    1244             :         [mutable setObject:[NSNumber numberWithFloat:1.0] forKey:[[[NSString alloc] initWithFormat:@"[%@]",shipKey] autorelease]];
    1245             :         rolesAndWeights = mutable;
    1246             :         
    1247             :         id thargonValue = [rolesAndWeights objectForKey:@"thargon"];
    1248             :         if (thargonValue != nil && [rolesAndWeights objectForKey:@"EQ_THARGON"] == nil)
    1249             :         {
    1250             :                 NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithDictionary:rolesAndWeights];
    1251             :                 [mutable setObject:thargonValue forKey:@"EQ_THARGON"];
    1252             :                 rolesAndWeights = mutable;
    1253             :         }
    1254             :         
    1255             :         for (roleEnum = [rolesAndWeights keyEnumerator]; (role = [roleEnum nextObject]); )
    1256             :         {
    1257             :                 probSet = [probabilitySets objectForKey:role];
    1258             :                 if (probSet == nil)
    1259             :                 {
    1260             :                         probSet = [OOMutableProbabilitySet probabilitySet];
    1261             :                         [probabilitySets setObject:probSet forKey:role];
    1262             :                 }
    1263             :                 
    1264             :                 [probSet setWeight:[rolesAndWeights oo_floatForKey:role] forObject:shipKey];
    1265             :         }
    1266             : }
    1267             : 
    1268             : 
    1269             : - (NSDictionary *) canonicalizeSubentityDeclaration:(id)declaration
    1270             :                                                                                         forShip:(NSString *)shipKey
    1271             :                                                                                    shipData:(NSDictionary *)shipData
    1272             :                                                                                  fatalError:(BOOL *)outFatalError
    1273             : {
    1274             :         NSDictionary                    *result = nil;
    1275             :         
    1276             :         assert(outFatalError != NULL);
    1277             :         *outFatalError = NO;
    1278             :         
    1279             :         if ([declaration isKindOfClass:[NSString class]])
    1280             :         {
    1281             :                 // Update old-style string-based declaration.
    1282             :                 OOStandardsDeprecated([NSString stringWithFormat:@"Old style sub-entity declarations are deprecated in %@",shipKey]);
    1283             :                 if (!OOEnforceStandards())
    1284             :                 {
    1285             :                         result = [self translateOldStyleSubentityDeclaration:declaration
    1286             :                                                                                                                  forShip:shipKey
    1287             :                                                                                                                 shipData:shipData
    1288             :                                                                                                           fatalError:outFatalError];
    1289             :                 }
    1290             :                 if (result != nil)
    1291             :                 {
    1292             :                         // Ensure internal translation made sense, and clean up a bit.
    1293             :                         result = [self validateNewStyleSubentityDeclaration:result
    1294             :                                                                                                                 forShip:shipKey
    1295             :                                                                                                          fatalError:outFatalError];
    1296             :                 }
    1297             :         }
    1298             :         else if ([declaration isKindOfClass:[NSDictionary class]])
    1299             :         {
    1300             :                 // Validate dictionary-based declaration.
    1301             :                 result = [self validateNewStyleSubentityDeclaration:declaration
    1302             :                                                                                                         forShip:shipKey
    1303             :                                                                                                  fatalError:outFatalError];
    1304             :         }
    1305             :         else
    1306             :         {
    1307             :                 OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ should be string or dictionary, found %@.", shipKey, [declaration class]);
    1308             :                 *outFatalError = YES;
    1309             :         }
    1310             :         
    1311             :         // For frangible ships, bad subentities are non-fatal.
    1312             :         if (*outFatalError && [[shipData oo_dictionaryForKey:shipKey] oo_boolForKey:@"frangible"])  *outFatalError = NO;
    1313             :         
    1314             :         return result;
    1315             : }
    1316             : 
    1317             : 
    1318             : - (NSDictionary *) translateOldStyleSubentityDeclaration:(NSString *)declaration
    1319             :                                                                                                  forShip:(NSString *)shipKey
    1320             :                                                                                                 shipData:(NSDictionary *)shipData
    1321             :                                                                                           fatalError:(BOOL *)outFatalError
    1322             : {
    1323             :         NSArray                                 *tokens = nil;
    1324             :         NSString                                *subentityKey = nil;
    1325             :         BOOL                                    isFlasher;
    1326             :         
    1327             :         tokens = ScanTokensFromString(declaration);
    1328             :         
    1329             :         subentityKey = [tokens objectAtIndex:0];
    1330             :         isFlasher = [subentityKey isEqualToString:@"*FLASHER*"];
    1331             :         
    1332             :         // Sanity check: require eight tokens.
    1333             :         if ([tokens count] != 8)
    1334             :         {
    1335             :                 if (!isFlasher)
    1336             :                 {
    1337             :                         OOLogERR(@"shipData.load.error.badSubentity", @"the shipdata.plist entry \"%@\" has a broken subentity definition \"%@\" (should have 8 tokens, has %lu).", shipKey, subentityKey, [tokens count]);
    1338             :                         *outFatalError = YES;
    1339             :                 }
    1340             :                 else
    1341             :                 {
    1342             :                         OOLogWARN(@"shipData.load.warning.badFlasher", @"the shipdata.plist entry \"%@\" has a broken flasher definition (should have 8 tokens, has %lu). This flasher will be ignored.", shipKey, [tokens count]);
    1343             :                 }
    1344             :                 return nil;
    1345             :         }
    1346             :         
    1347             :         if (isFlasher)
    1348             :         {
    1349             :                 return [self translateOldStyleFlasherDeclaration:tokens
    1350             :                                                                                                  forShip:shipKey
    1351             :                                                                                           fatalError:outFatalError];
    1352             :         }
    1353             :         else
    1354             :         {
    1355             :                 return [self translateOldStandardBasicSubentityDeclaration:tokens
    1356             :                                                                                                                    forShip:shipKey
    1357             :                                                                                                                   shipData:shipData
    1358             :                                                                                                                 fatalError:outFatalError];
    1359             :         }
    1360             : }
    1361             : 
    1362             : 
    1363             : - (NSDictionary *) translateOldStyleFlasherDeclaration:(NSArray *)tokens
    1364             :                                                                                            forShip:(NSString *)shipKey
    1365             :                                                                                         fatalError:(BOOL *)outFatalError
    1366             : {
    1367             :         Vector                                  position;
    1368             :         float                                   size, frequency, phase, hue;
    1369             :         NSDictionary                    *colorDict = nil;
    1370             :         NSDictionary                    *result = nil;
    1371             :         
    1372             :         position.x = [tokens oo_floatAtIndex:1];
    1373             :         position.y = [tokens oo_floatAtIndex:2];
    1374             :         position.z = [tokens oo_floatAtIndex:3];
    1375             :         
    1376             :         hue = [tokens oo_floatAtIndex:4];
    1377             :         frequency = [tokens oo_floatAtIndex:5];
    1378             :         phase = [tokens oo_floatAtIndex:6];
    1379             :         size = [tokens oo_floatAtIndex:7];
    1380             :         
    1381             :         colorDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:hue] forKey:@"hue"];
    1382             :         
    1383             :         result = [NSDictionary dictionaryWithObjectsAndKeys:
    1384             :                           @"flasher", @"type",
    1385             :                           OOPropertyListFromVector(position), @"position",
    1386             :                           [NSArray arrayWithObject:colorDict], @"colors",
    1387             :                           [NSNumber numberWithFloat:frequency], @"frequency",
    1388             :                           [NSNumber numberWithFloat:phase], @"phase",
    1389             :                           [NSNumber numberWithFloat:size], @"size",
    1390             :                           nil];
    1391             :         
    1392             :         OOLog(@"shipData.translateSubentity.flasher", @"Translated flasher declaration \"%@\" to %@", [tokens componentsJoinedByString:@" "], result);
    1393             :         
    1394             :         return result;
    1395             : }
    1396             : 
    1397             : 
    1398             : - (NSDictionary *) translateOldStandardBasicSubentityDeclaration:(NSArray *)tokens
    1399             :                                                                                                                  forShip:(NSString *)shipKey
    1400             :                                                                                                                 shipData:(NSDictionary *)shipData
    1401             :                                                                                                           fatalError:(BOOL *)outFatalError
    1402             : {
    1403             :         NSString                                *subentityKey = nil;
    1404             :         Vector                                  position;
    1405             :         Quaternion                              orientation;
    1406             :         NSMutableDictionary             *result = nil;
    1407             :         BOOL                                    isTurret, isDock = NO;
    1408             :         
    1409             :         subentityKey = [tokens oo_stringAtIndex:0];
    1410             :         
    1411             :         isTurret = [self shipIsBallTurretForKey:subentityKey inShipData:shipData];
    1412             :         
    1413             :         position.x = [tokens oo_floatAtIndex:1];
    1414             :         position.y = [tokens oo_floatAtIndex:2];
    1415             :         position.z = [tokens oo_floatAtIndex:3];
    1416             :         
    1417             :         orientation.w = [tokens oo_floatAtIndex:4];
    1418             :         orientation.x = [tokens oo_floatAtIndex:5];
    1419             :         orientation.y = [tokens oo_floatAtIndex:6];
    1420             :         orientation.z = [tokens oo_floatAtIndex:7];
    1421             :         
    1422             :         if(orientation.w == 0 && orientation.x == 0 && orientation.y == 0 && orientation.z == 0) 
    1423             :         {
    1424             :                 orientation.w = 1; // avoid dividing by zero.
    1425             :                 OOLogWARN(@"shipData.load.error", @"The ship %@ has an undefined orientation for its %@ subentity. Setting it now at (1,0,0,0)", shipKey, subentityKey);
    1426             :         }
    1427             :         
    1428             :         quaternion_normalize(&orientation);
    1429             :         
    1430             :         if (!isTurret)
    1431             :         {
    1432             :                 isDock = [subentityKey rangeOfString:@"dock"].location != NSNotFound;
    1433             :         }
    1434             :         
    1435             :         result = [NSMutableDictionary dictionaryWithCapacity:5];
    1436             :         [result setObject:isTurret ? @"ball_turret" : @"standard" forKey:@"type"];
    1437             :         [result setObject:subentityKey forKey:@"subentity_key"];
    1438             :         [result oo_setVector:position forKey:@"position"];
    1439             :         [result oo_setQuaternion:orientation forKey:@"orientation"];
    1440             :         if (isDock)  [result oo_setBool:YES forKey:@"is_dock"];
    1441             :         
    1442             :         OOLog(@"shipData.translateSubentity.standard", @"Translated subentity declaration \"%@\" to %@", [tokens componentsJoinedByString:@" "], result);
    1443             :         
    1444             :         return [[result copy] autorelease];
    1445             : }
    1446             : 
    1447             : 
    1448             : - (NSDictionary *) validateNewStyleSubentityDeclaration:(NSDictionary *)declaration
    1449             :                                                                                                 forShip:(NSString *)shipKey
    1450             :                                                                                          fatalError:(BOOL *)outFatalError
    1451             : {
    1452             :         NSString                                *type = nil;
    1453             :         
    1454             :         type = [declaration oo_stringForKey:@"type"];
    1455             :         if (type == nil)  type = @"standard";
    1456             :         
    1457             :         if ([type isEqualToString:@"flasher"])
    1458             :         {
    1459             :                 return [self validateNewStyleFlasherDeclaration:declaration forShip:shipKey fatalError:outFatalError];
    1460             :         }
    1461             :         else if ([type isEqualToString:@"standard"] || [type isEqualToString:@"ball_turret"])
    1462             :         {
    1463             :                 return [self validateNewStyleStandardSubentityDeclaration:declaration forShip:shipKey fatalError:outFatalError];
    1464             :         }
    1465             :         else
    1466             :         {
    1467             :                 OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ does not declare a valid type (must be standard, flasher or ball_turret).", shipKey);
    1468             :                 *outFatalError = YES;
    1469             :                 return nil;
    1470             :         }
    1471             : }
    1472             : 
    1473             : 
    1474             : - (NSDictionary *) validateNewStyleFlasherDeclaration:(NSDictionary *)declaration
    1475             :                                                                                           forShip:(NSString *)shipKey
    1476             :                                                                                    fatalError:(BOOL *)outFatalError
    1477             : {
    1478             :         NSMutableDictionary             *result = nil;
    1479             :         Vector                                  position = kZeroVector;
    1480             :         NSArray                                 *colors = nil;
    1481             :         id                                              colorDesc = nil;
    1482             :         float                                   size, frequency, phase, brightfraction;
    1483             :         BOOL                                    initiallyOn;
    1484             :         
    1485           0 : #define kDefaultFlasherColor @"redColor"
    1486             :         
    1487             :         // "Validate" is really "clean up", since all values have defaults.
    1488             :         colors = [declaration oo_arrayForKey:@"colors"];
    1489             :         if ([colors count] == 0)
    1490             :         {
    1491             :                 colorDesc = [declaration objectForKey:@"color"];
    1492             :                 if (colorDesc == nil) colorDesc = kDefaultFlasherColor;
    1493             :                 if ([colorDesc isKindOfClass:[NSArray class]])
    1494             :                 {
    1495             :                         // an easy made error is adding an array to "color" instead of "colors"
    1496             :                         OOLogWARN(@"shipData.load.warning.flasher.badColor", @"changing flasher for ship %@ from a color to a colors definition.", shipKey);
    1497             :                         colors = colorDesc;
    1498             :                 }
    1499             :                 else
    1500             :                 {
    1501             :                         colors = [NSArray arrayWithObject:colorDesc];
    1502             :                 }
    1503             :         }
    1504             :         
    1505             :         // Validate colours.
    1506             :         NSMutableArray *validColors = [NSMutableArray arrayWithCapacity:[colors count]];
    1507             :         foreach (colorDesc, colors)
    1508             :         {
    1509             :                 OOColor *color = [OOColor colorWithDescription:colorDesc];
    1510             :                 if (color != nil)
    1511             :                 {
    1512             :                         [validColors addObject:[color normalizedArray]];
    1513             :                 }
    1514             :                 else
    1515             :                 {
    1516             :                         OOLogWARN(@"shipdata.load.warning.flasher.badColor", @"skipping invalid colour specifier for flasher for ship %@.", shipKey);
    1517             :                 }
    1518             :         }
    1519             :         // Ensure there's at least one.
    1520             :         if ([validColors count] == 0)
    1521             :         {
    1522             :                 [validColors addObject:kDefaultFlasherColor];
    1523             :         }
    1524             :         colors = validColors;
    1525             :         
    1526             :         position = [declaration oo_vectorForKey:@"position"];
    1527             :         
    1528             :         size = [declaration oo_floatForKey:@"size" defaultValue:8.0];
    1529             :         
    1530             :         if (size <= 0)
    1531             :         {
    1532             :                 OOLogWARN(@"shipData.load.warning.flasher.badSize", @"skipping flasher of invalid size %g for ship %@.", size, shipKey);
    1533             :                 return nil;
    1534             :         }
    1535             : 
    1536             :         brightfraction = [declaration oo_floatForKey:@"bright_fraction" defaultValue:0.5];
    1537             :         if (brightfraction < 0.0 || brightfraction > 1.0)
    1538             :         {
    1539             :                 OOLogWARN(@"shipData.load.warning.flasher.badFraction", @"skipping flasher of invalid bright fraction %g for ship %@.", brightfraction, shipKey);
    1540             :                 return nil;
    1541             :         }
    1542             :         
    1543             :         frequency = [declaration oo_floatForKey:@"frequency" defaultValue:2.0];
    1544             :         phase = [declaration oo_floatForKey:@"phase" defaultValue:0.0];
    1545             :         initiallyOn = [declaration oo_boolForKey:@"initially_on" defaultValue:YES];
    1546             :         
    1547             :         result = [NSMutableDictionary dictionaryWithCapacity:8];
    1548             :         [result setObject:@"flasher" forKey:@"type"];
    1549             :         [result setObject:colors forKey:@"colors"];
    1550             :         [result oo_setVector:position forKey:@"position"];
    1551             :         [result setObject:[NSNumber numberWithFloat:size] forKey:@"size"];
    1552             :         [result setObject:[NSNumber numberWithFloat:frequency] forKey:@"frequency"];
    1553             :         if (phase != 0)  [result setObject:[NSNumber numberWithFloat:phase] forKey:@"phase"];
    1554             :         [result setObject:[NSNumber numberWithFloat:brightfraction] forKey:@"bright_fraction"];
    1555             :         [result setObject:[NSNumber numberWithBool:initiallyOn] forKey:@"initially_on"];
    1556             :         
    1557             :         return [[result copy] autorelease];
    1558             : }
    1559             : 
    1560             : 
    1561             : - (NSDictionary *) validateNewStyleStandardSubentityDeclaration:(NSDictionary *)declaration
    1562             :                                                                                                                 forShip:(NSString *)shipKey
    1563             :                                                                                                          fatalError:(BOOL *)outFatalError
    1564             : {
    1565             :         NSMutableDictionary             *result = nil;
    1566             :         NSString                                *subentityKey = nil;
    1567             :         Vector                                  position = kZeroVector;
    1568             :         Quaternion                              orientation = kIdentityQuaternion;
    1569             :         BOOL                                    isTurret;
    1570             :         BOOL                                    isDock = NO;
    1571             :         float                                   fireRate = -1.0f; // out of range constants
    1572             :         float                                   weaponRange = -1.0f;
    1573             :         float                                   weaponEnergy = -1.0f;
    1574             :         NSDictionary                    *scriptInfo = nil;
    1575             :         
    1576             :         subentityKey = [declaration objectForKey:@"subentity_key"];
    1577             :         if (subentityKey == nil)
    1578             :         {
    1579             :                 OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ specifies no subentity_key.", shipKey);
    1580             :                 *outFatalError = YES;
    1581             :                 return nil;
    1582             :         }
    1583             :         
    1584             :         isTurret = [[declaration oo_stringForKey:@"type"] isEqualToString:@"ball_turret"];
    1585             :         if (isTurret)
    1586             :         {
    1587             :                 fireRate = [declaration oo_floatForKey:@"fire_rate" defaultValue:-1.0f];
    1588             :                 if (fireRate < 0.25f && fireRate >= 0.0f)
    1589             :                 {
    1590             :                         OOLogWARN(@"shipData.load.warning.turret.badFireRate", @"ball turret fire rate of %g for subentity of ship %@ is invalid, using 0.25.", fireRate, shipKey);
    1591             :                         fireRate = 0.25f;
    1592             :                 }
    1593             :                 weaponRange = [declaration oo_floatForKey:@"weapon_range" defaultValue:-1.0f];
    1594             :                 if (weaponRange > TURRET_SHOT_RANGE * COMBAT_WEAPON_RANGE_FACTOR)
    1595             :                 {
    1596             :                         OOLogWARN(@"shipData.load.warning.turret.badWeaponRange", @"ball turret weapon range of %g for subentity of ship %@ is too high, using %.1f.", weaponRange, shipKey, TURRET_SHOT_RANGE * COMBAT_WEAPON_RANGE_FACTOR);
    1597             :                         weaponRange = TURRET_SHOT_RANGE * COMBAT_WEAPON_RANGE_FACTOR; // approx. range of primary plasma canon.
    1598             :                 }
    1599             : 
    1600             :                 weaponEnergy = [declaration oo_floatForKey:@"weapon_energy" defaultValue:-1.0f];
    1601             :                 if (weaponEnergy > 100.0f)
    1602             :                         
    1603             :                 {
    1604             :                         OOLogWARN(@"shipData.load.warning.turret.badWeaponEnergy", @"ball turret weapon energy of %g for subentity of ship %@ is too high, using 100.", weaponEnergy, shipKey);
    1605             :                         weaponEnergy = 100.0f;
    1606             :                 }
    1607             :         }
    1608             :         else
    1609             :         {
    1610             :                 isDock = [declaration oo_boolForKey:@"is_dock"];
    1611             :         }
    1612             :         
    1613             :         position = [declaration oo_vectorForKey:@"position"];
    1614             :         orientation = [declaration oo_quaternionForKey:@"orientation"];
    1615             :         quaternion_normalize(&orientation);
    1616             :         
    1617             :         scriptInfo = [declaration oo_dictionaryForKey:@"script_info"];
    1618             :         
    1619             :         result = [NSMutableDictionary dictionaryWithCapacity:10];
    1620             :         [result setObject:isTurret ? @"ball_turret" : @"standard" forKey:@"type"];
    1621             :         [result setObject:subentityKey forKey:@"subentity_key"];
    1622             :         [result oo_setVector:position forKey:@"position"];
    1623             :         [result oo_setQuaternion:orientation forKey:@"orientation"];
    1624             :         if (isDock) 
    1625             :         {
    1626             :                 [result oo_setBool:YES forKey:@"is_dock"];
    1627             : 
    1628             :                 NSString* docklabel = [declaration oo_stringForKey:@"dock_label" defaultValue:@"the docking bay"];
    1629             :                 [result setObject:docklabel forKey:@"dock_label"];
    1630             : 
    1631             :                 BOOL dockable = [declaration oo_boolForKey:@"allow_docking" defaultValue:YES];
    1632             :                 BOOL playerdockable = [declaration oo_boolForKey:@"disallowed_docking_collides" defaultValue:NO];
    1633             :                 BOOL undockable = [declaration oo_boolForKey:@"allow_launching" defaultValue:YES];
    1634             : 
    1635             :                 [result oo_setBool:dockable forKey:@"allow_docking"];
    1636             :                 [result oo_setBool:playerdockable forKey:@"disallowed_docking_collides"];
    1637             :                 [result oo_setBool:undockable forKey:@"allow_launching"];
    1638             : 
    1639             :         }
    1640             : 
    1641             :         if (isTurret)
    1642             :         {
    1643             :                 // default constants are defined and set in shipEntity
    1644             :                 if (fireRate > 0) [result oo_setFloat:fireRate forKey:@"fire_rate"];
    1645             :                 if (weaponRange >= 0) [result oo_setFloat:weaponRange forKey:@"weapon_range"];
    1646             :                 if (weaponEnergy >= 0) [result oo_setFloat:weaponEnergy forKey:@"weapon_energy"];
    1647             :         }
    1648             :         
    1649             :         if (scriptInfo != nil)
    1650             :         {
    1651             :                 [result setObject:scriptInfo forKey:@"script_info"];
    1652             :         }
    1653             :         
    1654             :         return [[result copy] autorelease];
    1655             : }
    1656             : 
    1657             : 
    1658             : - (BOOL) shipIsBallTurretForKey:(NSString *)shipKey inShipData:(NSDictionary *)shipData
    1659             : {
    1660             :         // Test for presence of setup_actions containing initialiseTurret.
    1661             :         NSArray                                 *setupActions = nil;
    1662             :         NSEnumerator                    *actionEnum = nil;
    1663             :         NSString                                *action = nil;
    1664             :         
    1665             :         setupActions = [[shipData oo_dictionaryForKey:shipKey] oo_arrayForKey:@"setup_actions"];
    1666             :         
    1667             :         for (actionEnum = [setupActions objectEnumerator]; (action = [actionEnum nextObject]); )
    1668             :         {
    1669             :                 if ([[ScanTokensFromString(action) objectAtIndex:0] isEqualToString:@"initialiseTurret"])  return YES;
    1670             :         }
    1671             :         
    1672             :         if ([shipKey isEqualToString:@"ballturret"])
    1673             :         {
    1674             :                 // compatibility for OXPs using old subentity declarations and the
    1675             :                 // core turret entity
    1676             :                 return YES;
    1677             :         }
    1678             : 
    1679             :         return NO;
    1680             : }
    1681             : 
    1682             : @end
    1683             : 
    1684             : 
    1685             : @implementation OOShipRegistry (Singleton)
    1686             : 
    1687             : /*      Canonical singleton boilerplate.
    1688             :         See Cocoa Fundamentals Guide: Creating a Singleton Instance.
    1689             :         See also +sharedRegistry above.
    1690             :         
    1691             :         NOTE: assumes single-threaded access.
    1692             : */
    1693             : 
    1694           0 : + (id) allocWithZone:(NSZone *)inZone
    1695             : {
    1696             :         if (sSingleton == nil)
    1697             :         {
    1698             :                 OOLog(@"shipData.load.begin", @"%@", @"Loading ship data.");
    1699             :                 sSingleton = [super allocWithZone:inZone];
    1700             :                 return sSingleton;
    1701             :         }
    1702             :         return nil;
    1703             : }
    1704             : 
    1705             : 
    1706           0 : - (id) copyWithZone:(NSZone *)inZone
    1707             : {
    1708             :         return self;
    1709             : }
    1710             : 
    1711             : 
    1712           0 : - (id) retain
    1713             : {
    1714             :         return self;
    1715             : }
    1716             : 
    1717             : 
    1718           0 : - (NSUInteger) retainCount
    1719             : {
    1720             :         return UINT_MAX;
    1721             : }
    1722             : 
    1723             : 
    1724           0 : - (void) release
    1725             : {}
    1726             : 
    1727             : 
    1728           0 : - (id) autorelease
    1729             : {
    1730             :         return self;
    1731             : }
    1732             : 
    1733             : @end
    1734             : 
    1735             : 
    1736             : static void GatherStringAddrsDict(NSDictionary *dict, NSMutableSet *strings, NSString *context);
    1737             : static void GatherStringAddrsArray(NSArray *array, NSMutableSet *strings, NSString *context);
    1738             : static void GatherStringAddrs(id object, NSMutableSet *strings, NSString *context);
    1739             : 
    1740             : 
    1741           0 : static void DumpStringAddrs(NSDictionary *dict, NSString *context)
    1742             : {
    1743             :         return;
    1744             :         static FILE *dump = NULL;
    1745             :         if (dump == NULL)  dump = fopen("strings.txt", "w");
    1746             :         if (dump == NULL)  return;
    1747             :         
    1748             :         NSAutoreleasePool *pool = [NSAutoreleasePool new];
    1749             :         NSMutableSet *strings = [NSMutableSet set];
    1750             :         GatherStringAddrs(dict, strings, context);
    1751             :         
    1752             :         NSEnumerator *entryEnum = nil;
    1753             :         NSDictionary *entry = nil;
    1754             :         for (entryEnum = [strings objectEnumerator]; (entry = [entryEnum nextObject]); )
    1755             :         {
    1756             :                 NSString *string = [entry objectForKey:@"string"];
    1757             :                 NSString *context = [entry objectForKey:@"context"];
    1758             :                 void *pointer = [[entry objectForKey:@"address"] pointerValue];
    1759             :                 
    1760             :                 string = [NSString stringWithFormat:@"%p\t%@:  \"%@\"", pointer, context, string];
    1761             :                 
    1762             :                 fprintf(dump, "%s\n", [string UTF8String]);
    1763             :         }
    1764             :         
    1765             :         fprintf(dump, "\n");
    1766             :         fflush(dump);
    1767             :         [pool release];
    1768             : }
    1769             : 
    1770             : 
    1771           0 : static void GatherStringAddrsDict(NSDictionary *dict, NSMutableSet *strings, NSString *context)
    1772             : {
    1773             :         NSEnumerator *keyEnum = nil;
    1774             :         id key = nil;
    1775             :         NSString *keyContext = [context stringByAppendingString:@" key"];
    1776             :         for (keyEnum = [dict keyEnumerator]; (key = [keyEnum nextObject]); )
    1777             :         {
    1778             :                 GatherStringAddrs(key, strings, keyContext);
    1779             :                 GatherStringAddrs([dict objectForKey:key], strings, [context stringByAppendingFormat:@".%@", key]);
    1780             :         }
    1781             : }
    1782             : 
    1783             : 
    1784           0 : static void GatherStringAddrsArray(NSArray *array, NSMutableSet *strings, NSString *context)
    1785             : {
    1786             :         NSEnumerator *vEnum = nil;
    1787             :         NSString *v = nil;
    1788             :         unsigned i = 0;
    1789             :         for (vEnum = [array objectEnumerator]; (v = [vEnum nextObject]); )
    1790             :         {
    1791             :                 GatherStringAddrs(v, strings, [context stringByAppendingFormat:@"[%u]", i++]);
    1792             :         }
    1793             : }
    1794             : 
    1795             : 
    1796           0 : static void GatherStringAddrs(id object, NSMutableSet *strings, NSString *context)
    1797             : {
    1798             :         if ([object isKindOfClass:[NSString class]])
    1799             :         {
    1800             :                 NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:object, @"string", [NSValue valueWithPointer:object], @"address", context, @"context", nil];
    1801             :                 [strings addObject:entry];
    1802             :         }
    1803             :         else if ([object isKindOfClass:[NSArray class]])
    1804             :         {
    1805             :                 GatherStringAddrsArray(object, strings, context);
    1806             :         }
    1807             :         else if ([object isKindOfClass:[NSDictionary class]])
    1808             :         {
    1809             :                 GatherStringAddrsDict(object, strings, context);
    1810             :         }
    1811             : }
    1812             : 
    1813             : 
    1814           0 : static NSComparisonResult SortDemoShipsByName (id a, id b, void* context)
    1815             : {
    1816             :         return [[a oo_stringForKey:@"name"] compare:[b oo_stringForKey:@"name"]];
    1817             : }
    1818             : 
    1819             : 
    1820           0 : static NSComparisonResult SortDemoCategoriesByName (id a, id b, void* context)
    1821             : {
    1822             :         return [OOShipLibraryCategoryPlural([[a oo_dictionaryAtIndex:0] oo_stringForKey:@"class"]) compare:OOShipLibraryCategoryPlural([[b oo_dictionaryAtIndex:0] oo_stringForKey:@"class"])];
    1823             : }

Generated by: LCOV version 1.14