[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     ! 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