Oolite 1.91.0.7604-240417-a536cbe
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 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
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
1003NSComparisonResult 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
1364static 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
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
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}
@ gvMouseDoubleClick
#define GUI_KEY_OK
OOGUITabStop OOGUITabSettings[GUI_MAX_COLUMNS]
NSInteger OOGUIRow
#define DESTROY(x)
Definition OOCocoa.h:77
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:833
#define DESC(key)
Definition Universe.h:839
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