Line data Source code
1 0 : /*
2 :
3 : PlayerEntityLoadSave.m
4 :
5 : Oolite
6 : Copyright (C) 2004-2013 Giles C Williams and contributors
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #import "PlayerEntityLoadSave.h"
26 : #import "PlayerEntityContracts.h"
27 : #import "PlayerEntityControls.h"
28 : #import "PlayerEntitySound.h"
29 :
30 : #import "NSFileManagerOOExtensions.h"
31 : #import "GameController.h"
32 : #import "ResourceManager.h"
33 : #import "OOStringExpander.h"
34 : #import "PlayerEntityControls.h"
35 : #import "ProxyPlayerEntity.h"
36 : #import "ShipEntityAI.h"
37 : #import "OOXMLExtensions.h"
38 : #import "OOSound.h"
39 : #import "OOColor.h"
40 : #import "OOStringParsing.h"
41 : #import "OOPListParsing.h"
42 : #import "StationEntity.h"
43 : #import "OOCollectionExtractors.h"
44 : #import "OOConstToString.h"
45 : #import "OOShipRegistry.h"
46 : #import "OOTexture.h"
47 : #import "NSStringOOExtensions.h"
48 : #import "NSNumberOOExtensions.h"
49 : #import "OOJavaScriptEngine.h"
50 :
51 :
52 : // Name of modifier key used to issue commands. See also -isCommandModifierKeyDown.
53 : #if OO_USE_CUSTOM_LOAD_SAVE
54 0 : #define COMMAND_MODIFIER_KEY "Ctrl"
55 : #endif
56 :
57 :
58 : static uint16_t PersonalityForCommanderDict(NSDictionary *dict);
59 :
60 :
61 : #if OO_USE_CUSTOM_LOAD_SAVE
62 :
63 : @interface MyOpenGLView (OOLoadSaveExtensions)
64 :
65 0 : - (BOOL)isCommandModifierKeyDown;
66 :
67 : @end
68 :
69 : #endif
70 :
71 :
72 : @interface PlayerEntity (OOLoadSavePrivate)
73 :
74 : #if OOLITE_USE_APPKIT_LOAD_SAVE
75 :
76 0 : - (BOOL) loadPlayerWithPanel;
77 0 : - (void) savePlayerWithPanel;
78 :
79 : #endif
80 :
81 : #if OO_USE_CUSTOM_LOAD_SAVE
82 :
83 0 : - (void) setGuiToLoadCommanderScreen;
84 0 : - (void) setGuiToSaveCommanderScreen: (NSString *)cdrName;
85 0 : - (void) setGuiToOverwriteScreen: (NSString *)cdrName;
86 0 : - (void) lsCommanders: (GuiDisplayGen *)gui directory: (NSString*)directory pageNumber: (int)page highlightName: (NSString *)highlightName;
87 0 : - (void) showCommanderShip: (int)cdrArrayIndex;
88 0 : - (int) findIndexOfCommander: (NSString *)cdrName;
89 0 : - (void) nativeSavePlayer: (NSString *)cdrName;
90 0 : - (BOOL) existingNativeSave: (NSString *)cdrName;
91 :
92 : #endif
93 :
94 0 : - (void) writePlayerToPath:(NSString *)path;
95 :
96 : @end
97 :
98 :
99 : @implementation PlayerEntity (LoadSave)
100 :
101 : - (BOOL)loadPlayer
102 : {
103 : BOOL OK = YES;
104 :
105 : #if OO_USE_APPKIT_LOAD_SAVE_ALWAYS
106 : OK = [self loadPlayerWithPanel];
107 : #elif OOLITE_USE_APPKIT_LOAD_SAVE
108 : // OS X: use system open/save dialogs in windowed mode, custom interface in full-screen.
109 : if ([[UNIVERSE gameController] inFullScreenMode])
110 : {
111 : [self setGuiToLoadCommanderScreen];
112 : }
113 : else
114 : {
115 : OK = [self loadPlayerWithPanel];
116 : }
117 : #else
118 : // Other platforms: use custom interface all the time.
119 : [self setGuiToLoadCommanderScreen];
120 : #endif
121 : return OK;
122 : }
123 :
124 :
125 : - (void)savePlayer
126 : {
127 : #if OO_USE_APPKIT_LOAD_SAVE_ALWAYS
128 : [self savePlayerWithPanel];
129 : #elif OOLITE_USE_APPKIT_LOAD_SAVE
130 : // OS X: use system open/save dialogs in windowed mode, custom interface in full-screen.
131 : if ([[UNIVERSE gameController] inFullScreenMode])
132 : {
133 : [self setGuiToSaveCommanderScreen:self.lastsaveName];
134 : }
135 : else
136 : {
137 : [self savePlayerWithPanel];
138 : }
139 : #else
140 : // Other platforms: use custom interface all the time.
141 : [self setGuiToSaveCommanderScreen:[self lastsaveName]];
142 : #endif
143 : }
144 :
145 : - (void) autosavePlayer
146 : {
147 : NSString *tmp_path = nil;
148 : NSString *tmp_name = nil;
149 : NSString *dir = [[UNIVERSE gameController] playerFileDirectory];
150 :
151 : tmp_name = [self lastsaveName];
152 : tmp_path = save_path;
153 :
154 : ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("AUTO_SAVE"));
155 :
156 : NSString *saveName = [self lastsaveName];
157 : NSString *autosaveSuffix = DESC(@"autosave-commander-suffix");
158 :
159 : if (![saveName hasSuffix:autosaveSuffix])
160 : {
161 : saveName = [saveName stringByAppendingString:autosaveSuffix];
162 : }
163 : NSString *savePath = [dir stringByAppendingPathComponent:[saveName stringByAppendingString:@".oolite-save"]];
164 :
165 : [self setLastsaveName:saveName];
166 :
167 : @try
168 : {
169 : [self writePlayerToPath:savePath];
170 : }
171 : @catch (id exception)
172 : {
173 : // Suppress exceptions silently. Warning the user about failed autosaves would be pretty unhelpful.
174 : }
175 :
176 : if (tmp_path != nil)
177 : {
178 : [save_path autorelease];
179 : save_path = [tmp_path copy];
180 : }
181 : [self setLastsaveName:tmp_name];
182 : }
183 :
184 :
185 : - (void) quicksavePlayer
186 : {
187 : MyOpenGLView *gameView = [UNIVERSE gameView];
188 : NSString *path = nil;
189 :
190 : path = save_path;
191 : if (!path) path = [[gameView gameController] playerFileToLoad];
192 : if (!path)
193 : {
194 : OOLog(@"quickSave.failed.noName", @"%@", @"ERROR no file name returned by [[gameView gameController] playerFileToLoad]");
195 : [NSException raise:@"OoliteGameNotSavedException"
196 : format:@"ERROR no file name returned by [[gameView gameController] playerFileToLoad]"];
197 : }
198 :
199 : ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("QUICK_SAVE"));
200 :
201 : [self writePlayerToPath:path];
202 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
203 : [self setGuiToStatusScreen];
204 : }
205 :
206 :
207 : - (void) setGuiToScenarioScreen:(int)page
208 : {
209 : NSArray *scenarios = [UNIVERSE scenarios];
210 : [UNIVERSE removeDemoShips];
211 : // GUI stuff
212 : {
213 : GuiDisplayGen *gui = [UNIVERSE gui];
214 : OOGUIRow start_row = GUI_ROW_SCENARIOS_START;
215 : OOGUIRow row = start_row;
216 : BOOL guiChanged = (gui_screen != GUI_SCREEN_NEWGAME);
217 :
218 : [gui clearAndKeepBackground:!guiChanged];
219 : [gui setTitle:DESC(@"oolite-newgame-title")];
220 :
221 : OOGUITabSettings tab_stops;
222 : tab_stops[0] = 0;
223 : tab_stops[1] = -480;
224 : [gui setTabStops:tab_stops];
225 :
226 : unsigned n_rows = GUI_MAX_ROWS_SCENARIOS;
227 : NSUInteger i, count = [scenarios count];
228 :
229 : NSDictionary *scenario = nil;
230 :
231 : [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-scenario-exit"), @" <----- ", nil] forRow:start_row - 2];
232 : [gui setColor:[OOColor redColor] forRow:start_row - 2];
233 : [gui setKey:@"exit" forRow:start_row - 2];
234 :
235 :
236 : if (page > 0)
237 : {
238 : [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:start_row - 1];
239 : [gui setColor:[OOColor greenColor] forRow:start_row - 1];
240 : [gui setKey:[NSString stringWithFormat:@"__page:%i",page-1] forRow:start_row - 1];
241 : }
242 :
243 : [self setShowDemoShips:NO];
244 :
245 : for (i = page*n_rows ; i < count && row < start_row + n_rows ; i++)
246 : {
247 : scenario = [[UNIVERSE scenarios] objectAtIndex:i];
248 : NSString *scenarioName = [NSString stringWithFormat:@" %@ ",[scenario oo_stringForKey:@"name"]];
249 : [gui setText:OOExpand(scenarioName) forRow:row];
250 : [gui setKey:[NSString stringWithFormat:@"Scenario:%lu", (unsigned long)i] forRow:row];
251 : ++row;
252 : }
253 :
254 : if ((page+1) * n_rows < count)
255 : {
256 : [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil] forRow:row];
257 : [gui setColor:[OOColor greenColor] forRow:row];
258 : [gui setKey:[NSString stringWithFormat:@"__page:%i",page+1] forRow:row];
259 : ++row;
260 : }
261 :
262 : gui_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
780 : [[OOJavaScriptEngine sharedEngine] garbageCollectionOpportunity:YES];
781 :
782 : // read saved position vector and primary role, check for an
783 : // appropriate station at those coordinates, if found, switch
784 : // docked station to that one.
785 : HPVector dockedPos = [fileDic oo_hpvectorForKey:@"docked_station_position"];
786 : NSString *dockedRole = [fileDic oo_stringForKey:@"docked_station_role" defaultValue:@""];
787 : StationEntity *saveStation = [UNIVERSE stationWithRole:dockedRole andPosition:dockedPos];
788 : if (saveStation != nil && [saveStation allowsSaving])
789 : {
790 : [self setDockedStation:saveStation];
791 : position = [saveStation position];
792 : }
793 : // and initialise markets for the secondary stations
794 : [UNIVERSE loadStationMarkets:[fileDic oo_arrayForKey:@"station_markets"]];
795 :
796 : OOLog(@"load.progress", @"%@", @"Completing JS startup");
797 : [self startUpComplete];
798 :
799 : // 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 : gui_screen = GUI_SCREEN_LOAD; // force evaluation of new gui screen on startup
802 : [self setGuiToStatusScreen];
803 : if (loadedOK) [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // trigger missionScreenOpportunity immediately after loading
804 : OOLog(@"load.progress", @"%@", @"Loading complete");
805 : return loadedOK;
806 : }
807 :
808 : @end
809 :
810 :
811 : @implementation PlayerEntity (OOLoadSavePrivate)
812 :
813 : #if OOLITE_USE_APPKIT_LOAD_SAVE
814 :
815 : - (BOOL)loadPlayerWithPanel
816 : {
817 : NSOpenPanel *oPanel = [NSOpenPanel openPanel];
818 :
819 : oPanel.allowsMultipleSelection = NO;
820 : oPanel.allowedFileTypes = [NSArray arrayWithObject:@"oolite-save"];
821 :
822 : if ([oPanel runModal] == NSOKButton)
823 : {
824 : NSURL *url = oPanel.URL;
825 : if (url.isFileURL)
826 : {
827 : return [self loadPlayerFromFile:url.path asNew:NO];
828 : }
829 : }
830 :
831 : return NO;
832 : }
833 :
834 :
835 : - (void) savePlayerWithPanel
836 : {
837 : NSSavePanel *sPanel = [NSSavePanel savePanel];
838 :
839 : sPanel.allowedFileTypes = [NSArray arrayWithObject:@"oolite-save"];
840 : sPanel.canSelectHiddenExtension = YES;
841 : sPanel.nameFieldStringValue = self.lastsaveName;
842 :
843 : if ([sPanel runModal] == NSOKButton)
844 : {
845 : NSURL *url = sPanel.URL;
846 : NSAssert(url.isFileURL, @"Save panel with default configuration should not provide non-file URLs.");
847 :
848 : NSString *path = url.path;
849 : NSString *newName = [path.lastPathComponent stringByDeletingPathExtension];
850 :
851 : ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("STANDARD_SAVE"));
852 :
853 : self.lastsaveName = newName;
854 : [self writePlayerToPath:path];
855 : }
856 : [self setGuiToStatusScreen];
857 : }
858 :
859 : #endif
860 :
861 :
862 : - (void) writePlayerToPath:(NSString *)path
863 : {
864 : NSString *errDesc = nil;
865 : NSDictionary *dict = nil;
866 : BOOL didSave = NO;
867 : [[UNIVERSE gameView] resetTypedString];
868 :
869 : if (!path)
870 : {
871 : OOLog(@"save.failed", @"***** SAVE ERROR: %s called with nil path.", __PRETTY_FUNCTION__);
872 : return;
873 : }
874 :
875 : dict = [self commanderDataDictionary];
876 : if (dict == nil) errDesc = @"could not construct commander data dictionary.";
877 : else didSave = [dict writeOOXMLToFile:path atomically:YES errorDescription:&errDesc];
878 : if (didSave)
879 : {
880 : [UNIVERSE clearPreviousMessage]; // allow this to be given time and again
881 : [UNIVERSE addMessage:DESC(@"game-saved") forCount:2];
882 : [save_path autorelease];
883 : save_path = [path copy];
884 : [[UNIVERSE gameController] setPlayerFileToLoad:save_path];
885 : [[UNIVERSE gameController] setPlayerFileDirectory:save_path];
886 : // no duplicated autosave immediately after a save.
887 : [UNIVERSE setAutoSaveNow:NO];
888 : }
889 : else
890 : {
891 : OOLog(@"save.failed", @"***** SAVE ERROR: %@", errDesc);
892 : [NSException raise:@"OoliteException"
893 : format:@"Attempt to save game to file '%@' failed: %@", path, errDesc];
894 : }
895 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
896 : [self setGuiToStatusScreen];
897 : }
898 :
899 :
900 : - (void)nativeSavePlayer:(NSString *)cdrName
901 : {
902 : NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
903 : NSString *savePath = [dir stringByAppendingPathComponent:[cdrName stringByAppendingPathExtension:@"oolite-save"]];
904 :
905 : ShipScriptEventNoCx(self, "playerWillSaveGame", OOJSSTR("STANDARD_SAVE"));
906 :
907 : [self setLastsaveName:cdrName];
908 :
909 : [self writePlayerToPath:savePath];
910 : }
911 :
912 :
913 : #if OO_USE_CUSTOM_LOAD_SAVE
914 :
915 : - (void) setGuiToLoadCommanderScreen
916 : {
917 : GuiDisplayGen *gui=[UNIVERSE gui];
918 : NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
919 :
920 : gui_screen = GUI_SCREEN_LOAD;
921 :
922 : [gui clear];
923 : [gui setTitle:DESC(@"loadscreen-title")];
924 :
925 : currentPage = 0;
926 : [self lsCommanders:gui directory:dir pageNumber: currentPage highlightName:nil];
927 :
928 : [gui setForegroundTextureKey:@"docked_overlay"];
929 : [gui setBackgroundTextureKey:@"load_save"];
930 :
931 : [[UNIVERSE gameView] suppressKeysUntilKeyUp];
932 :
933 : [self setShowDemoShips:YES];
934 : [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
935 : }
936 :
937 :
938 : - (void) setGuiToSaveCommanderScreen:(NSString *)cdrName
939 : {
940 : GuiDisplayGen *gui=[UNIVERSE gui];
941 : MyOpenGLView *gameView = [UNIVERSE gameView];
942 : NSString *dir = [[UNIVERSE gameController] playerFileDirectory];
943 :
944 : pollControls = NO;
945 : gui_screen = GUI_SCREEN_SAVE;
946 :
947 : [gui clear];
948 : [gui setTitle:DESC(@"savescreen-title")];
949 :
950 : currentPage = 0;
951 : [self lsCommanders:gui directory:dir pageNumber: currentPage highlightName:nil];
952 :
953 : [gui setText:DESC(@"savescreen-commander-name") forRow: INPUTROW];
954 : [gui setColor:[OOColor cyanColor] forRow:INPUTROW];
955 : [gui setShowTextCursor: YES];
956 : [gui setCurrentRow: INPUTROW];
957 :
958 : [gui setForegroundTextureKey:@"docked_overlay"];
959 : [gui setBackgroundTextureKey:@"load_save"];
960 :
961 : [gameView setTypedString:cdrName];
962 : [gameView suppressKeysUntilKeyUp];
963 :
964 : [self setShowDemoShips:YES];
965 : [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
966 : }
967 :
968 :
969 : - (void) setGuiToOverwriteScreen:(NSString *)cdrName
970 : {
971 : GuiDisplayGen *gui=[UNIVERSE gui];
972 : MyOpenGLView* gameView = [UNIVERSE gameView];
973 :
974 : // Don't poll controls
975 : pollControls=NO;
976 :
977 : gui_screen = GUI_SCREEN_SAVE_OVERWRITE;
978 :
979 : [gui clear];
980 : [gui setTitle:[NSString stringWithFormat:DESC(@"overwrite-save-commander-@"), cdrName]];
981 :
982 : [gui setText:[NSString stringWithFormat:DESC(@"overwritescreen-commander-@-already-exists-overwrite-query"), cdrName]
983 : forRow:SAVE_OVERWRITE_WARN_ROW align: GUI_ALIGN_CENTER];
984 :
985 : [gui setText:DESC(@"overwritescreen-yes") forRow: SAVE_OVERWRITE_YES_ROW align: GUI_ALIGN_CENTER];
986 : [gui setKey:GUI_KEY_OK forRow: SAVE_OVERWRITE_YES_ROW];
987 :
988 : [gui setText:DESC(@"overwritescreen-no") forRow: SAVE_OVERWRITE_NO_ROW align: GUI_ALIGN_CENTER];
989 : [gui setKey:GUI_KEY_OK forRow: SAVE_OVERWRITE_NO_ROW];
990 :
991 : [gui setSelectableRange: NSMakeRange(SAVE_OVERWRITE_YES_ROW, 2)];
992 : [gui setSelectedRow: SAVE_OVERWRITE_NO_ROW];
993 :
994 : // We can only leave this screen by answering yes or no, or esc. Therefore
995 : // use a specific overlay, to allow visual reminders of the available options.
996 : [gui setForegroundTextureKey:@"overwrite_overlay"];
997 : [gui setBackgroundTextureKey:@"load_save"];
998 :
999 : [self setShowDemoShips:NO];
1000 : [gameView setStringInput:gvStringInputNo];
1001 : [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.
1002 : }
1003 :
1004 0 : NSComparisonResult sortCommanders(id cdr1, id cdr2, void *context)
1005 : {
1006 : return [[cdr1 objectForKey:@"saved_game_path"] localizedCompare:[cdr2 objectForKey:@"saved_game_path"]];
1007 : }
1008 :
1009 : - (void) lsCommanders: (GuiDisplayGen *)gui
1010 : directory: (NSString*) directory
1011 : pageNumber: (int)page
1012 : highlightName: (NSString *)highlightName
1013 : {
1014 : NSFileManager *cdrFileManager=[NSFileManager defaultManager];
1015 : int rangeStart=STARTROW;
1016 : unsigned lastIndex;
1017 : unsigned i;
1018 : int row=STARTROW;
1019 :
1020 : // cdrArray defined in PlayerEntity.h
1021 : NSArray *cdrArray=[cdrFileManager commanderContentsOfPath: directory];
1022 :
1023 : // get commander details so a brief rundown of the commander's details may
1024 : // be displayed.
1025 : if (!cdrDetailArray)
1026 : cdrDetailArray=[[NSMutableArray alloc] init]; // alloc retains this so the retain further on in the code was unnecessary
1027 : else
1028 : [cdrDetailArray removeAllObjects];
1029 :
1030 : [cdrDetailArray addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
1031 : @"YES", @"isParentFolder",
1032 : [directory stringByDeletingLastPathComponent], @"saved_game_path", nil]];
1033 :
1034 : for(i = 0; i < [cdrArray count]; i++)
1035 : {
1036 : NSString* path = [cdrArray objectAtIndex:i];
1037 : BOOL exists, isDirectory = NO;
1038 :
1039 : exists = [cdrFileManager fileExistsAtPath:path isDirectory:&isDirectory];
1040 :
1041 : if (exists)
1042 : {
1043 : if (!isDirectory && [[[path pathExtension] lowercaseString] isEqualToString:@"oolite-save"])
1044 : {
1045 : NSDictionary *cdr = OODictionaryFromFile(path);
1046 : if(cdr)
1047 : {
1048 : // okay use the same dictionary but add a 'saved_game_path' attribute
1049 : NSMutableDictionary* cdr1 = [NSMutableDictionary dictionaryWithDictionary:cdr];
1050 : [cdr1 setObject: @"YES" forKey:@"isSavedGame"];
1051 : [cdr1 setObject: path forKey:@"saved_game_path"];
1052 : [cdrDetailArray addObject: cdr1];
1053 : }
1054 : }
1055 : if (isDirectory && ![[path lastPathComponent] hasPrefix:@"."])
1056 : {
1057 : [cdrDetailArray addObject: [NSDictionary dictionaryWithObjectsAndKeys: @"YES", @"isFolder", path, @"saved_game_path", nil]];
1058 : }
1059 : }
1060 : }
1061 :
1062 : if(![cdrDetailArray count])
1063 : {
1064 : // Empty directory; tell the user and exit immediately.
1065 : [gui setText:DESC(@"loadsavescreen-no-commanders-found") forRow:STARTROW align:GUI_ALIGN_CENTER];
1066 : return;
1067 : }
1068 :
1069 : [cdrDetailArray sortUsingFunction:sortCommanders context:NULL];
1070 :
1071 : // Do we need to highlight a name?
1072 : int highlightRowOnPage=STARTROW;
1073 : int highlightIdx=0;
1074 : if(highlightName)
1075 : {
1076 : highlightIdx=[self findIndexOfCommander: highlightName];
1077 : if(highlightIdx < 0)
1078 : {
1079 : OOLog(@"save.list.commanders.commanderNotFound", @"Commander %@ doesn't exist, very bad", highlightName);
1080 : highlightIdx=0;
1081 : }
1082 :
1083 : // figure out what page we need to be on
1084 : page=highlightIdx/NUMROWS;
1085 : highlightRowOnPage=highlightIdx % NUMROWS + STARTROW;
1086 : }
1087 :
1088 : // We now know for certain what page we're on -
1089 : // set the first index of the first commander on this page.
1090 : unsigned firstIndex=page * NUMROWS;
1091 :
1092 : // Set up the GUI.
1093 : OOGUITabSettings tabStop;
1094 : tabStop[0]=0;
1095 : tabStop[1]=160;
1096 : tabStop[2]=270;
1097 : [gui setTabStops: tabStop];
1098 :
1099 : // clear text lines here
1100 : for (i = EXITROW ; i < ENDROW + 1; i++)
1101 : {
1102 : [gui setText:@"" forRow:i align:GUI_ALIGN_LEFT];
1103 : [gui setColor: [OOColor yellowColor] forRow: i];
1104 : [gui setKey:GUI_KEY_SKIP forRow:i];
1105 : }
1106 :
1107 : [gui setColor: [OOColor greenColor] forRow: LABELROW];
1108 : [gui setArray: [NSArray arrayWithObjects: DESC(@"loadsavescreen-commander-name"), DESC(@"loadsavescreen-rating"), nil]
1109 : forRow:LABELROW];
1110 :
1111 : if (page)
1112 : {
1113 : [gui setColor:[OOColor greenColor] forRow:STARTROW-1];
1114 : [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil]
1115 : forRow:STARTROW-1];
1116 : [gui setKey:GUI_KEY_OK forRow:STARTROW-1];
1117 : rangeStart=STARTROW-1;
1118 : }
1119 :
1120 : if ([self status] == STATUS_START_GAME)
1121 : {
1122 : [gui setArray:[NSArray arrayWithObjects:DESC(@"oolite-loadsave-exit"), @" <----- ", nil] forRow:EXITROW];
1123 : [gui setColor:[OOColor redColor] forRow:EXITROW];
1124 : [gui setKey:GUI_KEY_OK forRow:EXITROW];
1125 : rangeStart = EXITROW;
1126 : }
1127 :
1128 :
1129 : if (firstIndex + NUMROWS >= [cdrDetailArray count])
1130 : {
1131 : lastIndex=[cdrDetailArray count];
1132 : [gui setSelectableRange: NSMakeRange(rangeStart, rangeStart + NUMROWS + 2)];
1133 : }
1134 : else
1135 : {
1136 : lastIndex=(page * NUMROWS) + NUMROWS;
1137 : [gui setColor:[OOColor greenColor] forRow:ENDROW];
1138 : [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil]
1139 : forRow:ENDROW];
1140 : [gui setKey:GUI_KEY_OK forRow:ENDROW];
1141 : [gui setSelectableRange: NSMakeRange(rangeStart, MOREROW)];
1142 : }
1143 :
1144 : for (i=firstIndex; i < lastIndex; i++)
1145 : {
1146 : NSDictionary *cdr=[cdrDetailArray objectAtIndex: i];
1147 : if ([cdr oo_boolForKey:@"isSavedGame"])
1148 : {
1149 : NSString *ratingDesc = OODisplayRatingStringFromKillCount([cdr oo_unsignedIntForKey:@"ship_kills"]);
1150 : [gui setArray:[NSArray arrayWithObjects:
1151 : [NSString stringWithFormat:@" %@ ",[cdr oo_stringForKey:@"player_save_name" defaultValue:[cdr oo_stringForKey:@"player_name"]]],
1152 : [NSString stringWithFormat:@" %@ ",ratingDesc],
1153 : nil]
1154 : forRow:row];
1155 : if ([[self lastsaveName] isEqualToString:[cdr oo_stringForKey:@"player_save_name" defaultValue:[cdr oo_stringForKey:@"player_name"]]])
1156 : {
1157 : highlightRowOnPage = row;
1158 : }
1159 :
1160 : [gui setKey:GUI_KEY_OK forRow:row];
1161 : row++;
1162 : }
1163 : if ([cdr oo_boolForKey:@"isParentFolder"])
1164 : {
1165 : [gui setArray:[NSArray arrayWithObjects:
1166 : [NSString stringWithFormat:@" (..) %@ ", [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]],
1167 : @"",
1168 : nil]
1169 : forRow:row];
1170 : [gui setColor: [OOColor orangeColor] forRow: row];
1171 : [gui setKey:GUI_KEY_OK forRow:row];
1172 : row++;
1173 : }
1174 : if ([cdr oo_boolForKey:@"isFolder"])
1175 : {
1176 : [gui setArray:[NSArray arrayWithObjects:
1177 : [NSString stringWithFormat:@" >> %@ ", [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]],
1178 : @"",
1179 : nil]
1180 : forRow:row];
1181 : [gui setColor: [OOColor orangeColor] forRow: row];
1182 : [gui setKey:GUI_KEY_OK forRow:row];
1183 : row++;
1184 : }
1185 : }
1186 : [gui setSelectedRow: highlightRowOnPage];
1187 : highlightIdx = (highlightRowOnPage - STARTROW) + (currentPage * NUMROWS);
1188 : // show the first ship, this will be the selected row
1189 : [self showCommanderShip: highlightIdx];
1190 : }
1191 :
1192 :
1193 : // check for an existing saved game...
1194 : - (BOOL) existingNativeSave: (NSString *)cdrName
1195 : {
1196 : NSString* dir = [[UNIVERSE gameController] playerFileDirectory];
1197 :
1198 : NSString *savePath=[dir stringByAppendingPathComponent:[cdrName stringByAppendingPathExtension:@"oolite-save"]];
1199 : return [[NSFileManager defaultManager] fileExistsAtPath:savePath];
1200 : }
1201 :
1202 :
1203 : // Get some brief details about the commander file.
1204 : - (void) showCommanderShip:(int)cdrArrayIndex
1205 : {
1206 : GuiDisplayGen *gui=[UNIVERSE gui];
1207 : [UNIVERSE removeDemoShips];
1208 : NSDictionary *cdr=[cdrDetailArray objectAtIndex: cdrArrayIndex];
1209 :
1210 : [gui setText:@"" forRow:CDRDESCROW align:GUI_ALIGN_LEFT];
1211 : [gui setText:@"" forRow:CDRDESCROW + 1 align:GUI_ALIGN_LEFT];
1212 : [gui setText:@"" forRow:CDRDESCROW + 2 align:GUI_ALIGN_LEFT];
1213 :
1214 : if ([cdr oo_boolForKey:@"isFolder"])
1215 : {
1216 : NSString *folderDesc=[NSString stringWithFormat: DESC(@"loadsavescreen-hold-@-and-press-return-to-open-folder-@"), @COMMAND_MODIFIER_KEY, [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]];
1217 : [gui setColor: [OOColor orangeColor] forRow: CDRDESCROW];
1218 : [gui addLongText: folderDesc startingAtRow: CDRDESCROW align: GUI_ALIGN_LEFT];
1219 : return;
1220 : }
1221 :
1222 : if ([cdr oo_boolForKey:@"isParentFolder"])
1223 : {
1224 : NSString *folderDesc=[NSString stringWithFormat: DESC(@"loadsavescreen-hold-@-and-press-return-to-open-parent-folder-@"), @COMMAND_MODIFIER_KEY, [[cdr oo_stringForKey:@"saved_game_path"] lastPathComponent]];
1225 : [gui setColor: [OOColor orangeColor] forRow: CDRDESCROW];
1226 : [gui addLongText: folderDesc startingAtRow: CDRDESCROW align: GUI_ALIGN_LEFT];
1227 : return;
1228 : }
1229 : [gui setColor:[gui colorFromSetting:nil defaultValue:nil] forRow: CDRDESCROW];
1230 :
1231 : if (![cdr oo_boolForKey:@"isSavedGame"]) return; // don't show things that aren't saved games
1232 :
1233 : if ([self dockedStation] == nil) [self setDockedAtMainStation];
1234 :
1235 : // Display the commander's ship.
1236 : NSString *shipDesc = [cdr oo_stringForKey:@"ship_desc"];
1237 : NSString *shipName = nil;
1238 : NSDictionary *shipDict = nil;
1239 : NSString *rating = nil;
1240 : uint16_t personality = PersonalityForCommanderDict(cdr);
1241 :
1242 : shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipDesc];
1243 : if(shipDict != nil)
1244 : {
1245 : NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:[shipDict count] + 1];
1246 : [dict setDictionary:shipDict];
1247 : id subEntStatus = [cdr objectForKey:@"subentities_status"];
1248 : // don't add it to the dictionary if there's no subentities_status key
1249 : if (subEntStatus != nil) [dict setObject:subEntStatus forKey:@"subentities_status"];
1250 : [self showShipyardModel:shipDesc shipData:dict personality:personality];
1251 : [dict release];
1252 : shipName = [shipDict oo_stringForKey:@"display_name"];
1253 : if (shipName == nil) shipName = [shipDict oo_stringForKey:KEY_NAME];
1254 : }
1255 : else
1256 : {
1257 : [self showShipyardModel:@"oolite-unknown-ship" shipData:nil personality:personality];
1258 : shipName = [cdr oo_stringForKey:@"ship_name" defaultValue:@"unknown"];
1259 : if (![[UNIVERSE useAddOns] isEqualToString:SCENARIO_OXP_DEFINITION_ALL])
1260 : {
1261 : shipName = [shipName stringByAppendingString:@" - OXPs disabled or not installed"];
1262 : }
1263 : else
1264 : {
1265 : shipName = [shipName stringByAppendingString:@" - OXP not installed"];
1266 : }
1267 : }
1268 :
1269 : // Make a short description of the commander
1270 : NSString *legalDesc = OODisplayStringFromLegalStatus([cdr oo_intForKey:@"legal_status"]);
1271 :
1272 : rating = KillCountToRatingAndKillString([cdr oo_unsignedIntForKey:@"ship_kills"]);
1273 : OOCreditsQuantity money = OODeciCreditsFromObject([cdr objectForKey:@"credits"]);
1274 :
1275 : // Nikos - Add some more information in the load game screen (current location, galaxy number and timestamp).
1276 : //-------------------------------------------------------------------------------------------------------------------------
1277 :
1278 : int galNumber;
1279 : NSString *timeStamp = nil;
1280 : NSString *locationName = [cdr oo_stringForKey:@"current_system_name"];
1281 :
1282 : // If there is no key containing the name of the current system in
1283 : // the savefile, calculating what it should have been is going to
1284 : // be tricky now that system generation isn't seed based - but
1285 : // this implies a save game well over 5 years old.
1286 : if (locationName == nil)
1287 : {
1288 : // Leaving the location blank in this case is probably okay
1289 : locationName = @"";
1290 : }
1291 :
1292 : galNumber = [cdr oo_intForKey:@"galaxy_number"] + 1; // Galaxy numbering starts at 0.
1293 :
1294 : NSString *locationGov = @"";
1295 : NSString *locationEco = @"";
1296 : NSString *locationTL = [cdr objectForKey:@"current_system_techlevel"] ? [NSString stringWithFormat:@"%u", [cdr oo_unsignedIntForKey:@"current_system_techlevel"] + 1] : nil;
1297 : if (locationTL)
1298 : {
1299 : locationGov = [NSString stringWithFormat:@"%c", [cdr oo_unsignedCharForKey:@"current_system_government"]];
1300 : locationEco = [NSString stringWithFormat:@" %c", (7 - [cdr oo_unsignedCharForKey:@"current_system_economy"]) + 16];
1301 : }
1302 : else locationTL = @"";
1303 :
1304 : timeStamp = ClockToString([cdr oo_doubleForKey:@"ship_clock" defaultValue:PLAYER_SHIP_CLOCK_START], NO);
1305 :
1306 : //-------------------------------------------------------------------------------------------------------------------------
1307 :
1308 : NSString *cdrDesc = nil;
1309 :
1310 : cdrDesc = [NSString stringWithFormat:DESC(@"loadsavescreen-commander-@-rated-@-has-@-legal-status-@-ship-@-location-@-g-@-eco-@-gov-@-tl-@-timestamp-@"),
1311 : [cdr oo_stringForKey:@"player_name"],
1312 : rating,
1313 : OOCredits(money),
1314 : legalDesc,
1315 : shipName,
1316 : locationName,
1317 : galNumber,
1318 : locationEco,
1319 : locationGov,
1320 : locationTL,
1321 : timeStamp];
1322 :
1323 : //-------------------------------------------------------------------------------------------------------------------------
1324 :
1325 : [gui addLongText:cdrDesc startingAtRow:CDRDESCROW align:GUI_ALIGN_LEFT];
1326 :
1327 : }
1328 :
1329 :
1330 : - (int) findIndexOfCommander: (NSString *)cdrName
1331 : {
1332 : unsigned i;
1333 : for (i=0; i < [cdrDetailArray count]; i++)
1334 : {
1335 : NSString *currentName = [[cdrDetailArray oo_dictionaryAtIndex: i] oo_stringForKey:@"player_save_name" defaultValue:[[cdrDetailArray oo_dictionaryAtIndex: i] oo_stringForKey:@"player_name"]];
1336 : if([cdrName compare: currentName] == NSOrderedSame)
1337 : {
1338 : return i;
1339 : }
1340 : }
1341 :
1342 : // not found!
1343 : return -1;
1344 : }
1345 :
1346 : #endif
1347 :
1348 : @end
1349 :
1350 :
1351 : #if OO_USE_CUSTOM_LOAD_SAVE
1352 :
1353 : @implementation MyOpenGLView (OOLoadSaveExtensions)
1354 :
1355 : - (BOOL)isCommandModifierKeyDown
1356 : {
1357 : return [self isCtrlDown];
1358 : }
1359 :
1360 : @end
1361 :
1362 : #endif
1363 :
1364 :
1365 0 : static uint16_t PersonalityForCommanderDict(NSDictionary *dict)
1366 : {
1367 : uint16_t personality = [dict oo_unsignedShortForKey:@"entity_personality" defaultValue:ENTITY_PERSONALITY_INVALID];
1368 :
1369 : if (personality == ENTITY_PERSONALITY_INVALID)
1370 : {
1371 : // For pre-1.74 saved games, generate a default personality based on some hashes.
1372 : personality = [[dict oo_stringForKey:@"ship_desc"] oo_hash] * [[dict oo_stringForKey:@"player_name"] oo_hash];
1373 : }
1374 :
1375 : return personality & ENTITY_PERSONALITY_MAX;
1376 : }
1377 :
1378 :
1379 0 : OOCreditsQuantity OODeciCreditsFromDouble(double doubleDeciCredits)
1380 : {
1381 : /* Clamp value to 0..kOOMaxCredits.
1382 : The important bit here is that kOOMaxCredits can't be represented
1383 : exactly as a double, and casting it rounds it up; casting this value
1384 : back to an OOCreditsQuantity truncates it. Comparing value directly to
1385 : kOOMaxCredits promotes kOOMaxCredits to a double, giving us this
1386 : problem.
1387 : nextafter(kOOMaxCredits, -1) gives us the highest non-truncated
1388 : credits value that's representable as a double (namely,
1389 : 18 446 744 073 709 549 568 decicredits, or 2047 less than kOOMaxCredits).
1390 : -- Ahruman 2011-02-27
1391 : */
1392 : if (doubleDeciCredits > 0)
1393 : {
1394 : doubleDeciCredits = round(doubleDeciCredits);
1395 : double threshold = nextafter(kOOMaxCredits, -1);
1396 :
1397 : if (doubleDeciCredits <= threshold)
1398 : {
1399 : return doubleDeciCredits;
1400 : }
1401 : else
1402 : {
1403 : return kOOMaxCredits;
1404 : }
1405 : }
1406 : else
1407 : {
1408 : return 0;
1409 : }
1410 : }
1411 :
1412 :
1413 0 : OOCreditsQuantity OODeciCreditsFromObject(id object)
1414 : {
1415 : if ([object isKindOfClass:[NSNumber class]] && [object oo_isFloatingPointNumber])
1416 : {
1417 : return OODeciCreditsFromDouble([object doubleValue]);
1418 : }
1419 : else
1420 : {
1421 : return OOUnsignedLongLongFromObject(object, 0);
1422 : }
1423 : }
|