/* * RogueMain.c * Brogue * * Created by Brian Walker on 12/26/08. * Copyright 2012. All rights reserved. * * This file is part of Brogue. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "Rogue.h" #include "IncludeGlobals.h" #include void rogueMain() { previousGameSeed = 0; mainBrogueJunction(); } void executeEvent(rogueEvent *theEvent) { rogue.playbackBetweenTurns = false; if (theEvent->eventType == KEYSTROKE) { executeKeystroke(theEvent->param1, theEvent->controlKey, theEvent->shiftKey); } else if (theEvent->eventType == MOUSE_UP || theEvent->eventType == RIGHT_MOUSE_UP) { executeMouseClick(theEvent); } } boolean fileExists(const char *pathname) { FILE *openedFile; openedFile = fopen(pathname, "rb"); if (openedFile) { fclose(openedFile); return true; } else { return false; } } // Player specifies a file; if all goes well, put it into path and return true. // Otherwise, return false. boolean chooseFile(char *path, char *prompt, char *defaultName, char *suffix) { if (getInputTextString(path, prompt, min(DCOLS-25, BROGUE_FILENAME_MAX - strlen(suffix)), defaultName, suffix, TEXT_INPUT_FILENAME, false) && path[0] != '\0') { strcat(path, suffix); return true; } else { return false; } } // If the file exists, copy it into currentFilePath. (Otherwise return false.) // Then, strip off the suffix, replace it with ANNOTATION_SUFFIX, // and if that file exists, copy that into annotationPathname. Return true. boolean openFile(const char *path) { short i; char buf[BROGUE_FILENAME_MAX]; boolean retval; if (fileExists(path)) { strcpy(currentFilePath, path); annotationPathname[0] = '\0'; // Clip off the suffix. strcpy(buf, path); for (i = strlen(path); buf[i] != '.' && i > 0; i--) continue; if (buf[i] == '.' && i + strlen(ANNOTATION_SUFFIX) < BROGUE_FILENAME_MAX) { buf[i] = '\0'; // Snip! strcat(buf, ANNOTATION_SUFFIX); strcpy(annotationPathname, buf); // Load the annotations file too. } retval = true; } else { retval = false; } return retval; } void benchmark() { short i, j, k; const color sparklesauce = {10, 0, 20, 60, 40, 100, 30, true}; enum displayGlyph theChar; unsigned long initialTime = (unsigned long) time(NULL); for (k=0; k<500; k++) { for (i=0; i for help at any time.", &backgroundMessageColor, false); } flavorMessage("The doors to the dungeon slam shut behind you."); } // Seed is used as the dungeon seed unless it's zero, in which case generate a new one. // Either way, previousGameSeed is set to the seed we use. // None of this seed stuff is applicable if we're playing a recording. void initializeRogue(unsigned long seed) { short i, j, k; item *theItem; boolean playingback, playbackFF, playbackPaused, wizard, displayAggroRangeMode; boolean trueColorMode; short oldRNG; playingback = rogue.playbackMode; // the only animals that need to go on the ark playbackPaused = rogue.playbackPaused; playbackFF = rogue.playbackFastForward; wizard = rogue.wizard; displayAggroRangeMode = rogue.displayAggroRangeMode; trueColorMode = rogue.trueColorMode; memset((void *) &rogue, 0, sizeof( playerCharacter )); // the flood rogue.playbackMode = playingback; rogue.playbackPaused = playbackPaused; rogue.playbackFastForward = playbackFF; rogue.wizard = wizard; rogue.displayAggroRangeMode = displayAggroRangeMode; rogue.trueColorMode = trueColorMode; rogue.gameHasEnded = false; rogue.gameInProgress = true; rogue.highScoreSaved = false; rogue.cautiousMode = false; rogue.milliseconds = 0; rogue.RNG = RNG_SUBSTANTIVE; if (!rogue.playbackMode) { rogue.seed = seedRandomGenerator(seed); previousGameSeed = rogue.seed; } //benchmark(); initRecording(); levels = malloc(sizeof(levelData) * (DEEPEST_LEVEL+1)); levels[0].upStairsLoc[0] = (DCOLS - 1) / 2 - 1; levels[0].upStairsLoc[1] = DROWS - 2; // reset enchant and gain strength frequencies rogue.lifePotionFrequency = 0; rogue.strengthPotionFrequency = 40; rogue.enchantScrollFrequency = 60; // all DF messages are eligible for display resetDFMessageEligibility(); // initialize the levels list for (i=0; inextItem = NULL; packItems = (item *) malloc(sizeof(item)); memset(packItems, '\0', sizeof(item)); packItems->nextItem = NULL; monsterItemsHopper = (item *) malloc(sizeof(item)); memset(monsterItemsHopper, '\0', sizeof(item)); monsterItemsHopper->nextItem = NULL; for (i = 0; i < MAX_ITEMS_IN_MONSTER_ITEMS_HOPPER; i++) { theItem = generateItem(ALL_ITEMS & ~FOOD, -1); // Monsters can't carry food: the food clock cannot be cheated! theItem->nextItem = monsterItemsHopper->nextItem; monsterItemsHopper->nextItem = theItem; } monsters = (creature *) malloc(sizeof(creature)); memset(monsters, '\0', sizeof(creature)); monsters->nextCreature = NULL; dormantMonsters = (creature *) malloc(sizeof(creature)); memset(dormantMonsters, '\0', sizeof(creature)); dormantMonsters->nextCreature = NULL; graveyard = (creature *) malloc(sizeof(creature)); memset(graveyard, '\0', sizeof(creature)); graveyard->nextCreature = NULL; purgatory = (creature *) malloc(sizeof(creature)); memset(purgatory, '\0', sizeof(creature)); purgatory->nextCreature = NULL; scentMap = NULL; safetyMap = allocGrid(); allySafetyMap = allocGrid(); chokeMap = allocGrid(); rogue.mapToSafeTerrain = allocGrid(); // Zero out the dynamic grids, as an essential safeguard against OOSes: fillGrid(safetyMap, 0); fillGrid(allySafetyMap, 0); fillGrid(chokeMap, 0); fillGrid(rogue.mapToSafeTerrain, 0); // initialize the player memset(&player, '\0', sizeof(creature)); player.info = monsterCatalog[0]; initializeGender(&player); player.movementSpeed = player.info.movementSpeed; player.attackSpeed = player.info.attackSpeed; clearStatus(&player); player.carriedItem = NULL; player.status[STATUS_NUTRITION] = player.maxStatus[STATUS_NUTRITION] = STOMACH_SIZE; player.currentHP = player.info.maxHP; player.creatureState = MONSTER_ALLY; player.ticksUntilTurn = 0; player.mutationIndex = -1; rogue.depthLevel = 1; rogue.deepestLevel = 1; rogue.scentTurnNumber = 1000; rogue.playerTurnNumber = 0; rogue.absoluteTurnNumber = 0; rogue.previousPoisonPercent = 0; rogue.foodSpawned = 0; rogue.lifePotionsSpawned = 0; rogue.gold = 0; rogue.goldGenerated = 0; rogue.disturbed = false; rogue.autoPlayingLevel = false; rogue.automationActive = false; rogue.justRested = false; rogue.justSearched = false; rogue.easyMode = false; rogue.inWater = false; rogue.creaturesWillFlashThisTurn = false; rogue.updatedSafetyMapThisTurn = false; rogue.updatedAllySafetyMapThisTurn = false; rogue.updatedMapToSafeTerrainThisTurn = false; rogue.updatedMapToShoreThisTurn = false; rogue.strength = 12; rogue.weapon = NULL; rogue.armor = NULL; rogue.ringLeft = NULL; rogue.ringRight = NULL; rogue.monsterSpawnFuse = rand_range(125, 175); rogue.ticksTillUpdateEnvironment = 100; rogue.mapToShore = NULL; rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1; rogue.xpxpThisTurn = 0; rogue.yendorWarden = NULL; rogue.flares = NULL; rogue.flareCount = rogue.flareCapacity = 0; rogue.minersLight = lightCatalog[MINERS_LIGHT]; rogue.clairvoyance = rogue.regenerationBonus = rogue.stealthBonus = rogue.transference = rogue.wisdomBonus = rogue.reaping = 0; rogue.lightMultiplier = 1; theItem = generateItem(FOOD, RATION); theItem = addItemToPack(theItem); theItem = generateItem(WEAPON, DAGGER); theItem->enchant1 = theItem->enchant2 = 0; theItem->flags &= ~(ITEM_CURSED | ITEM_RUNIC); identify(theItem); theItem = addItemToPack(theItem); equipItem(theItem, false); theItem = generateItem(WEAPON, DART); theItem->enchant1 = theItem->enchant2 = 0; theItem->quantity = 15; theItem->flags &= ~(ITEM_CURSED | ITEM_RUNIC); identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(ARMOR, LEATHER_ARMOR); theItem->enchant1 = 0; theItem->flags &= ~(ITEM_CURSED | ITEM_RUNIC); identify(theItem); theItem = addItemToPack(theItem); equipItem(theItem, false); player.status[STATUS_DONNING] = 0; recalculateEquipmentBonuses(); DEBUG { theItem = generateItem(RING, RING_CLAIRVOYANCE); theItem->enchant1 = max(DROWS, DCOLS); theItem->flags &= ~ITEM_CURSED; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(RING, RING_AWARENESS); theItem->enchant1 = 30; theItem->flags &= ~ITEM_CURSED; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(WEAPON, DAGGER); theItem->enchant1 = 50; theItem->enchant2 = W_QUIETUS; theItem->flags &= ~(ITEM_CURSED); theItem->flags |= (ITEM_PROTECTED | ITEM_RUNIC | ITEM_RUNIC_HINTED); theItem->damage.lowerBound = theItem->damage.upperBound = 25; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(ARMOR, LEATHER_ARMOR); theItem->enchant1 = 50; theItem->enchant2 = A_REFLECTION; theItem->flags &= ~(ITEM_CURSED | ITEM_RUNIC_HINTED); theItem->flags |= (ITEM_PROTECTED | ITEM_RUNIC); identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(STAFF, STAFF_FIRE); theItem->enchant1 = 10; theItem->charges = 300; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(STAFF, STAFF_LIGHTNING); theItem->enchant1 = 10; theItem->charges = 300; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(STAFF, STAFF_TUNNELING); theItem->enchant1 = 10; theItem->charges = 3000; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(STAFF, STAFF_OBSTRUCTION); theItem->enchant1 = 10; theItem->charges = 300; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(STAFF, STAFF_ENTRANCEMENT); theItem->enchant1 = 10; theItem->charges = 300; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(WAND, WAND_BECKONING); theItem->charges = 3000; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(WAND, WAND_DOMINATION); theItem->charges = 300; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(WAND, WAND_PLENTY); theItem->charges = 300; identify(theItem); theItem = addItemToPack(theItem); theItem = generateItem(WAND, WAND_NEGATION); theItem->charges = 300; identify(theItem); theItem = addItemToPack(theItem); // short i; // for (i=0; i < NUMBER_CHARM_KINDS && i < 4; i++) { // theItem = generateItem(CHARM, i); // theItem = addItemToPack(theItem); // } } blackOutScreen(); welcome(); } // call this once per level to set all the dynamic colors as a function of depth void updateColors() { short i; for (i=0; inextCreature; monst != NULL; monst = monst->nextCreature) { x = monst->xLoc; y = monst->yLoc; if (((monst->creatureState == MONSTER_TRACKING_SCENT && (stairDirection != 0 || monst->status[STATUS_LEVITATING])) || monst->creatureState == MONSTER_ALLY || monst == rogue.yendorWarden) && (stairDirection != 0 || monst->currentHP > 10 || monst->status[STATUS_LEVITATING]) && ((flying != 0) == ((monst->status[STATUS_LEVITATING] != 0) || cellHasTerrainFlag(x, y, T_PATHING_BLOCKER) || cellHasTerrainFlag(px, py, T_AUTO_DESCENT))) && !(monst->bookkeepingFlags & MB_CAPTIVE) && !(monst->info.flags & (MONST_WILL_NOT_USE_STAIRS | MONST_RESTRICTED_TO_LIQUID)) && !(cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) && !monst->status[STATUS_ENTRANCED] && !monst->status[STATUS_PARALYZED] && (mapToStairs[monst->xLoc][monst->yLoc] < 30000 || monst->creatureState == MONSTER_ALLY || monst == rogue.yendorWarden)) { monst->status[STATUS_ENTERS_LEVEL_IN] = clamp(mapToStairs[monst->xLoc][monst->yLoc] * monst->movementSpeed / 100 + 1, 1, 150); switch (stairDirection) { case 1: monst->bookkeepingFlags |= MB_APPROACHING_DOWNSTAIRS; break; case -1: monst->bookkeepingFlags |= MB_APPROACHING_UPSTAIRS; break; case 0: monst->bookkeepingFlags |= MB_APPROACHING_PIT; break; default: break; } } } } freeGrid(mapToStairs); } for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) { if (monst->mapToMe) { freeGrid(monst->mapToMe); monst->mapToMe = NULL; } if (rogue.patchVersion < 3 && monst->safetyMap) { freeGrid(monst->safetyMap); monst->safetyMap = NULL; } } levels[oldLevelNumber-1].monsters = monsters->nextCreature; levels[oldLevelNumber-1].dormantMonsters = dormantMonsters->nextCreature; levels[oldLevelNumber-1].items = floorItems->nextItem; for (i=0; i= 3 ? ANY_KIND_OF_VISIBLE : VISIBLE)) { // Remember visible cells upon exiting. storeMemories(i, j); } for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { levels[oldLevelNumber - 1].mapStorage[i][j].layers[layer] = pmap[i][j].layers[layer]; } levels[oldLevelNumber - 1].mapStorage[i][j].volume = pmap[i][j].volume; levels[oldLevelNumber - 1].mapStorage[i][j].flags = (pmap[i][j].flags & (rogue.patchVersion < 3 ? (PERMANENT_TILE_FLAGS & ~HAS_MONSTER) : PERMANENT_TILE_FLAGS)); levels[oldLevelNumber - 1].mapStorage[i][j].machineNumber = pmap[i][j].machineNumber; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedAppearance = pmap[i][j].rememberedAppearance; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedItemCategory = pmap[i][j].rememberedItemCategory; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedItemKind = pmap[i][j].rememberedItemKind; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedItemQuantity = pmap[i][j].rememberedItemQuantity; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedItemOriginDepth = pmap[i][j].rememberedItemOriginDepth; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedTerrain = pmap[i][j].rememberedTerrain; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedCellFlags = pmap[i][j].rememberedCellFlags; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedTerrainFlags = pmap[i][j].rememberedTerrainFlags; levels[oldLevelNumber - 1].mapStorage[i][j].rememberedTMFlags = pmap[i][j].rememberedTMFlags; } } levels[oldLevelNumber - 1].awaySince = rogue.absoluteTurnNumber; // Prepare the new level rogue.minersLightRadius = (DCOLS - 1) * FP_FACTOR; for (i = 0; i < rogue.depthLevel; i++) { rogue.minersLightRadius = rogue.minersLightRadius * 85 / 100; } rogue.minersLightRadius += FP_FACTOR * 225 / 100; updateColors(); updateRingBonuses(); // also updates miner's light if (!levels[rogue.depthLevel - 1].visited) { // level has not already been visited levels[rogue.depthLevel - 1].scentMap = allocGrid(); scentMap = levels[rogue.depthLevel - 1].scentMap; fillGrid(levels[rogue.depthLevel - 1].scentMap, 0); // generate new level oldSeed = (unsigned long) rand_range(0, 9999); oldSeed += (unsigned long) 10000 * rand_range(0, 9999); seedRandomGenerator(levels[rogue.depthLevel - 1].levelSeed); // Load up next level's monsters and items, since one might have fallen from above. monsters->nextCreature = levels[rogue.depthLevel-1].monsters; dormantMonsters->nextCreature = levels[rogue.depthLevel-1].dormantMonsters; floorItems->nextItem = levels[rogue.depthLevel-1].items; levels[rogue.depthLevel-1].monsters = NULL; levels[rogue.depthLevel-1].dormantMonsters = NULL; levels[rogue.depthLevel-1].items = NULL; digDungeon(); initializeLevel(); setUpWaypoints(); shuffleTerrainColors(100, false); // If we somehow failed to generate the amulet altar, // just toss an amulet in there somewhere. // It'll be fiiine! if (rogue.depthLevel == AMULET_LEVEL && !numberOfMatchingPackItems(AMULET, 0, 0, false) && levels[rogue.depthLevel-1].visited == false) { for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) { if (theItem->category & AMULET) { break; } } for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) { if (monst->carriedItem && (monst->carriedItem->category & AMULET)) { theItem = monst->carriedItem; break; } } if (!theItem) { placeItem(generateItem(AMULET, 0), 0, 0); } } seedRandomGenerator(oldSeed); //logLevel(); // Simulate 50 turns so the level is broken in (swamp gas accumulating, brimstone percolating, etc.). timeAway = 50; } else { // level has already been visited // restore level scentMap = levels[rogue.depthLevel - 1].scentMap; timeAway = clamp(0, rogue.absoluteTurnNumber - levels[rogue.depthLevel - 1].awaySince, 30000); for (i=0; inextCreature = levels[rogue.depthLevel - 1].monsters; dormantMonsters->nextCreature = levels[rogue.depthLevel - 1].dormantMonsters; floorItems->nextItem = levels[rogue.depthLevel - 1].items; levels[rogue.depthLevel-1].monsters = NULL; levels[rogue.depthLevel-1].dormantMonsters = NULL; levels[rogue.depthLevel-1].items = NULL; for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) { restoreItem(theItem); } if (rogue.patchVersion < 3) { mapToStairs = allocGrid(); mapToPit = allocGrid(); fillGrid(mapToStairs, 0); fillGrid(mapToPit, 0); calculateDistances(mapToStairs, player.xLoc, player.yLoc, T_PATHING_BLOCKER, NULL, true, true); calculateDistances(mapToPit, levels[rogue.depthLevel-1].playerExitedVia[0], levels[rogue.depthLevel-1].playerExitedVia[0], T_PATHING_BLOCKER, NULL, true, true); for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) { restoreMonster(monst, mapToStairs, mapToPit); } freeGrid(mapToStairs); freeGrid(mapToPit); } } // Simulate the environment! // First bury the player in limbo while we run the simulation, // so that any harmful terrain doesn't affect her during the process. px = player.xLoc; py = player.yLoc; player.xLoc = player.yLoc = 0; for (i = 0; i < 100 && i < (short) timeAway; i++) { updateEnvironment(); } player.xLoc = px; player.yLoc = py; if (!levels[rogue.depthLevel-1].visited) { levels[rogue.depthLevel-1].visited = true; if (rogue.depthLevel == AMULET_LEVEL) { messageWithColor("An alien energy permeates the area. The Amulet of Yendor must be nearby!", &itemMessageColor, false); } else if (rogue.depthLevel == DEEPEST_LEVEL) { messageWithColor("An overwhelming sense of peace and tranquility settles upon you.", &lightBlue, false); } } // Position the player. if (stairDirection == 0) { // fell into the level getQualifyingLocNear(loc, player.xLoc, player.yLoc, true, 0, (T_PATHING_BLOCKER), (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), false, false); } else { if (stairDirection == 1) { // heading downward player.xLoc = rogue.upLoc[0]; player.yLoc = rogue.upLoc[1]; } else if (stairDirection == -1) { // heading upward player.xLoc = rogue.downLoc[0]; player.yLoc = rogue.downLoc[1]; } placedPlayer = false; for (dir=0; dir<4 && !placedPlayer; dir++) { loc[0] = player.xLoc + nbDirs[dir][0]; loc[1] = player.yLoc + nbDirs[dir][1]; if (!cellHasTerrainFlag(loc[0], loc[1], T_PATHING_BLOCKER) && !(pmap[loc[0]][loc[1]].flags & (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE))) { placedPlayer = true; } } if (!placedPlayer) { getQualifyingPathLocNear(&loc[0], &loc[1], player.xLoc, player.yLoc, true, T_DIVIDES_LEVEL, 0, T_PATHING_BLOCKER, (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), false); } } player.xLoc = loc[0]; player.yLoc = loc[1]; pmap[player.xLoc][player.yLoc].flags |= HAS_PLAYER; if (connectingStairsDiscovered) { for (i = rogue.upLoc[0]-1; i <= rogue.upLoc[0] + 1; i++) { for (j = rogue.upLoc[1]-1; j <= rogue.upLoc[1] + 1; j++) { if (coordinatesAreInMap(i, j)) { discoverCell(i, j); } } } } if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_IS_DEEP_WATER) && !player.status[STATUS_LEVITATING] && !cellHasTerrainFlag(player.xLoc, player.yLoc, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))) { rogue.inWater = true; } if (levels[rogue.depthLevel - 1].visited && rogue.patchVersion >= 3) { mapToStairs = allocGrid(); mapToPit = allocGrid(); fillGrid(mapToStairs, 0); fillGrid(mapToPit, 0); calculateDistances(mapToStairs, player.xLoc, player.yLoc, T_PATHING_BLOCKER, NULL, true, true); calculateDistances(mapToPit, levels[rogue.depthLevel-1].playerExitedVia[0], levels[rogue.depthLevel-1].playerExitedVia[1], T_PATHING_BLOCKER, NULL, true, true); for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) { restoreMonster(monst, mapToStairs, mapToPit); } freeGrid(mapToStairs); freeGrid(mapToPit); } updateMapToShore(); updateVision(true); rogue.aggroRange = currentAggroValue(); // update monster states so none are hunting if there is no scent and they can't see the player for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) { updateMonsterState(monst); } rogue.playbackBetweenTurns = true; displayLevel(); refreshSideBar(-1, -1, false); if (rogue.playerTurnNumber) { rogue.playerTurnNumber++; // Increment even though no time has passed. } RNGCheck(); flushBufferToFile(); deleteAllFlares(); // So discovering something on the same turn that you fall down a level doesn't flash stuff on the previous level. hideCursor(); } void freeGlobalDynamicGrid(short ***grid) { if (*grid) { freeGrid(*grid); *grid = NULL; } } void freeCreature(creature *monst) { freeGlobalDynamicGrid(&(monst->mapToMe)); freeGlobalDynamicGrid(&(monst->safetyMap)); if (monst->carriedItem) { free(monst->carriedItem); monst->carriedItem = NULL; } if (monst->carriedMonster) { freeCreature(monst->carriedMonster); monst->carriedMonster = NULL; } free(monst); } void emptyGraveyard() { creature *monst, *monst2; for (monst = graveyard->nextCreature; monst != NULL; monst = monst2) { monst2 = monst->nextCreature; freeCreature(monst); } graveyard->nextCreature = NULL; } void freeEverything() { short i; creature *monst, *monst2; item *theItem, *theItem2; #ifdef AUDIT_RNG fclose(RNGLogFile); #endif freeGlobalDynamicGrid(&safetyMap); freeGlobalDynamicGrid(&allySafetyMap); freeGlobalDynamicGrid(&chokeMap); freeGlobalDynamicGrid(&rogue.mapToShore); freeGlobalDynamicGrid(&rogue.mapToSafeTerrain); for (i=0; inextCreature; freeCreature(monst); } levels[i].monsters = NULL; for (monst = levels[i].dormantMonsters; monst != NULL; monst = monst2) { monst2 = monst->nextCreature; freeCreature(monst); } levels[i].dormantMonsters = NULL; for (theItem = levels[i].items; theItem != NULL; theItem = theItem2) { theItem2 = theItem->nextItem; deleteItem(theItem); } levels[i].items = NULL; if (levels[i].scentMap) { freeGrid(levels[i].scentMap); levels[i].scentMap = NULL; } } scentMap = NULL; for (monst = monsters; monst != NULL; monst = monst2) { monst2 = monst->nextCreature; freeCreature(monst); } monsters = NULL; for (monst = dormantMonsters; monst != NULL; monst = monst2) { monst2 = monst->nextCreature; freeCreature(monst); } dormantMonsters = NULL; for (monst = graveyard; monst != NULL; monst = monst2) { monst2 = monst->nextCreature; freeCreature(monst); } graveyard = NULL; for (monst = purgatory; monst != NULL; monst = monst2) { monst2 = monst->nextCreature; freeCreature(monst); } purgatory = NULL; for (theItem = floorItems; theItem != NULL; theItem = theItem2) { theItem2 = theItem->nextItem; deleteItem(theItem); } floorItems = NULL; for (theItem = packItems; theItem != NULL; theItem = theItem2) { theItem2 = theItem->nextItem; deleteItem(theItem); } packItems = NULL; for (theItem = monsterItemsHopper; theItem != NULL; theItem = theItem2) { theItem2 = theItem->nextItem; deleteItem(theItem); } monsterItemsHopper = NULL; for (i=0; inextItem; theItem != NULL; theItem = theItem->nextItem) { identify(theItem); theItem->flags &= ~ITEM_MAGIC_DETECTED; } displayInventory(ALL_ITEMS, 0, 0, true, false); } } while (!(theEvent.eventType == KEYSTROKE && (theEvent.param1 == ACKNOWLEDGE_KEY || theEvent.param1 == ESCAPE_KEY) || theEvent.eventType == MOUSE_UP)); confirmMessages(); rogue.playbackMode = playback; } rogue.creaturesWillFlashThisTurn = false; if (D_IMMORTAL && !rogue.quit) { message("...but then you get better.", false); player.currentHP = player.info.maxHP; if (player.status[STATUS_NUTRITION] < 10) { player.status[STATUS_NUTRITION] = STOMACH_SIZE; } player.bookkeepingFlags &= ~MB_IS_DYING; rogue.gameInProgress = true; return; } if (rogue.highScoreSaved) { return; } rogue.highScoreSaved = true; if (rogue.quit) { blackOutScreen(); } else { copyDisplayBuffer(dbuf, displayBuffer); funkyFade(dbuf, &black, 0, 120, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), false); } if (useCustomPhrasing) { sprintf(buf, "%s on depth %i", killedBy, rogue.depthLevel); } else { sprintf(buf, "Killed by a%s %s on depth %i", (isVowelish(killedBy) ? "n" : ""), killedBy, rogue.depthLevel); } theEntry.score = rogue.gold; if (rogue.easyMode) { theEntry.score /= 10; } strcpy(highScoreText, buf); if (theEntry.score > 0) { sprintf(buf2, " with %li gold", theEntry.score); strcat(buf, buf2); } if (numberOfMatchingPackItems(AMULET, 0, 0, false) > 0) { strcat(buf, ", amulet in hand"); } strcat(buf, "."); strcat(highScoreText, "."); strcpy(theEntry.description, highScoreText); if (!rogue.quit) { printString(buf, (COLS - strLenWithoutEscapes(buf)) / 2, ROWS / 2, &gray, &black, 0); y = ROWS / 2 + 3; for (i = 0; i < FEAT_COUNT; i++) { //printf("\nConduct %i (%s) is %s.", i, featTable[i].name, rogue.featRecord[i] ? "true" : "false"); if (rogue.featRecord[i] && !featTable[i].initialValue) { sprintf(buf, "%s: %s", featTable[i].name, featTable[i].description); printString(buf, (COLS - strLenWithoutEscapes(buf)) / 2, y, &advancementMessageColor, &black, 0); y++; } } displayMoreSign(); } if (serverMode) { blackOutScreen(); saveRecordingNoPrompt(recordingFilename); } else { if (!rogue.playbackMode && saveHighScore(theEntry)) { printHighScores(true); } blackOutScreen(); saveRecording(recordingFilename); } if (!rogue.playbackMode) { if (!rogue.quit) { notifyEvent(GAMEOVER_DEATH, theEntry.score, 0, theEntry.description, recordingFilename); } else { notifyEvent(GAMEOVER_QUIT, theEntry.score, 0, theEntry.description, recordingFilename); } } else { notifyEvent(GAMEOVER_RECORDING, 0, 0, "recording ended", "none"); } rogue.gameHasEnded = true; } void victory(boolean superVictory) { char buf[COLS*3], victoryVerb[20]; item *theItem; short i, j, gemCount = 0; unsigned long totalValue = 0; rogueHighScoresEntry theEntry; boolean qualified, isPlayback; cellDisplayBuffer dbuf[COLS][ROWS]; char recordingFilename[BROGUE_FILENAME_MAX] = {0}; rogue.gameInProgress = false; flushBufferToFile(); // // First screen - Congratulations... // deleteMessages(); if (superVictory) { message( "Light streams through the portal, and you are teleported out of the dungeon.", false); copyDisplayBuffer(dbuf, displayBuffer); funkyFade(dbuf, &superVictoryColor, 0, 240, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), false); displayMoreSign(); printString("Congratulations; you have transcended the Dungeons of Doom! ", mapToWindowX(0), mapToWindowY(-1), &black, &white, 0); displayMoreSign(); clearDisplayBuffer(dbuf); deleteMessages(); strcpy(displayedMessage[0], "You retire in splendor, forever renowned for your remarkable triumph. "); } else { message( "You are bathed in sunlight as you throw open the heavy doors.", false); copyDisplayBuffer(dbuf, displayBuffer); funkyFade(dbuf, &white, 0, 240, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), false); displayMoreSign(); printString("Congratulations; you have escaped from the Dungeons of Doom! ", mapToWindowX(0), mapToWindowY(-1), &black, &white, 0); displayMoreSign(); clearDisplayBuffer(dbuf); deleteMessages(); strcpy(displayedMessage[0], "You sell your treasures and live out your days in fame and glory."); } // // Second screen - Show inventory and item's value // printString(displayedMessage[0], mapToWindowX(0), mapToWindowY(-1), &white, &black, dbuf); plotCharToBuffer(G_GOLD, mapToWindowX(2), mapToWindowY(1), &yellow, &black, dbuf); printString("Gold", mapToWindowX(4), mapToWindowY(1), &white, &black, dbuf); sprintf(buf, "%li", rogue.gold); printString(buf, mapToWindowX(60), mapToWindowY(1), &itemMessageColor, &black, dbuf); totalValue += rogue.gold; for (i = 4, theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) { if (theItem->category & GEM) { gemCount += theItem->quantity; } if (theItem->category == AMULET && superVictory) { plotCharToBuffer(G_AMULET, mapToWindowX(2), min(ROWS-1, i + 1), &yellow, &black, dbuf); printString("The Birthright of Yendor", mapToWindowX(4), min(ROWS-1, i + 1), &itemMessageColor, &black, dbuf); sprintf(buf, "%li", max(0, itemValue(theItem) * 2)); printString(buf, mapToWindowX(60), min(ROWS-1, i + 1), &itemMessageColor, &black, dbuf); totalValue += max(0, itemValue(theItem) * 2); i++; } else { identify(theItem); itemName(theItem, buf, true, true, &white); upperCase(buf); plotCharToBuffer(theItem->displayChar, mapToWindowX(2), min(ROWS-1, i + 1), &yellow, &black, dbuf); printString(buf, mapToWindowX(4), min(ROWS-1, i + 1), &white, &black, dbuf); if (itemValue(theItem) > 0) { sprintf(buf, "%li", max(0, itemValue(theItem))); printString(buf, mapToWindowX(60), min(ROWS-1, i + 1), &itemMessageColor, &black, dbuf); } totalValue += max(0, itemValue(theItem)); i++; } } i++; printString("TOTAL:", mapToWindowX(2), min(ROWS-1, i + 1), &lightBlue, &black, dbuf); sprintf(buf, "%li", totalValue); printString(buf, mapToWindowX(60), min(ROWS-1, i + 1), &lightBlue, &black, dbuf); funkyFade(dbuf, &white, 0, 120, COLS/2, ROWS/2, true); displayMoreSign(); // // Third screen - List of achievements with recording save prompt // blackOutScreen(); i = 4; printString("Achievements", mapToWindowX(2), i++, &lightBlue, &black, NULL); i++; for (j = 0; i < ROWS && j < FEAT_COUNT; j++) { if (rogue.featRecord[j]) { sprintf(buf, "%s: %s", featTable[j].name, featTable[j].description); printString(buf, mapToWindowX(2), i, &advancementMessageColor, &black, NULL); i++; } } strcpy(victoryVerb, superVictory ? "Mastered" : "Escaped"); if (gemCount == 0) { sprintf(theEntry.description, "%s the Dungeons of Doom!", victoryVerb); } else if (gemCount == 1) { sprintf(theEntry.description, "%s the Dungeons of Doom with a lumenstone!", victoryVerb); } else { sprintf(theEntry.description, "%s the Dungeons of Doom with %i lumenstones!", victoryVerb, gemCount); } theEntry.score = totalValue; if (rogue.easyMode) { theEntry.score /= 10; } if (!rogue.wizard && !rogue.playbackMode) { qualified = saveHighScore(theEntry); } else { qualified = false; } isPlayback = rogue.playbackMode; rogue.playbackMode = false; rogue.playbackMode = isPlayback; if (serverMode) { // There's no save recording prompt, so let the player see achievements. displayMoreSign(); saveRecordingNoPrompt(recordingFilename); } else { saveRecording(recordingFilename); printHighScores(qualified); } if (!rogue.playbackMode) { if (superVictory) { notifyEvent(GAMEOVER_SUPERVICTORY, theEntry.score, 0, theEntry.description, recordingFilename); } else { notifyEvent(GAMEOVER_VICTORY, theEntry.score, 0, theEntry.description, recordingFilename); } } else { notifyEvent(GAMEOVER_RECORDING, 0, 0, "recording ended", "none"); } rogue.gameHasEnded = true; } void enableEasyMode() { if (rogue.easyMode) { message("Alas, all hope of salvation is lost. You shed scalding tears at your plight.", false); return; } message("A dark presence surrounds you, whispering promises of stolen power.", true); if (confirm("Succumb to demonic temptation (i.e. enable Easy Mode)?", false)) { recordKeystroke(EASY_MODE_KEY, false, true); message("An ancient and terrible evil burrows into your willing flesh!", true); player.info.displayChar = '&'; rogue.easyMode = true; refreshDungeonCell(player.xLoc, player.yLoc); refreshSideBar(-1, -1, false); message("Wracked by spasms, your body contorts into an ALL-POWERFUL AMPERSAND!!!", false); message("You have a feeling that you will take 20% as much damage from now on.", false); message("But great power comes at a great price -- specifically, a 90% income tax rate.", false); } else { message("The evil dissipates, hissing, from the air around you.", false); } } // takes a flag of the form Fl(n) and returns n short unflag(unsigned long flag) { short i; for (i=0; i<32; i++) { if (flag >> i == 1) { return i; } } return -1; }