1824 lines
58 KiB
Objective-C
1824 lines
58 KiB
Objective-C
/*
|
|
|
|
OOShipRegistry.m
|
|
|
|
|
|
Copyright (C) 2008-2013 Jens Ayton and contributors
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
*/
|
|
|
|
#import "OOShipRegistry.h"
|
|
#import "OOCacheManager.h"
|
|
#import "ResourceManager.h"
|
|
#import "OOCollectionExtractors.h"
|
|
#import "NSDictionaryOOExtensions.h"
|
|
#import "OOProbabilitySet.h"
|
|
#import "OORoleSet.h"
|
|
#import "OOStringParsing.h"
|
|
#import "OOMesh.h"
|
|
#import "GameController.h"
|
|
#import "OOLegacyScriptWhitelist.h"
|
|
#import "OODeepCopy.h"
|
|
#import "OOColor.h"
|
|
#import "OOStringExpander.h"
|
|
#import "OOShipLibraryDescriptions.h"
|
|
#import "Universe.h"
|
|
#import "OOJSScript.h"
|
|
|
|
#import "OODebugStandards.h"
|
|
|
|
#define PRELOAD 0
|
|
|
|
|
|
static void DumpStringAddrs(NSDictionary *dict, NSString *context);
|
|
static NSComparisonResult SortDemoShipsByName (id a, id b, void* context);
|
|
static NSComparisonResult SortDemoCategoriesByName (id a, id b, void* context);
|
|
|
|
|
|
static OOShipRegistry *sSingleton = nil;
|
|
|
|
|
|
static NSString * const kShipRegistryCacheName = @"ship registry";
|
|
static NSString * const kShipDataCacheKey = @"ship data";
|
|
static NSString * const kPlayerShipsCacheKey = @"player ships";
|
|
static NSString * const kRoleWeightsCacheKey = @"role weights";
|
|
static NSString * const kDefaultDemoShip = @"coriolis-station";
|
|
static NSString * const kVisualEffectRegistryCacheName = @"visual effect registry";
|
|
static NSString * const kVisualEffectDataCacheKey = @"visual effect data";
|
|
|
|
|
|
@interface OOShipRegistry (OODataLoader)
|
|
|
|
- (void) loadShipData;
|
|
- (void) loadDemoShipConditions;
|
|
- (void) loadDemoShips;
|
|
- (void) loadCachedRoleProbabilitySets;
|
|
- (void) buildRoleProbabilitySets;
|
|
|
|
- (BOOL) applyLikeShips:(NSMutableDictionary *)ioData withKey:(NSString *)likeKey;
|
|
- (BOOL) loadAndMergeShipyard:(NSMutableDictionary *)ioData;
|
|
- (BOOL) stripPrivateKeys:(NSMutableDictionary *)ioData;
|
|
- (BOOL) makeShipEntriesMutable:(NSMutableDictionary *)ioData;
|
|
- (BOOL) loadAndApplyShipDataOverrides:(NSMutableDictionary *)ioData;
|
|
- (BOOL) canonicalizeAndTagSubentities:(NSMutableDictionary *)ioData;
|
|
- (BOOL) removeUnusableEntries:(NSMutableDictionary *)ioData shipMode:(BOOL)shipMode;
|
|
- (BOOL) sanitizeConditions:(NSMutableDictionary *)ioData;
|
|
|
|
#if PRELOAD
|
|
- (BOOL) preloadShipMeshes:(NSMutableDictionary *)ioData;
|
|
#endif
|
|
|
|
- (NSMutableDictionary *) mergeShip:(NSDictionary *)child withParent:(NSDictionary *)parent;
|
|
- (void) mergeShipRoles:(NSString *)roles forShipKey:(NSString *)shipKey intoProbabilityMap:(NSMutableDictionary *)probabilitySets;
|
|
|
|
- (NSDictionary *) canonicalizeSubentityDeclaration:(id)declaration
|
|
forShip:(NSString *)shipKey
|
|
shipData:(NSDictionary *)shipData
|
|
fatalError:(BOOL *)outFatalError;
|
|
- (NSDictionary *) translateOldStyleSubentityDeclaration:(NSString *)declaration
|
|
forShip:(NSString *)shipKey
|
|
shipData:(NSDictionary *)shipData
|
|
fatalError:(BOOL *)outFatalError;
|
|
- (NSDictionary *) translateOldStyleFlasherDeclaration:(NSArray *)tokens
|
|
forShip:(NSString *)shipKey
|
|
fatalError:(BOOL *)outFatalError;
|
|
- (NSDictionary *) translateOldStandardBasicSubentityDeclaration:(NSArray *)tokens
|
|
forShip:(NSString *)shipKey
|
|
shipData:(NSDictionary *)shipData
|
|
fatalError:(BOOL *)outFatalError;
|
|
- (NSDictionary *) validateNewStyleSubentityDeclaration:(NSDictionary *)declaration
|
|
forShip:(NSString *)shipKey
|
|
fatalError:(BOOL *)outFatalError;
|
|
- (NSDictionary *) validateNewStyleFlasherDeclaration:(NSDictionary *)declaration
|
|
forShip:(NSString *)shipKey
|
|
fatalError:(BOOL *)outFatalError;
|
|
- (NSDictionary *) validateNewStyleStandardSubentityDeclaration:(NSDictionary *)declaration
|
|
forShip:(NSString *)shipKey
|
|
fatalError:(BOOL *)outFatalError;
|
|
|
|
- (BOOL) shipIsBallTurretForKey:(NSString *)shipKey inShipData:(NSDictionary *)shipData;
|
|
|
|
@end
|
|
|
|
|
|
@implementation OOShipRegistry
|
|
|
|
+ (OOShipRegistry *) sharedRegistry
|
|
{
|
|
if (sSingleton == nil)
|
|
{
|
|
sSingleton = [[self alloc] init];
|
|
}
|
|
|
|
return sSingleton;
|
|
}
|
|
|
|
|
|
+ (void) reload
|
|
{
|
|
if (sSingleton != nil)
|
|
{
|
|
/* CIM: 'release' doesn't work - the class definition
|
|
* overrides it, so this leaks memory. Needs a proper reset
|
|
* method for reloading the ship registry data instead */
|
|
[sSingleton release];
|
|
sSingleton = nil;
|
|
|
|
(void) [self sharedRegistry];
|
|
}
|
|
}
|
|
|
|
|
|
- (id) init
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
OOCacheManager *cache = [OOCacheManager sharedCache];
|
|
|
|
_shipData = [[cache objectForKey:kShipDataCacheKey inCache:kShipRegistryCacheName] retain];
|
|
_playerShips = [[cache objectForKey:kPlayerShipsCacheKey inCache:kShipRegistryCacheName] retain];
|
|
_effectData = [[cache objectForKey:kVisualEffectDataCacheKey inCache:kVisualEffectRegistryCacheName] retain];
|
|
if ([_shipData count] == 0) // Don't accept nil or empty
|
|
{
|
|
[self loadShipData];
|
|
if ([_shipData count] == 0)
|
|
{
|
|
[NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load any ship data."];
|
|
}
|
|
if ([_playerShips count] == 0)
|
|
{
|
|
[NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load any player ships."];
|
|
}
|
|
}
|
|
|
|
[self loadDemoShipConditions];
|
|
[self loadDemoShips]; // testing only
|
|
if ([_demoShips count] == 0)
|
|
{
|
|
[NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load or synthesize any demo ships."];
|
|
}
|
|
|
|
[self loadCachedRoleProbabilitySets];
|
|
if (_probabilitySets == nil)
|
|
{
|
|
[self buildRoleProbabilitySets];
|
|
if ([_probabilitySets count] == 0)
|
|
{
|
|
[NSException raise:@"OOShipRegistryLoadFailure" format:@"Could not load or synthesize role probability sets."];
|
|
}
|
|
}
|
|
|
|
[pool release];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void) dealloc
|
|
{
|
|
[_shipData release];
|
|
[_demoShips release];
|
|
[_playerShips release];
|
|
[_probabilitySets release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
- (NSDictionary *) shipInfoForKey:(NSString *)key
|
|
{
|
|
return [_shipData objectForKey:key];
|
|
}
|
|
|
|
|
|
- (void) setShipInfoForKey:(NSString *)key with:(NSDictionary *)newShipData
|
|
{
|
|
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionaryWithDictionary:_shipData];
|
|
[mutableDict setObject:OODeepCopy(newShipData) forKey:key];
|
|
DESTROY(_shipData);
|
|
_shipData = [[NSDictionary dictionaryWithDictionary:mutableDict] retain];
|
|
}
|
|
|
|
|
|
- (NSDictionary *) effectInfoForKey:(NSString *)key
|
|
{
|
|
return [_effectData objectForKey:key];
|
|
}
|
|
|
|
|
|
- (NSDictionary *) shipyardInfoForKey:(NSString *)key
|
|
{
|
|
return [[self shipInfoForKey:key] objectForKey:@"_oo_shipyard"];
|
|
}
|
|
|
|
|
|
- (OOProbabilitySet *) probabilitySetForRole:(NSString *)role
|
|
{
|
|
if (role == nil) return nil;
|
|
return [_probabilitySets objectForKey:role];
|
|
}
|
|
|
|
|
|
- (NSArray *) demoShipKeys
|
|
{
|
|
// with condition scripts in use, can't cache this value
|
|
[self loadDemoShips];
|
|
|
|
return [[_demoShips copy] autorelease];
|
|
}
|
|
|
|
|
|
- (NSArray *) playerShipKeys
|
|
{
|
|
return _playerShips;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation OOShipRegistry (OOConveniences)
|
|
|
|
- (NSArray *) shipKeys
|
|
{
|
|
return [_shipData allKeys];
|
|
}
|
|
|
|
- (NSArray *) shipRoles
|
|
{
|
|
return [_probabilitySets allKeys];
|
|
}
|
|
|
|
- (NSArray *) shipKeysWithRole:(NSString *)role
|
|
{
|
|
return [[self probabilitySetForRole:role] allObjects];
|
|
}
|
|
|
|
|
|
- (NSString *) randomShipKeyForRole:(NSString *)role
|
|
{
|
|
return [[self probabilitySetForRole:role] randomObject];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation OOShipRegistry (OODataLoader)
|
|
|
|
/* -loadShipData
|
|
|
|
Load the data for all ships. This consists of five stages:
|
|
* Load merges shipdata.plist dictionary.
|
|
* Apply all like_ship entries.
|
|
* Load shipdata-overrides.plist and apply patches.
|
|
* Load shipyard.plist, add shipyard data into ship dictionaries, and
|
|
create _playerShips array.
|
|
* Build role->ship type probability sets.
|
|
*/
|
|
- (void) loadShipData
|
|
{
|
|
NSMutableDictionary *result = nil;
|
|
|
|
[_shipData release];
|
|
_shipData = nil;
|
|
[_playerShips release];
|
|
_playerShips = nil;
|
|
|
|
// Load shipdata.plist.
|
|
result = [[[ResourceManager dictionaryFromFilesNamed:@"shipdata.plist"
|
|
inFolder:@"Config"
|
|
mergeMode:MERGE_BASIC
|
|
cache:NO] mutableCopy] autorelease];
|
|
if (result == nil) return;
|
|
|
|
DumpStringAddrs(result, @"shipdata.plist");
|
|
|
|
// Make each entry mutable to simplify later stages. Also removes any entries that aren't dictionaries.
|
|
if (![self makeShipEntriesMutable:result]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished initial cleanup...");
|
|
|
|
// Apply patches.
|
|
if (![self loadAndApplyShipDataOverrides:result]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished applying patches...");
|
|
|
|
// Strip private keys (anything starting with _oo_).
|
|
if (![self stripPrivateKeys:result]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished stripping private keys...");
|
|
|
|
// Resolve like_ship entries.
|
|
if (![self applyLikeShips:result withKey:@"like_ship"]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished resolving like_ships...");
|
|
|
|
// Clean up subentity declarations and tag subentities so they won't be pruned.
|
|
if (![self canonicalizeAndTagSubentities:result]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished cleaning up subentities...");
|
|
|
|
// Clean out templates and invalid entries.
|
|
if (![self removeUnusableEntries:result shipMode:YES]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished removing invalid entries...");
|
|
|
|
// Add shipyard entries into shipdata entries.
|
|
if (![self loadAndMergeShipyard:result]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished adding shipyard entries...");
|
|
|
|
// Sanitize conditions.
|
|
if (![self sanitizeConditions:result]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished validating data...");
|
|
|
|
#if PRELOAD
|
|
// Preload and cache meshes.
|
|
if (![self preloadShipMeshes:result]) return;
|
|
OOLog(@"shipData.load.progress", @"%@", @"Finished loading meshes...");
|
|
#endif
|
|
|
|
_shipData = OODeepCopy(result);
|
|
[[OOCacheManager sharedCache] setObject:_shipData forKey:kShipDataCacheKey inCache:kShipRegistryCacheName];
|
|
|
|
OOLog(@"shipData.load.done", @"%@", @"Ship data loaded.");
|
|
|
|
[_effectData release];
|
|
_effectData = nil;
|
|
|
|
result = [[[ResourceManager dictionaryFromFilesNamed:@"effectdata.plist"
|
|
inFolder:@"Config"
|
|
mergeMode:MERGE_BASIC
|
|
cache:NO] mutableCopy] autorelease];
|
|
if (result == nil) return;
|
|
|
|
// Make each entry mutable to simplify later stages. Also removes any entries that aren't dictionaries.
|
|
if (![self makeShipEntriesMutable:result]) return;
|
|
OOLog(@"effectData.load.progress", @"%@", @"Finished initial cleanup...");
|
|
|
|
// Strip private keys (anything starting with _oo_).
|
|
if (![self stripPrivateKeys:result]) return;
|
|
OOLog(@"effectData.load.progress", @"%@", @"Finished stripping private keys...");
|
|
|
|
// Resolve like_effect entries.
|
|
if (![self applyLikeShips:result withKey:@"like_effect"]) return;
|
|
OOLog(@"effectData.load.progress", @"%@", @"Finished resolving like_effects...");
|
|
|
|
// Clean up subentity declarations and tag subentities so they won't be pruned.
|
|
if (![self canonicalizeAndTagSubentities:result]) return;
|
|
OOLog(@"effectData.load.progress", @"%@", @"Finished cleaning up subentities...");
|
|
|
|
// Clean out templates and invalid entries.
|
|
if (![self removeUnusableEntries:result shipMode:NO]) return;
|
|
OOLog(@"effectData.load.progress", @"%@", @"Finished removing invalid entries...");
|
|
|
|
_effectData = OODeepCopy(result);
|
|
[[OOCacheManager sharedCache] setObject:_effectData forKey:kVisualEffectDataCacheKey inCache:kVisualEffectRegistryCacheName];
|
|
|
|
OOLog(@"effectData.load.done", @"%@", @"Effect data loaded.");
|
|
}
|
|
|
|
|
|
- (void) loadDemoShipConditions
|
|
{
|
|
NSMutableArray *conditionScripts = [[NSMutableArray alloc] init];
|
|
NSEnumerator *enumerator = nil;
|
|
NSDictionary *key = nil;
|
|
NSArray *initialDemoShips = nil;
|
|
|
|
initialDemoShips = [ResourceManager arrayFromFilesNamed:@"shiplibrary.plist"
|
|
inFolder:@"Config"
|
|
andMerge:YES
|
|
cache:NO];
|
|
|
|
for (enumerator = [initialDemoShips objectEnumerator]; (key = [enumerator nextObject]); )
|
|
{
|
|
NSString *conditions = [key oo_stringForKey:kOODemoShipConditions defaultValue:nil];
|
|
if (conditions != nil)
|
|
{
|
|
[conditionScripts addObject:conditions];
|
|
}
|
|
}
|
|
|
|
[[OOCacheManager sharedCache] setObject:conditionScripts forKey:@"demoship conditions" inCache:@"condition scripts"];
|
|
[conditionScripts release];
|
|
}
|
|
|
|
|
|
/* -loadDemoShips
|
|
|
|
Load demoships.plist, and filter out non-existent ships. If no existing
|
|
ships remain, try adding coriolis; if this fails, add any ship in
|
|
shipdata.
|
|
*/
|
|
- (void) loadDemoShips
|
|
{
|
|
NSEnumerator *enumerator = nil;
|
|
NSDictionary *key = nil;
|
|
NSArray *initialDemoShips = nil;
|
|
NSMutableArray *demoShips = nil;
|
|
|
|
DESTROY(_demoShips);
|
|
|
|
initialDemoShips = [ResourceManager arrayFromFilesNamed:@"shiplibrary.plist"
|
|
inFolder:@"Config"
|
|
andMerge:YES
|
|
cache:NO];
|
|
demoShips = [NSMutableArray arrayWithArray:initialDemoShips];
|
|
|
|
// Note: iterate over initialDemoShips to avoid mutating the collection being enu,erated.
|
|
for (enumerator = [initialDemoShips objectEnumerator]; (key = [enumerator nextObject]); )
|
|
{
|
|
NSString *shipKey = [key oo_stringForKey:kOODemoShipKey];
|
|
if (![key isKindOfClass:[NSDictionary class]] || [self shipInfoForKey:shipKey] == nil)
|
|
{
|
|
[demoShips removeObject:key];
|
|
}
|
|
else
|
|
{
|
|
NSString *conditions = [key oo_stringForKey:kOODemoShipConditions defaultValue:nil];
|
|
if (conditions != nil)
|
|
{
|
|
if ([PLAYER status] == STATUS_START_GAME)
|
|
{
|
|
// conditions always false here
|
|
[demoShips removeObject:key];
|
|
}
|
|
else
|
|
{
|
|
OOJSScript *condScript = [UNIVERSE getConditionScript:conditions];
|
|
if (condScript != nil) // should always be non-nil, but just in case
|
|
{
|
|
JSContext *context = OOJSAcquireContext();
|
|
BOOL OK;
|
|
JSBool allow_use;
|
|
jsval result;
|
|
jsval args[] = { OOJSValueFromNativeObject(context, shipKey) };
|
|
|
|
OK = [condScript callMethod:OOJSID("allowShowLibraryShip")
|
|
inContext:context
|
|
withArguments:args count:sizeof args / sizeof *args
|
|
result:&result];
|
|
|
|
if (OK) OK = JS_ValueToBoolean(context, result, &allow_use);
|
|
|
|
OOJSRelinquishContext(context);
|
|
if (OK && !allow_use)
|
|
{
|
|
/* if the script exists, the function exists, the function
|
|
* returns a bool, and that bool is false, hide the
|
|
* ship. Otherwise allow it as default */
|
|
[demoShips removeObject:key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ([demoShips count] == 0)
|
|
{
|
|
NSString *shipKey = nil;
|
|
if ([self shipInfoForKey:kDefaultDemoShip] != nil)
|
|
{
|
|
shipKey = kDefaultDemoShip;
|
|
}
|
|
else
|
|
{
|
|
shipKey = [[_shipData allKeys] objectAtIndex:0];
|
|
}
|
|
[demoShips addObject:[NSDictionary dictionaryWithObject:shipKey forKey:kOODemoShipKey]];
|
|
}
|
|
|
|
// now separate out the demoships by class, and add some extra keys
|
|
NSMutableDictionary *demoList = [NSMutableDictionary dictionaryWithCapacity:8];
|
|
NSMutableArray *demoClass = nil;
|
|
foreach(key, demoShips)
|
|
{
|
|
NSString *class = [key oo_stringForKey:kOODemoShipClass defaultValue:@"ship"];
|
|
if ([OOShipLibraryCategoryPlural(class) length] == 0)
|
|
{
|
|
OOLog(@"shipdata.load.warning",@"Unexpected class '%@' in shiplibrary.plist for '%@'",class,[key oo_stringForKey:kOODemoShipKey]);
|
|
class = @"ship";
|
|
}
|
|
demoClass = [demoList objectForKey:class];
|
|
if (demoClass == nil)
|
|
{
|
|
[demoList setObject:[NSMutableArray array] forKey:class];
|
|
demoClass = [demoList objectForKey:class];
|
|
}
|
|
NSMutableDictionary *demoEntry = [NSMutableDictionary dictionaryWithDictionary:key];
|
|
// add "name" object to dictionary from ship definition
|
|
[demoEntry setObject:[[self shipInfoForKey:[demoEntry oo_stringForKey:@"ship"]] oo_stringForKey:kOODemoShipName] forKey:kOODemoShipName];
|
|
// set "class" object to standard ship if not otherwise set
|
|
if (![[demoEntry oo_stringForKey:kOODemoShipClass defaultValue:nil] isEqualToString:class])
|
|
{
|
|
[demoEntry setObject:class forKey:kOODemoShipClass];
|
|
}
|
|
[demoClass addObject:demoEntry];
|
|
}
|
|
// sort each ship list by name
|
|
NSString *demoClassName = nil;
|
|
foreach(demoClassName, demoList)
|
|
{
|
|
[[demoList objectForKey:demoClassName] sortUsingFunction:SortDemoShipsByName context:NULL];
|
|
}
|
|
|
|
// and then sort the ship list list by class name
|
|
_demoShips = [[[demoList allValues] sortedArrayUsingFunction:SortDemoCategoriesByName context:NULL] retain];
|
|
}
|
|
|
|
|
|
- (void) loadCachedRoleProbabilitySets
|
|
{
|
|
NSDictionary *cachedSets = nil;
|
|
NSMutableDictionary *restoredSets = nil;
|
|
NSEnumerator *roleEnum = nil;
|
|
NSString *role = nil;
|
|
|
|
cachedSets = [[OOCacheManager sharedCache] objectForKey:kRoleWeightsCacheKey inCache:kShipRegistryCacheName];
|
|
if (cachedSets == nil) return;
|
|
|
|
restoredSets = [NSMutableDictionary dictionaryWithCapacity:[cachedSets count]];
|
|
for (roleEnum = [cachedSets keyEnumerator]; (role = [roleEnum nextObject]); )
|
|
{
|
|
[restoredSets setObject:[OOProbabilitySet probabilitySetWithPropertyListRepresentation:[cachedSets objectForKey:role]] forKey:role];
|
|
}
|
|
|
|
_probabilitySets = [restoredSets copy];
|
|
}
|
|
|
|
|
|
- (void) buildRoleProbabilitySets
|
|
{
|
|
NSMutableDictionary *probabilitySets = nil;
|
|
NSEnumerator *shipEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSDictionary *shipEntry = nil;
|
|
NSString *roles = nil;
|
|
NSEnumerator *roleEnum = nil;
|
|
NSString *role = nil;
|
|
OOProbabilitySet *pset = nil;
|
|
NSMutableDictionary *cacheEntry = nil;
|
|
|
|
probabilitySets = [NSMutableDictionary dictionary];
|
|
|
|
// Build role sets
|
|
for (shipEnum = [_shipData keyEnumerator]; (shipKey = [shipEnum nextObject]); )
|
|
{
|
|
shipEntry = [_shipData objectForKey:shipKey];
|
|
roles = [shipEntry oo_stringForKey:@"roles"];
|
|
[self mergeShipRoles:roles forShipKey:shipKey intoProbabilityMap:probabilitySets];
|
|
}
|
|
|
|
// Convert role sets to immutable form, and build cache entry.
|
|
// Note: we iterate over a copy of the keys to avoid mutating while iterating.
|
|
cacheEntry = [NSMutableDictionary dictionaryWithCapacity:[probabilitySets count]];
|
|
for (roleEnum = [[probabilitySets allKeys] objectEnumerator]; (role = [roleEnum nextObject]); )
|
|
{
|
|
pset = [probabilitySets objectForKey:role];
|
|
pset = [[pset copy] autorelease];
|
|
[probabilitySets setObject:pset forKey:role];
|
|
[cacheEntry setObject:[pset propertyListRepresentation] forKey:role];
|
|
}
|
|
|
|
_probabilitySets = [probabilitySets copy];
|
|
[[OOCacheManager sharedCache] setObject:cacheEntry forKey:kRoleWeightsCacheKey inCache:kShipRegistryCacheName];
|
|
}
|
|
|
|
|
|
/* -applyLikeShips:
|
|
|
|
Implement like_ship by copying inherited ship and overwriting with child
|
|
ship values. Done iteratively to report recursive references of arbitrary
|
|
depth. Also removes and reports ships whose like_ship entry does not
|
|
resolve, and handles reference loops by removing all ships involved.
|
|
|
|
We start with a set of keys all ships that have a like_ships entry. In
|
|
each iteration, every ship whose like_ship entry does not refer to a ship
|
|
which itself has a like_ship entry is finalized. If the set of pending
|
|
ships does not shrink in an iteration, the remaining ships cannot be
|
|
resolved (either their like_ships do not exist, or they form reference
|
|
cycles) so we stop looping and report it.
|
|
*/
|
|
- (BOOL) applyLikeShips:(NSMutableDictionary *)ioData withKey:(NSString *)likeKey
|
|
{
|
|
NSMutableSet *remainingLikeShips = nil;
|
|
NSEnumerator *enumerator = nil;
|
|
NSString *key = nil;
|
|
NSString *parentKey = nil;
|
|
NSDictionary *shipEntry = nil;
|
|
NSDictionary *parentEntry = nil;
|
|
NSUInteger count, lastCount;
|
|
NSMutableArray *reportedBadShips = nil;
|
|
|
|
// Build set of ships with like_ship references
|
|
remainingLikeShips = [NSMutableSet set];
|
|
for (enumerator = [ioData keyEnumerator]; (key = [enumerator nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:key];
|
|
if ([shipEntry oo_stringForKey:likeKey] != nil)
|
|
{
|
|
[remainingLikeShips addObject:key];
|
|
}
|
|
}
|
|
|
|
count = lastCount = [remainingLikeShips count];
|
|
while (count != 0)
|
|
{
|
|
for (enumerator = [[[remainingLikeShips copy] autorelease] objectEnumerator]; (key = [enumerator nextObject]); )
|
|
{
|
|
// Look up like_ship entry
|
|
shipEntry = [ioData objectForKey:key];
|
|
parentKey = [shipEntry objectForKey:likeKey];
|
|
if (![remainingLikeShips containsObject:parentKey])
|
|
{
|
|
// If parent is fully resolved, we can resolve this child.
|
|
parentEntry = [ioData objectForKey:parentKey];
|
|
shipEntry = [self mergeShip:shipEntry withParent:parentEntry];
|
|
if (shipEntry != nil)
|
|
{
|
|
[remainingLikeShips removeObject:key];
|
|
[ioData setObject:shipEntry forKey:key];
|
|
}
|
|
}
|
|
}
|
|
|
|
count = [remainingLikeShips count];
|
|
if (count == lastCount)
|
|
{
|
|
/* Fail: we couldn't resolve all like_ship entries.
|
|
Remove unresolved entries, building a list of the ones that
|
|
don't have is_external_dependency set.
|
|
*/
|
|
reportedBadShips = [NSMutableArray array];
|
|
for (enumerator = [remainingLikeShips objectEnumerator]; (key = [enumerator nextObject]); )
|
|
{
|
|
if (![[ioData oo_dictionaryForKey:key] oo_boolForKey:@"is_external_dependency"])
|
|
{
|
|
[reportedBadShips addObject:key];
|
|
}
|
|
[ioData removeObjectForKey:key];
|
|
}
|
|
|
|
if ([reportedBadShips count] != 0)
|
|
{
|
|
[reportedBadShips sortUsingSelector:@selector(caseInsensitiveCompare:)];
|
|
OOLogERR(@"shipData.merge.failed", @"one or more shipdata.plist entries have %@ references that cannot be resolved: %@", likeKey, [reportedBadShips componentsJoinedByString:@", "]); // FIXME: distinguish shipdata and effectdata
|
|
OOStandardsError(@"Likely missing a dependency in a manifest.plist");
|
|
}
|
|
break;
|
|
}
|
|
lastCount = count;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (NSMutableDictionary *) mergeShip:(NSDictionary *)child withParent:(NSDictionary *)parent
|
|
{
|
|
NSMutableDictionary *result = [[parent mutableCopy] autorelease];
|
|
if (result == nil) return nil;
|
|
|
|
[result addEntriesFromDictionary:child];
|
|
[result removeObjectForKey:@"like_ship"];
|
|
|
|
// Certain properties cannot be inherited.
|
|
if ([child oo_stringForKey:@"display_name"] == nil) [result removeObjectForKey:@"display_name"];
|
|
if ([child oo_stringForKey:@"is_template"] == nil) [result removeObjectForKey:@"is_template"];
|
|
|
|
// Since both 'scanClass' and 'scan_class' are accepted as valid keys for the scanClass property,
|
|
// we may end up with conflicting scanClass and scan_class keys from like_ship relationships getting
|
|
// merged in the result dictionary. We want to always have the child overriding the parent setting
|
|
// and we do that by determining which of the two keys belongs to the child dictionary and removing
|
|
// the other one from the result - Nikos 20100512
|
|
if ([result oo_stringForKey:@"scan_class"] != nil && [result oo_stringForKey:@"scanClass"] != nil)
|
|
{
|
|
if ([child oo_stringForKey:@"scanClass"] != nil)
|
|
[result removeObjectForKey:@"scan_class"];
|
|
else
|
|
[result removeObjectForKey:@"scanClass"];
|
|
}
|
|
// TODO: all normalised/non-normalised value name pairs need to be catered for. - Kaks 2010-05-13
|
|
if ([result oo_stringForKey:@"escort_role"] != nil && [result oo_stringForKey:@"escort-role"] != nil)
|
|
{
|
|
if ([child oo_stringForKey:@"escort-role"] != nil)
|
|
[result removeObjectForKey:@"escort_role"];
|
|
else
|
|
[result removeObjectForKey:@"escort-role"];
|
|
}
|
|
if ([result oo_stringForKey:@"escort_ship"] != nil && [result oo_stringForKey:@"escort-ship"] != nil)
|
|
{
|
|
if ([child oo_stringForKey:@"escort-ship"] != nil)
|
|
[result removeObjectForKey:@"escort_ship"];
|
|
else
|
|
[result removeObjectForKey:@"escort-ship"];
|
|
}
|
|
if ([result oo_stringForKey:@"is_carrier"] != nil && [result oo_stringForKey:@"isCarrier"] != nil)
|
|
{
|
|
if ([child oo_stringForKey:@"isCarrier"] != nil)
|
|
[result removeObjectForKey:@"is_carrier"];
|
|
else
|
|
[result removeObjectForKey:@"isCarrier"];
|
|
}
|
|
if ([result oo_stringForKey:@"has_shipyard"] != nil && [result oo_stringForKey:@"hasShipyard"] != nil)
|
|
{
|
|
if ([child oo_stringForKey:@"hasShipyard"] != nil)
|
|
[result removeObjectForKey:@"has_shipyard"];
|
|
else
|
|
[result removeObjectForKey:@"hasShipyard"];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
- (BOOL) makeShipEntriesMutable:(NSMutableDictionary *)ioData
|
|
{
|
|
NSEnumerator *shipKeyEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSDictionary *shipEntry = nil;
|
|
|
|
for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
if (![shipEntry isKindOfClass:[NSDictionary class]])
|
|
{
|
|
OOLogERR(@"shipData.load.badEntry", @"the shipdata.plist entry \"%@\" is not a dictionary.", shipKey);
|
|
[ioData removeObjectForKey:shipKey];
|
|
}
|
|
else
|
|
{
|
|
shipEntry = [shipEntry mutableCopy];
|
|
|
|
[ioData setObject:shipEntry forKey:shipKey];
|
|
[shipEntry release];
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (BOOL) loadAndApplyShipDataOverrides:(NSMutableDictionary *)ioData
|
|
{
|
|
NSEnumerator *shipKeyEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSMutableDictionary *shipEntry = nil;
|
|
NSDictionary *overrides = nil;
|
|
NSDictionary *overridesEntry = nil;
|
|
|
|
overrides = [ResourceManager dictionaryFromFilesNamed:@"shipdata-overrides.plist"
|
|
inFolder:@"Config"
|
|
mergeMode:MERGE_SMART
|
|
cache:NO];
|
|
|
|
for (shipKeyEnum = [overrides keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
if (shipEntry != nil)
|
|
{
|
|
overridesEntry = [overrides objectForKey:shipKey];
|
|
if (![overridesEntry isKindOfClass:[NSDictionary class]])
|
|
{
|
|
OOLogERR(@"shipData.load.error", @"the shipdata-overrides.plist entry \"%@\" is not a dictionary.", shipKey);
|
|
}
|
|
else
|
|
{
|
|
[shipEntry addEntriesFromDictionary:overridesEntry];
|
|
}
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (BOOL) stripPrivateKeys:(NSMutableDictionary *)ioData
|
|
{
|
|
NSEnumerator *shipKeyEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSMutableDictionary *shipEntry = nil;
|
|
NSEnumerator *attrKeyEnum = nil;
|
|
NSString *attrKey = nil;
|
|
|
|
for (shipKeyEnum = [ioData keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
|
|
for (attrKeyEnum = [shipEntry keyEnumerator]; (attrKey = [attrKeyEnum nextObject]); )
|
|
{
|
|
if ([attrKey hasPrefix:@"_oo_"])
|
|
{
|
|
[shipEntry removeObjectForKey:attrKey];
|
|
}
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
/* -loadAndMergeShipyard:
|
|
|
|
Load shipyard.plist, add its entries to appropriate shipyard entries as
|
|
a dictionary under the key "shipyard", and build list of player ships.
|
|
Before that, we strip out any "shipyard" entries already in shipdata, and
|
|
apply any shipyard-overrides.plist stuff to shipyard.
|
|
*/
|
|
- (BOOL) loadAndMergeShipyard:(NSMutableDictionary *)ioData
|
|
{
|
|
NSEnumerator *shipKeyEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSMutableDictionary *shipEntry = nil;
|
|
NSDictionary *shipyard = nil;
|
|
NSDictionary *shipyardOverrides = nil;
|
|
NSDictionary *shipyardEntry = nil;
|
|
NSDictionary *shipyardOverridesEntry = nil;
|
|
NSMutableArray *playerShips = nil;
|
|
|
|
// Strip out any shipyard stuff in shipdata (there shouldn't be any).
|
|
for (shipKeyEnum = [ioData keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
if ([shipEntry objectForKey:@"_oo_shipyard"] != nil)
|
|
{
|
|
[shipEntry removeObjectForKey:@"_oo_shipyard"];
|
|
}
|
|
}
|
|
|
|
shipyard = [ResourceManager dictionaryFromFilesNamed:@"shipyard.plist"
|
|
inFolder:@"Config"
|
|
mergeMode:MERGE_BASIC
|
|
cache:NO];
|
|
shipyardOverrides = [ResourceManager dictionaryFromFilesNamed:@"shipyard-overrides.plist"
|
|
inFolder:@"Config"
|
|
mergeMode:MERGE_SMART
|
|
cache:NO];
|
|
|
|
playerShips = [NSMutableArray arrayWithCapacity:[shipyard count]];
|
|
|
|
// Insert merged shipyard and shipyardOverrides entries.
|
|
for (shipKeyEnum = [shipyard keyEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
if (shipEntry != nil)
|
|
{
|
|
shipyardEntry = [shipyard objectForKey:shipKey];
|
|
shipyardOverridesEntry = [shipyardOverrides objectForKey:shipKey];
|
|
shipyardEntry = [shipyardEntry dictionaryByAddingEntriesFromDictionary:shipyardOverridesEntry];
|
|
|
|
[shipEntry setObject:shipyardEntry forKey:@"_oo_shipyard"];
|
|
|
|
[playerShips addObject:shipKey];
|
|
}
|
|
else
|
|
{
|
|
OOLogWARN(@"shipData.load.shipyard.unknown", @"the shipyard.plist entry \"%@\" does not have a corresponding shipdata.plist entry, ignoring.", shipKey);
|
|
}
|
|
}
|
|
|
|
_playerShips = [playerShips copy];
|
|
[[OOCacheManager sharedCache] setObject:_playerShips forKey:kPlayerShipsCacheKey inCache:kShipRegistryCacheName];
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (BOOL) canonicalizeAndTagSubentities:(NSMutableDictionary *)ioData
|
|
{
|
|
NSEnumerator *shipKeyEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSMutableDictionary *shipEntry = nil;
|
|
NSArray *subentityDeclarations = nil;
|
|
NSEnumerator *subentityEnum = nil;
|
|
id subentityDecl = nil;
|
|
NSDictionary *subentityDict = nil;
|
|
NSString *subentityKey = nil;
|
|
NSMutableDictionary *subentityShipEntry = nil;
|
|
NSMutableSet *badSubentities = nil;
|
|
NSString *badSubentitiesList = nil;
|
|
NSMutableArray *okSubentities = nil;
|
|
BOOL remove, fatal;
|
|
|
|
// Convert all subentity declarations to dictionaries and add
|
|
// _oo_is_subentity=YES to all entries used as subentities.
|
|
|
|
// Iterate over all ships. (Iterates over a copy of keys since it mutates the dictionary.)
|
|
for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
remove = NO;
|
|
badSubentities = nil;
|
|
|
|
// Iterate over each subentity declaration of each ship
|
|
subentityDeclarations = [shipEntry oo_arrayForKey:@"subentities"];
|
|
if (subentityDeclarations != nil)
|
|
{
|
|
okSubentities = [NSMutableArray arrayWithCapacity:[subentityDeclarations count]];
|
|
for (subentityEnum = [subentityDeclarations objectEnumerator]; (subentityDecl = [subentityEnum nextObject]); )
|
|
{
|
|
subentityDict = [self canonicalizeSubentityDeclaration:subentityDecl forShip:shipKey shipData:ioData fatalError:&fatal];
|
|
|
|
// If entry is broken, we need to kill this ship.
|
|
if (fatal)
|
|
{
|
|
OOStandardsError(@"Bad subentity definition found");
|
|
remove = YES;
|
|
}
|
|
else if (subentityDict != nil)
|
|
{
|
|
[okSubentities addObject:subentityDict];
|
|
|
|
// Tag subentities.
|
|
if (![[subentityDict oo_stringForKey:@"type"] isEqualToString:@"flasher"])
|
|
{
|
|
subentityKey = [subentityDict oo_stringForKey:@"subentity_key"];
|
|
subentityShipEntry = [ioData objectForKey:subentityKey];
|
|
if (subentityKey == nil || subentityShipEntry == nil)
|
|
{
|
|
// Oops, reference to non-existent subent.
|
|
if (badSubentities == nil) badSubentities = [NSMutableSet set];
|
|
[badSubentities addObject:subentityKey];
|
|
}
|
|
else
|
|
{
|
|
// Subent exists, add _oo_is_subentity so roles aren't required.
|
|
[subentityShipEntry oo_setBool:YES forKey:@"_oo_is_subentity"];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set updated subentity list.
|
|
if ([okSubentities count] != 0)
|
|
{
|
|
[shipEntry setObject:okSubentities forKey:@"subentities"];
|
|
}
|
|
else
|
|
{
|
|
[shipEntry removeObjectForKey:@"subentities"];
|
|
}
|
|
|
|
if (badSubentities != nil)
|
|
{
|
|
if (![shipEntry oo_boolForKey:@"is_external_dependency"])
|
|
{
|
|
badSubentitiesList = [[[badSubentities allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] componentsJoinedByString:@", "];
|
|
OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" has unresolved subentit%@ %@.", shipKey, ([badSubentities count] == 1) ? @"y" : @"ies", badSubentitiesList);
|
|
OOStandardsError(@"Bad subentity definition found");
|
|
}
|
|
remove = YES;
|
|
}
|
|
|
|
if (remove)
|
|
{
|
|
// Removal is deferred to avoid bogus "entry doesn't exist" errors.
|
|
[shipEntry oo_setBool:YES forKey:@"_oo_deferred_remove"];
|
|
}
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (BOOL) removeUnusableEntries:(NSMutableDictionary *)ioData shipMode:(BOOL)shipMode
|
|
{
|
|
NSEnumerator *shipKeyEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSMutableDictionary *shipEntry = nil;
|
|
BOOL remove;
|
|
NSString *modelName = nil;
|
|
|
|
// Clean out invalid entries and templates. (Iterates over a copy of keys since it mutates the dictionary.)
|
|
for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
remove = NO;
|
|
|
|
if ([shipEntry oo_boolForKey:@"is_template"] || [shipEntry oo_boolForKey:@"_oo_deferred_remove"]) remove = YES;
|
|
else if (shipMode && [[shipEntry oo_stringForKey:@"roles"] length] == 0 && ![shipEntry oo_boolForKey:@"_oo_is_subentity"] && ![shipEntry oo_boolForKey:@"_oo_is_effect"])
|
|
{
|
|
OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies no %@.", shipKey, @"roles");
|
|
remove = YES;
|
|
OOStandardsError(@"Error in shipdata.plist");
|
|
}
|
|
else
|
|
{
|
|
modelName = [shipEntry oo_stringForKey:@"model"];
|
|
if (shipMode && [modelName length] == 0)
|
|
{
|
|
OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies no %@.", shipKey, @"model");
|
|
OOStandardsError(@"Error in shipdata.plist");
|
|
remove = YES;
|
|
}
|
|
else if ([modelName length] != 0 && [ResourceManager pathForFileNamed:modelName inFolder:@"Models"] == nil)
|
|
{
|
|
OOLogERR(@"shipData.load.error", @"the shipdata.plist entry \"%@\" specifies non-existent model \"%@\".", shipKey, modelName);
|
|
OOStandardsError(@"Error in shipdata.plist");
|
|
remove = YES;
|
|
}
|
|
}
|
|
if (remove) [ioData removeObjectForKey:shipKey];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
/* Transform conditions, determinant (if conditions array) and
|
|
shipyard.conditions from hasShipyard to sanitized form.
|
|
Also get list of condition_scripts
|
|
*/
|
|
- (BOOL) sanitizeConditions:(NSMutableDictionary *)ioData
|
|
{
|
|
NSEnumerator *shipKeyEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSMutableDictionary *shipEntry = nil;
|
|
NSMutableDictionary *mutableShipyard = nil;
|
|
NSArray *conditions = nil;
|
|
NSArray *hasShipyard = nil;
|
|
NSArray *shipyardConditions = nil;
|
|
NSString *condition_script = nil;
|
|
NSString *shipyard_condition_script = nil;
|
|
|
|
NSMutableArray *conditionScripts = [[NSMutableArray alloc] init];
|
|
|
|
for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
conditions = [shipEntry objectForKey:@"conditions"];
|
|
condition_script = [shipEntry oo_stringForKey:@"condition_script"];
|
|
if (condition_script != nil)
|
|
{
|
|
if (![conditionScripts containsObject:condition_script])
|
|
{
|
|
[conditionScripts addObject:condition_script];
|
|
}
|
|
}
|
|
|
|
hasShipyard = [shipEntry objectForKey:@"has_shipyard"];
|
|
if (![hasShipyard isKindOfClass:[NSArray class]]) hasShipyard = nil; // May also be fuzzy boolean
|
|
if (hasShipyard == nil)
|
|
{
|
|
hasShipyard = [shipEntry objectForKey:@"hasShipyard"];
|
|
if (![hasShipyard isKindOfClass:[NSArray class]]) hasShipyard = nil; // May also be fuzzy boolean
|
|
}
|
|
shipyardConditions = [[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] objectForKey:@"conditions"];
|
|
shipyard_condition_script = [[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] oo_stringForKey:@"condition_script"];
|
|
if (shipyard_condition_script != nil)
|
|
{
|
|
if (![conditionScripts containsObject:shipyard_condition_script])
|
|
{
|
|
[conditionScripts addObject:shipyard_condition_script];
|
|
}
|
|
}
|
|
|
|
|
|
if (conditions == nil && hasShipyard && shipyardConditions == nil) continue;
|
|
|
|
if (conditions != nil)
|
|
{
|
|
OOStandardsDeprecated([NSString stringWithFormat:@"The 'conditions' key is deprecated in shipdata entry %@",shipKey]);
|
|
if (!OOEnforceStandards())
|
|
{
|
|
if ([conditions isKindOfClass:[NSArray class]])
|
|
{
|
|
conditions = OOSanitizeLegacyScriptConditions(conditions, [NSString stringWithFormat:@"<shipdata.plist entry \"%@\">", shipKey]);
|
|
}
|
|
else
|
|
{
|
|
OOLogWARN(@"shipdata.load.warning", @"conditions for shipdata.plist entry \"%@\" are not an array, ignoring.", shipKey);
|
|
conditions = nil;
|
|
}
|
|
|
|
if (conditions != nil)
|
|
{
|
|
[shipEntry setObject:conditions forKey:@"conditions"];
|
|
}
|
|
else
|
|
{
|
|
[shipEntry removeObjectForKey:@"conditions"];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasShipyard != nil)
|
|
{
|
|
hasShipyard = OOSanitizeLegacyScriptConditions(hasShipyard, [NSString stringWithFormat:@"<shipdata.plist entry \"%@\" hasShipyard conditions>", shipKey]);
|
|
OOStandardsDeprecated([NSString stringWithFormat:@"Use of legacy script conditions in the 'has_shipyard' key is deprecated in shipyard entry %@",shipKey]);
|
|
if (!OOEnforceStandards())
|
|
{
|
|
if (hasShipyard != nil)
|
|
{
|
|
[shipEntry setObject:hasShipyard forKey:@"has_shipyard"];
|
|
}
|
|
else
|
|
{
|
|
[shipEntry removeObjectForKey:@"hasShipyard"];
|
|
[shipEntry removeObjectForKey:@"has_shipyard"];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shipyardConditions != nil)
|
|
{
|
|
OOStandardsDeprecated([NSString stringWithFormat:@"The 'conditions' key is deprecated in shipyard entry %@",shipKey]);
|
|
if (!OOEnforceStandards())
|
|
{
|
|
mutableShipyard = [[[shipEntry oo_dictionaryForKey:@"_oo_shipyard"] mutableCopy] autorelease];
|
|
|
|
if ([shipyardConditions isKindOfClass:[NSArray class]])
|
|
{
|
|
shipyardConditions = OOSanitizeLegacyScriptConditions(shipyardConditions, [NSString stringWithFormat:@"<shipyard.plist entry \"%@\">", shipKey]);
|
|
}
|
|
else
|
|
{
|
|
OOLogWARN(@"shipdata.load.warning", @"conditions for shipyard.plist entry \"%@\" are not an array, ignoring.", shipKey);
|
|
shipyardConditions = nil;
|
|
}
|
|
|
|
if (shipyardConditions != nil)
|
|
{
|
|
[mutableShipyard setObject:shipyardConditions forKey:@"conditions"];
|
|
}
|
|
else
|
|
{
|
|
[mutableShipyard removeObjectForKey:@"conditions"];
|
|
}
|
|
|
|
[shipEntry setObject:mutableShipyard forKey:@"_oo_shipyard"];
|
|
}
|
|
}
|
|
}
|
|
|
|
[[OOCacheManager sharedCache] setObject:conditionScripts forKey:@"ship conditions" inCache:@"condition scripts"];
|
|
[conditionScripts release];
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
#if PRELOAD
|
|
- (BOOL) preloadShipMeshes:(NSMutableDictionary *)ioData
|
|
{
|
|
NSEnumerator *shipKeyEnum = nil;
|
|
NSString *shipKey = nil;
|
|
NSMutableDictionary *shipEntry = nil;
|
|
BOOL remove;
|
|
NSString *modelName = nil;
|
|
OOMesh *mesh = nil;
|
|
NSAutoreleasePool *pool = nil;
|
|
NSUInteger i = 0, count;
|
|
|
|
count = [ioData count];
|
|
|
|
// Preload ship meshes. (Iterates over a copy of keys since it mutates the dictionary.)
|
|
for (shipKeyEnum = [[ioData allKeys] objectEnumerator]; (shipKey = [shipKeyEnum nextObject]); )
|
|
{
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
[[GameController sharedController] setProgressBarValue:(float)i++ / (float)count];
|
|
|
|
shipEntry = [ioData objectForKey:shipKey];
|
|
remove = NO;
|
|
|
|
modelName = [shipEntry oo_stringForKey:@"model"];
|
|
mesh = [OOMesh meshWithName:modelName
|
|
materialDictionary:[shipEntry oo_dictionaryForKey:@"materials"]
|
|
shadersDictionary:[shipEntry oo_dictionaryForKey:@"shaders"]
|
|
smooth:[shipEntry oo_boolForKey:@"smooth"]
|
|
shaderMacros:nil
|
|
shaderBindingTarget:nil];
|
|
|
|
[pool release]; // NOTE: mesh is now invalid, but pointer nil check is OK.
|
|
|
|
if (mesh == nil)
|
|
{
|
|
// FIXME: what if it's a subentity? Need to rearrange things.
|
|
OOLogERR(@"shipData.load.error", @"model \"%@\" could not be loaded for ship \"%@\", removing.", modelName, shipKey);
|
|
[ioData removeObjectForKey:shipKey];
|
|
}
|
|
}
|
|
|
|
[[GameController sharedController] setProgressBarValue:-1.0f];
|
|
|
|
return YES;
|
|
}
|
|
#endif
|
|
|
|
|
|
- (void) mergeShipRoles:(NSString *)roles
|
|
forShipKey:(NSString *)shipKey
|
|
intoProbabilityMap:(NSMutableDictionary *)probabilitySets
|
|
{
|
|
NSDictionary *rolesAndWeights = nil;
|
|
NSEnumerator *roleEnum = nil;
|
|
NSString *role = nil;
|
|
OOMutableProbabilitySet *probSet = nil;
|
|
|
|
|
|
/* probabilitySets is a dictionary whose keys are roles and whose values
|
|
are mutable probability sets, whose values are ship keys.
|
|
|
|
When creating new ships Oolite looks up this probability map.
|
|
To upgrade all soliton 'thargon' roles to 'EQ_THARGON' we need
|
|
to swap these roles here.
|
|
*/
|
|
|
|
rolesAndWeights = OOParseRolesFromString(roles);
|
|
// add default [shipKey] role
|
|
NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithDictionary:rolesAndWeights];
|
|
[mutable setObject:[NSNumber numberWithFloat:1.0] forKey:[[[NSString alloc] initWithFormat:@"[%@]",shipKey] autorelease]];
|
|
rolesAndWeights = mutable;
|
|
|
|
id thargonValue = [rolesAndWeights objectForKey:@"thargon"];
|
|
if (thargonValue != nil && [rolesAndWeights objectForKey:@"EQ_THARGON"] == nil)
|
|
{
|
|
NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithDictionary:rolesAndWeights];
|
|
[mutable setObject:thargonValue forKey:@"EQ_THARGON"];
|
|
rolesAndWeights = mutable;
|
|
}
|
|
|
|
for (roleEnum = [rolesAndWeights keyEnumerator]; (role = [roleEnum nextObject]); )
|
|
{
|
|
probSet = [probabilitySets objectForKey:role];
|
|
if (probSet == nil)
|
|
{
|
|
probSet = [OOMutableProbabilitySet probabilitySet];
|
|
[probabilitySets setObject:probSet forKey:role];
|
|
}
|
|
|
|
[probSet setWeight:[rolesAndWeights oo_floatForKey:role] forObject:shipKey];
|
|
}
|
|
}
|
|
|
|
|
|
- (NSDictionary *) canonicalizeSubentityDeclaration:(id)declaration
|
|
forShip:(NSString *)shipKey
|
|
shipData:(NSDictionary *)shipData
|
|
fatalError:(BOOL *)outFatalError
|
|
{
|
|
NSDictionary *result = nil;
|
|
|
|
assert(outFatalError != NULL);
|
|
*outFatalError = NO;
|
|
|
|
if ([declaration isKindOfClass:[NSString class]])
|
|
{
|
|
// Update old-style string-based declaration.
|
|
OOStandardsDeprecated([NSString stringWithFormat:@"Old style sub-entity declarations are deprecated in %@",shipKey]);
|
|
if (!OOEnforceStandards())
|
|
{
|
|
result = [self translateOldStyleSubentityDeclaration:declaration
|
|
forShip:shipKey
|
|
shipData:shipData
|
|
fatalError:outFatalError];
|
|
}
|
|
if (result != nil)
|
|
{
|
|
// Ensure internal translation made sense, and clean up a bit.
|
|
result = [self validateNewStyleSubentityDeclaration:result
|
|
forShip:shipKey
|
|
fatalError:outFatalError];
|
|
}
|
|
}
|
|
else if ([declaration isKindOfClass:[NSDictionary class]])
|
|
{
|
|
// Validate dictionary-based declaration.
|
|
result = [self validateNewStyleSubentityDeclaration:declaration
|
|
forShip:shipKey
|
|
fatalError:outFatalError];
|
|
}
|
|
else
|
|
{
|
|
OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ should be string or dictionary, found %@.", shipKey, [declaration class]);
|
|
*outFatalError = YES;
|
|
}
|
|
|
|
// For frangible ships, bad subentities are non-fatal.
|
|
if (*outFatalError && [[shipData oo_dictionaryForKey:shipKey] oo_boolForKey:@"frangible"]) *outFatalError = NO;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
- (NSDictionary *) translateOldStyleSubentityDeclaration:(NSString *)declaration
|
|
forShip:(NSString *)shipKey
|
|
shipData:(NSDictionary *)shipData
|
|
fatalError:(BOOL *)outFatalError
|
|
{
|
|
NSArray *tokens = nil;
|
|
NSString *subentityKey = nil;
|
|
BOOL isFlasher;
|
|
|
|
tokens = ScanTokensFromString(declaration);
|
|
|
|
subentityKey = [tokens objectAtIndex:0];
|
|
isFlasher = [subentityKey isEqualToString:@"*FLASHER*"];
|
|
|
|
// Sanity check: require eight tokens.
|
|
if ([tokens count] != 8)
|
|
{
|
|
if (!isFlasher)
|
|
{
|
|
OOLogERR(@"shipData.load.error.badSubentity", @"the shipdata.plist entry \"%@\" has a broken subentity definition \"%@\" (should have 8 tokens, has %lu).", shipKey, subentityKey, [tokens count]);
|
|
*outFatalError = YES;
|
|
}
|
|
else
|
|
{
|
|
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]);
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
if (isFlasher)
|
|
{
|
|
return [self translateOldStyleFlasherDeclaration:tokens
|
|
forShip:shipKey
|
|
fatalError:outFatalError];
|
|
}
|
|
else
|
|
{
|
|
return [self translateOldStandardBasicSubentityDeclaration:tokens
|
|
forShip:shipKey
|
|
shipData:shipData
|
|
fatalError:outFatalError];
|
|
}
|
|
}
|
|
|
|
|
|
- (NSDictionary *) translateOldStyleFlasherDeclaration:(NSArray *)tokens
|
|
forShip:(NSString *)shipKey
|
|
fatalError:(BOOL *)outFatalError
|
|
{
|
|
Vector position;
|
|
float size, frequency, phase, hue;
|
|
NSDictionary *colorDict = nil;
|
|
NSDictionary *result = nil;
|
|
|
|
position.x = [tokens oo_floatAtIndex:1];
|
|
position.y = [tokens oo_floatAtIndex:2];
|
|
position.z = [tokens oo_floatAtIndex:3];
|
|
|
|
hue = [tokens oo_floatAtIndex:4];
|
|
frequency = [tokens oo_floatAtIndex:5];
|
|
phase = [tokens oo_floatAtIndex:6];
|
|
size = [tokens oo_floatAtIndex:7];
|
|
|
|
colorDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:hue] forKey:@"hue"];
|
|
|
|
result = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
@"flasher", @"type",
|
|
OOPropertyListFromVector(position), @"position",
|
|
[NSArray arrayWithObject:colorDict], @"colors",
|
|
[NSNumber numberWithFloat:frequency], @"frequency",
|
|
[NSNumber numberWithFloat:phase], @"phase",
|
|
[NSNumber numberWithFloat:size], @"size",
|
|
nil];
|
|
|
|
OOLog(@"shipData.translateSubentity.flasher", @"Translated flasher declaration \"%@\" to %@", [tokens componentsJoinedByString:@" "], result);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
- (NSDictionary *) translateOldStandardBasicSubentityDeclaration:(NSArray *)tokens
|
|
forShip:(NSString *)shipKey
|
|
shipData:(NSDictionary *)shipData
|
|
fatalError:(BOOL *)outFatalError
|
|
{
|
|
NSString *subentityKey = nil;
|
|
Vector position;
|
|
Quaternion orientation;
|
|
NSMutableDictionary *result = nil;
|
|
BOOL isTurret, isDock = NO;
|
|
|
|
subentityKey = [tokens oo_stringAtIndex:0];
|
|
|
|
isTurret = [self shipIsBallTurretForKey:subentityKey inShipData:shipData];
|
|
|
|
position.x = [tokens oo_floatAtIndex:1];
|
|
position.y = [tokens oo_floatAtIndex:2];
|
|
position.z = [tokens oo_floatAtIndex:3];
|
|
|
|
orientation.w = [tokens oo_floatAtIndex:4];
|
|
orientation.x = [tokens oo_floatAtIndex:5];
|
|
orientation.y = [tokens oo_floatAtIndex:6];
|
|
orientation.z = [tokens oo_floatAtIndex:7];
|
|
|
|
if(orientation.w == 0 && orientation.x == 0 && orientation.y == 0 && orientation.z == 0)
|
|
{
|
|
orientation.w = 1; // avoid dividing by zero.
|
|
OOLogWARN(@"shipData.load.error", @"The ship %@ has an undefined orientation for its %@ subentity. Setting it now at (1,0,0,0)", shipKey, subentityKey);
|
|
}
|
|
|
|
quaternion_normalize(&orientation);
|
|
|
|
if (!isTurret)
|
|
{
|
|
isDock = [subentityKey rangeOfString:@"dock"].location != NSNotFound;
|
|
}
|
|
|
|
result = [NSMutableDictionary dictionaryWithCapacity:5];
|
|
[result setObject:isTurret ? @"ball_turret" : @"standard" forKey:@"type"];
|
|
[result setObject:subentityKey forKey:@"subentity_key"];
|
|
[result oo_setVector:position forKey:@"position"];
|
|
[result oo_setQuaternion:orientation forKey:@"orientation"];
|
|
if (isDock) [result oo_setBool:YES forKey:@"is_dock"];
|
|
|
|
OOLog(@"shipData.translateSubentity.standard", @"Translated subentity declaration \"%@\" to %@", [tokens componentsJoinedByString:@" "], result);
|
|
|
|
return [[result copy] autorelease];
|
|
}
|
|
|
|
|
|
- (NSDictionary *) validateNewStyleSubentityDeclaration:(NSDictionary *)declaration
|
|
forShip:(NSString *)shipKey
|
|
fatalError:(BOOL *)outFatalError
|
|
{
|
|
NSString *type = nil;
|
|
|
|
type = [declaration oo_stringForKey:@"type"];
|
|
if (type == nil) type = @"standard";
|
|
|
|
if ([type isEqualToString:@"flasher"])
|
|
{
|
|
return [self validateNewStyleFlasherDeclaration:declaration forShip:shipKey fatalError:outFatalError];
|
|
}
|
|
else if ([type isEqualToString:@"standard"] || [type isEqualToString:@"ball_turret"])
|
|
{
|
|
return [self validateNewStyleStandardSubentityDeclaration:declaration forShip:shipKey fatalError:outFatalError];
|
|
}
|
|
else
|
|
{
|
|
OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ does not declare a valid type (must be standard, flasher or ball_turret).", shipKey);
|
|
*outFatalError = YES;
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
|
|
- (NSDictionary *) validateNewStyleFlasherDeclaration:(NSDictionary *)declaration
|
|
forShip:(NSString *)shipKey
|
|
fatalError:(BOOL *)outFatalError
|
|
{
|
|
NSMutableDictionary *result = nil;
|
|
Vector position = kZeroVector;
|
|
NSArray *colors = nil;
|
|
id colorDesc = nil;
|
|
float size, frequency, phase, brightfraction;
|
|
BOOL initiallyOn;
|
|
|
|
#define kDefaultFlasherColor @"redColor"
|
|
|
|
// "Validate" is really "clean up", since all values have defaults.
|
|
colors = [declaration oo_arrayForKey:@"colors"];
|
|
if ([colors count] == 0)
|
|
{
|
|
colorDesc = [declaration objectForKey:@"color"];
|
|
if (colorDesc == nil) colorDesc = kDefaultFlasherColor;
|
|
if ([colorDesc isKindOfClass:[NSArray class]])
|
|
{
|
|
// an easy made error is adding an array to "color" instead of "colors"
|
|
OOLogWARN(@"shipData.load.warning.flasher.badColor", @"changing flasher for ship %@ from a color to a colors definition.", shipKey);
|
|
colors = colorDesc;
|
|
}
|
|
else
|
|
{
|
|
colors = [NSArray arrayWithObject:colorDesc];
|
|
}
|
|
}
|
|
|
|
// Validate colours.
|
|
NSMutableArray *validColors = [NSMutableArray arrayWithCapacity:[colors count]];
|
|
foreach (colorDesc, colors)
|
|
{
|
|
OOColor *color = [OOColor colorWithDescription:colorDesc];
|
|
if (color != nil)
|
|
{
|
|
[validColors addObject:[color normalizedArray]];
|
|
}
|
|
else
|
|
{
|
|
OOLogWARN(@"shipdata.load.warning.flasher.badColor", @"skipping invalid colour specifier for flasher for ship %@.", shipKey);
|
|
}
|
|
}
|
|
// Ensure there's at least one.
|
|
if ([validColors count] == 0)
|
|
{
|
|
[validColors addObject:kDefaultFlasherColor];
|
|
}
|
|
colors = validColors;
|
|
|
|
position = [declaration oo_vectorForKey:@"position"];
|
|
|
|
size = [declaration oo_floatForKey:@"size" defaultValue:8.0];
|
|
|
|
if (size <= 0)
|
|
{
|
|
OOLogWARN(@"shipData.load.warning.flasher.badSize", @"skipping flasher of invalid size %g for ship %@.", size, shipKey);
|
|
return nil;
|
|
}
|
|
|
|
brightfraction = [declaration oo_floatForKey:@"bright_fraction" defaultValue:0.5];
|
|
if (brightfraction < 0.0 || brightfraction > 1.0)
|
|
{
|
|
OOLogWARN(@"shipData.load.warning.flasher.badFraction", @"skipping flasher of invalid bright fraction %g for ship %@.", brightfraction, shipKey);
|
|
return nil;
|
|
}
|
|
|
|
frequency = [declaration oo_floatForKey:@"frequency" defaultValue:2.0];
|
|
phase = [declaration oo_floatForKey:@"phase" defaultValue:0.0];
|
|
initiallyOn = [declaration oo_boolForKey:@"initially_on" defaultValue:YES];
|
|
|
|
result = [NSMutableDictionary dictionaryWithCapacity:8];
|
|
[result setObject:@"flasher" forKey:@"type"];
|
|
[result setObject:colors forKey:@"colors"];
|
|
[result oo_setVector:position forKey:@"position"];
|
|
[result setObject:[NSNumber numberWithFloat:size] forKey:@"size"];
|
|
[result setObject:[NSNumber numberWithFloat:frequency] forKey:@"frequency"];
|
|
if (phase != 0) [result setObject:[NSNumber numberWithFloat:phase] forKey:@"phase"];
|
|
[result setObject:[NSNumber numberWithFloat:brightfraction] forKey:@"bright_fraction"];
|
|
[result setObject:[NSNumber numberWithBool:initiallyOn] forKey:@"initially_on"];
|
|
|
|
return [[result copy] autorelease];
|
|
}
|
|
|
|
|
|
- (NSDictionary *) validateNewStyleStandardSubentityDeclaration:(NSDictionary *)declaration
|
|
forShip:(NSString *)shipKey
|
|
fatalError:(BOOL *)outFatalError
|
|
{
|
|
NSMutableDictionary *result = nil;
|
|
NSString *subentityKey = nil;
|
|
Vector position = kZeroVector;
|
|
Quaternion orientation = kIdentityQuaternion;
|
|
BOOL isTurret;
|
|
BOOL isDock = NO;
|
|
float fireRate = -1.0f; // out of range constants
|
|
float weaponRange = -1.0f;
|
|
float weaponEnergy = -1.0f;
|
|
NSDictionary *scriptInfo = nil;
|
|
|
|
subentityKey = [declaration objectForKey:@"subentity_key"];
|
|
if (subentityKey == nil)
|
|
{
|
|
OOLogERR(@"shipData.load.error.badSubentity", @"subentity declaration for ship %@ specifies no subentity_key.", shipKey);
|
|
*outFatalError = YES;
|
|
return nil;
|
|
}
|
|
|
|
isTurret = [[declaration oo_stringForKey:@"type"] isEqualToString:@"ball_turret"];
|
|
if (isTurret)
|
|
{
|
|
fireRate = [declaration oo_floatForKey:@"fire_rate" defaultValue:-1.0f];
|
|
if (fireRate < 0.25f && fireRate >= 0.0f)
|
|
{
|
|
OOLogWARN(@"shipData.load.warning.turret.badFireRate", @"ball turret fire rate of %g for subentity of ship %@ is invalid, using 0.25.", fireRate, shipKey);
|
|
fireRate = 0.25f;
|
|
}
|
|
weaponRange = [declaration oo_floatForKey:@"weapon_range" defaultValue:-1.0f];
|
|
if (weaponRange > TURRET_SHOT_RANGE * COMBAT_WEAPON_RANGE_FACTOR)
|
|
{
|
|
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);
|
|
weaponRange = TURRET_SHOT_RANGE * COMBAT_WEAPON_RANGE_FACTOR; // approx. range of primary plasma canon.
|
|
}
|
|
|
|
weaponEnergy = [declaration oo_floatForKey:@"weapon_energy" defaultValue:-1.0f];
|
|
if (weaponEnergy > 100.0f)
|
|
|
|
{
|
|
OOLogWARN(@"shipData.load.warning.turret.badWeaponEnergy", @"ball turret weapon energy of %g for subentity of ship %@ is too high, using 100.", weaponEnergy, shipKey);
|
|
weaponEnergy = 100.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
isDock = [declaration oo_boolForKey:@"is_dock"];
|
|
}
|
|
|
|
position = [declaration oo_vectorForKey:@"position"];
|
|
orientation = [declaration oo_quaternionForKey:@"orientation"];
|
|
quaternion_normalize(&orientation);
|
|
|
|
scriptInfo = [declaration oo_dictionaryForKey:@"script_info"];
|
|
|
|
result = [NSMutableDictionary dictionaryWithCapacity:10];
|
|
[result setObject:isTurret ? @"ball_turret" : @"standard" forKey:@"type"];
|
|
[result setObject:subentityKey forKey:@"subentity_key"];
|
|
[result oo_setVector:position forKey:@"position"];
|
|
[result oo_setQuaternion:orientation forKey:@"orientation"];
|
|
if (isDock)
|
|
{
|
|
[result oo_setBool:YES forKey:@"is_dock"];
|
|
|
|
NSString* docklabel = [declaration oo_stringForKey:@"dock_label" defaultValue:@"the docking bay"];
|
|
[result setObject:docklabel forKey:@"dock_label"];
|
|
|
|
BOOL dockable = [declaration oo_boolForKey:@"allow_docking" defaultValue:YES];
|
|
BOOL playerdockable = [declaration oo_boolForKey:@"disallowed_docking_collides" defaultValue:NO];
|
|
BOOL undockable = [declaration oo_boolForKey:@"allow_launching" defaultValue:YES];
|
|
|
|
[result oo_setBool:dockable forKey:@"allow_docking"];
|
|
[result oo_setBool:playerdockable forKey:@"disallowed_docking_collides"];
|
|
[result oo_setBool:undockable forKey:@"allow_launching"];
|
|
|
|
}
|
|
|
|
if (isTurret)
|
|
{
|
|
// default constants are defined and set in shipEntity
|
|
if (fireRate > 0) [result oo_setFloat:fireRate forKey:@"fire_rate"];
|
|
if (weaponRange >= 0) [result oo_setFloat:weaponRange forKey:@"weapon_range"];
|
|
if (weaponEnergy >= 0) [result oo_setFloat:weaponEnergy forKey:@"weapon_energy"];
|
|
}
|
|
|
|
if (scriptInfo != nil)
|
|
{
|
|
[result setObject:scriptInfo forKey:@"script_info"];
|
|
}
|
|
|
|
return [[result copy] autorelease];
|
|
}
|
|
|
|
|
|
- (BOOL) shipIsBallTurretForKey:(NSString *)shipKey inShipData:(NSDictionary *)shipData
|
|
{
|
|
// Test for presence of setup_actions containing initialiseTurret.
|
|
NSArray *setupActions = nil;
|
|
NSEnumerator *actionEnum = nil;
|
|
NSString *action = nil;
|
|
|
|
setupActions = [[shipData oo_dictionaryForKey:shipKey] oo_arrayForKey:@"setup_actions"];
|
|
|
|
for (actionEnum = [setupActions objectEnumerator]; (action = [actionEnum nextObject]); )
|
|
{
|
|
if ([[ScanTokensFromString(action) objectAtIndex:0] isEqualToString:@"initialiseTurret"]) return YES;
|
|
}
|
|
|
|
if ([shipKey isEqualToString:@"ballturret"])
|
|
{
|
|
// compatibility for OXPs using old subentity declarations and the
|
|
// core turret entity
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation OOShipRegistry (Singleton)
|
|
|
|
/* Canonical singleton boilerplate.
|
|
See Cocoa Fundamentals Guide: Creating a Singleton Instance.
|
|
See also +sharedRegistry above.
|
|
|
|
NOTE: assumes single-threaded access.
|
|
*/
|
|
|
|
+ (id) allocWithZone:(NSZone *)inZone
|
|
{
|
|
if (sSingleton == nil)
|
|
{
|
|
OOLog(@"shipData.load.begin", @"%@", @"Loading ship data.");
|
|
sSingleton = [super allocWithZone:inZone];
|
|
return sSingleton;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
|
|
- (id) copyWithZone:(NSZone *)inZone
|
|
{
|
|
return self;
|
|
}
|
|
|
|
|
|
- (id) retain
|
|
{
|
|
return self;
|
|
}
|
|
|
|
|
|
- (NSUInteger) retainCount
|
|
{
|
|
return UINT_MAX;
|
|
}
|
|
|
|
|
|
- (void) release
|
|
{}
|
|
|
|
|
|
- (id) autorelease
|
|
{
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
static void GatherStringAddrsDict(NSDictionary *dict, NSMutableSet *strings, NSString *context);
|
|
static void GatherStringAddrsArray(NSArray *array, NSMutableSet *strings, NSString *context);
|
|
static void GatherStringAddrs(id object, NSMutableSet *strings, NSString *context);
|
|
|
|
|
|
static void DumpStringAddrs(NSDictionary *dict, NSString *context)
|
|
{
|
|
return;
|
|
static FILE *dump = NULL;
|
|
if (dump == NULL) dump = fopen("strings.txt", "w");
|
|
if (dump == NULL) return;
|
|
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
NSMutableSet *strings = [NSMutableSet set];
|
|
GatherStringAddrs(dict, strings, context);
|
|
|
|
NSEnumerator *entryEnum = nil;
|
|
NSDictionary *entry = nil;
|
|
for (entryEnum = [strings objectEnumerator]; (entry = [entryEnum nextObject]); )
|
|
{
|
|
NSString *string = [entry objectForKey:@"string"];
|
|
NSString *context = [entry objectForKey:@"context"];
|
|
void *pointer = [[entry objectForKey:@"address"] pointerValue];
|
|
|
|
string = [NSString stringWithFormat:@"%p\t%@: \"%@\"", pointer, context, string];
|
|
|
|
fprintf(dump, "%s\n", [string UTF8String]);
|
|
}
|
|
|
|
fprintf(dump, "\n");
|
|
fflush(dump);
|
|
[pool release];
|
|
}
|
|
|
|
|
|
static void GatherStringAddrsDict(NSDictionary *dict, NSMutableSet *strings, NSString *context)
|
|
{
|
|
NSEnumerator *keyEnum = nil;
|
|
id key = nil;
|
|
NSString *keyContext = [context stringByAppendingString:@" key"];
|
|
for (keyEnum = [dict keyEnumerator]; (key = [keyEnum nextObject]); )
|
|
{
|
|
GatherStringAddrs(key, strings, keyContext);
|
|
GatherStringAddrs([dict objectForKey:key], strings, [context stringByAppendingFormat:@".%@", key]);
|
|
}
|
|
}
|
|
|
|
|
|
static void GatherStringAddrsArray(NSArray *array, NSMutableSet *strings, NSString *context)
|
|
{
|
|
NSEnumerator *vEnum = nil;
|
|
NSString *v = nil;
|
|
unsigned i = 0;
|
|
for (vEnum = [array objectEnumerator]; (v = [vEnum nextObject]); )
|
|
{
|
|
GatherStringAddrs(v, strings, [context stringByAppendingFormat:@"[%u]", i++]);
|
|
}
|
|
}
|
|
|
|
|
|
static void GatherStringAddrs(id object, NSMutableSet *strings, NSString *context)
|
|
{
|
|
if ([object isKindOfClass:[NSString class]])
|
|
{
|
|
NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:object, @"string", [NSValue valueWithPointer:object], @"address", context, @"context", nil];
|
|
[strings addObject:entry];
|
|
}
|
|
else if ([object isKindOfClass:[NSArray class]])
|
|
{
|
|
GatherStringAddrsArray(object, strings, context);
|
|
}
|
|
else if ([object isKindOfClass:[NSDictionary class]])
|
|
{
|
|
GatherStringAddrsDict(object, strings, context);
|
|
}
|
|
}
|
|
|
|
|
|
static NSComparisonResult SortDemoShipsByName (id a, id b, void* context)
|
|
{
|
|
return [[a oo_stringForKey:@"name"] compare:[b oo_stringForKey:@"name"]];
|
|
}
|
|
|
|
|
|
static NSComparisonResult SortDemoCategoriesByName (id a, id b, void* context)
|
|
{
|
|
return [OOShipLibraryCategoryPlural([[a oo_dictionaryAtIndex:0] oo_stringForKey:@"class"]) compare:OOShipLibraryCategoryPlural([[b oo_dictionaryAtIndex:0] oo_stringForKey:@"class"])];
|
|
}
|