Line data Source code
1 0 : /* 2 : 3 : ShipEntityLoadRestore.m 4 : 5 : 6 : Oolite 7 : Copyright (C) 2004-2013 Giles C Williams and contributors 8 : 9 : This program is free software; you can redistribute it and/or 10 : modify it under the terms of the GNU General Public License 11 : as published by the Free Software Foundation; either version 2 12 : of the License, or (at your option) any later version. 13 : 14 : This program is distributed in the hope that it will be useful, 15 : but WITHOUT ANY WARRANTY; without even the implied warranty of 16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 : GNU General Public License for more details. 18 : 19 : You should have received a copy of the GNU General Public License 20 : along with this program; if not, write to the Free Software 21 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 22 : MA 02110-1301, USA. 23 : 24 : */ 25 : 26 : #import "ShipEntityLoadRestore.h" 27 : #import "Universe.h" 28 : 29 : #import "OOShipRegistry.h" 30 : #import "OORoleSet.h" 31 : #import "OOCollectionExtractors.h" 32 : #import "OOConstToString.h" 33 : #import "OOShipGroup.h" 34 : #import "OOEquipmentType.h" 35 : #import "AI.h" 36 : #import "ShipEntityAI.h" 37 : 38 : 39 0 : #define KEY_SHIP_KEY @"ship_key" 40 0 : #define KEY_SHIPDATA_OVERRIDES @"shipdata_overrides" 41 0 : #define KEY_SHIPDATA_DELETES @"shipdata_deletes" 42 0 : #define KEY_PRIMARY_ROLE @"primary_role" 43 0 : #define KEY_POSITION @"position" 44 0 : #define KEY_ORIENTATION @"orientation" 45 0 : #define KEY_ROLES @"roles" 46 0 : #define KEY_FUEL @"fuel" 47 0 : #define KEY_BOUNTY @"bounty" 48 0 : #define KEY_ENERGY_LEVEL @"energy_level" 49 0 : #define KEY_EQUIPMENT @"equipment" 50 0 : #define KEY_MISSILES @"missiles" 51 0 : #define KEY_FORWARD_WEAPON @"forward_weapon_type" 52 0 : #define KEY_AFT_WEAPON @"aft_weapon_type" 53 0 : #define KEY_SCAN_CLASS @"scan_class" 54 : 55 : // AI is a complete pickled AI state. 56 0 : #define KEY_AI @"AI" 57 : 58 : // Group IDs are numbers synchronised through the context object. 59 0 : #define KEY_GROUP_ID @"group" 60 0 : #define KEY_GROUP_NAME @"group_name" 61 0 : #define KEY_IS_GROUP_LEADER @"is_group_leader" 62 0 : #define KEY_ESCORT_GROUP_ID @"escort_group" 63 : 64 : 65 : static void StripIgnoredKeys(NSMutableDictionary *dict); 66 : static NSUInteger GroupIDForGroup(OOShipGroup *group, NSMutableDictionary *context); 67 : static OOShipGroup *GroupForGroupID(NSUInteger groupID, NSMutableDictionary *context); 68 : 69 : 70 : @interface ShipEntity (LoadRestoreInternal) 71 : 72 0 : - (void) simplifyShipdata:(NSMutableDictionary *)data andGetDeletes:(NSArray **)deletes; 73 : 74 : @end 75 : 76 : 77 : @implementation ShipEntity (LoadRestore) 78 : 79 : - (NSDictionary *) savedShipDictionaryWithContext:(NSMutableDictionary *)context 80 : { 81 : NSMutableDictionary *result = [NSMutableDictionary dictionary]; 82 : if (context == nil) context = [NSMutableDictionary dictionary]; 83 : 84 : [result setObject:_shipKey forKey:KEY_SHIP_KEY]; 85 : 86 : NSMutableDictionary *updatedShipInfo = [NSMutableDictionary dictionaryWithDictionary:shipinfoDictionary]; 87 : 88 : [updatedShipInfo setObject:[[self roleSet] roleString] forKey:KEY_ROLES]; 89 : [updatedShipInfo oo_setUnsignedInteger:fuel forKey:KEY_FUEL]; 90 : [updatedShipInfo oo_setUnsignedLongLong:bounty forKey:KEY_BOUNTY]; 91 : [updatedShipInfo setObject:OOStringFromWeaponType(forward_weapon_type) forKey:KEY_FORWARD_WEAPON]; 92 : [updatedShipInfo setObject:OOStringFromWeaponType(aft_weapon_type) forKey:KEY_AFT_WEAPON]; 93 : [updatedShipInfo setObject:OOStringFromScanClass(scanClass) forKey:KEY_SCAN_CLASS]; 94 : 95 : NSArray *deletes = nil; 96 : [self simplifyShipdata:updatedShipInfo andGetDeletes:&deletes]; 97 : 98 : [result setObject:updatedShipInfo forKey:KEY_SHIPDATA_OVERRIDES]; 99 : if (deletes != nil) [result setObject:deletes forKey:KEY_SHIPDATA_DELETES]; 100 : 101 : if (!HPvector_equal([self position], kZeroHPVector)) 102 : { 103 : [result oo_setHPVector:[self position] forKey:KEY_POSITION]; 104 : } 105 : if (!quaternion_equal([self normalOrientation], kIdentityQuaternion)) 106 : { 107 : [result oo_setQuaternion:[self normalOrientation] forKey:KEY_ORIENTATION]; 108 : } 109 : 110 : if (energy != maxEnergy) [result oo_setFloat:energy / maxEnergy forKey:KEY_ENERGY_LEVEL]; 111 : 112 : [result setObject:[self primaryRole] forKey:KEY_PRIMARY_ROLE]; 113 : 114 : // Add equipment. 115 : NSArray *equipment = [[self equipmentEnumerator] allObjects]; 116 : if ([equipment count] != 0) [result setObject:equipment forKey:KEY_EQUIPMENT]; 117 : 118 : // Add missiles. 119 : if (missiles > 0) 120 : { 121 : NSMutableArray *missileArray = [NSMutableArray array]; 122 : unsigned i; 123 : for (i = 0; i < missiles; i++) 124 : { 125 : NSString *missileType = [missile_list[i] identifier]; 126 : if (missileType != nil) [missileArray addObject:missileType]; 127 : } 128 : [result setObject:missileArray forKey:KEY_MISSILES]; 129 : } 130 : 131 : // Add groups. 132 : if (_group != nil) 133 : { 134 : [result oo_setUnsignedInteger:GroupIDForGroup(_group, context) forKey:KEY_GROUP_ID]; 135 : if ([_group leader] == self) [result oo_setBool:YES forKey:KEY_IS_GROUP_LEADER]; 136 : NSString *groupName = [_group name]; 137 : if (groupName != nil) 138 : { 139 : [result setObject:groupName forKey:KEY_GROUP_NAME]; 140 : } 141 : } 142 : if (_escortGroup != nil) 143 : { 144 : [result oo_setUnsignedInteger:GroupIDForGroup(_escortGroup, context) forKey:KEY_ESCORT_GROUP_ID]; 145 : } 146 : /* Eric: 147 : The escortGroup property is removed from the lead ship, on entering witchspace. 148 : But it is needed in the save file to correctly restore an escorted group. 149 : */ 150 : else if (_group != nil && [_group leader] == self) 151 : { 152 : [result oo_setUnsignedInteger:GroupIDForGroup(_group, context) forKey:KEY_ESCORT_GROUP_ID]; 153 : } 154 : 155 : // FIXME: AI. 156 : // Eric: I think storing the AI name should be enough. On entering a wormhole, the stack is cleared so there are no preserved AI states. 157 : // Also the AI restarts itself with the GLOBAL state, so no need to store any old state. 158 : if ([[[self getAI] name] isEqualToString:@"nullAI.plist"]) 159 : { 160 : // might be a JS version 161 : [result setObject:[[self getAI] associatedJS] forKey:KEY_AI]; 162 : // if there isn't, loading nullAI.js will load nullAI.plist anyway 163 : } 164 : else 165 : { 166 : [result setObject:[[self getAI] name] forKey:KEY_AI]; 167 : } 168 : 169 : return result; 170 : } 171 : 172 : 173 : + (id) shipRestoredFromDictionary:(NSDictionary *)dict 174 : useFallback:(BOOL)fallback 175 : context:(NSMutableDictionary *)context 176 : { 177 : if (dict == nil) return nil; 178 : if (context == nil) context = [NSMutableDictionary dictionary]; 179 : 180 : ShipEntity *ship = nil; 181 : 182 : NSString *shipKey = [dict oo_stringForKey:KEY_SHIP_KEY]; 183 : NSDictionary *shipData = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey]; 184 : 185 : if (shipData != nil) 186 : { 187 : NSMutableDictionary *mergedData = [NSMutableDictionary dictionaryWithDictionary:shipData]; 188 : 189 : StripIgnoredKeys(mergedData); 190 : NSArray *deletes = [dict oo_arrayForKey:KEY_SHIPDATA_DELETES]; 191 : if (deletes != nil) [mergedData removeObjectsForKeys:deletes]; 192 : [mergedData addEntriesFromDictionary:[dict oo_dictionaryForKey:KEY_SHIPDATA_OVERRIDES]]; 193 : [mergedData oo_setBool:NO forKey:@"auto_ai"]; 194 : [mergedData oo_setUnsignedInteger:0 forKey:@"escorts"]; 195 : 196 : Class shipClass = [UNIVERSE shipClassForShipDictionary:mergedData]; 197 : ship = [[[shipClass alloc] initWithKey:shipKey definition:mergedData] autorelease]; 198 : 199 : // FIXME: restore AI. 200 : [ship setAITo:[dict oo_stringForKey:KEY_AI defaultValue:@"nullAI.plist"]]; 201 : 202 : [ship setPrimaryRole:[dict oo_stringForKey:KEY_PRIMARY_ROLE]]; 203 : 204 : } 205 : else 206 : { 207 : // Unknown ship; fall back on role if desired and possible. 208 : NSString *shipPrimaryRole = [dict oo_stringForKey:KEY_PRIMARY_ROLE]; 209 : if (!fallback || shipPrimaryRole == nil) return nil; 210 : 211 : ship = [[UNIVERSE newShipWithRole:shipPrimaryRole] autorelease]; 212 : if (ship == nil) return nil; 213 : } 214 : 215 : // The following stuff is deliberately set up the same way even if using role fallback. 216 : [ship setPosition:[dict oo_hpvectorForKey:KEY_POSITION]]; 217 : [ship setNormalOrientation:[dict oo_quaternionForKey:KEY_ORIENTATION]]; 218 : 219 : float energyLevel = [dict oo_floatForKey:KEY_ENERGY_LEVEL defaultValue:1.0f]; 220 : [ship setEnergy:energyLevel * [ship maxEnergy]]; 221 : 222 : [ship removeAllEquipment]; 223 : NSEnumerator *eqEnum = nil; 224 : NSString *eqKey = nil; 225 : for (eqEnum = [[dict oo_arrayForKey:KEY_EQUIPMENT] objectEnumerator]; (eqKey = [eqEnum nextObject]); ) 226 : { 227 : [ship addEquipmentItem:eqKey withValidation:NO inContext:@"loading"]; 228 : } 229 : 230 : [ship removeMissiles]; 231 : for (eqEnum = [[dict oo_arrayForKey:KEY_MISSILES] objectEnumerator]; (eqKey = [eqEnum nextObject]); ) 232 : { 233 : [ship addEquipmentItem:eqKey withValidation:NO inContext:@"loading"]; 234 : } 235 : 236 : // Groups. 237 : NSUInteger groupID = [dict oo_integerForKey:KEY_GROUP_ID defaultValue:NSNotFound]; 238 : if (groupID != NSNotFound) 239 : { 240 : OOShipGroup *group = GroupForGroupID(groupID, context); 241 : [ship setGroup:group]; // Handles adding to group 242 : if ([dict oo_boolForKey:KEY_IS_GROUP_LEADER]) [group setLeader:ship]; 243 : NSString *groupName = [dict oo_stringForKey:KEY_GROUP_NAME]; 244 : if (groupName != nil) [group setName:groupName]; 245 : if ([ship hasPrimaryRole:@"escort"] && ship != [group leader]) 246 : { 247 : [ship setOwner:[group leader]]; 248 : } 249 : } 250 : 251 : groupID = [dict oo_integerForKey:KEY_ESCORT_GROUP_ID defaultValue:NSNotFound]; 252 : if (groupID != NSNotFound) 253 : { 254 : OOShipGroup *group = GroupForGroupID(groupID, context); 255 : [group setLeader:ship]; 256 : [group setName:@"escort group"]; 257 : [ship setEscortGroup:group]; 258 : } 259 : 260 : return ship; 261 : } 262 : 263 : 264 0 : - (void) simplifyShipdata:(NSMutableDictionary *)data andGetDeletes:(NSArray **)deletes 265 : { 266 : NSParameterAssert(data != nil && deletes != NULL); 267 : *deletes = nil; 268 : 269 : // Get original ship data. 270 : NSMutableDictionary *referenceData = [NSMutableDictionary dictionaryWithDictionary:[[OOShipRegistry sharedRegistry] shipInfoForKey:[self shipDataKey]]]; 271 : 272 : // Discard stuff that we handle separately. 273 : StripIgnoredKeys(referenceData); 274 : StripIgnoredKeys(data); 275 : 276 : // Note items that are in referenceData, but not data. 277 : NSMutableArray *foundDeletes = [NSMutableArray array]; 278 : NSEnumerator *enumerator = nil; 279 : NSString *key = nil; 280 : for (enumerator = [referenceData keyEnumerator]; (key = [enumerator nextObject]); ) 281 : { 282 : if ([data objectForKey:key] == nil) 283 : { 284 : [foundDeletes addObject:key]; 285 : } 286 : } 287 : if ([foundDeletes count] != 0) *deletes = foundDeletes; 288 : 289 : // after rev3010 this loop was using cycles without doing anything - commenting this whole loop out for now. -- kaks 20100207 290 : /* 291 : // Discard anything that hasn't changed. 292 : for (enumerator = [data keyEnumerator]; (key = [enumerator nextObject]); ) 293 : { 294 : id referenceVal = [referenceData objectForKey:key]; 295 : id myVal = [data objectForKey:key]; 296 : if ([referenceVal isEqual:myVal]) 297 : { 298 : // [data removeObjectForKey:key]; 299 : } 300 : } 301 : */ 302 : } 303 : 304 : @end 305 : 306 : 307 0 : static void StripIgnoredKeys(NSMutableDictionary *dict) 308 : { 309 : static NSArray *ignoredKeys = nil; 310 : if (ignoredKeys == nil) ignoredKeys = [[NSArray alloc] initWithObjects:@"ai_type", @"has_ecm", @"has_scoop", @"has_escape_pod", @"has_energy_bomb", @"has_fuel_injection", @"has_cloaking_device", @"has_military_jammer", @"has_military_scanner_filter", @"has_shield_booster", @"has_shield_enhancer", @"escorts", @"escort_role", @"escort-ship", @"conditions", @"missiles", @"auto_ai", nil]; 311 : 312 : NSEnumerator *keyEnum = nil; 313 : NSString *key = nil; 314 : for (keyEnum = [ignoredKeys objectEnumerator]; (key = [keyEnum nextObject]); ) 315 : { 316 : [dict removeObjectForKey:key]; 317 : } 318 : } 319 : 320 : 321 0 : static NSUInteger GroupIDForGroup(OOShipGroup *group, NSMutableDictionary *context) 322 : { 323 : NSMutableDictionary *groupIDs = [context objectForKey:@"groupIDs"]; 324 : if (groupIDs == nil) 325 : { 326 : groupIDs = [NSMutableDictionary dictionary]; 327 : [context setObject:groupIDs forKey:@"groupIDs"]; 328 : } 329 : 330 : NSValue *key = [NSValue valueWithNonretainedObject:group]; 331 : NSNumber *groupIDObj = [groupIDs objectForKey:key]; 332 : unsigned groupID; 333 : if (groupIDObj == nil) 334 : { 335 : // Assign a new group ID. 336 : groupID = [context oo_unsignedIntForKey:@"nextGroupID"]; 337 : groupIDObj = [NSNumber numberWithUnsignedInt:groupID]; 338 : [context oo_setUnsignedInteger:groupID + 1 forKey:@"nextGroupID"]; 339 : [groupIDs setObject:groupIDObj forKey:key]; 340 : 341 : /* Also keep references to the groups. This isn't necessary at the 342 : time of writing, but would be if we e.g. switched to pickling 343 : ships in wormholes all the time (each wormhole would then need a 344 : persistent context). We can't simply use the groups instead of 345 : NSValues as keys, becuase dictionary keys must be copyable. 346 : */ 347 : NSMutableSet *groups = [context objectForKey:@"groups"]; 348 : if (groups == nil) 349 : { 350 : groups = [NSMutableSet set]; 351 : [context setObject:groups forKey:@"groups"]; 352 : } 353 : [groups addObject:group]; 354 : } 355 : else 356 : { 357 : groupID = [groupIDObj unsignedIntValue]; 358 : } 359 : 360 : 361 : return groupID; 362 : } 363 : 364 : 365 0 : static OOShipGroup *GroupForGroupID(NSUInteger groupID, NSMutableDictionary *context) 366 : { 367 : NSNumber *key = [NSNumber numberWithUnsignedInteger:groupID]; 368 : 369 : NSMutableDictionary *groups = [context objectForKey:@"groupsByID"]; 370 : if (groups == nil) 371 : { 372 : groups = [NSMutableDictionary dictionary]; 373 : [context setObject:groups forKey:@"groupsByID"]; 374 : } 375 : 376 : OOShipGroup *group = [groups objectForKey:key]; 377 : if (group == nil) 378 : { 379 : group = [[[OOShipGroup alloc] init] autorelease]; 380 : [groups setObject:group forKey:key]; 381 : } 382 : 383 : return group; 384 : }