Line data Source code
1 0 : /*
2 :
3 : PlayerEntityLoadSave.m
4 :
5 : Oolite
6 : Copyright (C) 2004-2013 Giles C Williams and contributors
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #import "PlayerEntityLoadSave.h"
26 : #import "PlayerEntityContracts.h"
27 : #import "PlayerEntityControls.h"
28 : #import "PlayerEntitySound.h"
29 :
30 : #import "NSFileManagerOOExtensions.h"
31 : #import "GameController.h"
32 : #import "ResourceManager.h"
33 : #import "OOStringExpander.h"
34 : #import "PlayerEntityControls.h"
35 : #import "ProxyPlayerEntity.h"
36 : #import "ShipEntityAI.h"
37 : #import "OOXMLExtensions.h"
38 : #import "OOSound.h"
39 : #import "OOColor.h"
40 : #import "OOStringParsing.h"
41 : #import "OOPListParsing.h"
42 : #import "StationEntity.h"
43 : #import "OOCollectionExtractors.h"
44 : #import "OOConstToString.h"
45 : #import "OOShipRegistry.h"
46 : #import "OOTexture.h"
47 : #import "NSStringOOExtensions.h"
48 : #import "NSNumberOOExtensions.h"
49 : #import "OOJavaScriptEngine.h"
50 :
51 :
52 : // Name of modifier key used to issue commands. See also -isCommandModifierKeyDown.
53 : #if OO_USE_CUSTOM_LOAD_SAVE
54 0 : #define COMMAND_MODIFIER_KEY "Ctrl"
55 : #endif
56 :
57 :
58 : static uint16_t PersonalityForCommanderDict(NSDictionary *dict);
59 :
60 :
61 : #if OO_USE_CUSTOM_LOAD_SAVE
62 :
63 : @interface MyOpenGLView (OOLoadSaveExtensions)
64 :
65 0 : - (BOOL)isCommandModifierKeyDown;
66 :
67 : @end
68 :
69 : #endif
70 :
71 :
72 : @interface PlayerEntity (OOLoadSavePrivate)
73 :
74 : #if OOLITE_USE_APPKIT_LOAD_SAVE
75 :
76 0 : - (BOOL) loadPlayerWithPanel;
77 0 : - (void) savePlayerWithPanel;
78 :
79 : #endif
80 :
81 : #if OO_USE_CUSTOM_LOAD_SAVE
82 :
83 0 : - (void) setGuiToLoadCommanderScreen;
84 0 : - (void) setGuiToSaveCommanderScreen: (NSString *)cdrName;
85 0 : - (void) setGuiToOverwriteScreen: (NSString *)cdrName;
86 0 : - (void) lsCommanders: (GuiDisplayGen *)gui directory: (NSString*)directory pageNumber: (int)page highlightName: (NSString *)highlightName;
87 0 : - (void) showCommanderShip: (int)cdrArrayIndex;
88 0 : - (int) findIndexOfCommander: (NSString *)cdrName;
89 0 : - (void) nativeSavePlayer: (NSString *)cdrName;
90 0 : - (BOOL) existingNativeSave: (NSString *)cdrName;
91 :
92 : #endif
93 :
94 0 : - (void) writePlayerToPath:(NSString *)path;
95 :
96 : @end
97 :
98 :
99 : @implementation PlayerEntity (LoadSave)
100 :
101 : - (BOOL)loadPlayer
102 : {
103 : BOOL OK = YES;
104 :
105 : #if OO_USE_APPKIT_LOAD_SAVE_ALWAYS
106 : OK = [self loadPlayerWithPanel];
107 : #elif OOLITE_USE_APPKIT_LOAD_SAVE
108 : // OS X: use system open/save dialogs in windowed mode, custom interface in full-screen.
109 : if ([[UNIVERSE gameController] inFullScreenMode])
110 : {
111 : [self setGuiToLoadCommanderScreen];
112 : }
113 : else
114 : {
115 : OK = [self loadPlayerWithPanel];
116 : }
117 : #else
118 : // Other platforms: use custom interface all the time.
119 : [self setGuiToLoadCommanderScreen];
120 : #endif
121 : return OK;
122 : }
123 :
124 :
125 : - (void)savePlayer
126 : {
127 : #if OO_USE_APPKIT_LOAD_SAVE_ALWAYS
128 : [self savePlayerWithPanel];
129 : #elif OOLITE_USE_APPKIT_LOAD_SAVE
130 : // OS X: use system open/save dialogs in windowed mode, custom interface in full-screen.
131 : if ([[UNIVERSE gameController] inFullScreenMode])
132 : {
133 : [self setGuiToSaveCommanderScreen:self.lastsaveName];
134 : }
135 : else
136 : {
137 : [self savePlayerWithPanel];
138 : }
139 : #else
140 : // Other platforms: use custom interface all the time.
141 : [self setGuiToSaveCommanderScreen:[self lastsaveName]];
142 : #endif
143 : }
144 :
145 : - (void) autosavePlayer
146 : {
147 : NSString *tmp_path = nil;
148 : NSString *tmp_name = nil;
149 : NSString *dir = [[UNIVERSE gameController] playerFileDirectory];
150 :
151 : tmp_name = [self lastsaveName];
152 : tmp_path = save_path;
153 :
154 : ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("AUTO_SAVE"));
155 :
156 : NSString *saveName = [self lastsaveName];
157 : NSString *autosaveSuffix = DESC(@"autosave-commander-suffix");
158 :
159 : if (![saveName hasSuffix:autosaveSuffix])
160 : {
161 : saveName = [saveName stringByAppendingString:autosaveSuffix];
162 : }
163 : NSString *savePath = [dir stringByAppendingPathComponent:[saveName stringByAppendingString:@".oolite-save"]];
164 :
165 : [self setLastsaveName:saveName];
166 :
167 : @try
168 : {
169 : [self writePlayerToPath:savePath];
170 : }
171 : @catch (id exception)
172 : {
173 : // Suppress exceptions silently. Warning the user about failed autosaves would be pretty unhelpful.
174 : }
175 :
176 : if (tmp_path != nil)
177 : {
178 : [save_path autorelease];
179 : save_path = [tmp_path copy];
180 : }
181 : [self setLastsaveName:tmp_name];
182 : }
183 :
184 :
185 : - (void) quicksavePlayer
186 : {
187 : MyOpenGLView *gameView = [UNIVERSE gameView];
188 : NSString *path = nil;
189 :
190 : path = save_path;
191 : if (!path) path = [[gameView gameController] playerFileToLoad];
192 : if (!path)
193 : {
194 : OOLog(@"quickSave.failed.noName", @"%@", @"ERROR no file name returned by [[gameView gameController] playerFileToLoad]");
195 : [NSException raise:@"OoliteGameNotSavedException"
196 : format:@"ERROR no file name returned by [[gameView gameController] playerFileToLoad]"];
197 : }
198 :
199 : ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("QUICK_SAVE"));
200 :
201 : [self writePlayerToPath:path];
202 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
203 : [self setGuiToStatusScreen];
204 : }
205 :
206 :
207 : - (void) setGuiToScenarioScreen:(int)page
208 : {
209 : NSArray *scenarios = [UNIVERSE scenarios];
210 : [UNIVERSE removeDemoShips];
211 : // GUI stuff
212 : {
213 : GuiDisplayGen *gui = [UNIVERSE gui];
214 : OOGUIRow start_row = GUI_ROW_SCENARIOS_START;
215 : OOGUIRow row = start_row;
216 : BOOL guiChanged = (gui_screen != GUI_SCREEN_NEWGAME);
217 :
218 : [gui clearAndKeepBackground:!guiChanged];
219 : [gui setTitle:DESC(@"oolite-newgame-title")];
220 :
221 : OOGUITabSettings tab_stops;
222 : tab_stops[0] = 0;
223 : tab_stops[1] = -480;
224 : [gui setTabStops:tab_stops];
225 :
226 : unsigned n_rows = GUI_MAX_ROWS_SCENARIOS;
227 : NSUInteger i, count = [scenarios count];
228 :
229 : NSDictionary *scenario = nil;
230 :
231 : [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-scenario-exit"), @" <----- ", nil] forRow:start_row - 2];
232 : [gui setColor:[OOColor redColor] forRow:start_row - 2];
233 : [gui setKey:@"exit" forRow:start_row - 2];
234 :
235 :
236 : if (page > 0)
237 : {
238 : [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:start_row - 1];
239 : [gui setColor:[OOColor greenColor] forRow:start_row - 1];
240 : [gui setKey:[NSString stringWithFormat:@"__page:%i",page-1] forRow:start_row - 1];
241 : }
242 :
243 : [self setShowDemoShips:NO];
244 :
245 : for (i = page*n_rows ; i < count && row < start_row + n_rows ; i++)
246 : {
247 : scenario = [[UNIVERSE scenarios] objectAtIndex:i];
248 : NSString *scenarioName = [NSString stringWithFormat:@" %@ ",[scenario oo_stringForKey:@"name"]];
249 : [gui setText:OOExpand(scenarioName) forRow:row];
250 : [gui setKey:[NSString stringWithFormat:@"Scenario:%lu", (unsigned long)i] forRow:row];
251 : ++row;
252 : }
253 :
254 : if ((page+1) * n_rows < count)
255 : {
256 : [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil] forRow:row];
257 : [gui setColor:[OOColor greenColor] forRow:row];
258 : [gui setKey:[NSString stringWithFormat:@"__page:%i",page+1] forRow:row];
259 : ++row;
260 : }
261 :
262 : [gui setSelectableRange:NSMakeRange(start_row - 2,3 + row - start_row)];
263 : [gui setSelectedRow:start_row];
264 : [self showScenarioDetails];
265 :
266 : gui_screen = GUI_SCREEN_NEWGAME;
267 :
268 : if (guiChanged)
269 : {
270 : [gui setBackgroundTextureKey:@"newgame"];
271 : [gui setForegroundTextureKey:@"newgame_overlay"];
272 : }
273 : }
274 :
275 : [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
276 : }
277 :
278 : - (void) addScenarioModel:(NSString *)shipKey
279 : {
280 : [self showShipModelWithKey:shipKey shipData:nil personality:0 factorX:1.2 factorY:0.8 factorZ:6.4 inContext:@"scenario"];
281 : }
282 :
283 :
284 : - (void) showScenarioDetails
285 : {
286 : GuiDisplayGen* gui = [UNIVERSE gui];
287 : NSString* key = [gui selectedRowKey];
288 : [UNIVERSE removeDemoShips];
289 :
290 : if ([key hasPrefix:@"Scenario"])
291 : {
292 : int item = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1];
293 : NSDictionary *scenario = [[UNIVERSE scenarios] objectAtIndex:item];
294 : [self setShowDemoShips:NO];
295 : for (NSUInteger i=GUI_ROW_SCENARIOS_DETAIL;i<=27;i++)
296 : {
297 : [gui setText:@"" forRow:i];
298 : }
299 : if (scenario)
300 : {
301 : [gui addLongText:OOExpand([scenario oo_stringForKey:@"description"]) startingAtRow:GUI_ROW_SCENARIOS_DETAIL align:GUI_ALIGN_LEFT];
302 : NSString *shipKey = [scenario oo_stringForKey:@"model"];
303 : if (shipKey != nil)
304 : {
305 : [self addScenarioModel:shipKey];
306 : [self setShowDemoShips:YES];
307 : }
308 : }
309 :
310 : }
311 : }
312 :
313 :
314 : - (BOOL) startScenario
315 : {
316 : GuiDisplayGen* gui = [UNIVERSE gui];
317 : NSString* key = [gui selectedRowKey];
318 :
319 : if ([key isEqualToString:@"exit"])
320 : {
321 : // intended to return to main menu
322 : return NO;
323 : }
324 : if ([key hasPrefix:@"__page"])
325 : {
326 : int page = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1];
327 : [self setGuiToScenarioScreen:page];
328 : return YES;
329 : }
330 : int selection = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1];
331 :
332 : NSDictionary *scenario = [[UNIVERSE scenarios] objectAtIndex:selection];
333 : NSString *file = [scenario oo_stringForKey:@"file" defaultValue:nil];
334 : if (file == nil)
335 : {
336 : OOLog(@"scenario.init.error", @"%@", @"No file entry found for scenario");
337 : return NO;
338 : }
339 : NSString *path = [ResourceManager pathForFileNamed:file inFolder:@"Scenarios"];
340 : if (path == nil)
341 : {
342 : OOLog(@"scenario.init.error", @"Game file not found for scenario %@",file);
343 : return NO;
344 : }
345 : BOOL result = [self loadPlayerFromFile:path asNew:YES];
346 : if (!result)
347 : {
348 : return NO;
349 : }
350 : [scenarioKey release];
351 : scenarioKey = [[scenario oo_stringForKey:@"scenario" defaultValue:nil] retain];
352 :
353 : // don't drop the save game directory in
354 : return YES;
355 : }
356 :
357 :
358 :
359 :
360 : #if OO_USE_CUSTOM_LOAD_SAVE
361 :
362 : - (NSString *)commanderSelector
363 : {
364 : MyOpenGLView *gameView = [UNIVERSE gameView];
365 : GuiDisplayGen *gui = [UNIVERSE gui];
366 : NSString *dir = [[UNIVERSE gameController] playerFileDirectory];
367 :
368 : int idx;
369 : if([self handleGUIUpDownArrowKeys])
370 : {
371 : int guiSelectedRow=[gui selectedRow];
372 : idx=(guiSelectedRow - STARTROW) + (currentPage * NUMROWS);
373 : if (guiSelectedRow != MOREROW && guiSelectedRow != BACKROW && guiSelectedRow != EXITROW)
374 : {
375 : [self showCommanderShip: idx];
376 : }
377 : else
378 : {
379 : [UNIVERSE removeDemoShips];
380 : [gui setText:@"" forRow:CDRDESCROW align:GUI_ALIGN_LEFT];
381 : [gui setText:@"" forRow:CDRDESCROW + 1 align:GUI_ALIGN_LEFT];
382 : [gui setText:@"" forRow:CDRDESCROW + 2 align:GUI_ALIGN_LEFT];
383 : }
384 :
385 : }
386 : else
387 : {
388 : idx=([gui selectedRow] - STARTROW) + (currentPage * NUMROWS);
389 : }
390 :
391 : // handle page <-- and page --> keys
392 : if (([self checkKeyPress:n_key_gui_arrow_left] || [self checkKeyPress:n_key_gui_page_up]) && [[gui keyForRow:BACKROW] isEqual: GUI_KEY_OK])
393 : {
394 : currentPage--;
395 : [self playMenuPagePrevious];
396 : [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil];
397 : [gameView suppressKeysUntilKeyUp];
398 : }
399 : if (([self checkKeyPress:n_key_gui_arrow_right] || [self checkKeyPress:n_key_gui_page_down]) && [[gui keyForRow:MOREROW] isEqual: GUI_KEY_OK])
400 : {
401 : currentPage++;
402 : [self playMenuPageNext];
403 : [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil];
404 : [gameView suppressKeysUntilKeyUp];
405 : }
406 :
407 : // Enter pressed - find the commander name underneath.
408 : // ignore Ctrl for the moment - we check for it explicitly later
409 : if ([self checkKeyPress:n_key_gui_select ignore_ctrl:YES]||[gameView isDown:gvMouseDoubleClick])
410 : {
411 : NSDictionary *cdr;
412 : switch ([gui selectedRow])
413 : {
414 : case EXITROW:
415 : if ([self status] == STATUS_START_GAME)
416 : {
417 : [self setGuiToIntroFirstGo:YES];
418 : return nil;
419 : }
420 : break;
421 : case BACKROW:
422 : currentPage--;
423 : [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil];
424 : [gameView suppressKeysUntilKeyUp];
425 : break;
426 : case MOREROW:
427 : currentPage++;
428 : [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil];
429 : [gameView suppressKeysUntilKeyUp];
430 : break;
431 : default:
432 : cdr=[cdrDetailArray objectAtIndex: idx];
433 : if ([cdr oo_boolForKey:@"isSavedGame"])
434 : return [cdr oo_stringForKey:@"saved_game_path"];
435 : else
436 : {
437 : if ([gameView isCommandModifierKeyDown]||[gameView isDown:gvMouseDoubleClick])
438 : {
439 : // change directory to the selected path
440 : NSString* newDir = [cdr oo_stringForKey:@"saved_game_path"];
441 : [[UNIVERSE gameController] setPlayerFileDirectory: newDir];
442 : dir = newDir;
443 : currentPage = 0;
444 : [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil];
445 : [gameView suppressKeysUntilKeyUp];
446 : }
447 : }
448 : }
449 : }
450 :
451 : if([gameView isDown: 27]) // escape key
452 : {
453 : [self setGuiToStatusScreen];
454 : }
455 : return nil;
456 : }
457 :
458 :
459 : - (void) saveCommanderInputHandler
460 : {
461 : MyOpenGLView *gameView = [UNIVERSE gameView];
462 : GuiDisplayGen *gui = [UNIVERSE gui];
463 : NSString *dir = [[UNIVERSE gameController] playerFileDirectory];
464 :
465 : if ([self handleGUIUpDownArrowKeys])
466 : {
467 : int guiSelectedRow=[gui selectedRow];
468 : int idx = (guiSelectedRow - STARTROW) + (currentPage * NUMROWS);
469 : if (guiSelectedRow != MOREROW && guiSelectedRow != BACKROW)
470 : {
471 : [self showCommanderShip: idx];
472 : if ([(NSDictionary *)[cdrDetailArray objectAtIndex:idx] oo_boolForKey:@"isSavedGame"]) // don't show things that aren't saved games
473 : commanderNameString = [[cdrDetailArray oo_dictionaryAtIndex:idx] oo_stringForKey:@"player_save_name" defaultValue:[[cdrDetailArray oo_dictionaryAtIndex:idx] oo_stringForKey:@"player_name"]];
474 : else
475 : commanderNameString = [gameView typedString];
476 : }
477 : else
478 : {
479 : [UNIVERSE removeDemoShips];
480 : [gui setText:@"" forRow:CDRDESCROW align:GUI_ALIGN_LEFT];
481 : [gui setText:@"" forRow:CDRDESCROW + 1 align:GUI_ALIGN_LEFT];
482 : [gui setText:@"" forRow:CDRDESCROW + 2 align:GUI_ALIGN_LEFT];
483 : }
484 : }
485 : else
486 : {
487 : commanderNameString = [gameView typedString];
488 : }
489 :
490 : [gameView setTypedString: commanderNameString];
491 :
492 : [gui setText:
493 : [NSString stringWithFormat:DESC(@"savescreen-commander-name-@"), commanderNameString]
494 : forRow: INPUTROW];
495 : [gui setColor:[OOColor cyanColor] forRow:INPUTROW];
496 :
497 : // handle page <-- and page --> keys, and on-screen buttons
498 : if (((([gameView isDown:gvMouseDoubleClick] || [self checkKeyPress:n_key_gui_select]) && [gui selectedRow] == BACKROW) || ([self checkKeyPress:n_key_gui_arrow_left] || [self checkKeyPress:n_key_gui_page_up]))
499 : && [[gui keyForRow:BACKROW] isEqual: GUI_KEY_OK])
500 : {
501 : currentPage--;
502 : [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil];
503 : [gameView suppressKeysUntilKeyUp];
504 : }
505 : //
506 : if (((([gameView isDown:gvMouseDoubleClick] || [self checkKeyPress:n_key_gui_select]) && [gui selectedRow] == MOREROW) || ([self checkKeyPress:n_key_gui_arrow_right] || [self checkKeyPress:n_key_gui_page_down]))
507 : && [[gui keyForRow:MOREROW] isEqual: GUI_KEY_OK])
508 : {
509 : currentPage++;
510 : [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil];
511 : [gameView suppressKeysUntilKeyUp];
512 : }
513 :
514 : // ignore Ctrl if pressed together with Enter for the moment - we check for it explicitly immediately after
515 : if(([self checkKeyPress:n_key_gui_select ignore_ctrl:YES]||[gameView isDown:gvMouseDoubleClick]) && [commanderNameString length])
516 : {
517 : if ([gameView isCommandModifierKeyDown]||[gameView isDown:gvMouseDoubleClick])
518 : {
519 : int guiSelectedRow=[gui selectedRow];
520 : int idx = (guiSelectedRow - STARTROW) + (currentPage * NUMROWS);
521 : NSDictionary* cdr = [cdrDetailArray objectAtIndex:idx];
522 :
523 : if (![cdr oo_boolForKey:@"isSavedGame"]) // don't open saved games
524 : {
525 : // change directory to the selected path
526 : NSString* newDir = [cdr oo_stringForKey:@"saved_game_path"];
527 : [[UNIVERSE gameController] setPlayerFileDirectory: newDir];
528 : dir = newDir;
529 : currentPage = 0;
530 : [self lsCommanders: gui directory: dir pageNumber: currentPage highlightName: nil];
531 : [gameView suppressKeysUntilKeyUp];
532 : }
533 : }
534 : else
535 : {
536 : pollControls = YES;
537 : if ([self existingNativeSave: commanderNameString])
538 : {
539 : [gameView suppressKeysUntilKeyUp];
540 : [self setGuiToOverwriteScreen: commanderNameString];
541 : }
542 : else
543 : {
544 : [self nativeSavePlayer: commanderNameString];
545 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
546 : [self setGuiToStatusScreen];
547 : }
548 : }
549 : }
550 :
551 : if([gameView isDown: 27]) // escape key
552 : {
553 : // get out of here
554 : pollControls = YES;
555 : [[UNIVERSE gameView] resetTypedString];
556 : [self setGuiToStatusScreen];
557 : }
558 : }
559 :
560 :
561 : - (void) overwriteCommanderInputHandler
562 : {
563 : MyOpenGLView *gameView = [UNIVERSE gameView];
564 : GuiDisplayGen *gui = [UNIVERSE gui];
565 :
566 : [self handleGUIUpDownArrowKeys];
567 :
568 : // Translation issue: we can't confidently use raw Y and N ascii as shortcuts. It's better to use the load-previous-commander keys.
569 : id valueYes = [[[UNIVERSE descriptions] oo_stringForKey:@"load-previous-commander-yes" defaultValue:@"y"] lowercaseString];
570 : id valueNo = [[[UNIVERSE descriptions] oo_stringForKey:@"load-previous-commander-no" defaultValue:@"n"] lowercaseString];
571 : unsigned char cYes, cNo;
572 :
573 : cYes = [valueYes characterAtIndex: 0] & 0x00ff; // Use lower byte of unichar.
574 : cNo = [valueNo characterAtIndex: 0] & 0x00ff; // Use lower byte of unichar.
575 :
576 : if (([self checkKeyPress:n_key_gui_select] && ([gui selectedRow] == SAVE_OVERWRITE_YES_ROW))||[gameView isDown:cYes]||[gameView isDown:cYes - 32])
577 : {
578 : pollControls=YES;
579 : [self nativeSavePlayer: commanderNameString];
580 : [self playSaveOverwriteYes];
581 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
582 : [self setGuiToStatusScreen];
583 : }
584 :
585 : if (([self checkKeyPress:n_key_gui_select] && ([gui selectedRow] == SAVE_OVERWRITE_NO_ROW))||[gameView isDown:27]||[gameView isDown:cNo]||[gameView isDown:cNo - 32])
586 : {
587 : // esc or NO was pressed - get out of here
588 : pollControls=YES;
589 : [self playSaveOverwriteNo];
590 : [self setGuiToSaveCommanderScreen:@""];
591 : }
592 : }
593 :
594 : #endif
595 :
596 :
597 : - (BOOL) loadPlayerFromFile:(NSString *)fileToOpen asNew:(BOOL)asNew
598 : {
599 : /* TODO: it would probably be better to load by creating a new
600 : PlayerEntity, verifying that's OK, then replacing the global player.
601 :
602 : Actually, it'd be better to separate PlayerEntity into OOPlayer and
603 : OOPlayerShipEntity. And then move most of OOPlayerShipEntity into
604 : ShipEntity, and make NPC ships behave more like player ships.
605 : -- Ahruman
606 : */
607 :
608 : BOOL loadedOK = YES;
609 : NSDictionary *fileDic = nil;
610 : NSString *fail_reason = nil;
611 :
612 : if (fileToOpen == nil)
613 : {
614 : fail_reason = DESC(@"loadfailed-no-file-specified");
615 : loadedOK = NO;
616 : }
617 :
618 : if (loadedOK)
619 : {
620 : OOLog(@"load.progress", @"%@", @"Reading file");
621 : fileDic = OODictionaryFromFile(fileToOpen);
622 : if (fileDic == nil)
623 : {
624 : fail_reason = DESC(@"loadfailed-could-not-load-file");
625 : loadedOK = NO;
626 : }
627 : }
628 :
629 : if (loadedOK)
630 : {
631 : OOLog(@"load.progress", @"%@", @"Restricting scenario");
632 : NSString *scenarioRestrict = [fileDic oo_stringForKey:@"scenario_restriction" defaultValue:nil];
633 : if (scenarioRestrict == nil)
634 : {
635 : // older save game - use the 'strict' key instead
636 : BOOL strict = [fileDic oo_boolForKey:@"strict" defaultValue:NO];
637 : if (strict)
638 : {
639 : scenarioRestrict = SCENARIO_OXP_DEFINITION_NONE;
640 : }
641 : else
642 : {
643 : scenarioRestrict = SCENARIO_OXP_DEFINITION_ALL;
644 : }
645 : }
646 :
647 : if (![UNIVERSE setUseAddOns:scenarioRestrict fromSaveGame:YES forceReinit:YES])
648 : {
649 : fail_reason = DESC(@"loadfailed-saved-game-failed-to-load");
650 : loadedOK = NO;
651 : }
652 : }
653 :
654 :
655 : if (loadedOK)
656 : {
657 : OOLog(@"load.progress", @"%@", @"Creating player ship");
658 : // Check that player ship exists
659 : NSString *shipKey = nil;
660 : NSDictionary *shipDict = nil;
661 :
662 : shipKey = [fileDic oo_stringForKey:@"ship_desc"];
663 : shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey];
664 :
665 : if (shipDict == nil)
666 : {
667 : loadedOK = NO;
668 : if (shipKey != nil) fail_reason = [NSString stringWithFormat:DESC(@"loadfailed-could-not-find-ship-type-@-please-reinstall-the-appropriate-OXP"), shipKey];
669 : else fail_reason = DESC(@"loadfailed-invalid-saved-game-no-ship-specified");
670 : }
671 : }
672 :
673 : if (loadedOK)
674 : {
675 : OOLog(@"load.progress", @"%@", @"Initialising player entity");
676 : if (![self setUpAndConfirmOK:YES saveGame:YES])
677 : {
678 : fail_reason = DESC(@"loadfailed-could-not-reset-javascript");
679 : loadedOK = NO;
680 : }
681 : }
682 :
683 : if (loadedOK)
684 : {
685 : OOLog(@"load.progress", @"%@", @"Loading commander data");
686 : if (![self setCommanderDataFromDictionary:fileDic])
687 : {
688 : // this could still be a reset js issue, if switching from strict / unrestricted
689 : // TODO: use "could not reset js message" if that's the case.
690 : fail_reason = DESC(@"loadfailed-could-not-set-up-player-ship");
691 : loadedOK = NO;
692 : }
693 : }
694 :
695 : if (loadedOK)
696 : {
697 : OOLog(@"load.progress", @"%@", @"Recording save path");
698 : if (!asNew)
699 : {
700 : [save_path autorelease];
701 : save_path = [fileToOpen retain];
702 :
703 : [[[UNIVERSE gameView] gameController] setPlayerFileToLoad:fileToOpen];
704 : [[[UNIVERSE gameView] gameController] setPlayerFileDirectory:fileToOpen];
705 : }
706 : }
707 : else
708 : {
709 : OOLog(@"load.failed", @"***** Failed to load saved game \"%@\": %@", [fileToOpen lastPathComponent], fail_reason ? fail_reason : (NSString *)@"unknown error");
710 : [[UNIVERSE gameController] setPlayerFileToLoad:nil];
711 : [UNIVERSE handleGameOver];
712 : [UNIVERSE clearPreviousMessage];
713 : [UNIVERSE addMessage:DESC(@"loadfailed-saved-game-failed-to-load") forCount: 9.0];
714 : if (fail_reason != nil) [UNIVERSE addMessage: fail_reason forCount: 9.0];
715 : return NO;
716 : }
717 :
718 : OOLog(@"load.progress", @"%@", @"Creating system");
719 : [UNIVERSE setTimeAccelerationFactor:TIME_ACCELERATION_FACTOR_DEFAULT];
720 : [UNIVERSE setSystemTo:system_id];
721 : [UNIVERSE removeAllEntitiesExceptPlayer];
722 : [UNIVERSE setGalaxyTo: galaxy_number andReinit:YES]; // set overridden planet names on long range map
723 : [UNIVERSE setUpSpace];
724 : [UNIVERSE setAutoSaveNow:NO];
725 :
726 : OOLog(@"load.progress", @"%@", @"Resetting player flight variables");
727 : [self setDockedAtMainStation];
728 : StationEntity *dockedStation = [self dockedStation];
729 :
730 : [UNIVERSE enterGUIViewModeWithMouseInteraction:NO];
731 :
732 : if (dockedStation)
733 : {
734 : position = [dockedStation position];
735 : [self setOrientation: kIdentityQuaternion];
736 : v_forward = vector_forward_from_quaternion(orientation);
737 : v_right = vector_right_from_quaternion(orientation);
738 : v_up = vector_up_from_quaternion(orientation);
739 : }
740 :
741 : flightRoll = 0.0;
742 : flightPitch = 0.0;
743 : flightYaw = 0.0;
744 : flightSpeed = 0.0;
745 :
746 : [self setEntityPersonalityInt:PersonalityForCommanderDict(fileDic)];
747 :
748 : OOLog(@"load.progress", @"%@", @"Loading system market");
749 : // dockedStation is always the main station at this point;
750 : // "localMarket" save key always refers to the main station (system) market
751 : NSArray *market = [fileDic oo_arrayForKey:@"localMarket"];
752 : if (market != nil)
753 : {
754 : [dockedStation setLocalMarket:market];
755 : }
756 : else
757 : {
758 : [dockedStation initialiseLocalMarket];
759 : }
760 :
761 : [self calculateCurrentCargo];
762 :
763 : OOLog(@"load.progress", @"%@", @"Setting scenario key");
764 : // set scenario key if the scenario allows saving and has one
765 : NSString *scenario = [fileDic oo_stringForKey:@"scenario_key" defaultValue:nil];
766 : DESTROY(scenarioKey);
767 : if (scenario != nil)
768 : {
769 : scenarioKey = [scenario retain];
770 : }
771 :
772 : OOLog(@"load.progress", @"%@", @"Starting JS engine");
773 : // Remember the savegame target, run js startUp.
774 : [self completeSetUpAndSetTarget:NO];
775 : // run initial system population
776 : OOLog(@"load.progress", @"%@", @"Populating initial system");
777 : [UNIVERSE populateNormalSpace];
778 :
779 : // might as well start off with a collected JS environment
780 : [[OOJavaScriptEngine sharedEngine] garbageCollectionOpportunity:YES];
781 :
782 : // read saved position vector and primary role, check for an
783 : // appropriate station at those coordinates, if found, switch
784 : // docked station to that one.
785 : HPVector dockedPos = [fileDic oo_hpvectorForKey:@"docked_station_position"];
786 : NSString *dockedRole = [fileDic oo_stringForKey:@"docked_station_role" defaultValue:@""];
787 : StationEntity *saveStation = [UNIVERSE stationWithRole:dockedRole andPosition:dockedPos];
788 : if (saveStation != nil && [saveStation allowsSaving])
789 : {
790 : [self setDockedStation:saveStation];
791 : position = [saveStation position];
792 : }
793 : // and initialise markets for the secondary stations
794 : [UNIVERSE loadStationMarkets:[fileDic oo_arrayForKey:@"station_markets"]];
795 :
796 : OOLog(@"load.progress", @"%@", @"Completing JS startup");
797 : [self startUpComplete];
798 :
799 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
800 : gui_screen = GUI_SCREEN_LOAD; // force evaluation of new gui screen on startup
801 : [self setGuiToStatusScreen];
802 : if (loadedOK) [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // trigger missionScreenOpportunity immediately after loading
803 : OOLog(@"load.progress", @"%@", @"Loading complete");
804 : return loadedOK;
805 : }
806 :
807 : @end
808 :
809 :
810 : @implementation PlayerEntity (OOLoadSavePrivate)
811 :
812 : #if OOLITE_USE_APPKIT_LOAD_SAVE
813 :
814 : - (BOOL)loadPlayerWithPanel
815 : {
816 : NSOpenPanel *oPanel = [NSOpenPanel openPanel];
817 :
818 : oPanel.allowsMultipleSelection = NO;
819 : oPanel.allowedFileTypes = [NSArray arrayWithObject:@"oolite-save"];
820 :
821 : if ([oPanel runModal] == NSOKButton)
822 : {
823 : NSURL *url = oPanel.URL;
824 : if (url.isFileURL)
825 : {
826 : return [self loadPlayerFromFile:url.path asNew:NO];
827 : }
828 : }
829 :
830 : return NO;
831 : }
832 :
833 :
834 : - (void) savePlayerWithPanel
835 : {
836 : NSSavePanel *sPanel = [NSSavePanel savePanel];
837 :
838 : sPanel.allowedFileTypes = [NSArray arrayWithObject:@"oolite-save"];
839 : sPanel.canSelectHiddenExtension = YES;
840 : sPanel.nameFieldStringValue = self.lastsaveName;
841 :
842 : if ([sPanel runModal] == NSOKButton)
843 : {
844 : NSURL *url = sPanel.URL;
845 : NSAssert(url.isFileURL, @"Save panel with default configuration should not provide non-file URLs.");
846 :
847 : NSString *path = url.path;
848 : NSString *newName = [path.lastPathComponent stringByDeletingPathExtension];
849 :
850 : ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("STANDARD_SAVE"));
851 :
852 : self.lastsaveName = newName;
853 : [self writePlayerToPath:path];
854 : }
855 : [self setGuiToStatusScreen];
856 : }
857 :
858 : #endif
859 :
860 :
861 : - (void) writePlayerToPath:(NSString *)path
862 : {
863 : NSString *errDesc = nil;
864 : NSDictionary *dict = nil;
865 : BOOL didSave = NO;
866 : [[UNIVERSE gameView] resetTypedString];
867 :
868 : if (!path)
869 : {
870 : OOLog(@"save.failed", @"***** SAVE ERROR: %s called with nil path.", __PRETTY_FUNCTION__);
871 : return;
872 : }
873 :
874 : dict = [self commanderDataDictionary];
875 : if (dict == nil) errDesc = @"could not construct commander data dictionary.";
876 : else didSave = [dict writeOOXMLToFile:path atomically:YES errorDescription:&errDesc];
877 : if (didSave)
878 : {
879 : [UNIVERSE clearPreviousMessage]; // allow this to be given time and again
880 : [UNIVERSE addMessage:DESC(@"game-saved") forCount:2];
881 : [save_path autorelease];
882 : save_path = [path copy];
883 : [[UNIVERSE gameController] setPlayerFileToLoad:save_path];
884 : [[UNIVERSE gameController] setPlayerFileDirectory:save_path];
885 : // no duplicated autosave immediately after a save.
886 : [UNIVERSE setAutoSaveNow:NO];
887 : }
888 : else
889 : {
890 : OOLog(@"save.failed", @"***** SAVE ERROR: %@", errDesc);
891 : [NSException raise:@"OoliteException"
892 : format:@"Attempt to save game to file '%@' failed: %@", path, errDesc];
893 : }
894 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
895 : [self setGuiToStatusScreen];
896 : }
897 :
898 :
899 : - (void)nativeSavePlayer:(NSString *)cdrName
900 : {
901 : NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
902 : NSString *savePath = [dir stringByAppendingPathComponent:[cdrName stringByAppendingPathExtension:@"oolite-save"]];
903 :
904 : ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("STANDARD_SAVE"));
905 :
906 : [self setLastsaveName:cdrName];
907 :
908 : [self writePlayerToPath:savePath];
909 : }
910 :
911 :
912 : #if OO_USE_CUSTOM_LOAD_SAVE
913 :
914 : - (void) setGuiToLoadCommanderScreen
915 : {
916 : GuiDisplayGen *gui=[UNIVERSE gui];
917 : NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
918 :
919 : gui_screen = GUI_SCREEN_LOAD;
920 :
921 : [gui clear];
922 : [gui setTitle:DESC(@"loadscreen-title")];
923 :
924 : currentPage = 0;
925 : [self lsCommanders:gui directory:dir pageNumber: currentPage highlightName:nil];
926 :
927 : [gui setForegroundTextureKey:@"docked_overlay"];
928 : [gui setBackgroundTextureKey:@"load_save"];
929 :
930 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
931 :
932 : [self setShowDemoShips:YES];
933 : [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
934 : }
935 :
936 :
937 : - (void) setGuiToSaveCommanderScreen:(NSString *)cdrName
938 : {
939 : GuiDisplayGen *gui=[UNIVERSE gui];
940 : MyOpenGLView *gameView = [UNIVERSE gameView];
941 : NSString *dir = [[UNIVERSE gameController] playerFileDirectory];
942 :
943 : pollControls = NO;
944 : gui_screen = GUI_SCREEN_SAVE;
945 :
946 : [gui clear];
947 : [gui setTitle:DESC(@"savescreen-title")];
948 :
949 : currentPage = 0;
950 : [self lsCommanders:gui directory:dir pageNumber: currentPage highlightName:nil];
951 :
952 : [gui setText:DESC(@"savescreen-commander-name") forRow: INPUTROW];
953 : [gui setColor:[OOColor cyanColor] forRow:INPUTROW];
954 : [gui setShowTextCursor: YES];
955 : [gui setCurrentRow: INPUTROW];
956 :
957 : [gui setForegroundTextureKey:@"docked_overlay"];
958 : [gui setBackgroundTextureKey:@"load_save"];
959 :
960 : [gameView setTypedString:cdrName];
961 : [gameView suppressKeysUntilKeyUp];
962 :
963 : [self setShowDemoShips:YES];
964 : [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
965 : }
966 :
967 :
968 : - (void) setGuiToOverwriteScreen:(NSString *)cdrName
969 : {
970 : GuiDisplayGen *gui=[UNIVERSE gui];
971 : MyOpenGLView* gameView = [UNIVERSE gameView];
972 :
973 : // Don't poll controls
974 : pollControls=NO;
975 :
976 : gui_screen = GUI_SCREEN_SAVE_OVERWRITE;
977 :
978 : [gui clear];
979 : [gui setTitle:[NSString stringWithFormat:DESC(@"overwrite-save-commander-@"), cdrName]];
980 :
981 : [gui setText:[NSString stringWithFormat:DESC(@"overwritescreen-commander-@-already-exists-overwrite-query"), cdrName]
982 : forRow:SAVE_OVERWRITE_WARN_ROW align: GUI_ALIGN_CENTER];
983 :
984 : [gui setText:DESC(@"overwritescreen-yes") forRow: SAVE_OVERWRITE_YES_ROW align: GUI_ALIGN_CENTER];
985 : [gui setKey:GUI_KEY_OK forRow: SAVE_OVERWRITE_YES_ROW];
986 :
987 : [gui setText:DESC(@"overwritescreen-no") forRow: SAVE_OVERWRITE_NO_ROW align: GUI_ALIGN_CENTER];
988 : [gui setKey:GUI_KEY_OK forRow: SAVE_OVERWRITE_NO_ROW];
989 :
990 : [gui setSelectableRange: NSMakeRange(SAVE_OVERWRITE_YES_ROW, 2)];
991 : [gui setSelectedRow: SAVE_OVERWRITE_NO_ROW];
992 :
993 : // We can only leave this screen by answering yes or no, or esc. Therefore
994 : // use a specific overlay, to allow visual reminders of the available options.
995 : [gui setForegroundTextureKey:@"overwrite_overlay"];
996 : [gui setBackgroundTextureKey:@"load_save"];
997 :
998 : [self setShowDemoShips:NO];
999 : [gameView setStringInput:gvStringInputNo];
1000 : [UNIVERSE enterGUIViewModeWithMouseInteraction:NO]; // FIXME: should be YES, but was NO before introducing new mouse mode stuff. If set to YES, choices can be selected but not activated.
1001 : }
1002 :
1003 0 : NSComparisonResult sortCommanders(id cdr1, id cdr2, void *context)
1004 : {
1005 : return [[cdr1 objectForKey:@"saved_game_path"] localizedCompare:[cdr2 objectForKey:@"saved_game_path"]];
1006 : }
1007 :
1008 : - (void) lsCommanders: (GuiDisplayGen *)gui
1009 : directory: (NSString*) directory
1010 : pageNumber: (int)page
1011 : highlightName: (NSString *)highlightName
1012 : {
1013 : NSFileManager *cdrFileManager=[NSFileManager defaultManager];
1014 : int rangeStart=STARTROW;
1015 : unsigned lastIndex;
1016 : unsigned i;
1017 : int row=STARTROW;
1018 :
1019 : // cdrArray defined in PlayerEntity.h
1020 : NSArray *cdrArray=[cdrFileManager commanderContentsOfPath: directory];
1021 :
1022 : // get commander details so a brief rundown of the commander's details may
1023 : // be displayed.
1024 : if (!cdrDetailArray)
1025 : cdrDetailArray=[[NSMutableArray alloc] init]; // alloc retains this so the retain further on in the code was unnecessary
1026 : else
1027 : [cdrDetailArray removeAllObjects];
1028 :
1029 : [cdrDetailArray addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
1030 : @"YES", @"isParentFolder",
1031 : [directory stringByDeletingLastPathComponent], @"saved_game_path", nil]];
1032 :
1033 : for(i = 0; i < [cdrArray count]; i++)
1034 : {
1035 : NSString* path = [cdrArray objectAtIndex:i];
1036 : BOOL exists, isDirectory = NO;
1037 :
1038 : exists = [cdrFileManager fileExistsAtPath:path isDirectory:&isDirectory];
1039 :
1040 : if (exists)
1041 : {
1042 : if (!isDirectory && [[[path pathExtension] lowercaseString] isEqualToString:@"oolite-save"])
1043 : {
1044 : NSDictionary *cdr = OODictionaryFromFile(path);
1045 : if(cdr)
1046 : {
1047 : // okay use the same dictionary but add a 'saved_game_path' attribute
1048 : NSMutableDictionary* cdr1 = [NSMutableDictionary dictionaryWithDictionary:cdr];
1049 : [cdr1 setObject: @"YES" forKey:@"isSavedGame"];
1050 : [cdr1 setObject: path forKey:@"saved_game_path"];
1051 : [cdrDetailArray addObject: cdr1];
1052 : }
1053 : }
1054 : if (isDirectory && ![[path lastPathComponent] hasPrefix:@"."])
1055 : {
1056 : [cdrDetailArray addObject: [NSDictionary dictionaryWithObjectsAndKeys: @"YES", @"isFolder", path, @"saved_game_path", nil]];
1057 : }
1058 : }
1059 : }
1060 :
1061 : if(![cdrDetailArray count])
1062 : {
1063 : // Empty directory; tell the user and exit immediately.
1064 : [gui setText:DESC(@"loadsavescreen-no-commanders-found") forRow:STARTROW align:GUI_ALIGN_CENTER];
1065 : return;
1066 : }
1067 :
1068 : [cdrDetailArray sortUsingFunction:sortCommanders context:NULL];
1069 :
1070 : // Do we need to highlight a name?
1071 : int highlightRowOnPage=STARTROW;
1072 : int highlightIdx=0;
1073 : if(highlightName)
1074 : {
1075 : highlightIdx=[self findIndexOfCommander: highlightName];
1076 : if(highlightIdx < 0)
1077 : {
1078 : OOLog(@"save.list.commanders.commanderNotFound", @"Commander %@ doesn't exist, very bad", highlightName);
1079 : highlightIdx=0;
1080 : }
1081 :
1082 : // figure out what page we need to be on
1083 : page=highlightIdx/NUMROWS;
1084 : highlightRowOnPage=highlightIdx % NUMROWS + STARTROW;
1085 : }
1086 :
1087 : // We now know for certain what page we're on -
1088 : // set the first index of the first commander on this page.
1089 : unsigned firstIndex=page * NUMROWS;
1090 :
1091 : // Set up the GUI.
1092 : OOGUITabSettings tabStop;
1093 : tabStop[0]=0;
1094 : tabStop[1]=160;
1095 : tabStop[2]=270;
1096 : [gui setTabStops: tabStop];
1097 :
1098 : // clear text lines here
1099 : for (i = EXITROW ; i < ENDROW + 1; i++)
1100 : {
1101 : [gui setText:@"" forRow:i align:GUI_ALIGN_LEFT];
1102 : [gui setColor: [OOColor yellowColor] forRow: i];
1103 : [gui setKey:GUI_KEY_SKIP forRow:i];
1104 : }
1105 :
1106 : [gui setColor: [OOColor greenColor] forRow: LABELROW];
1107 : [gui setArray: [NSArray arrayWithObjects: DESC(@"loadsavescreen-commander-name"), DESC(@"loadsavescreen-rating"), nil]
1108 : forRow:LABELROW];
1109 :
1110 : if (page)
1111 : {
1112 : [gui setColor:[OOColor greenColor] forRow:STARTROW-1];
1113 : [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil]
1114 : forRow:STARTROW-1];
1115 : [gui setKey:GUI_KEY_OK forRow:STARTROW-1];
1116 : rangeStart=STARTROW-1;
1117 : }
1118 :
1119 : if ([self status] == STATUS_START_GAME)
1120 : {
1121 : [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-loadsave-exit"), @" <----- ", nil] forRow:EXITROW];
1122 : [gui setColor:[OOColor redColor] forRow:EXITROW];
1123 : [gui setKey:GUI_KEY_OK forRow:EXITROW];
1124 : rangeStart = EXITROW;
1125 : }
1126 :
1127 :
1128 : if (firstIndex + NUMROWS >= [cdrDetailArray count])
1129 : {
1130 : lastIndex=[cdrDetailArray count];
1131 : [gui setSelectableRange: NSMakeRange(rangeStart, rangeStart + NUMROWS + 2)];
1132 : }
1133 : else
1134 : {
1135 : lastIndex=(page * NUMROWS) + NUMROWS;
1136 : [gui setColor:[OOColor greenColor] forRow:ENDROW];
1137 : [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil]
1138 : forRow:ENDROW];
1139 : [gui setKey:GUI_KEY_OK forRow:ENDROW];
1140 : [gui setSelectableRange: NSMakeRange(rangeStart, MOREROW)];
1141 : }
1142 :
1143 : for (i=firstIndex; i < lastIndex; i++)
1144 : {
1145 : NSDictionary *cdr=[cdrDetailArray objectAtIndex: i];
1146 : if ([cdr oo_boolForKey:@"isSavedGame"])
1147 : {
1148 : NSString *ratingDesc = OODisplayRatingStringFromKillCount([cdr oo_unsignedIntForKey:@"ship_kills"]);
1149 : [gui setArray:[NSArray arrayWithObjects:
1150 : [NSString stringWithFormat:@" %@ ",[cdr oo_stringForKey:@"player_save_name" defaultValue:[cdr oo_stringForKey:@"player_name"]]],
1151 : [NSString stringWithFormat:@" %@ ",ratingDesc],
1152 : nil]
1153 : forRow:row];
1154 : if ([[self lastsaveName] isEqualToString:[cdr oo_stringForKey:@"player_save_name" defaultValue:[cdr oo_stringForKey:@"player_name"]]])
1155 : {
1156 : highlightRowOnPage = row;
1157 : }
1158 :
1159 : [gui setKey:GUI_KEY_OK forRow:row];
1160 : row++;
1161 : }
1162 : if ([cdr oo_boolForKey:@"isParentFolder"])
1163 : {
1164 : [gui setArray:[NSArray arrayWithObjects:
1165 : [NSString stringWithFormat:@" (..) %@ ", [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]],
1166 : @"",
1167 : nil]
1168 : forRow:row];
1169 : [gui setColor: [OOColor orangeColor] forRow: row];
1170 : [gui setKey:GUI_KEY_OK forRow:row];
1171 : row++;
1172 : }
1173 : if ([cdr oo_boolForKey:@"isFolder"])
1174 : {
1175 : [gui setArray:[NSArray arrayWithObjects:
1176 : [NSString stringWithFormat:@" >> %@ ", [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]],
1177 : @"",
1178 : nil]
1179 : forRow:row];
1180 : [gui setColor: [OOColor orangeColor] forRow: row];
1181 : [gui setKey:GUI_KEY_OK forRow:row];
1182 : row++;
1183 : }
1184 : }
1185 : [gui setSelectedRow: highlightRowOnPage];
1186 : highlightIdx = (highlightRowOnPage - STARTROW) + (currentPage * NUMROWS);
1187 : // show the first ship, this will be the selected row
1188 : [self showCommanderShip: highlightIdx];
1189 : }
1190 :
1191 :
1192 : // check for an existing saved game...
1193 : - (BOOL) existingNativeSave: (NSString *)cdrName
1194 : {
1195 : NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
1196 :
1197 : NSString *savePath=[dir stringByAppendingPathComponent:[cdrName stringByAppendingPathExtension:@"oolite-save"]];
1198 : return [[NSFileManager defaultManager] fileExistsAtPath:savePath];
1199 : }
1200 :
1201 :
1202 : // Get some brief details about the commander file.
1203 : - (void) showCommanderShip:(int)cdrArrayIndex
1204 : {
1205 : GuiDisplayGen *gui=[UNIVERSE gui];
1206 : [UNIVERSE removeDemoShips];
1207 : NSDictionary *cdr=[cdrDetailArray objectAtIndex: cdrArrayIndex];
1208 :
1209 : [gui setText:@"" forRow:CDRDESCROW align:GUI_ALIGN_LEFT];
1210 : [gui setText:@"" forRow:CDRDESCROW + 1 align:GUI_ALIGN_LEFT];
1211 : [gui setText:@"" forRow:CDRDESCROW + 2 align:GUI_ALIGN_LEFT];
1212 :
1213 : if ([cdr oo_boolForKey:@"isFolder"])
1214 : {
1215 : NSString *folderDesc=[NSString stringWithFormat: DESC(@"loadsavescreen-hold-@-and-press-return-to-open-folder-@"), @COMMAND_MODIFIER_KEY, [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]];
1216 : [gui setColor: [OOColor orangeColor] forRow: CDRDESCROW];
1217 : [gui addLongText: folderDesc startingAtRow: CDRDESCROW align: GUI_ALIGN_LEFT];
1218 : return;
1219 : }
1220 :
1221 : if ([cdr oo_boolForKey:@"isParentFolder"])
1222 : {
1223 : NSString *folderDesc=[NSString stringWithFormat: DESC(@"loadsavescreen-hold-@-and-press-return-to-open-parent-folder-@"), @COMMAND_MODIFIER_KEY, [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]];
1224 : [gui setColor: [OOColor orangeColor] forRow: CDRDESCROW];
1225 : [gui addLongText: folderDesc startingAtRow: CDRDESCROW align: GUI_ALIGN_LEFT];
1226 : return;
1227 : }
1228 : [gui setColor:[gui colorFromSetting:nil defaultValue:nil] forRow: CDRDESCROW];
1229 :
1230 : if (![cdr oo_boolForKey:@"isSavedGame"]) return; // don't show things that aren't saved games
1231 :
1232 : if ([self dockedStation] == nil) [self setDockedAtMainStation];
1233 :
1234 : // Display the commander's ship.
1235 : NSString *shipDesc = [cdr oo_stringForKey:@"ship_desc"];
1236 : NSString *shipName = nil;
1237 : NSDictionary *shipDict = nil;
1238 : NSString *rating = nil;
1239 : uint16_t personality = PersonalityForCommanderDict(cdr);
1240 :
1241 : shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipDesc];
1242 : if(shipDict != nil)
1243 : {
1244 : NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:[shipDict count] + 1];
1245 : [dict setDictionary:shipDict];
1246 : id subEntStatus = [cdr objectForKey:@"subentities_status"];
1247 : // don't add it to the dictionary if there's no subentities_status key
1248 : if (subEntStatus != nil) [dict setObject:subEntStatus forKey:@"subentities_status"];
1249 : [self showShipyardModel:shipDesc shipData:dict personality:personality];
1250 : [dict release];
1251 : shipName = [shipDict oo_stringForKey:@"display_name"];
1252 : if (shipName == nil) shipName = [shipDict oo_stringForKey:KEY_NAME];
1253 : }
1254 : else
1255 : {
1256 : [self showShipyardModel:@"oolite-unknown-ship" shipData:nil personality:personality];
1257 : shipName = [cdr oo_stringForKey:@"ship_name" defaultValue:@"unknown"];
1258 : if (![[UNIVERSE useAddOns] isEqualToString:SCENARIO_OXP_DEFINITION_ALL])
1259 : {
1260 : shipName = [shipName stringByAppendingString:@" - OXPs disabled or not installed"];
1261 : }
1262 : else
1263 : {
1264 : shipName = [shipName stringByAppendingString:@" - OXP not installed"];
1265 : }
1266 : }
1267 :
1268 : // Make a short description of the commander
1269 : NSString *legalDesc = OODisplayStringFromLegalStatus([cdr oo_intForKey:@"legal_status"]);
1270 :
1271 : rating = KillCountToRatingAndKillString([cdr oo_unsignedIntForKey:@"ship_kills"]);
1272 : OOCreditsQuantity money = OODeciCreditsFromObject([cdr objectForKey:@"credits"]);
1273 :
1274 : // Nikos - Add some more information in the load game screen (current location, galaxy number and timestamp).
1275 : //-------------------------------------------------------------------------------------------------------------------------
1276 :
1277 : int galNumber;
1278 : NSString *timeStamp = nil;
1279 : NSString *locationName = [cdr oo_stringForKey:@"current_system_name"];
1280 :
1281 : // If there is no key containing the name of the current system in
1282 : // the savefile, calculating what it should have been is going to
1283 : // be tricky now that system generation isn't seed based - but
1284 : // this implies a save game well over 5 years old.
1285 : if (locationName == nil)
1286 : {
1287 : // Leaving the location blank in this case is probably okay
1288 : locationName = @"";
1289 : }
1290 :
1291 : galNumber = [cdr oo_intForKey:@"galaxy_number"] + 1; // Galaxy numbering starts at 0.
1292 :
1293 : NSString *locationGov = @"";
1294 : NSString *locationEco = @"";
1295 : NSString *locationTL = [cdr objectForKey:@"current_system_techlevel"] ? [NSString stringWithFormat:@"%u", [cdr oo_unsignedIntForKey:@"current_system_techlevel"] + 1] : nil;
1296 : if (locationTL)
1297 : {
1298 : locationGov = [NSString stringWithFormat:@"%c", [cdr oo_unsignedCharForKey:@"current_system_government"]];
1299 : locationEco = [NSString stringWithFormat:@" %c", (7 - [cdr oo_unsignedCharForKey:@"current_system_economy"]) + 16];
1300 : }
1301 : else locationTL = @"";
1302 :
1303 : timeStamp = ClockToString([cdr oo_doubleForKey:@"ship_clock" defaultValue:PLAYER_SHIP_CLOCK_START], NO);
1304 :
1305 : //-------------------------------------------------------------------------------------------------------------------------
1306 :
1307 : NSString *cdrDesc = nil;
1308 :
1309 : cdrDesc = [NSString stringWithFormat:DESC(@"loadsavescreen-commander-@-rated-@-has-@-legal-status-@-ship-@-location-@-g-@-eco-@-gov-@-tl-@-timestamp-@"),
1310 : [cdr oo_stringForKey:@"player_name"],
1311 : rating,
1312 : OOCredits(money),
1313 : legalDesc,
1314 : shipName,
1315 : locationName,
1316 : galNumber,
1317 : locationEco,
1318 : locationGov,
1319 : locationTL,
1320 : timeStamp];
1321 :
1322 : //-------------------------------------------------------------------------------------------------------------------------
1323 :
1324 : [gui addLongText:cdrDesc startingAtRow:CDRDESCROW align:GUI_ALIGN_LEFT];
1325 :
1326 : }
1327 :
1328 :
1329 : - (int) findIndexOfCommander: (NSString *)cdrName
1330 : {
1331 : unsigned i;
1332 : for (i=0; i < [cdrDetailArray count]; i++)
1333 : {
1334 : NSString *currentName = [[cdrDetailArray oo_dictionaryAtIndex: i] oo_stringForKey:@"player_save_name" defaultValue:[[cdrDetailArray oo_dictionaryAtIndex: i] oo_stringForKey:@"player_name"]];
1335 : if([cdrName compare: currentName] == NSOrderedSame)
1336 : {
1337 : return i;
1338 : }
1339 : }
1340 :
1341 : // not found!
1342 : return -1;
1343 : }
1344 :
1345 : #endif
1346 :
1347 : @end
1348 :
1349 :
1350 : #if OO_USE_CUSTOM_LOAD_SAVE
1351 :
1352 : @implementation MyOpenGLView (OOLoadSaveExtensions)
1353 :
1354 : - (BOOL)isCommandModifierKeyDown
1355 : {
1356 : return [self isCtrlDown];
1357 : }
1358 :
1359 : @end
1360 :
1361 : #endif
1362 :
1363 :
1364 0 : static uint16_t PersonalityForCommanderDict(NSDictionary *dict)
1365 : {
1366 : uint16_t personality = [dict oo_unsignedShortForKey:@"entity_personality" defaultValue:ENTITY_PERSONALITY_INVALID];
1367 :
1368 : if (personality == ENTITY_PERSONALITY_INVALID)
1369 : {
1370 : // For pre-1.74 saved games, generate a default personality based on some hashes.
1371 : personality = [[dict oo_stringForKey:@"ship_desc"] oo_hash] * [[dict oo_stringForKey:@"player_name"] oo_hash];
1372 : }
1373 :
1374 : return personality & ENTITY_PERSONALITY_MAX;
1375 : }
1376 :
1377 :
1378 0 : OOCreditsQuantity OODeciCreditsFromDouble(double doubleDeciCredits)
1379 : {
1380 : /* Clamp value to 0..kOOMaxCredits.
1381 : The important bit here is that kOOMaxCredits can't be represented
1382 : exactly as a double, and casting it rounds it up; casting this value
1383 : back to an OOCreditsQuantity truncates it. Comparing value directly to
1384 : kOOMaxCredits promotes kOOMaxCredits to a double, giving us this
1385 : problem.
1386 : nextafter(kOOMaxCredits, -1) gives us the highest non-truncated
1387 : credits value that's representable as a double (namely,
1388 : 18 446 744 073 709 549 568 decicredits, or 2047 less than kOOMaxCredits).
1389 : -- Ahruman 2011-02-27
1390 : */
1391 : if (doubleDeciCredits > 0)
1392 : {
1393 : doubleDeciCredits = round(doubleDeciCredits);
1394 : double threshold = nextafter(kOOMaxCredits, -1);
1395 :
1396 : if (doubleDeciCredits <= threshold)
1397 : {
1398 : return doubleDeciCredits;
1399 : }
1400 : else
1401 : {
1402 : return kOOMaxCredits;
1403 : }
1404 : }
1405 : else
1406 : {
1407 : return 0;
1408 : }
1409 : }
1410 :
1411 :
1412 0 : OOCreditsQuantity OODeciCreditsFromObject(id object)
1413 : {
1414 : if ([object isKindOfClass:[NSNumber class]] && [object oo_isFloatingPointNumber])
1415 : {
1416 : return OODeciCreditsFromDouble([object doubleValue]);
1417 : }
1418 : else
1419 : {
1420 : return OOUnsignedLongLongFromObject(object, 0);
1421 : }
1422 : }
|