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 : }
|