[BACK]Return to Recordings.c CVS log [TXT][DIR] Up to [contributed] / brogue-ce / src / brogue

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