Annotation of brogue-ce/src/brogue/Recordings.c, Revision 1.1.1.1
1.1 rubenllo 1: /*
2: * Recordings.c
3: * Brogue
4: *
5: * Created by Brian Walker on 8/8/10.
6: * Copyright 2012. All rights reserved.
7: *
8: * This program is free software: you can redistribute it and/or modify
9: * it under the terms of the GNU Affero General Public License as
10: * published by the Free Software Foundation, either version 3 of the
11: * 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 Affero General Public License for more details.
17: *
18: * You should have received a copy of the GNU Affero General Public License
19: * along with this program. If not, see <http://www.gnu.org/licenses/>.
20: *
21: */
22:
23: #include <time.h>
24: #include <math.h>
25: #include <limits.h>
26: #include "Rogue.h"
27: #include "IncludeGlobals.h"
28:
29: #define RECORDING_HEADER_LENGTH 32 // bytes at the start of the recording file to store global data
30:
31: static const long keystrokeTable[] = {UP_ARROW, LEFT_ARROW, DOWN_ARROW, RIGHT_ARROW,
32: ESCAPE_KEY, RETURN_KEY, DELETE_KEY, TAB_KEY, NUMPAD_0, NUMPAD_1,
33: NUMPAD_2, NUMPAD_3, NUMPAD_4, NUMPAD_5, NUMPAD_6, NUMPAD_7, NUMPAD_8, NUMPAD_9};
34:
35: static const int keystrokeCount = sizeof(keystrokeTable) / sizeof(*keystrokeTable);
36:
37: enum recordingSeekModes {
38: RECORDING_SEEK_MODE_TURN,
39: RECORDING_SEEK_MODE_DEPTH
40: };
41:
42: void recordChar(unsigned char c) {
43: inputRecordBuffer[locationInRecordingBuffer++] = c;
44: recordingLocation++;
45: }
46:
47: void considerFlushingBufferToFile() {
48: if (locationInRecordingBuffer >= INPUT_RECORD_BUFFER) {
49: flushBufferToFile();
50: }
51: }
52:
53: // compresses a int into a char, discarding stuff we don't need
54: unsigned char compressKeystroke(long c) {
55: short i;
56:
57: for (i = 0; i < keystrokeCount; i++) {
58: if (keystrokeTable[i] == c) {
59: return (unsigned char) (128 + i);
60: }
61: }
62: if (c < 256) {
63: return (unsigned char) c;
64: }
65: return UNKNOWN_KEY;
66: }
67:
68: void numberToString(unsigned long number, short numberOfBytes, unsigned char *recordTo) {
69: short i;
70: unsigned long n;
71:
72: n = number;
73: for (i=numberOfBytes - 1; i >= 0; i--) {
74: recordTo[i] = n % 256;
75: n /= 256;
76: }
77: if (n > 0) {
78: printf("\nError: the number %li does not fit in %i bytes.", number, numberOfBytes);
79: brogueAssert(false);
80: }
81: }
82:
83: // numberOfBytes can't be greater than 10
84: void recordNumber(unsigned long number, short numberOfBytes) {
85: short i;
86: unsigned char c[10];
87:
88: numberToString(number, numberOfBytes, c);
89: for (i=0; i<numberOfBytes; i++) {
90: recordChar(c[i]);
91: }
92: }
93:
94: // Events are recorded as follows:
95: // Keystrokes: Event type, keystroke value, modifier flags. (3 bytes.)
96: // All other events: Event type, x-coordinate of the event, y-coordinate of the event, modifier flags. (4 bytes.)
97: // Note: these must be sanitized, because user input may contain more than one byte per parameter.
98: void recordEvent(rogueEvent *event) {
99: unsigned char c;
100:
101: if (rogue.playbackMode) {
102: return;
103: }
104:
105: recordChar((unsigned char) event->eventType);
106:
107: if (event->eventType == KEYSTROKE) {
108: // record which key
109: c = compressKeystroke(event->param1);
110: if (c == UNKNOWN_KEY) {
111: return;
112: }
113: recordChar(c);
114: } else {
115: recordChar((unsigned char) event->param1);
116: recordChar((unsigned char) event->param2);
117: }
118:
119: // record the modifier keys
120: c = 0;
121: if (event->controlKey) {
122: c += Fl(1);
123: }
124: if (event->shiftKey) {
125: c += Fl(2);
126: }
127: recordChar(c);
128: }
129:
130: // For convenience.
131: void recordKeystroke(int keystroke, boolean controlKey, boolean shiftKey) {
132: rogueEvent theEvent;
133:
134: if (rogue.playbackMode) {
135: return;
136: }
137:
138: theEvent.eventType = KEYSTROKE;
139: theEvent.param1 = keystroke;
140: theEvent.controlKey = controlKey;
141: theEvent.shiftKey = shiftKey;
142: recordEvent(&theEvent);
143: }
144:
145: // record a series of keystrokes; string must end with a null terminator
146: void recordKeystrokeSequence(unsigned char *keystrokeSequence) {
147: short i;
148: for (i=0; keystrokeSequence[i] != '\0'; i++) {
149: recordKeystroke(keystrokeSequence[i], false, false);
150: }
151: }
152:
153: // For convenience.
154: void recordMouseClick(short x, short y, boolean controlKey, boolean shiftKey) {
155: rogueEvent theEvent;
156:
157: if (rogue.playbackMode) {
158: return;
159: }
160:
161: theEvent.eventType = MOUSE_UP;
162: theEvent.param1 = x;
163: theEvent.param2 = y;
164: theEvent.controlKey = controlKey;
165: theEvent.shiftKey = shiftKey;
166: recordEvent(&theEvent);
167: }
168:
169: void writeHeaderInfo(char *path) {
170: unsigned char c[RECORDING_HEADER_LENGTH];
171: short i;
172: FILE *recordFile;
173:
174: // Zero out the entire header to start.
175: for (i=0; i<RECORDING_HEADER_LENGTH; i++) {
176: c[i] = 0;
177: }
178:
179: // Note the version string to gracefully deny compatibility when necessary.
180: for (i = 0; rogue.versionString[i] != '\0'; i++) {
181: c[i] = rogue.versionString[i];
182: }
183: c[15] = rogue.wizard;
184: i = 16;
185: numberToString(rogue.seed, 4, &c[i]);
186: i += 4;
187: numberToString(rogue.playerTurnNumber, 4, &c[i]);
188: i += 4;
189: numberToString(rogue.deepestLevel, 4, &c[i]);
190: i += 4;
191: numberToString(lengthOfPlaybackFile, 4, &c[i]);
192: i += 4;
193:
194: if (!fileExists(path)) {
195: recordFile = fopen(path, "wb");
196: if (recordFile) {
197: fclose(recordFile);
198: }
199: }
200:
201: recordFile = fopen(path, "r+b");
202: rewind(recordFile);
203: for (i=0; i<RECORDING_HEADER_LENGTH; i++) {
204: putc(c[i], recordFile);
205: }
206: if (recordFile) {
207: fclose(recordFile);
208: }
209:
210: if (lengthOfPlaybackFile < RECORDING_HEADER_LENGTH) {
211: lengthOfPlaybackFile = RECORDING_HEADER_LENGTH;
212: }
213: }
214:
215: void flushBufferToFile() {
216: short i;
217: FILE *recordFile;
218:
219: if (rogue.playbackMode) {
220: return;
221: }
222:
223: lengthOfPlaybackFile += locationInRecordingBuffer;
224: writeHeaderInfo(currentFilePath);
225:
226: if (locationInRecordingBuffer != 0) {
227:
228: recordFile = fopen(currentFilePath, "ab");
229:
230: for (i=0; i<locationInRecordingBuffer; i++) {
231: putc(inputRecordBuffer[i], recordFile);
232: }
233:
234: if (recordFile) {
235: fclose(recordFile);
236: }
237:
238: locationInRecordingBuffer = 0;
239: }
240: }
241:
242: void fillBufferFromFile() {
243: // short i;
244: FILE *recordFile;
245:
246: recordFile = fopen(currentFilePath, "rb");
247: fseek(recordFile, positionInPlaybackFile, SEEK_SET);
248:
249: fread((void *) inputRecordBuffer, 1, INPUT_RECORD_BUFFER, recordFile);
250:
251: positionInPlaybackFile = ftell(recordFile);
252: fclose(recordFile);
253:
254: locationInRecordingBuffer = 0;
255: }
256:
257: unsigned char recallChar() {
258: unsigned char c;
259: if (recordingLocation > lengthOfPlaybackFile) {
260: return END_OF_RECORDING;
261: }
262: c = inputRecordBuffer[locationInRecordingBuffer++];
263: recordingLocation++;
264: if (locationInRecordingBuffer >= INPUT_RECORD_BUFFER) {
265: fillBufferFromFile();
266: }
267: return c;
268: }
269:
270: long uncompressKeystroke(unsigned char c) {
271: if (c >= 128 && (c - 128) < keystrokeCount) {
272: return keystrokeTable[c - 128];
273: }
274: return (long)c;
275: }
276:
277: unsigned long recallNumber(short numberOfBytes) {
278: short i;
279: unsigned long n;
280:
281: n = 0;
282:
283: for (i=0; i<numberOfBytes; i++) {
284: n *= 256;
285: n += (unsigned long) recallChar();
286: }
287: return n;
288: }
289:
290: #define OOS_APOLOGY "Playback of the recording has diverged from the originally recorded game.\n\n\
291: This could be caused by recording or playing the file on a modified version of Brogue, or it could \
292: simply be the result of a bug. (The recording feature is still in beta for this reason.)\n\n\
293: If this is a different computer from the one on which the recording was saved, the recording \
294: might succeed on the original computer."
295:
296: void playbackPanic() {
297: cellDisplayBuffer rbuf[COLS][ROWS];
298:
299: if (!rogue.playbackOOS) {
300: rogue.playbackFastForward = false;
301: rogue.playbackPaused = true;
302: rogue.playbackOOS = true;
303: rogue.creaturesWillFlashThisTurn = false;
304: blackOutScreen();
305: displayLevel();
306: refreshSideBar(-1, -1, false);
307:
308: confirmMessages();
309: message("Playback is out of sync.", false);
310:
311: printTextBox(OOS_APOLOGY, 0, 0, 0, &white, &black, rbuf, NULL, 0);
312:
313: rogue.playbackMode = false;
314: displayMoreSign();
315: rogue.playbackMode = true;
316:
317: overlayDisplayBuffer(rbuf, 0);
318:
319: printf("Playback panic at location %li! Turn number %li.\n", recordingLocation - 1, rogue.playerTurnNumber);
320: overlayDisplayBuffer(rbuf, 0);
321:
322: mainInputLoop();
323: }
324: }
325:
326: void recallEvent(rogueEvent *event) {
327: unsigned char c;
328: boolean tryAgain;
329:
330: do {
331: tryAgain = false;
332: c = recallChar();
333: event->eventType = c;
334:
335: switch (c) {
336: case KEYSTROKE:
337: // record which key
338: event->param1 = uncompressKeystroke(recallChar());
339: event->param2 = 0;
340: break;
341: case SAVED_GAME_LOADED:
342: tryAgain = true;
343: flashTemporaryAlert(" Saved game loaded ", 1000);
344: break;
345: case MOUSE_UP:
346: case MOUSE_DOWN:
347: case MOUSE_ENTERED_CELL:
348: case RIGHT_MOUSE_UP:
349: case RIGHT_MOUSE_DOWN:
350: event->param1 = recallChar();
351: event->param2 = recallChar();
352: break;
353: case RNG_CHECK:
354: case END_OF_RECORDING:
355: case EVENT_ERROR:
356: default:
357: message("Unrecognized event type in playback.", true);
358: printf("Unrecognized event type in playback: event ID %i", c);
359: tryAgain = true;
360: playbackPanic();
361: break;
362: }
363: } while (tryAgain && !rogue.gameHasEnded);
364:
365: // record the modifier keys
366: c = recallChar();
367: event->controlKey = (c & Fl(1)) ? true : false;
368: event->shiftKey = (c & Fl(2)) ? true : false;
369: }
370:
371: void loadNextAnnotation() {
372: unsigned long currentReadTurn;
373: short i;
374: FILE *annotationFile;
375:
376: if (rogue.nextAnnotationTurn == -1) {
377: return;
378: }
379:
380: annotationFile = fopen(annotationPathname, "r");
381: fseek(annotationFile, rogue.locationInAnnotationFile, SEEK_SET);
382:
383: for (;;) {
384:
385: // load turn number
386: if (fscanf(annotationFile, "%lu\t", &(currentReadTurn)) != 1) {
387: if (feof(annotationFile)) {
388: rogue.nextAnnotation[0] = '\0';
389: rogue.nextAnnotationTurn = -1;
390: break;
391: } else {
392: // advance to the end of the line
393: fgets(rogue.nextAnnotation, 5000, annotationFile);
394: continue;
395: }
396: }
397:
398: // load description
399: fgets(rogue.nextAnnotation, 5000, annotationFile);
400:
401: if (currentReadTurn > rogue.playerTurnNumber ||
402: (currentReadTurn <= 1 && rogue.playerTurnNumber <= 1 && currentReadTurn >= rogue.playerTurnNumber)) {
403: rogue.nextAnnotationTurn = currentReadTurn;
404:
405: // strip the newline off the end
406: rogue.nextAnnotation[strlen(rogue.nextAnnotation) - 1] = '\0';
407: // strip out any gremlins in the annotation
408: for (i=0; i<5000 && rogue.nextAnnotation[i]; i++) {
409: if (rogue.nextAnnotation[i] < ' '
410: || rogue.nextAnnotation[i] > '~') {
411: rogue.nextAnnotation[i] = ' ';
412: }
413: }
414: break;
415: }
416: }
417: rogue.locationInAnnotationFile = ftell(annotationFile);
418: fclose(annotationFile);
419: }
420:
421: void displayAnnotation() {
422: cellDisplayBuffer rbuf[COLS][ROWS];
423:
424: if (rogue.playbackMode
425: && rogue.playerTurnNumber == rogue.nextAnnotationTurn) {
426:
427: if (!rogue.playbackFastForward) {
428: refreshSideBar(-1, -1, false);
429:
430: printTextBox(rogue.nextAnnotation, player.xLoc, 0, 0, &black, &white, rbuf, NULL, 0);
431:
432: rogue.playbackMode = false;
433: displayMoreSign();
434: rogue.playbackMode = true;
435:
436: overlayDisplayBuffer(rbuf, 0);
437: }
438:
439: loadNextAnnotation();
440: }
441: }
442:
443: // Attempts to extract the patch version of versionString into patchVersion,
444: // according to the global pattern. Returns 0 if successful.
445: static boolean getPatchVersion(char *versionString, unsigned short *patchVersion) {
446: int n = sscanf(versionString, BROGUE_PATCH_VERSION_PATTERN, patchVersion);
447: return n == 1;
448: }
449:
450: // creates a game recording file, or if in playback mode,
451: // initializes based on and starts reading from the recording file
452: void initRecording() {
453: short i;
454: boolean wizardMode;
455: unsigned short gamePatch, recPatch;
456: char buf[100], *versionString = rogue.versionString;
457: FILE *recordFile;
458:
459: #ifdef AUDIT_RNG
460: if (fileExists(RNG_LOG)) {
461: remove(RNG_LOG);
462: }
463: RNGLogFile = fopen(RNG_LOG, "a");
464: #endif
465:
466: locationInRecordingBuffer = 0;
467: positionInPlaybackFile = 0;
468: recordingLocation = 0;
469: maxLevelChanges = 0;
470: rogue.playbackOOS = false;
471: rogue.playbackOmniscience = false;
472: rogue.nextAnnotationTurn = 0;
473: rogue.nextAnnotation[0] = '\0';
474: rogue.locationInAnnotationFile = 0;
475: rogue.patchVersion = 0;
476:
477: if (rogue.playbackMode) {
478: lengthOfPlaybackFile = 100000; // so recall functions don't freak out
479: rogue.playbackDelayPerTurn = DEFAULT_PLAYBACK_DELAY;
480: rogue.playbackDelayThisTurn = rogue.playbackDelayPerTurn;
481: rogue.playbackPaused = false;
482:
483: fillBufferFromFile();
484:
485: for (i=0; i<15; i++) {
486: versionString[i] = recallChar();
487: }
488: wizardMode = recallChar();
489:
490: if (getPatchVersion(versionString, &recPatch)
491: && getPatchVersion(BROGUE_RECORDING_VERSION_STRING, &gamePatch)
492: && recPatch <= gamePatch) {
493: rogue.patchVersion = recPatch;
494: } else if (strcmp(versionString, "CE 1.9") == 0) {
495: // Temporary measure until next release, as "CE 1.9" recording string
496: // doesn't have a patch version (".0"), but we can load it.
497: rogue.patchVersion = 0;
498: } else if (strcmp(versionString, BROGUE_RECORDING_VERSION_STRING) != 0) {
499: // If we have neither a patch pattern match nor an exact match, we can't load.
500: rogue.playbackMode = false;
501: rogue.playbackFastForward = false;
502: sprintf(buf, "This file is from version %s and cannot be opened in version %s.", versionString, BROGUE_VERSION_STRING);
503: dialogAlert(buf);
504: rogue.playbackMode = true;
505: rogue.playbackPaused = true;
506: rogue.playbackFastForward = false;
507: rogue.playbackOOS = false;
508: rogue.gameHasEnded = true;
509: }
510:
511: if (wizardMode != rogue.wizard && rogue.patchVersion > 1) { // (don't perform the check for version 1.9.1 or earlier)
512: // wizard game cannot be played in normal mode and vice versa
513: rogue.playbackMode = false;
514: rogue.playbackFastForward = false;
515: if (wizardMode) {
516: sprintf(buf, "This game was played in wizard mode. You must start Brogue in wizard mode to replay it.");
517: } else {
518: sprintf(buf, "To play this regular recording, please restart Brogue without the wizard mode option.");
519: }
520: dialogAlert(buf);
521: rogue.playbackMode = true;
522: rogue.playbackPaused = true;
523: rogue.playbackFastForward = false;
524: rogue.playbackOOS = false;
525: rogue.gameHasEnded = true;
526: }
527:
528: rogue.seed = recallNumber(4); // master random seed
529: rogue.howManyTurns = recallNumber(4); // how many turns are in this recording
530: maxLevelChanges = recallNumber(4); // how many times the player changes depths
531: lengthOfPlaybackFile = recallNumber(4);
532: seedRandomGenerator(rogue.seed);
533: previousGameSeed = rogue.seed;
534:
535: if (fileExists(annotationPathname)) {
536: loadNextAnnotation();
537: } else {
538: rogue.nextAnnotationTurn = -1;
539: }
540: } else {
541: // If present, set the patch version for playing the game.
542: getPatchVersion(BROGUE_RECORDING_VERSION_STRING, &rogue.patchVersion);
543: strcpy(versionString, BROGUE_RECORDING_VERSION_STRING);
544:
545: lengthOfPlaybackFile = 1;
546: remove(currentFilePath);
547: recordFile = fopen(currentFilePath, "wb"); // create the file
548: fclose(recordFile);
549:
550: flushBufferToFile(); // header info never makes it into inputRecordBuffer when recording
551: rogue.recording = true;
552: }
553: rogue.currentTurnNumber = 0;
554: }
555:
556: void OOSCheck(unsigned long x, short numberOfBytes) {
557: unsigned char eventType;
558: unsigned long recordedNumber;
559:
560: if (rogue.playbackMode) {
561: eventType = recallChar();
562: recordedNumber = recallNumber(numberOfBytes);
563: if (eventType != RNG_CHECK || recordedNumber != x) {
564: if (eventType != RNG_CHECK) {
565: printf("Event type mismatch in RNG check.\n");
566: playbackPanic();
567: } else if (recordedNumber != x) {
568: printf("Expected RNG output of %li; got %i.\n", recordedNumber, (int) x);
569: playbackPanic();
570: }
571: }
572: } else {
573: recordChar(RNG_CHECK);
574: recordNumber(x, numberOfBytes);
575: considerFlushingBufferToFile();
576: }
577: }
578:
579: // compare a random number once per player turn so we instantly know if we are out of sync during playback
580: void RNGCheck() {
581: short oldRNG;
582: unsigned long randomNumber;
583:
584: oldRNG = rogue.RNG;
585: rogue.RNG = RNG_SUBSTANTIVE;
586:
587: //#ifdef AUDIT_RNG
588: //reportRNGState();
589: //#endif
590:
591: randomNumber = (unsigned long) rand_range(0, 255);
592: OOSCheck(randomNumber, 1);
593:
594: rogue.RNG = oldRNG;
595: }
596:
597: boolean unpause() {
598: if (rogue.playbackOOS) {
599: flashTemporaryAlert(" Out of sync ", 2000);
600: } else if (rogue.playbackPaused) {
601: rogue.playbackPaused = false;
602: return true;
603: }
604: return false;
605: }
606:
607: #define PLAYBACK_HELP_LINE_COUNT 20
608:
609: void printPlaybackHelpScreen() {
610: short i, j;
611: cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
612: char helpText[PLAYBACK_HELP_LINE_COUNT][80] = {
613: "Commands:",
614: "",
615: " <space>: ****pause or unpause playback",
616: " k or up arrow: ****play back faster",
617: " j or down arrow: ****play back slower",
618: " <: ****go to previous level",
619: " >: ****go to next level",
620: " 0-9: ****skip to specified turn number",
621: "l or right arrow: ****advance one turn (shift for 5 turns; control for 20)",
622: "",
623: " <tab>: ****enable or disable omniscience",
624: " return: ****examine surroundings",
625: " i: ****display inventory",
626: " D: ****display discovered items",
627: " V: ****view saved recording",
628: " O: ****open and resume saved game",
629: " N: ****begin a new game",
630: " Q: ****quit to title screen",
631: "",
632: " -- press any key to continue --"
633: };
634:
635: // Replace the "****"s with color escapes.
636: for (i=0; i<PLAYBACK_HELP_LINE_COUNT; i++) {
637: for (j=0; helpText[i][j]; j++) {
638: if (helpText[i][j] == '*') {
639: j = encodeMessageColor(helpText[i], j, &white);
640: }
641: }
642: }
643:
644: clearDisplayBuffer(dbuf);
645:
646: for (i=0; i<PLAYBACK_HELP_LINE_COUNT; i++) {
647: printString(helpText[i], mapToWindowX(5), mapToWindowY(i), &itemMessageColor, &black, dbuf);
648: }
649:
650: for (i=0; i<COLS; i++) {
651: for (j=0; j<ROWS; j++) {
652: dbuf[i][j].opacity = (i < STAT_BAR_WIDTH ? 0 : INTERFACE_OPACITY);
653: }
654: }
655: overlayDisplayBuffer(dbuf, rbuf);
656:
657: rogue.playbackMode = false;
658: waitForAcknowledgment();
659: rogue.playbackMode = true;
660: overlayDisplayBuffer(rbuf, NULL);
661: }
662:
663: static void resetPlayback() {
664: boolean omniscient, stealth, trueColors;
665:
666: omniscient = rogue.playbackOmniscience;
667: stealth = rogue.displayAggroRangeMode;
668: trueColors = rogue.trueColorMode;
669:
670: freeEverything();
671: randomNumbersGenerated = 0;
672: rogue.playbackMode = true;
673: initializeRogue(0); // Seed argument is ignored because we're in playback.
674:
675: rogue.playbackOmniscience = omniscient;
676: rogue.displayAggroRangeMode = stealth;
677: rogue.trueColorMode = trueColors;
678:
679: rogue.playbackFastForward = false;
680: blackOutScreen();
681: rogue.playbackFastForward = true;
682: startLevel(rogue.depthLevel, 1);
683: }
684:
685: static void seek(unsigned long seekTarget, enum recordingSeekModes seekMode) {
686: unsigned long progressBarRefreshInterval = 1, startTurnNumber = 0, targetTurnNumber = 0, avgTurnsPerLevel = 1;
687: rogueEvent theEvent;
688: boolean pauseState, useProgressBar = false, arrivedAtDestination = false;
689: cellDisplayBuffer dbuf[COLS][ROWS];
690:
691: pauseState = rogue.playbackPaused;
692:
693: // configure progress bar
694: switch (seekMode) {
695: case RECORDING_SEEK_MODE_DEPTH :
696: if (maxLevelChanges > 0) {
697: avgTurnsPerLevel = rogue.howManyTurns / maxLevelChanges;
698: }
699: if (seekTarget <= rogue.depthLevel) {
700: startTurnNumber = 0;
701: targetTurnNumber = avgTurnsPerLevel * seekTarget;
702: } else {
703: startTurnNumber = rogue.playerTurnNumber;
704: targetTurnNumber = rogue.playerTurnNumber + avgTurnsPerLevel;
705: }
706: break;
707: case RECORDING_SEEK_MODE_TURN :
708: if (seekTarget < rogue.playerTurnNumber) {
709: startTurnNumber = 0;
710: targetTurnNumber = seekTarget;
711: } else {
712: startTurnNumber = rogue.playerTurnNumber;
713: targetTurnNumber = seekTarget;
714: }
715: break;
716: }
717:
718: if (targetTurnNumber - startTurnNumber > 100) {
719: useProgressBar = true;
720: progressBarRefreshInterval = max(1, (targetTurnNumber) / 500);
721: }
722:
723: // there is no rewind, so start over at depth 1
724: if ((seekMode == RECORDING_SEEK_MODE_TURN && seekTarget < rogue.playerTurnNumber)
725: || (seekMode == RECORDING_SEEK_MODE_DEPTH && seekTarget <= rogue.depthLevel)) {
726:
727: resetPlayback();
728: if ((seekMode == RECORDING_SEEK_MODE_DEPTH && seekTarget == 1)
729: || (seekMode == RECORDING_SEEK_MODE_TURN && seekTarget == 0)) {
730: arrivedAtDestination = true;
731: }
732: }
733:
734: if (useProgressBar) {
735: clearDisplayBuffer(dbuf);
736: rectangularShading((COLS - 20) / 2, ROWS / 2, 20, 1, &black, INTERFACE_OPACITY, dbuf);
737: overlayDisplayBuffer(dbuf, 0);
738: commitDraws();
739: }
740: rogue.playbackFastForward = true;
741:
742: while (!arrivedAtDestination && !rogue.gameHasEnded && !rogue.playbackOOS) {
743: if (useProgressBar && !(rogue.playerTurnNumber % progressBarRefreshInterval)) {
744: rogue.playbackFastForward = false; // so that pauseBrogue looks for inputs
745: printProgressBar((COLS - 20) / 2, ROWS / 2, "[ Loading... ]",
746: rogue.playerTurnNumber - startTurnNumber,
747: targetTurnNumber - startTurnNumber, &darkPurple, false);
748: while (pauseBrogue(0)) { // pauseBrogue(0) is necessary to flush the display to the window in SDL
749: if (rogue.gameHasEnded) {
750: return;
751: }
752: rogue.creaturesWillFlashThisTurn = false; // prevent monster flashes from showing up on screen
753: nextBrogueEvent(&theEvent, true, false, true); // eat the input if it isn't clicking x
754: }
755: rogue.playbackFastForward = true;
756: }
757:
758: rogue.RNG = RNG_COSMETIC; // dancing terrain colors can't influence recordings
759: rogue.playbackDelayThisTurn = 0;
760: nextBrogueEvent(&theEvent, false, true, false);
761: rogue.RNG = RNG_SUBSTANTIVE;
762: executeEvent(&theEvent);
763:
764: if ((seekMode == RECORDING_SEEK_MODE_DEPTH && rogue.depthLevel == seekTarget)
765: || (seekMode == RECORDING_SEEK_MODE_TURN && rogue.playerTurnNumber == seekTarget)) {
766:
767: arrivedAtDestination = true;
768: }
769: }
770:
771: rogue.playbackPaused = pauseState;
772: rogue.playbackFastForward = false;
773: confirmMessages();
774: updateMessageDisplay();
775: refreshSideBar(-1, -1, false);
776: displayLevel();
777: }
778:
779: void promptToAdvanceToLocation(short keystroke) {
780: char entryText[30], buf[max(30, DCOLS)];
781: unsigned long destinationFrame;
782: boolean enteredText;
783:
784: if (rogue.playbackOOS || !rogue.playbackPaused || unpause()) {
785: buf[0] = (keystroke == '0' ? '\0' : keystroke);
786: buf[1] = '\0';
787:
788: rogue.playbackMode = false;
789: enteredText = getInputTextString(entryText, "Go to turn number: ", 9, buf, "", TEXT_INPUT_NUMBERS, false);
790: confirmMessages();
791: rogue.playbackMode = true;
792:
793: if (enteredText && entryText[0] != '\0') {
794: sscanf(entryText, "%lu", &destinationFrame);
795:
796: if (destinationFrame >= rogue.howManyTurns) {
797: flashTemporaryAlert(" Past end of recording ", 3000);
798: } else if (rogue.playbackOOS && destinationFrame > rogue.playerTurnNumber) {
799: flashTemporaryAlert(" Out of sync ", 3000);
800: } else if (destinationFrame == rogue.playerTurnNumber) {
801: sprintf(buf, " Already at turn %li ", destinationFrame);
802: flashTemporaryAlert(buf, 1000);
803: } else {
804: seek(destinationFrame, RECORDING_SEEK_MODE_TURN);
805: }
806: rogue.playbackPaused = true;
807: }
808: }
809: }
810:
811: void pausePlayback() {
812: //short oldRNG;
813: if (!rogue.playbackPaused) {
814: rogue.playbackPaused = true;
815: messageWithColor(KEYBOARD_LABELS ? "recording paused. Press space to play." : "recording paused.",
816: &teal, false);
817: refreshSideBar(-1, -1, false);
818: //oldRNG = rogue.RNG;
819: //rogue.RNG = RNG_SUBSTANTIVE;
820: mainInputLoop();
821: //rogue.RNG = oldRNG;
822: messageWithColor("recording unpaused.", &teal, false);
823: rogue.playbackPaused = false;
824: refreshSideBar(-1, -1, false);
825: rogue.playbackDelayThisTurn = DEFAULT_PLAYBACK_DELAY;
826: }
827: }
828:
829: // Used to interact with playback -- e.g. changing speed, pausing.
830: boolean executePlaybackInput(rogueEvent *recordingInput) {
831: signed long key;
832: short newDelay, frameCount, x, y;
833: unsigned long destinationFrame;
834: boolean proceed;
835: char path[BROGUE_FILENAME_MAX];
836:
837: if (!rogue.playbackMode) {
838: return false;
839: }
840:
841: if (recordingInput->eventType == KEYSTROKE) {
842: key = recordingInput->param1;
843: stripShiftFromMovementKeystroke(&key);
844:
845: switch (key) {
846: case UP_ARROW:
847: case UP_KEY:
848: newDelay = max(1, min(rogue.playbackDelayPerTurn * 2/3, rogue.playbackDelayPerTurn - 1));
849: if (newDelay != rogue.playbackDelayPerTurn) {
850: flashTemporaryAlert(" Faster ", 300);
851: }
852: rogue.playbackDelayPerTurn = newDelay;
853: rogue.playbackDelayThisTurn = rogue.playbackDelayPerTurn;
854: return true;
855: case DOWN_ARROW:
856: case DOWN_KEY:
857: newDelay = min(3000, max(rogue.playbackDelayPerTurn * 3/2, rogue.playbackDelayPerTurn + 1));
858: if (newDelay != rogue.playbackDelayPerTurn) {
859: flashTemporaryAlert(" Slower ", 300);
860: }
861: rogue.playbackDelayPerTurn = newDelay;
862: rogue.playbackDelayThisTurn = rogue.playbackDelayPerTurn;
863: return true;
864: case ACKNOWLEDGE_KEY:
865: if (rogue.playbackOOS && rogue.playbackPaused) {
866: flashTemporaryAlert(" Out of sync ", 2000);
867: } else {
868: rogue.playbackPaused = !rogue.playbackPaused;
869: }
870: return true;
871: case TAB_KEY:
872: rogue.playbackOmniscience = !rogue.playbackOmniscience;
873: displayLevel();
874: refreshSideBar(-1, -1, false);
875: if (rogue.playbackOmniscience) {
876: messageWithColor("Omniscience enabled.", &teal, false);
877: } else {
878: messageWithColor("Omniscience disabled.", &teal, false);
879: }
880: return true;
881: case ASCEND_KEY:
882: seek(max(rogue.depthLevel - 1, 1), RECORDING_SEEK_MODE_DEPTH);
883: return true;
884: case DESCEND_KEY:
885: if (rogue.depthLevel == maxLevelChanges) {
886: flashTemporaryAlert(" Already reached deepest depth explored ", 2000);
887: return false;
888: }
889: seek(rogue.depthLevel + 1, RECORDING_SEEK_MODE_DEPTH);
890: return true;
891: case INVENTORY_KEY:
892: rogue.playbackMode = false;
893: displayInventory(ALL_ITEMS, 0, 0, true, false);
894: rogue.playbackMode = true;
895: return true;
896: case RIGHT_KEY:
897: case RIGHT_ARROW:
898: case LEFT_KEY:
899: case LEFT_ARROW:
900: if (key == RIGHT_KEY || key == RIGHT_ARROW) {
901: frameCount = 1;
902: } else {
903: frameCount = -1;
904: }
905: if (recordingInput->shiftKey) {
906: frameCount *= 5;
907: }
908: if (recordingInput->controlKey) {
909: frameCount *= 20;
910: }
911:
912: if (frameCount < 0) {
913: if ((unsigned long) (frameCount * -1) > rogue.playerTurnNumber) {
914: destinationFrame = 0;
915: } else {
916: destinationFrame = rogue.playerTurnNumber + frameCount;
917: }
918: } else {
919: destinationFrame = min(rogue.playerTurnNumber + frameCount, rogue.howManyTurns - 1);
920: }
921:
922: if (destinationFrame == rogue.playerTurnNumber) {
923: flashTemporaryAlert(" Already at end of recording ", 1000);
924: } else if (frameCount < 0) {
925: rogue.playbackMode = false;
926: proceed = (rogue.playerTurnNumber < 100 || confirm("Rewind?", true));
927: rogue.playbackMode = true;
928: if (proceed) {
929: seek(destinationFrame, RECORDING_SEEK_MODE_TURN);
930: }
931: } else {
932: // advance by the right number of turns
933: seek(destinationFrame, RECORDING_SEEK_MODE_TURN);
934: }
935: return true;
936: case BROGUE_HELP_KEY:
937: printPlaybackHelpScreen();
938: return true;
939: case DISCOVERIES_KEY:
940: rogue.playbackMode = false;
941: printDiscoveriesScreen();
942: rogue.playbackMode = true;
943: return true;
944: case MESSAGE_ARCHIVE_KEY:
945: rogue.playbackMode = false;
946: displayMessageArchive();
947: rogue.playbackMode = true;
948: return true;
949: case VIEW_RECORDING_KEY:
950: confirmMessages();
951: rogue.playbackMode = false;
952: if (dialogChooseFile(path, RECORDING_SUFFIX, "View recording: ")) {
953: if (fileExists(path)) {
954: strcpy(rogue.nextGamePath, path);
955: rogue.nextGame = NG_VIEW_RECORDING;
956: rogue.gameHasEnded = true;
957: } else {
958: message("File not found.", false);
959: }
960: }
961: rogue.playbackMode = true;
962: return true;
963: case LOAD_SAVED_GAME_KEY:
964: confirmMessages();
965: rogue.playbackMode = false;
966: if (dialogChooseFile(path, GAME_SUFFIX, "Open saved game: ")) {
967: if (fileExists(path)) {
968: strcpy(rogue.nextGamePath, path);
969: rogue.nextGame = NG_OPEN_GAME;
970: rogue.gameHasEnded = true;
971: } else {
972: message("File not found.", false);
973: }
974: }
975: rogue.playbackMode = true;
976: return true;
977: case NEW_GAME_KEY:
978: rogue.playbackMode = false;
979: if (confirm("Close recording and begin a new game?", true)) {
980: rogue.nextGame = NG_NEW_GAME;
981: rogue.gameHasEnded = true;
982: }
983: rogue.playbackMode = true;
984: return true;
985: case QUIT_KEY:
986: //freeEverything();
987: rogue.gameHasEnded = true;
988: rogue.playbackOOS = false;
989: rogue.creaturesWillFlashThisTurn = false;
990: notifyEvent(GAMEOVER_RECORDING, 0, 0, "recording ended", "none");
991: return true;
992: case TRUE_COLORS_KEY:
993: rogue.trueColorMode = !rogue.trueColorMode;
994: displayLevel();
995: refreshSideBar(-1, -1, false);
996: if (rogue.trueColorMode) {
997: messageWithColor(KEYBOARD_LABELS ? "Color effects disabled. Press '\\' again to enable." : "Color effects disabled.",
998: &teal, false);
999: } else {
1000: messageWithColor(KEYBOARD_LABELS ? "Color effects enabled. Press '\\' again to disable." : "Color effects enabled.",
1001: &teal, false);
1002: }
1003: return true;
1004: case AGGRO_DISPLAY_KEY:
1005: rogue.displayAggroRangeMode = !rogue.displayAggroRangeMode;
1006: displayLevel();
1007: refreshSideBar(-1, -1, false);
1008: if (rogue.displayAggroRangeMode) {
1009: messageWithColor(KEYBOARD_LABELS ? "Stealth range displayed. Press ']' again to hide." : "Stealth range displayed.",
1010: &teal, false);
1011: } else {
1012: messageWithColor(KEYBOARD_LABELS ? "Stealth range hidden. Press ']' again to display." : "Stealth range hidden.",
1013: &teal, false);
1014: }
1015: return true;
1016: case GRAPHICS_KEY:
1017: if (hasGraphics) {
1018: graphicsEnabled = setGraphicsEnabled(!graphicsEnabled);
1019: if (graphicsEnabled) {
1020: messageWithColor(KEYBOARD_LABELS ? "Enabled graphical tiles. Press 'G' again to disable." : "Enable graphical tiles.",
1021: &teal, false);
1022: } else {
1023: messageWithColor(KEYBOARD_LABELS ? "Disabled graphical tiles. Press 'G' again to enable." : "Disabled graphical tiles.",
1024: &teal, false);
1025: }
1026: }
1027: return true;
1028: case SEED_KEY:
1029: //rogue.playbackMode = false;
1030: //DEBUG {displayGrid(safetyMap); displayMoreSign(); displayLevel();}
1031: //rogue.playbackMode = true;
1032: printSeed();
1033: return true;
1034: case SWITCH_TO_PLAYING_KEY:
1035: #ifdef ENABLE_PLAYBACK_SWITCH
1036: if (!rogue.gameHasEnded && !rogue.playbackOOS) {
1037: switchToPlaying();
1038: lengthOfPlaybackFile = recordingLocation;
1039: }
1040: return true;
1041: #endif
1042: return false;
1043: case ESCAPE_KEY:
1044: if (!rogue.playbackPaused) {
1045: rogue.playbackPaused = true;
1046: return true;
1047: }
1048: return false;
1049: default:
1050: if (key >= '0' && key <= '9'
1051: || key >= NUMPAD_0 && key <= NUMPAD_9) {
1052:
1053: promptToAdvanceToLocation(key);
1054: return true;
1055: }
1056: return false;
1057: }
1058: } else if (recordingInput->eventType == MOUSE_UP) {
1059: x = recordingInput->param1;
1060: y = recordingInput->param2;
1061: if (windowToMapX(x) >= 0 && windowToMapX(x) < DCOLS && y >= 0 && y < MESSAGE_LINES) {
1062: // If the click location is in the message block, display the message archive.
1063: rogue.playbackMode = false;
1064: displayMessageArchive();
1065: rogue.playbackMode = true;
1066: return true;
1067: } else if (!rogue.playbackPaused) {
1068: // clicking anywhere else pauses the playback
1069: rogue.playbackPaused = true;
1070: return true;
1071: }
1072: } else if (recordingInput->eventType == RIGHT_MOUSE_UP) {
1073: rogue.playbackMode = false;
1074: displayInventory(ALL_ITEMS, 0, 0, true, false);
1075: rogue.playbackMode = true;
1076: return true;
1077: }
1078: return false;
1079: }
1080:
1081: // Pass in defaultPath (the file name WITHOUT suffix), and the suffix.
1082: // Get back either defaultPath, or "defaultPath N",
1083: // where N is the lowest counting number that doesn't collide with an existing file.
1084: void getAvailableFilePath(char *returnPath, const char *defaultPath, const char *suffix) {
1085: char fullPath[BROGUE_FILENAME_MAX];
1086: short fileNameIterator = 2;
1087:
1088: strcpy(returnPath, defaultPath);
1089: sprintf(fullPath, "%s%s", returnPath, suffix);
1090: while (fileExists(fullPath)) {
1091: sprintf(returnPath, "%s (%i)", defaultPath, fileNameIterator);
1092: sprintf(fullPath, "%s%s", returnPath, suffix);
1093: fileNameIterator++;
1094: }
1095: }
1096:
1097: boolean characterForbiddenInFilename(const char theChar) {
1098: if (theChar == '/' || theChar == '\\' || theChar == ':') {
1099: return true;
1100: } else {
1101: return false;
1102: }
1103: }
1104:
1105: void getDefaultFilePath(char *defaultPath, boolean gameOver) {
1106: char seed[21];
1107:
1108: // 32-bit numbers are printed in full
1109: // 64-bit numbers longer than 11 digits are shortened to e.g "184...51615"
1110: sprintf(seed, "%llu", (unsigned long long)rogue.seed);
1111: if (strlen(seed) > 11) sprintf(seed+3, "...%05lu", (unsigned long)(rogue.seed % 100000));
1112:
1113: if (serverMode) {
1114: // With WebBrogue, filenames must fit into 30 bytes, including extension and terminal \0.
1115: // It is enough for the seed and the optional file counter. The longest filename will be:
1116: // "#184...51615 (999).broguesave" (30 bytes)
1117: sprintf(defaultPath, "#%s", seed);
1118: return;
1119: }
1120:
1121: if (!gameOver) {
1122: sprintf(defaultPath, "Saved #%s at depth %d", seed, rogue.depthLevel);
1123: } else if (rogue.quit) {
1124: sprintf(defaultPath, "#%s Quit at depth %d", seed, rogue.depthLevel);
1125: } else if (player.bookkeepingFlags & MB_IS_DYING) {
1126: sprintf(defaultPath, "#%s Died at depth %d", seed, rogue.depthLevel);
1127: } else if (rogue.depthLevel > 26) {
1128: sprintf(defaultPath, "#%s Mastered the dungeons", seed);
1129: } else {
1130: sprintf(defaultPath, "#%s Escaped the dungeons", seed);
1131: }
1132: if (rogue.wizard) {
1133: strcat(defaultPath, " (wizard)");
1134: } else if (rogue.easyMode) {
1135: strcat(defaultPath, " (easy)");
1136: }
1137: }
1138:
1139: void saveGameNoPrompt() {
1140: char filePath[BROGUE_FILENAME_MAX], defaultPath[BROGUE_FILENAME_MAX];
1141: if (rogue.playbackMode) {
1142: return;
1143: }
1144: getDefaultFilePath(defaultPath, false);
1145: getAvailableFilePath(filePath, defaultPath, GAME_SUFFIX);
1146: flushBufferToFile();
1147: strcat(filePath, GAME_SUFFIX);
1148: rename(currentFilePath, filePath);
1149: strcpy(currentFilePath, filePath);
1150: rogue.gameHasEnded = true;
1151: rogue.recording = false;
1152: }
1153:
1154: void saveGame() {
1155: char filePath[BROGUE_FILENAME_MAX], defaultPath[BROGUE_FILENAME_MAX];
1156: boolean askAgain;
1157:
1158: if (rogue.playbackMode) {
1159: return; // Call me paranoid, but I'd rather it be impossible to embed malware in a recording.
1160: }
1161:
1162: getDefaultFilePath(defaultPath, false);
1163: getAvailableFilePath(filePath, defaultPath, GAME_SUFFIX);
1164:
1165: deleteMessages();
1166: do {
1167: askAgain = false;
1168: if (getInputTextString(filePath, "Save game as (<esc> to cancel): ",
1169: BROGUE_FILENAME_MAX - strlen(GAME_SUFFIX), filePath, GAME_SUFFIX, TEXT_INPUT_FILENAME, false)) {
1170: strcat(filePath, GAME_SUFFIX);
1171: if (!fileExists(filePath) || confirm("File of that name already exists. Overwrite?", true)) {
1172: remove(filePath);
1173: flushBufferToFile();
1174: rename(currentFilePath, filePath);
1175: strcpy(currentFilePath, filePath);
1176: rogue.recording = false;
1177: message("Saved.", true);
1178: rogue.gameHasEnded = true;
1179: } else {
1180: askAgain = true;
1181: }
1182: }
1183: } while (askAgain);
1184: deleteMessages();
1185: }
1186:
1187: void saveRecordingNoPrompt(char *filePath) {
1188: char defaultPath[BROGUE_FILENAME_MAX];
1189: if (rogue.playbackMode) {
1190: return;
1191: }
1192: getDefaultFilePath(defaultPath, true);
1193: getAvailableFilePath(filePath, defaultPath, RECORDING_SUFFIX);
1194: strcat(filePath, RECORDING_SUFFIX);
1195: remove(filePath);
1196: rename(currentFilePath, filePath);
1197: rogue.recording = false;
1198: }
1199:
1200: void saveRecording(char *filePath) {
1201: char defaultPath[BROGUE_FILENAME_MAX];
1202: boolean askAgain;
1203:
1204: if (rogue.playbackMode) {
1205: return;
1206: }
1207:
1208: getDefaultFilePath(defaultPath, true);
1209: getAvailableFilePath(filePath, defaultPath, RECORDING_SUFFIX);
1210:
1211: deleteMessages();
1212: do {
1213: askAgain = false;
1214: if (getInputTextString(filePath, "Save recording as (<esc> to cancel): ",
1215: BROGUE_FILENAME_MAX - strlen(RECORDING_SUFFIX), filePath, RECORDING_SUFFIX, TEXT_INPUT_FILENAME, false)) {
1216:
1217: strcat(filePath, RECORDING_SUFFIX);
1218: if (!fileExists(filePath) || confirm("File of that name already exists. Overwrite?", true)) {
1219: remove(filePath);
1220: rename(currentFilePath, filePath);
1221: rogue.recording = false;
1222: } else {
1223: askAgain = true;
1224: }
1225: } else { // Declined to save recording; save it anyway as LastRecording, and delete LastRecording if it already exists.
1226: strcpy(filePath, LAST_RECORDING_NAME);
1227: strcat(filePath, RECORDING_SUFFIX);
1228: if (fileExists(filePath)) {
1229: remove(filePath);
1230: }
1231: rename(currentFilePath, filePath);
1232: rogue.recording = false;
1233: }
1234: } while (askAgain);
1235: deleteMessages();
1236: }
1237:
1238: void copyFile(char *fromFilePath, char *toFilePath, unsigned long fromFileLength) {
1239: unsigned long m, n;
1240: unsigned char fileBuffer[INPUT_RECORD_BUFFER];
1241: FILE *fromFile, *toFile;
1242:
1243: remove(toFilePath);
1244:
1245: fromFile = fopen(fromFilePath, "rb");
1246: toFile = fopen(toFilePath, "wb");
1247:
1248: for (n = 0; n < fromFileLength; n += m) {
1249: m = min(INPUT_RECORD_BUFFER, fromFileLength - n);
1250: fread((void *) fileBuffer, 1, m, fromFile);
1251: fwrite((void *) fileBuffer, 1, m, toFile);
1252: }
1253:
1254: fclose(fromFile);
1255: fclose(toFile);
1256: }
1257:
1258: // at the end of loading a saved game, this function transitions into active play mode.
1259: void switchToPlaying() {
1260: char lastGamePath[BROGUE_FILENAME_MAX];
1261:
1262: getAvailableFilePath(lastGamePath, LAST_GAME_NAME, GAME_SUFFIX);
1263: strcat(lastGamePath, GAME_SUFFIX);
1264:
1265: rogue.playbackMode = false;
1266: rogue.playbackFastForward = false;
1267: rogue.playbackOmniscience = false;
1268: rogue.recording = true;
1269: locationInRecordingBuffer = 0;
1270: copyFile(currentFilePath, lastGamePath, recordingLocation);
1271: #ifndef ENABLE_PLAYBACK_SWITCH
1272: if (DELETE_SAVE_FILE_AFTER_LOADING) {
1273: remove(currentFilePath);
1274: }
1275: #endif
1276:
1277: strcpy(currentFilePath, lastGamePath);
1278:
1279: blackOutScreen();
1280: refreshSideBar(-1, -1, false);
1281: updateMessageDisplay();
1282: displayLevel();
1283: }
1284:
1285: // Return whether the load was cancelled by an event
1286: boolean loadSavedGame() {
1287: unsigned long progressBarInterval;
1288: unsigned long previousRecordingLocation;
1289: rogueEvent theEvent;
1290:
1291: cellDisplayBuffer dbuf[COLS][ROWS];
1292:
1293: randomNumbersGenerated = 0;
1294: rogue.playbackMode = true;
1295: rogue.playbackFastForward = true;
1296: initializeRogue(0); // Calls initRecording(). Seed argument is ignored because we're initially in playback mode.
1297: if (!rogue.gameHasEnded) {
1298: blackOutScreen();
1299: startLevel(rogue.depthLevel, 1);
1300: }
1301:
1302: if (rogue.howManyTurns > 0) {
1303:
1304: progressBarInterval = max(1, lengthOfPlaybackFile / 100);
1305: previousRecordingLocation = -1; // unsigned
1306: clearDisplayBuffer(dbuf);
1307: rectangularShading((COLS - 20) / 2, ROWS / 2, 20, 1, &black, INTERFACE_OPACITY, dbuf);
1308: rogue.playbackFastForward = false;
1309: overlayDisplayBuffer(dbuf, 0);
1310: rogue.playbackFastForward = true;
1311:
1312: while (recordingLocation < lengthOfPlaybackFile
1313: && rogue.playerTurnNumber < rogue.howManyTurns
1314: && !rogue.gameHasEnded
1315: && !rogue.playbackOOS) {
1316:
1317: rogue.RNG = RNG_COSMETIC;
1318: nextBrogueEvent(&theEvent, false, true, false);
1319: rogue.RNG = RNG_SUBSTANTIVE;
1320:
1321: executeEvent(&theEvent);
1322:
1323: if (recordingLocation / progressBarInterval != previousRecordingLocation / progressBarInterval && !rogue.playbackOOS) {
1324: rogue.playbackFastForward = false; // so that pauseBrogue looks for inputs
1325: printProgressBar((COLS - 20) / 2, ROWS / 2, "[ Loading... ]", recordingLocation, lengthOfPlaybackFile, &darkPurple, false);
1326: while (pauseBrogue(0)) { // pauseBrogue(0) is necessary to flush the display to the window in SDL, as well as look for inputs
1327: rogue.creaturesWillFlashThisTurn = false; // prevent monster flashes from showing up on screen
1328: nextBrogueEvent(&theEvent, true, false, true);
1329: if (rogue.gameHasEnded || theEvent.eventType == KEYSTROKE && theEvent.param1 == ESCAPE_KEY) {
1330: return false;
1331: }
1332: }
1333: rogue.playbackFastForward = true;
1334: previousRecordingLocation = recordingLocation;
1335: }
1336: }
1337: }
1338:
1339: if (!rogue.gameHasEnded && !rogue.playbackOOS) {
1340: switchToPlaying();
1341: recordChar(SAVED_GAME_LOADED);
1342: }
1343: return true;
1344: }
1345:
1346: // the following functions are used to create human-readable descriptions of playback files for debugging purposes
1347:
1348: void describeKeystroke(unsigned char key, char *description) {
1349: short i;
1350: long c;
1351: const long keyList[50] = {UP_KEY, DOWN_KEY, LEFT_KEY, RIGHT_KEY, UP_ARROW, LEFT_ARROW,
1352: DOWN_ARROW, RIGHT_ARROW, UPLEFT_KEY, UPRIGHT_KEY, DOWNLEFT_KEY, DOWNRIGHT_KEY,
1353: DESCEND_KEY, ASCEND_KEY, REST_KEY, AUTO_REST_KEY, SEARCH_KEY, INVENTORY_KEY,
1354: ACKNOWLEDGE_KEY, EQUIP_KEY, UNEQUIP_KEY, APPLY_KEY, THROW_KEY, RELABEL_KEY, DROP_KEY, CALL_KEY,
1355: //FIGHT_KEY, FIGHT_TO_DEATH_KEY,
1356: BROGUE_HELP_KEY, DISCOVERIES_KEY, RETURN_KEY,
1357: EXPLORE_KEY, AUTOPLAY_KEY, SEED_KEY, EASY_MODE_KEY, ESCAPE_KEY,
1358: RETURN_KEY, DELETE_KEY, TAB_KEY, PERIOD_KEY, VIEW_RECORDING_KEY, NUMPAD_0,
1359: NUMPAD_1, NUMPAD_2, NUMPAD_3, NUMPAD_4, NUMPAD_5, NUMPAD_6, NUMPAD_7, NUMPAD_8,
1360: NUMPAD_9, UNKNOWN_KEY};
1361: const char descList[51][30] = {"up", "down", "left", "right", "up arrow", "left arrow",
1362: "down arrow", "right arrow", "upleft", "upright", "downleft", "downright",
1363: "descend", "ascend", "rest", "auto rest", "search", "inventory", "acknowledge",
1364: "equip", "unequip", "apply", "throw", "relabel", "drop", "call",
1365: //"fight", "fight to death",
1366: "help", "discoveries", "repeat travel", "explore", "autoplay", "seed",
1367: "easy mode", "escape", "return", "delete", "tab", "period", "open file",
1368: "numpad 0", "numpad 1", "numpad 2", "numpad 3", "numpad 4", "numpad 5", "numpad 6",
1369: "numpad 7", "numpad 8", "numpad 9", "unknown", "ERROR"};
1370:
1371: c = uncompressKeystroke(key);
1372: for (i=0; i < 50 && keyList[i] != c; i++);
1373: if (key >= 32 && key <= 126) {
1374: sprintf(description, "Key: %c\t(%s)", key, descList[i]);
1375: } else {
1376: sprintf(description, "Key: %i\t(%s)", key, descList[i]);
1377: }
1378: }
1379:
1380: void appendModifierKeyDescription(char *description) {
1381: unsigned char c = recallChar();
1382:
1383: if (c & Fl(1)) {
1384: strcat(description, " + CRTL");
1385: }
1386: if (c & Fl(2)) {
1387: strcat(description, " + SHIFT");
1388: }
1389: }
1390:
1391: // Deprecated! Only used to parse recordings, a debugging feature.
1392: boolean selectFile(char *prompt, char *defaultName, char *suffix) {
1393: boolean retval;
1394: char newFilePath[BROGUE_FILENAME_MAX];
1395:
1396: retval = false;
1397:
1398: if (chooseFile(newFilePath, prompt, defaultName, suffix)) {
1399: if (openFile(newFilePath)) {
1400: retval = true;
1401: } else {
1402: confirmMessages();
1403: message("File not found.", false);
1404: retval = false;
1405: }
1406: }
1407: return retval;
1408: }
1409:
1410: void parseFile() {
1411: FILE *descriptionFile;
1412: unsigned long oldFileLoc, oldRecLoc, oldLength, oldBufLoc, i, seed, numTurns, numDepths, fileLength, startLoc;
1413: unsigned char c;
1414: char description[1000], versionString[500];
1415: short x, y;
1416:
1417: if (selectFile("Parse recording: ", "Recording.broguerec", "")) {
1418:
1419: oldFileLoc = positionInPlaybackFile;
1420: oldRecLoc = recordingLocation;
1421: oldBufLoc = locationInRecordingBuffer;
1422: oldLength = lengthOfPlaybackFile;
1423:
1424: positionInPlaybackFile = 0;
1425: locationInRecordingBuffer = 0;
1426: recordingLocation = 0;
1427: lengthOfPlaybackFile = 10000000; // hack so that the recalls don't freak out
1428: fillBufferFromFile();
1429:
1430: descriptionFile = fopen("Recording Description.txt", "w");
1431:
1432: for (i=0; i<16; i++) {
1433: versionString[i] = recallChar();
1434: }
1435:
1436: seed = recallNumber(4);
1437: numTurns = recallNumber(4);
1438: numDepths = recallNumber(4);
1439: fileLength = recallNumber(4);
1440:
1441: fprintf(descriptionFile, "Parsed file \"%s\":\n\tVersion: %s\n\tSeed: %li\n\tNumber of turns: %li\n\tNumber of depth changes: %li\n\tFile length: %li\n",
1442: currentFilePath,
1443: versionString,
1444: seed,
1445: numTurns,
1446: numDepths,
1447: fileLength);
1448: for (i=0; recordingLocation < fileLength; i++) {
1449: startLoc = recordingLocation;
1450: c = recallChar();
1451: switch (c) {
1452: case KEYSTROKE:
1453: describeKeystroke(recallChar(), description);
1454: appendModifierKeyDescription(description);
1455: break;
1456: case MOUSE_UP:
1457: case MOUSE_DOWN:
1458: case MOUSE_ENTERED_CELL:
1459: x = (short) recallChar();
1460: y = (short) recallChar();
1461: sprintf(description, "Mouse click: (%i, %i)", x, y);
1462: appendModifierKeyDescription(description);
1463: break;
1464: case RNG_CHECK:
1465: sprintf(description, "\tRNG check: %i", (short) recallChar());
1466: break;
1467: case SAVED_GAME_LOADED:
1468: strcpy(description, "Saved game loaded");
1469: break;
1470: default:
1471: sprintf(description, "UNKNOWN EVENT TYPE: %i", (short) c);
1472: break;
1473: }
1474: fprintf(descriptionFile, "\nEvent %li, loc %li, length %li:%s\t%s", i, startLoc, recordingLocation - startLoc, (i < 10 ? " " : ""), description);
1475: }
1476:
1477: fclose(descriptionFile);
1478:
1479: positionInPlaybackFile = oldFileLoc;
1480: recordingLocation = oldRecLoc;
1481: lengthOfPlaybackFile = oldLength;
1482: locationInRecordingBuffer = oldBufLoc;
1483: message("File parsed.", false);
1484: } else {
1485: confirmMessages();
1486: }
1487: }
1488:
1489: void RNGLog(char *message) {
1490: #ifdef AUDIT_RNG
1491: fputs(message, RNGLogFile);
1492: #endif
1493: }
CVSweb