Oolite
Loading...
Searching...
No Matches
PlayerEntityLoadSave.m
Go to the documentation of this file.
1/*
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
28#import "PlayerEntitySound.h"
29
31#import "GameController.h"
32#import "ResourceManager.h"
33#import "OOStringExpander.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"
44#import "OOConstToString.h"
45#import "OOShipRegistry.h"
46#import "OOTexture.h"
50
51
52// Name of modifier key used to issue commands. See also -isCommandModifierKeyDown.
53#if OO_USE_CUSTOM_LOAD_SAVE
54#define COMMAND_MODIFIER_KEY "Ctrl"
55#endif
56
57
58static uint16_t PersonalityForCommanderDict(NSDictionary *dict);
59
60
61#if OO_USE_CUSTOM_LOAD_SAVE
62
63@interface MyOpenGLView (OOLoadSaveExtensions)
64
66
67@end
68
69#endif
70
71
72@interface PlayerEntity (OOLoadSavePrivate)
73
74#if OOLITE_USE_APPKIT_LOAD_SAVE
75
76- (BOOL) loadPlayerWithPanel;
77- (void) savePlayerWithPanel;
78
79#endif
80
81#if OO_USE_CUSTOM_LOAD_SAVE
82
83- (void) setGuiToLoadCommanderScreen;
84- (void) setGuiToSaveCommanderScreen: (NSString *)cdrName;
85- (void) setGuiToOverwriteScreen: (NSString *)cdrName;
86- (void) lsCommanders: (GuiDisplayGen *)gui directory: (NSString*)directory pageNumber: (int)page highlightName: (NSString *)highlightName;
87- (void) showCommanderShip: (int)cdrArrayIndex;
88- (int) findIndexOfCommander: (NSString *)cdrName;
89- (void) nativeSavePlayer: (NSString *)cdrName;
90- (BOOL) existingNativeSave: (NSString *)cdrName;
91
92#endif
93
94- (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 {
112 }
113 else
114 {
115 OK = [self loadPlayerWithPanel];
116 }
117#else
118 // Other platforms: use custom interface all the time.
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];
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_screen = GUI_SCREEN_NEWGAME;
263
264 [gui setSelectableRange:NSMakeRange(start_row - 2,3 + row - start_row)];
265 [gui setSelectedRow:start_row];
266 [self showScenarioDetails];
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
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 // if the file was specified in the command line at startup, DO NOT suppress the keys!
800 if ([[UNIVERSE gameController] finishedLaunching]) [[UNIVERSE gameView] suppressKeysUntilKeyUp];
801 if (asNew)
802 {
803 gui_screen = GUI_SCREEN_NEWGAME;
804 }
805 else
806 {
807 gui_screen = GUI_SCREEN_LOAD; // force evaluation of new gui screen on startup
808 }
809 [self setGuiToStatusScreen];
810 if (loadedOK) [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // trigger missionScreenOpportunity immediately after loading
811 OOLog(@"load.progress", @"%@", @"Loading complete");
812 return loadedOK;
813}
814
815@end
816
817
818@implementation PlayerEntity (OOLoadSavePrivate)
819
820#if OOLITE_USE_APPKIT_LOAD_SAVE
821
822- (BOOL)loadPlayerWithPanel
823{
824 NSOpenPanel *oPanel = [NSOpenPanel openPanel];
825
826 oPanel.allowsMultipleSelection = NO;
827 oPanel.allowedFileTypes = [NSArray arrayWithObject:@"oolite-save"];
828
829 if ([oPanel runModal] == NSOKButton)
830 {
831 NSURL *url = oPanel.URL;
832 if (url.isFileURL)
833 {
834 return [self loadPlayerFromFile:url.path asNew:NO];
835 }
836 }
837
838 return NO;
839}
840
841
842- (void) savePlayerWithPanel
843{
844 NSSavePanel *sPanel = [NSSavePanel savePanel];
845
846 sPanel.allowedFileTypes = [NSArray arrayWithObject:@"oolite-save"];
847 sPanel.canSelectHiddenExtension = YES;
848 sPanel.nameFieldStringValue = self.lastsaveName;
849
850 if ([sPanel runModal] == NSOKButton)
851 {
852 NSURL *url = sPanel.URL;
853 NSAssert(url.isFileURL, @"Save panel with default configuration should not provide non-file URLs.");
854
855 NSString *path = url.path;
856 NSString *newName = [path.lastPathComponent stringByDeletingPathExtension];
857
858 ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("STANDARD_SAVE"));
859
860 self.lastsaveName = newName;
861 [self writePlayerToPath:path];
862 }
863 [self setGuiToStatusScreen];
864}
865
866#endif
867
868
869- (void) writePlayerToPath:(NSString *)path
870{
871 NSString *errDesc = nil;
872 NSDictionary *dict = nil;
873 BOOL didSave = NO;
874 [[UNIVERSE gameView] resetTypedString];
875
876 if (!path)
877 {
878 OOLog(@"save.failed", @"***** SAVE ERROR: %s called with nil path.", __PRETTY_FUNCTION__);
879 return;
880 }
881
882 dict = [self commanderDataDictionary];
883 if (dict == nil) errDesc = @"could not construct commander data dictionary.";
884 else didSave = [dict writeOOXMLToFile:path atomically:YES errorDescription:&errDesc];
885 if (didSave)
886 {
887 [UNIVERSE clearPreviousMessage]; // allow this to be given time and again
888 [UNIVERSE addMessage:DESC(@"game-saved") forCount:2];
889 [save_path autorelease];
890 save_path = [path copy];
891 [[UNIVERSE gameController] setPlayerFileToLoad:save_path];
892 [[UNIVERSE gameController] setPlayerFileDirectory:save_path];
893 // no duplicated autosave immediately after a save.
894 [UNIVERSE setAutoSaveNow:NO];
895 }
896 else
897 {
898 OOLog(@"save.failed", @"***** SAVE ERROR: %@", errDesc);
899 [NSException raise:@"OoliteException"
900 format:@"Attempt to save game to file '%@' failed: %@", path, errDesc];
901 }
902 [[UNIVERSE gameView] suppressKeysUntilKeyUp];
903 [self setGuiToStatusScreen];
904}
905
906
907- (void)nativeSavePlayer:(NSString *)cdrName
908{
909 NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
910 NSString *savePath = [dir stringByAppendingPathComponent:[cdrName stringByAppendingPathExtension:@"oolite-save"]];
911
912 ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("STANDARD_SAVE"));
913
914 [self setLastsaveName:cdrName];
915
916 [self writePlayerToPath:savePath];
917}
918
919
920#if OO_USE_CUSTOM_LOAD_SAVE
921
922- (void) setGuiToLoadCommanderScreen
923{
924 GuiDisplayGen *gui=[UNIVERSE gui];
925 NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
926
927 gui_screen = GUI_SCREEN_LOAD;
928
929 [gui clear];
930 [gui setTitle:DESC(@"loadscreen-title")];
931
932 currentPage = 0;
933 [self lsCommanders:gui directory:dir pageNumber: currentPage highlightName:nil];
934
935 [gui setForegroundTextureKey:@"docked_overlay"];
936 [gui setBackgroundTextureKey:@"load_save"];
937
938 [[UNIVERSE gameView] suppressKeysUntilKeyUp];
939
940 [self setShowDemoShips:YES];
941 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
942}
943
944
945- (void) setGuiToSaveCommanderScreen:(NSString *)cdrName
946{
947 GuiDisplayGen *gui=[UNIVERSE gui];
948 MyOpenGLView *gameView = [UNIVERSE gameView];
949 NSString *dir = [[UNIVERSE gameController] playerFileDirectory];
950
951 pollControls = NO;
952 gui_screen = GUI_SCREEN_SAVE;
953
954 [gui clear];
955 [gui setTitle:DESC(@"savescreen-title")];
956
957 currentPage = 0;
958 [self lsCommanders:gui directory:dir pageNumber: currentPage highlightName:nil];
959
960 [gui setText:DESC(@"savescreen-commander-name") forRow: INPUTROW];
961 [gui setColor:[OOColor cyanColor] forRow:INPUTROW];
962 [gui setShowTextCursor: YES];
963 [gui setCurrentRow: INPUTROW];
964
965 [gui setForegroundTextureKey:@"docked_overlay"];
966 [gui setBackgroundTextureKey:@"load_save"];
967
968 [gameView setTypedString:cdrName];
969 [gameView suppressKeysUntilKeyUp];
970
971 [self setShowDemoShips:YES];
972 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
973}
974
975
976- (void) setGuiToOverwriteScreen:(NSString *)cdrName
977{
978 GuiDisplayGen *gui=[UNIVERSE gui];
979 MyOpenGLView* gameView = [UNIVERSE gameView];
980
981 // Don't poll controls
982 pollControls=NO;
983
984 gui_screen = GUI_SCREEN_SAVE_OVERWRITE;
985
986 [gui clear];
987 [gui setTitle:[NSString stringWithFormat:DESC(@"overwrite-save-commander-@"), cdrName]];
988
989 [gui setText:[NSString stringWithFormat:DESC(@"overwritescreen-commander-@-already-exists-overwrite-query"), cdrName]
990 forRow:SAVE_OVERWRITE_WARN_ROW align: GUI_ALIGN_CENTER];
991
992 [gui setText:DESC(@"overwritescreen-yes") forRow: SAVE_OVERWRITE_YES_ROW align: GUI_ALIGN_CENTER];
993 [gui setKey:GUI_KEY_OK forRow: SAVE_OVERWRITE_YES_ROW];
994
995 [gui setText:DESC(@"overwritescreen-no") forRow: SAVE_OVERWRITE_NO_ROW align: GUI_ALIGN_CENTER];
996 [gui setKey:GUI_KEY_OK forRow: SAVE_OVERWRITE_NO_ROW];
997
998 [gui setSelectableRange: NSMakeRange(SAVE_OVERWRITE_YES_ROW, 2)];
999 [gui setSelectedRow: SAVE_OVERWRITE_NO_ROW];
1000
1001 // We can only leave this screen by answering yes or no, or esc. Therefore
1002 // use a specific overlay, to allow visual reminders of the available options.
1003 [gui setForegroundTextureKey:@"overwrite_overlay"];
1004 [gui setBackgroundTextureKey:@"load_save"];
1005
1006 [self setShowDemoShips:NO];
1007 [gameView setStringInput:gvStringInputNo];
1008 [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.
1009}
1010
1011NSComparisonResult sortCommanders(id cdr1, id cdr2, void *context)
1012{
1013 return [[cdr1 objectForKey:@"saved_game_path"] localizedCompare:[cdr2 objectForKey:@"saved_game_path"]];
1014}
1015
1016- (void) lsCommanders: (GuiDisplayGen *)gui
1017 directory: (NSString*) directory
1018 pageNumber: (int)page
1019 highlightName: (NSString *)highlightName
1020{
1021 NSFileManager *cdrFileManager=[NSFileManager defaultManager];
1022 int rangeStart=STARTROW;
1023 unsigned lastIndex;
1024 unsigned i;
1025 int row=STARTROW;
1026
1027 // cdrArray defined in PlayerEntity.h
1028 NSArray *cdrArray=[cdrFileManager commanderContentsOfPath: directory];
1029
1030 // get commander details so a brief rundown of the commander's details may
1031 // be displayed.
1032 if (!cdrDetailArray)
1033 cdrDetailArray=[[NSMutableArray alloc] init]; // alloc retains this so the retain further on in the code was unnecessary
1034 else
1035 [cdrDetailArray removeAllObjects];
1036
1037 [cdrDetailArray addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
1038 @"YES", @"isParentFolder",
1039 [directory stringByDeletingLastPathComponent], @"saved_game_path", nil]];
1040
1041 for(i = 0; i < [cdrArray count]; i++)
1042 {
1043 NSString* path = [cdrArray objectAtIndex:i];
1044 BOOL exists, isDirectory = NO;
1045
1046 exists = [cdrFileManager fileExistsAtPath:path isDirectory:&isDirectory];
1047
1048 if (exists)
1049 {
1050 if (!isDirectory && [[[path pathExtension] lowercaseString] isEqualToString:@"oolite-save"])
1051 {
1052 NSDictionary *cdr = OODictionaryFromFile(path);
1053 if(cdr)
1054 {
1055 // okay use the same dictionary but add a 'saved_game_path' attribute
1056 NSMutableDictionary* cdr1 = [NSMutableDictionary dictionaryWithDictionary:cdr];
1057 [cdr1 setObject: @"YES" forKey:@"isSavedGame"];
1058 [cdr1 setObject: path forKey:@"saved_game_path"];
1059 [cdrDetailArray addObject: cdr1];
1060 }
1061 }
1062 if (isDirectory && ![[path lastPathComponent] hasPrefix:@"."])
1063 {
1064 [cdrDetailArray addObject: [NSDictionary dictionaryWithObjectsAndKeys: @"YES", @"isFolder", path, @"saved_game_path", nil]];
1065 }
1066 }
1067 }
1068
1069 if(![cdrDetailArray count])
1070 {
1071 // Empty directory; tell the user and exit immediately.
1072 [gui setText:DESC(@"loadsavescreen-no-commanders-found") forRow:STARTROW align:GUI_ALIGN_CENTER];
1073 return;
1074 }
1075
1076 [cdrDetailArray sortUsingFunction:sortCommanders context:NULL];
1077
1078 // Do we need to highlight a name?
1079 int highlightRowOnPage=STARTROW;
1080 int highlightIdx=0;
1081 if(highlightName)
1082 {
1083 highlightIdx=[self findIndexOfCommander: highlightName];
1084 if(highlightIdx < 0)
1085 {
1086 OOLog(@"save.list.commanders.commanderNotFound", @"Commander %@ doesn't exist, very bad", highlightName);
1087 highlightIdx=0;
1088 }
1089
1090 // figure out what page we need to be on
1091 page=highlightIdx/NUMROWS;
1092 highlightRowOnPage=highlightIdx % NUMROWS + STARTROW;
1093 }
1094
1095 // We now know for certain what page we're on -
1096 // set the first index of the first commander on this page.
1097 unsigned firstIndex=page * NUMROWS;
1098
1099 // Set up the GUI.
1100 OOGUITabSettings tabStop;
1101 tabStop[0]=0;
1102 tabStop[1]=160;
1103 tabStop[2]=270;
1104 [gui setTabStops: tabStop];
1105
1106 // clear text lines here
1107 for (i = EXITROW ; i < ENDROW + 1; i++)
1108 {
1109 [gui setText:@"" forRow:i align:GUI_ALIGN_LEFT];
1110 [gui setColor: [OOColor yellowColor] forRow: i];
1111 [gui setKey:GUI_KEY_SKIP forRow:i];
1112 }
1113
1114 [gui setColor: [OOColor greenColor] forRow: LABELROW];
1115 [gui setArray: [NSArray arrayWithObjects: DESC(@"loadsavescreen-commander-name"), DESC(@"loadsavescreen-rating"), nil]
1116 forRow:LABELROW];
1117
1118 if (page)
1119 {
1120 [gui setColor:[OOColor greenColor] forRow:STARTROW-1];
1121 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil]
1122 forRow:STARTROW-1];
1123 [gui setKey:GUI_KEY_OK forRow:STARTROW-1];
1124 rangeStart=STARTROW-1;
1125 }
1126
1127 if ([self status] == STATUS_START_GAME)
1128 {
1129 [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-loadsave-exit"), @" <----- ", nil] forRow:EXITROW];
1130 [gui setColor:[OOColor redColor] forRow:EXITROW];
1131 [gui setKey:GUI_KEY_OK forRow:EXITROW];
1132 rangeStart = EXITROW;
1133 }
1134
1135
1136 if (firstIndex + NUMROWS >= [cdrDetailArray count])
1137 {
1138 lastIndex=[cdrDetailArray count];
1139 [gui setSelectableRange: NSMakeRange(rangeStart, rangeStart + NUMROWS + 2)];
1140 }
1141 else
1142 {
1143 lastIndex=(page * NUMROWS) + NUMROWS;
1144 [gui setColor:[OOColor greenColor] forRow:ENDROW];
1145 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil]
1146 forRow:ENDROW];
1147 [gui setKey:GUI_KEY_OK forRow:ENDROW];
1148 [gui setSelectableRange: NSMakeRange(rangeStart, MOREROW)];
1149 }
1150
1151 for (i=firstIndex; i < lastIndex; i++)
1152 {
1153 NSDictionary *cdr=[cdrDetailArray objectAtIndex: i];
1154 if ([cdr oo_boolForKey:@"isSavedGame"])
1155 {
1156 NSString *ratingDesc = OODisplayRatingStringFromKillCount([cdr oo_unsignedIntForKey:@"ship_kills"]);
1157 [gui setArray:[NSArray arrayWithObjects:
1158 [NSString stringWithFormat:@" %@ ",[cdr oo_stringForKey:@"player_save_name" defaultValue:[cdr oo_stringForKey:@"player_name"]]],
1159 [NSString stringWithFormat:@" %@ ",ratingDesc],
1160 nil]
1161 forRow:row];
1162 if ([[self lastsaveName] isEqualToString:[cdr oo_stringForKey:@"player_save_name" defaultValue:[cdr oo_stringForKey:@"player_name"]]])
1163 {
1164 highlightRowOnPage = row;
1165 }
1166
1167 [gui setKey:GUI_KEY_OK forRow:row];
1168 row++;
1169 }
1170 if ([cdr oo_boolForKey:@"isParentFolder"])
1171 {
1172 [gui setArray:[NSArray arrayWithObjects:
1173 [NSString stringWithFormat:@" (..) %@ ", [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]],
1174 @"",
1175 nil]
1176 forRow:row];
1177 [gui setColor: [OOColor orangeColor] forRow: row];
1178 [gui setKey:GUI_KEY_OK forRow:row];
1179 row++;
1180 }
1181 if ([cdr oo_boolForKey:@"isFolder"])
1182 {
1183 [gui setArray:[NSArray arrayWithObjects:
1184 [NSString stringWithFormat:@" >> %@ ", [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]],
1185 @"",
1186 nil]
1187 forRow:row];
1188 [gui setColor: [OOColor orangeColor] forRow: row];
1189 [gui setKey:GUI_KEY_OK forRow:row];
1190 row++;
1191 }
1192 }
1193 [gui setSelectedRow: highlightRowOnPage];
1194 highlightIdx = (highlightRowOnPage - STARTROW) + (currentPage * NUMROWS);
1195 // show the first ship, this will be the selected row
1196 [self showCommanderShip: highlightIdx];
1197}
1198
1199
1200// check for an existing saved game...
1201- (BOOL) existingNativeSave: (NSString *)cdrName
1202{
1203 NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
1204
1205 NSString *savePath=[dir stringByAppendingPathComponent:[cdrName stringByAppendingPathExtension:@"oolite-save"]];
1206 return [[NSFileManager defaultManager] fileExistsAtPath:savePath];
1207}
1208
1209
1210// Get some brief details about the commander file.
1211- (void) showCommanderShip:(int)cdrArrayIndex
1212{
1213 GuiDisplayGen *gui=[UNIVERSE gui];
1214 [UNIVERSE removeDemoShips];
1215 NSDictionary *cdr=[cdrDetailArray objectAtIndex: cdrArrayIndex];
1216
1217 [gui setText:@"" forRow:CDRDESCROW align:GUI_ALIGN_LEFT];
1218 [gui setText:@"" forRow:CDRDESCROW + 1 align:GUI_ALIGN_LEFT];
1219 [gui setText:@"" forRow:CDRDESCROW + 2 align:GUI_ALIGN_LEFT];
1220
1221 if ([cdr oo_boolForKey:@"isFolder"])
1222 {
1223 NSString *folderDesc=[NSString stringWithFormat: DESC(@"loadsavescreen-hold-@-and-press-return-to-open-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
1229 if ([cdr oo_boolForKey:@"isParentFolder"])
1230 {
1231 NSString *folderDesc=[NSString stringWithFormat: DESC(@"loadsavescreen-hold-@-and-press-return-to-open-parent-folder-@"), @COMMAND_MODIFIER_KEY, [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]];
1232 [gui setColor: [OOColor orangeColor] forRow: CDRDESCROW];
1233 [gui addLongText: folderDesc startingAtRow: CDRDESCROW align: GUI_ALIGN_LEFT];
1234 return;
1235 }
1236 [gui setColor:[gui colorFromSetting:nil defaultValue:nil] forRow: CDRDESCROW];
1237
1238 if (![cdr oo_boolForKey:@"isSavedGame"]) return; // don't show things that aren't saved games
1239
1240 if ([self dockedStation] == nil) [self setDockedAtMainStation];
1241
1242 // Display the commander's ship.
1243 NSString *shipDesc = [cdr oo_stringForKey:@"ship_desc"];
1244 NSString *shipName = nil;
1245 NSDictionary *shipDict = nil;
1246 NSString *rating = nil;
1247 uint16_t personality = PersonalityForCommanderDict(cdr);
1248
1249 shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipDesc];
1250 if(shipDict != nil)
1251 {
1252 NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:[shipDict count] + 1];
1253 [dict setDictionary:shipDict];
1254 id subEntStatus = [cdr objectForKey:@"subentities_status"];
1255 // don't add it to the dictionary if there's no subentities_status key
1256 if (subEntStatus != nil) [dict setObject:subEntStatus forKey:@"subentities_status"];
1257 [self showShipyardModel:shipDesc shipData:dict personality:personality];
1258 [dict release];
1259 shipName = [shipDict oo_stringForKey:@"display_name"];
1260 if (shipName == nil) shipName = [shipDict oo_stringForKey:KEY_NAME];
1261 }
1262 else
1263 {
1264 [self showShipyardModel:@"oolite-unknown-ship" shipData:nil personality:personality];
1265 shipName = [cdr oo_stringForKey:@"ship_name" defaultValue:@"unknown"];
1266 if (![[UNIVERSE useAddOns] isEqualToString:SCENARIO_OXP_DEFINITION_ALL])
1267 {
1268 shipName = [shipName stringByAppendingString:@" - OXPs disabled or not installed"];
1269 }
1270 else
1271 {
1272 shipName = [shipName stringByAppendingString:@" - OXP not installed"];
1273 }
1274 }
1275
1276 // Make a short description of the commander
1277 NSString *legalDesc = OODisplayStringFromLegalStatus([cdr oo_intForKey:@"legal_status"]);
1278
1279 rating = KillCountToRatingAndKillString([cdr oo_unsignedIntForKey:@"ship_kills"]);
1280 OOCreditsQuantity money = OODeciCreditsFromObject([cdr objectForKey:@"credits"]);
1281
1282 // Nikos - Add some more information in the load game screen (current location, galaxy number and timestamp).
1283 //-------------------------------------------------------------------------------------------------------------------------
1284
1285 int galNumber;
1286 NSString *timeStamp = nil;
1287 NSString *locationName = [cdr oo_stringForKey:@"current_system_name"];
1288
1289 // If there is no key containing the name of the current system in
1290 // the savefile, calculating what it should have been is going to
1291 // be tricky now that system generation isn't seed based - but
1292 // this implies a save game well over 5 years old.
1293 if (locationName == nil)
1294 {
1295 // Leaving the location blank in this case is probably okay
1296 locationName = @"";
1297 }
1298
1299 galNumber = [cdr oo_intForKey:@"galaxy_number"] + 1; // Galaxy numbering starts at 0.
1300
1301 NSString *locationGov = @"";
1302 NSString *locationEco = @"";
1303 NSString *locationTL = [cdr objectForKey:@"current_system_techlevel"] ? [NSString stringWithFormat:@"%u", [cdr oo_unsignedIntForKey:@"current_system_techlevel"] + 1] : nil;
1304 if (locationTL)
1305 {
1306 locationGov = [NSString stringWithFormat:@"%c", [cdr oo_unsignedCharForKey:@"current_system_government"]];
1307 locationEco = [NSString stringWithFormat:@" %c", (7 - [cdr oo_unsignedCharForKey:@"current_system_economy"]) + 16];
1308 }
1309 else locationTL = @"";
1310
1311 timeStamp = ClockToString([cdr oo_doubleForKey:@"ship_clock" defaultValue:PLAYER_SHIP_CLOCK_START], NO);
1312
1313 //-------------------------------------------------------------------------------------------------------------------------
1314
1315 NSString *cdrDesc = nil;
1316
1317 cdrDesc = [NSString stringWithFormat:DESC(@"loadsavescreen-commander-@-rated-@-has-@-legal-status-@-ship-@-location-@-g-@-eco-@-gov-@-tl-@-timestamp-@"),
1318 [cdr oo_stringForKey:@"player_name"],
1319 rating,
1320 OOCredits(money),
1321 legalDesc,
1322 shipName,
1323 locationName,
1324 galNumber,
1325 locationEco,
1326 locationGov,
1327 locationTL,
1328 timeStamp];
1329
1330 //-------------------------------------------------------------------------------------------------------------------------
1331
1332 [gui addLongText:cdrDesc startingAtRow:CDRDESCROW align:GUI_ALIGN_LEFT];
1333
1334}
1335
1336
1337- (int) findIndexOfCommander: (NSString *)cdrName
1338{
1339 unsigned i;
1340 for (i=0; i < [cdrDetailArray count]; i++)
1341 {
1342 NSString *currentName = [[cdrDetailArray oo_dictionaryAtIndex: i] oo_stringForKey:@"player_save_name" defaultValue:[[cdrDetailArray oo_dictionaryAtIndex: i] oo_stringForKey:@"player_name"]];
1343 if([cdrName compare: currentName] == NSOrderedSame)
1344 {
1345 return i;
1346 }
1347 }
1348
1349 // not found!
1350 return -1;
1351}
1352
1353#endif
1354
1355@end
1356
1357
1358#if OO_USE_CUSTOM_LOAD_SAVE
1359
1360@implementation MyOpenGLView (OOLoadSaveExtensions)
1361
1362- (BOOL)isCommandModifierKeyDown
1363{
1364 return [self isCtrlDown];
1365}
1366
1367@end
1368
1369#endif
1370
1371
1372static uint16_t PersonalityForCommanderDict(NSDictionary *dict)
1373{
1374 uint16_t personality = [dict oo_unsignedShortForKey:@"entity_personality" defaultValue:ENTITY_PERSONALITY_INVALID];
1375
1376 if (personality == ENTITY_PERSONALITY_INVALID)
1377 {
1378 // For pre-1.74 saved games, generate a default personality based on some hashes.
1379 personality = [[dict oo_stringForKey:@"ship_desc"] oo_hash] * [[dict oo_stringForKey:@"player_name"] oo_hash];
1380 }
1381
1382 return personality & ENTITY_PERSONALITY_MAX;
1383}
1384
1385
1387{
1388 /* Clamp value to 0..kOOMaxCredits.
1389 The important bit here is that kOOMaxCredits can't be represented
1390 exactly as a double, and casting it rounds it up; casting this value
1391 back to an OOCreditsQuantity truncates it. Comparing value directly to
1392 kOOMaxCredits promotes kOOMaxCredits to a double, giving us this
1393 problem.
1394 nextafter(kOOMaxCredits, -1) gives us the highest non-truncated
1395 credits value that's representable as a double (namely,
1396 18 446 744 073 709 549 568 decicredits, or 2047 less than kOOMaxCredits).
1397 -- Ahruman 2011-02-27
1398 */
1399 if (doubleDeciCredits > 0)
1400 {
1401 doubleDeciCredits = round(doubleDeciCredits);
1402 double threshold = nextafter(kOOMaxCredits, -1);
1403
1404 if (doubleDeciCredits <= threshold)
1405 {
1406 return doubleDeciCredits;
1407 }
1408 else
1409 {
1410 return kOOMaxCredits;
1411 }
1412 }
1413 else
1414 {
1415 return 0;
1416 }
1417}
1418
1419
1421{
1422 if ([object isKindOfClass:[NSNumber class]] && [object oo_isFloatingPointNumber])
1423 {
1424 return OODeciCreditsFromDouble([object doubleValue]);
1425 }
1426 else
1427 {
1428 return OOUnsignedLongLongFromObject(object, 0);
1429 }
1430}
@ gvMouseDoubleClick
#define GUI_KEY_OK
OOGUITabStop OOGUITabSettings[GUI_MAX_COLUMNS]
NSInteger OOGUIRow
#define DESTROY(x)
Definition OOCocoa.h:75
unsigned long long OOUnsignedLongLongFromObject(id object, unsigned long long defaultValue)
#define OOJSSTR(str)
#define OOLog(class, format,...)
Definition OOLogging.h:88
NSDictionary * OODictionaryFromFile(NSString *path)
return self
unsigned count
return nil
Vector vector_up_from_quaternion(Quaternion quat)
Vector vector_right_from_quaternion(Quaternion quat)
Vector vector_forward_from_quaternion(Quaternion quat)
NSString * ClockToString(double clock, BOOL adjusting)
uint64_t OOCreditsQuantity
Definition OOTypes.h:182
#define kOOMaxCredits
Definition OOTypes.h:183
#define ENDROW
#define SAVE_OVERWRITE_NO_ROW
#define EXITROW
#define MOREROW
#define NUMROWS
#define STARTROW
#define SAVE_OVERWRITE_YES_ROW
#define BACKROW
OOCreditsQuantity OODeciCreditsFromObject(id object)
OOCreditsQuantity OODeciCreditsFromDouble(double doubleDeciCredits)
static uint16_t PersonalityForCommanderDict(NSDictionary *dict)
@ GUI_ROW_SCENARIOS_START
@ GUI_ROW_SCENARIOS_DETAIL
@ GUI_MAX_ROWS_SCENARIOS
NSString * OODisplayRatingStringFromKillCount(unsigned kills)
NSString * OODisplayStringFromLegalStatus(int legalStatus)
NSString * KillCountToRatingAndKillString(unsigned kills)
#define PLAYER_SHIP_CLOCK_START
#define SCENARIO_OXP_DEFINITION_NONE
#define SCENARIO_OXP_DEFINITION_ALL
#define ShipScriptEventNoCx(ship, event,...)
#define ENTITY_PERSONALITY_INVALID
Definition ShipEntity.h:111
#define ENTITY_PERSONALITY_MAX
Definition ShipEntity.h:110
#define UNIVERSE
Definition Universe.h:842
#define DESC(key)
Definition Universe.h:848
HPVector position
Definition Entity.h:112
NSString * playerFileToLoad
BOOL setBackgroundTextureKey:(NSString *key)
OOColor * colorFromSetting:defaultValue:(NSString *setting,[defaultValue] OOColor *def)
BOOL setSelectedRow:(OOGUIRow row)
OOGUIRow addLongText:startingAtRow:align:(NSString *str,[startingAtRow] OOGUIRow row,[align] OOGUIAlignment alignment)
BOOL setForegroundTextureKey:(NSString *key)
void setText:forRow:(NSString *str,[forRow] OOGUIRow row)
void setText:forRow:align:(NSString *str,[forRow] OOGUIRow row,[align] OOGUIAlignment alignment)
void clearAndKeepBackground:(BOOL keepBackground)
OOGUIRow selectedRow
void setSelectableRange:(NSRange range)
void setColor:forRow:(OOColor *color,[forRow] OOGUIRow row)
NSString * selectedRowKey()
void setTitle:(NSString *str)
void setTabStops:(OOGUITabSettings stops)
void setShowTextCursor:(BOOL yesno)
void setCurrentRow:(OOGUIRow value)
void setArray:forRow:(NSArray *arr,[forRow] OOGUIRow row)
void setKey:forRow:(NSString *str,[forRow] OOGUIRow row)
void suppressKeysUntilKeyUp()
NSMutableString * typedString
void setStringInput:(enum StringInput value)
void setTypedString:(NSString *value)
GameController * gameController
OOColor * cyanColor()
Definition OOColor.m:286
OOColor * orangeColor()
Definition OOColor.m:304
OOColor * redColor()
Definition OOColor.m:268
OOColor * greenColor()
Definition OOColor.m:274
OOColor * yellowColor()
Definition OOColor.m:292
OOJavaScriptEngine * sharedEngine()
void garbageCollectionOpportunity:(BOOL force)
OOShipRegistry * sharedRegistry()
NSDictionary * shipInfoForKey:(NSString *key)
NSString * pathForFileNamed:inFolder:(NSString *fileName,[inFolder] NSString *folderName)
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque