Oolite 1.91.0.7644-241112-7f5034b
Loading...
Searching...
No Matches
ShipEntityLoadRestore.m
Go to the documentation of this file.
1/*
2
3ShipEntityLoadRestore.m
4
5
6Oolite
7Copyright (C) 2004-2013 Giles C Williams and contributors
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22MA 02110-1301, USA.
23
24*/
25
27#import "Universe.h"
28
29#import "OOShipRegistry.h"
30#import "OORoleSet.h"
32#import "OOConstToString.h"
33#import "OOShipGroup.h"
34#import "OOEquipmentType.h"
35#import "AI.h"
36#import "ShipEntityAI.h"
37
38
39#define KEY_SHIP_KEY @"ship_key"
40#define KEY_SHIPDATA_OVERRIDES @"shipdata_overrides"
41#define KEY_SHIPDATA_DELETES @"shipdata_deletes"
42#define KEY_PRIMARY_ROLE @"primary_role"
43#define KEY_POSITION @"position"
44#define KEY_ORIENTATION @"orientation"
45#define KEY_ROLES @"roles"
46#define KEY_FUEL @"fuel"
47#define KEY_BOUNTY @"bounty"
48#define KEY_ENERGY_LEVEL @"energy_level"
49#define KEY_EQUIPMENT @"equipment"
50#define KEY_MISSILES @"missiles"
51#define KEY_FORWARD_WEAPON @"forward_weapon_type"
52#define KEY_AFT_WEAPON @"aft_weapon_type"
53#define KEY_SCAN_CLASS @"scan_class"
54
55// AI is a complete pickled AI state.
56#define KEY_AI @"AI"
57
58// Group IDs are numbers synchronised through the context object.
59#define KEY_GROUP_ID @"group"
60#define KEY_GROUP_NAME @"group_name"
61#define KEY_IS_GROUP_LEADER @"is_group_leader"
62#define KEY_ESCORT_GROUP_ID @"escort_group"
63
64
65static void StripIgnoredKeys(NSMutableDictionary *dict);
66static NSUInteger GroupIDForGroup(OOShipGroup *group, NSMutableDictionary *context);
67static OOShipGroup *GroupForGroupID(NSUInteger groupID, NSMutableDictionary *context);
68
69
70@interface ShipEntity (LoadRestoreInternal)
71
72- (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- (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
307static 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
321static 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
365static 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}
const HPVector kZeroHPVector
Definition OOHPVector.m:28
unsigned count
return nil
const Quaternion kIdentityQuaternion
#define KEY_EQUIPMENT
#define KEY_MISSILES
static NSUInteger GroupIDForGroup(OOShipGroup *group, NSMutableDictionary *context)
static void StripIgnoredKeys(NSMutableDictionary *dict)
#define KEY_IS_GROUP_LEADER
static OOShipGroup * GroupForGroupID(NSUInteger groupID, NSMutableDictionary *context)
NSString * name()
Definition AI.m:364
NSString * associatedJS()
Definition AI.m:370
GLfloat maxEnergy
Definition Entity.h:143
void setNormalOrientation:(Quaternion quat)
Definition Entity.m:744
HPVector position
Definition Entity.h:112
void setEnergy:(GLfloat amount)
Definition Entity.m:811
Quaternion normalOrientation()
Definition Entity.m:738
void setPosition:(HPVector posn)
Definition Entity.m:647
NSString * roleString()
Definition OORoleSet.m:115
void setName:(NSString *name)
void setLeader:(ShipEntity *leader)
ShipEntity * leader()
OOShipRegistry * sharedRegistry()
NSDictionary * shipInfoForKey:(NSString *key)
BOOL addEquipmentItem:withValidation:inContext:(NSString *equipmentKey,[withValidation] BOOL validateAddition,[inContext] NSString *context)
OORoleSet * roleSet
Definition ShipEntity.h:332
void setGroup:(OOShipGroup *group)
NSString * primaryRole
Definition ShipEntity.h:333
void setPrimaryRole:(NSString *role)
OOCreditsQuantity removeMissiles()
NSEnumerator * equipmentEnumerator()
void removeAllEquipment()
void setAITo:(NSString *aiString)
void simplifyShipdata:andGetDeletes:(NSMutableDictionary *data, [andGetDeletes] NSArray **deletes)
void setEscortGroup:(OOShipGroup *group)
void setOwner:(Entity *who_owns_entity)