#include "game.h" #include #include "fov.h" #include #include #include #include #include "util.h" #include "renderer.h" #include "consts.h" #include "runtime.h" Game::Game(std::mt19937& rng, Muxer& muxer, Renderer& renderer) : rng(rng), muxer(muxer), renderer(renderer), sign(renderer.getFont()) { losePopLampTimer.accumulate(losePopLampTimer.getDt()); initialFade.start(1000); helpProgress.start(1500); do { loadMap(); tick(); tick(); tick(); for (int y = player_y-1; y <= player_y+1; y++) { for (int x = player_x-1; x <= player_x+1; x++) { map.tile(x,y) = Tile::Floor; } } tick(); } while (!isInitialCaveBigEnough()); std::ifstream textFile(Runtime::getResourcePath("childoflight.txt")); std::string line; while (std::getline(textFile, line)) { signTexts.push_back(line); } } inline bool isTileSetOrNotLit(const Map& map, int x, int y) { return (map.inBounds(x, y) && (map.tile(x,y) == Tile::Wall || !map.at(x,y).lit)); } inline bool isTileSet(const Map& map, int x, int y, Tile val = Tile::Wall) { return (map.inBounds(x, y) && map.tile(x,y) == val); } inline void incrementIfSet(const Game& game, int& count, int x, int y, Tile val = Tile::Wall) { if (isTileSet(game.map, x, y, val)) { count++; } } inline int getZoomLevel(const Game& game) { return game.curZoom - INIT_ZOOM; } inline void tickIndividual(Game& game, std::vector& mapDoubleBuffer, int x, int y, bool onlyDark) { if (onlyDark && game.map.at(x,y).lit) { return; } if (game.map.tile(x,y) == Tile::Lamp) { return; } int count = 0; incrementIfSet(game, count, x-1, y-1); incrementIfSet(game, count, x-1, y ); incrementIfSet(game, count, x-1, y+1); incrementIfSet(game, count, x , y-1); incrementIfSet(game, count, x , y ); incrementIfSet(game, count, x , y+1); incrementIfSet(game, count, x+1, y-1); incrementIfSet(game, count, x+1, y ); incrementIfSet(game, count, x+1, y+1); int tempIndex = game.map.getTrueX(x) + game.map.getTrueY(y) * game.map.getWidth(); if (count >= 5) { mapDoubleBuffer[tempIndex] = Tile::Wall; } else { mapDoubleBuffer[tempIndex] = Tile::Floor; } if (mapDoubleBuffer[tempIndex] != game.map.tile(x,y)) { game.map.at(x,y).dirtyRender = true; game.dirtyRender = true; game.map.markDirtyAround(x,y); } } void Game::tickDirty(bool onlyDark) { mapDoubleBuffer = map.tiles(); std::vector dirty = map.getDirty(); map.resetDirty(); for (auto [x,y] : dirty) { tickIndividual(*this, mapDoubleBuffer, x, y, onlyDark); } std::swap(map.tiles(), mapDoubleBuffer); } void Game::tickOuter(bool onlyDark) { mapDoubleBuffer = map.tiles(); map.resetDirty(); for (int y = 0; y < CHUNK_HEIGHT + 2; y++) { for (int x = map.getLeft(); x < map.getRight(); x++) { tickIndividual(*this, mapDoubleBuffer, x, map.getTop() + y, onlyDark); tickIndividual(*this, mapDoubleBuffer, x, map.getBottom() - 1 - y, onlyDark); } } for (int x = 0; x < CHUNK_WIDTH + 2; x++) { for (int y = map.getTop() + CHUNK_HEIGHT + 2; y < map.getBottom() - CHUNK_HEIGHT - 2; y++) { tickIndividual(*this, mapDoubleBuffer, map.getLeft() + x, y, onlyDark); tickIndividual(*this, mapDoubleBuffer, map.getRight() - 1 - x, y, onlyDark); } } std::swap(map.tiles(), mapDoubleBuffer); } void Game::tick( int x1, int y1, int x2, int y2, bool invert, bool onlyDark) { mapDoubleBuffer = map.tiles(); map.resetDirty(); for (int y = map.getTop(); y < map.getBottom(); y++) { for (int x = map.getLeft(); x < map.getRight(); x++) { if (invert == (x >= x1 && x < x2 && y >= y1 && y < y2)) { continue; } tickIndividual(*this, mapDoubleBuffer, x, y, onlyDark); } } std::swap(map.tiles(), mapDoubleBuffer); } void Game::tick(bool onlyDark) { tick( map.getLeft(), map.getTop(), map.getRight(), map.getBottom(), false, onlyDark); } bool Game::isInitialCaveBigEnough() const { std::set accessible; std::deque search; search.emplace_back(player_x, player_y); auto check_fn = [&](int x, int y) { coord potential = {x,y}; if (!accessible.count(potential) && !isTileSet(map, x, y)) { accessible.insert(potential); search.push_back(potential); } }; while (!search.empty()) { auto [x,y] = search.front(); search.pop_front(); check_fn(x-1, y-1); check_fn(x-1, y ); check_fn(x-1, y+1); check_fn(x , y-1); check_fn(x , y+1); check_fn(x+1, y-1); check_fn(x+1, y ); check_fn(x+1, y+1); if (accessible.size() >= 200) { return true; } } return false; } bool Game::movePlayer(int x, int y) { if (map.tile(x,y) == Tile::Floor && !map.at(x,y).sign) { if (map.tile(player_x, player_y) == Tile::Floor) { map.tile(player_x, player_y) = Tile::Dust; map.at(player_x, player_y).dustLife = 1; numDust++; } player_oldx = player_x; player_oldy = player_y; player_x = x; player_y = y; muxer.setPlayerLoc(x, y); moving = true; moveProgress.start(1000/6); dirtyLighting = true; //std::cout << player_x << "," << player_y << std::endl; int chunkX, chunkY, old_chunkX, old_chunkY; toChunkPos(player_x, player_y, chunkX, chunkY); toChunkPos(player_oldx, player_oldy, old_chunkX, old_chunkY); if ((chunkX != old_chunkX) || (chunkY != old_chunkY)) { loadMap(); } return true; } else { if (!alreadyBumped) { muxer.playSoundAtPosition("bump", x, y); alreadyBumped = true; bumpCooldown.reset(); } return false; } } void Game::recalculateLighting() { litSpots = 0; dirtyRender = true; for (int y=map.getTop(); y(data); return game.map.inBounds(x,y) && game.map.tile(x,y) == Tile::Wall; }); fov_settings_set_apply_lighting_function( &fov, [] (void* data, int x, int y, int dx, int dy, void* source) { Game& game = *static_cast(data); if (game.map.inBounds(x, y)) { MapData& sourceData = *static_cast(source); //double lightRadius = static_cast(sourceData.lightRadius); if (!game.map.at(x,y).lit) { game.litSpots++; } game.map.at(x,y).lit = true; sourceData.litTiles.emplace(x,y); } }); for (int y = map.getTop(); y < map.getBottom(); y++) { for (int x = map.getLeft(); x < map.getRight(); x++) { Source ls = Source::None; int lightRadius = 0; if (renderPlayer && player_x == x && player_y == y) { ls = Source::Player; lightRadius = RADIUS; } else if (map.tile(x,y) == Tile::Dust) { ls = Source::Dust; lightRadius = 2; } else if (map.tile(x,y) == Tile::Lamp) { ls = Source::Lamp; lightRadius = RADIUS; } map.at(x,y).lightType = ls; if (ls != Source::None) { map.at(x,y).lightRadius = lightRadius; map.at(x,y).litTiles.emplace(x,y); fov_circle( &fov, static_cast(this), static_cast(&map.at(x,y)), x, y, lightRadius); map.at(x,y).lit = true; } } } litSpots += map.getUnloadedLitTiles(); dirtyLighting = false; } void Game::recalculateRender() { bool placedSignNear = false; for (int y = map.getTop(); y < map.getBottom(); y++) { for (int x = map.getLeft(); x < map.getRight(); x++) { if (map.at(x,y).dirtyRender) { map.at(x,y).dirtyRender = false; if (map.tile(x,y) == Tile::Floor && (!map.at(x,y).lit || !map.at(x,y).wasLit)) { /*int renderDesc = 0; if (isTileSetOrNotLit(map, x-1, y-1)) renderDesc |= (1 << 7); if (isTileSetOrNotLit(map, x , y-1)) renderDesc |= (1 << 6); if (isTileSetOrNotLit(map, x+1, y-1)) renderDesc |= (1 << 5); if (isTileSetOrNotLit(map, x+1, y )) renderDesc |= (1 << 4); if (isTileSetOrNotLit(map, x+1, y+1)) renderDesc |= (1 << 3); if (isTileSetOrNotLit(map, x , y+1)) renderDesc |= (1 << 2); if (isTileSetOrNotLit(map, x-1, y+1)) renderDesc |= (1 << 1); if (isTileSetOrNotLit(map, x-1, y )) renderDesc |= (1 << 0);*/ if (/*renderDesc == 0 && */map.at(x,y).sign && !storyDone) { map.at(x,y).renderId = TilesetIndex(24, 13); } else if (std::bernoulli_distribution(0.05)(rng)) { static const std::vector furnishings { TilesetIndex(20, 16), TilesetIndex(21, 2), TilesetIndex(22, 2), TilesetIndex(21, 3), TilesetIndex(22, 3)}; double likelihood = 0.005; bool tryNear = false; if (nextSignIndex < (litSpots / 1000 + 1) && std::sqrt(std::pow(x-player_x, 2)+std::pow(y-player_y, 2)) < 30) { if (placedSignNear) { likelihood = 0.05; } else { likelihood = 0.1; tryNear = true; } } if (/*renderDesc == 0 &&*/ !storyDone && !(x == player_x && y == player_y) && map.at(x,y).text.empty() && std::bernoulli_distribution(likelihood)(rng)) { map.at(x,y).renderId = TilesetIndex(24, 13); map.at(x,y).sign = true; if (tryNear) { placedSignNear = true; } } else { map.at(x,y).renderId = furnishings.at(std::uniform_int_distribution(0, furnishings.size()-1)(rng)); map.at(x,y).sign = false; } } else { map.at(x,y).sign = false; map.at(x,y).renderId = -1; } } else if (map.tile(x,y) == Tile::Wall) { map.at(x,y).sign = false; static bool initWalls = false; static std::vector wallRenders(256, TilesetIndex(21, 12)); if (!initWalls) { initWalls = true; // Start in top left and go clockwise. wallRenders[0b00011100] = TilesetIndex(16, 14); wallRenders[0b00111100] = TilesetIndex(16, 14); wallRenders[0b00011110] = TilesetIndex(16, 14); wallRenders[0b00111110] = TilesetIndex(16, 14); wallRenders[0b00111101] = TilesetIndex(16, 14); wallRenders[0b10011101] = TilesetIndex(16, 14); wallRenders[0b01011110] = TilesetIndex(16, 14); wallRenders[0b10011100] = TilesetIndex(16, 14); wallRenders[0b00011111] = TilesetIndex(17, 14); wallRenders[0b10011111] = TilesetIndex(17, 14); wallRenders[0b00111111] = TilesetIndex(17, 14); wallRenders[0b10111111] = TilesetIndex(17, 14); wallRenders[0b01011111] = TilesetIndex(17, 14); wallRenders[0b00000111] = TilesetIndex(18, 14); wallRenders[0b00001111] = TilesetIndex(18, 14); wallRenders[0b10000111] = TilesetIndex(18, 14); wallRenders[0b10001111] = TilesetIndex(18, 14); wallRenders[0b10101111] = TilesetIndex(18, 14); wallRenders[0b10100111] = TilesetIndex(18, 14); wallRenders[0b01111100] = TilesetIndex(16, 15); wallRenders[0b11111100] = TilesetIndex(16, 15); wallRenders[0b01111110] = TilesetIndex(16, 15); wallRenders[0b11111110] = TilesetIndex(16, 15); wallRenders[0b01111101] = TilesetIndex(16, 15); wallRenders[0b11000111] = TilesetIndex(18, 15); wallRenders[0b11001111] = TilesetIndex(18, 15); wallRenders[0b11100111] = TilesetIndex(18, 15); wallRenders[0b11101111] = TilesetIndex(18, 15); wallRenders[0b11010111] = TilesetIndex(18, 15); wallRenders[0b01110000] = TilesetIndex(16, 16); wallRenders[0b01111000] = TilesetIndex(16, 16); wallRenders[0b11110000] = TilesetIndex(16, 16); wallRenders[0b11111000] = TilesetIndex(16, 16); wallRenders[0b11111010] = TilesetIndex(16, 16); wallRenders[0b11110010] = TilesetIndex(16, 16); wallRenders[0b11110001] = TilesetIndex(17, 16); wallRenders[0b11110011] = TilesetIndex(17, 16); wallRenders[0b11111001] = TilesetIndex(17, 16); wallRenders[0b11111011] = TilesetIndex(17, 16); wallRenders[0b11110101] = TilesetIndex(17, 16); wallRenders[0b11000001] = TilesetIndex(18, 16); wallRenders[0b11000011] = TilesetIndex(18, 16); wallRenders[0b11100001] = TilesetIndex(18, 16); wallRenders[0b11100011] = TilesetIndex(18, 16); wallRenders[0b11001001] = TilesetIndex(18, 16); wallRenders[0b11011001] = TilesetIndex(18, 16); wallRenders[0b11101011] = TilesetIndex(18, 16); wallRenders[0b11110111] = TilesetIndex(21, 14); wallRenders[0b11111101] = TilesetIndex(22, 14); wallRenders[0b11011111] = TilesetIndex(21, 15); wallRenders[0b01111111] = TilesetIndex(22, 15); } int renderDesc = 0; if (isTileSetOrNotLit(map, x-1, y-1)) renderDesc |= (1 << 7); if (isTileSetOrNotLit(map, x , y-1)) renderDesc |= (1 << 6); if (isTileSetOrNotLit(map, x+1, y-1)) renderDesc |= (1 << 5); if (isTileSetOrNotLit(map, x+1, y )) renderDesc |= (1 << 4); if (isTileSetOrNotLit(map, x+1, y+1)) renderDesc |= (1 << 3); if (isTileSetOrNotLit(map, x , y+1)) renderDesc |= (1 << 2); if (isTileSetOrNotLit(map, x-1, y+1)) renderDesc |= (1 << 1); if (isTileSetOrNotLit(map, x-1, y )) renderDesc |= (1 << 0); map.at(x,y).renderId = wallRenders.at(renderDesc); if (wallRenders.at(renderDesc) == TilesetIndex(21, 12) && renderDesc != 255) { //std::cout << renderDesc << std::endl; /*std::cout << ((renderDesc & (1 << 7)) ? 'X' : 'O'); std::cout << ((renderDesc & (1 << 6)) ? 'X' : 'O'); std::cout << ((renderDesc & (1 << 5)) ? 'X' : 'O'); std::cout << std::endl; std::cout << ((renderDesc & (1 << 0)) ? 'X' : 'O'); std::cout << ' '; std::cout << ((renderDesc & (1 << 4)) ? 'X' : 'O'); std::cout << " " << renderDesc << std::endl; std::cout << ((renderDesc & (1 << 1)) ? 'X' : 'O'); std::cout << ((renderDesc & (1 << 2)) ? 'X' : 'O'); std::cout << ((renderDesc & (1 << 3)) ? 'X' : 'O'); std::cout << std::endl;*/ } } else { map.at(x,y).renderId = -1; map.at(x,y).sign = false; } } } } dirtyRender = false; } bool Game::processKeys(const Input& keystate) { int px = player_x; int py = player_y; Direction dir = Direction::up; if (keystate.up) { py--; } else if (keystate.down) { py++; dir = Direction::down; } if (keystate.left) { px--; dir = Direction::left; } else if (keystate.right) { px++; dir = Direction::right; } if (!(player_x == px && player_y == py)) { playerAnim.setAnimation("walk"); playerAnim.setDirection(dir); bool succeeds = movePlayer(px, py); if (!succeeds && px != player_x) { succeeds = movePlayer(px, player_y); } if (!succeeds && py != player_y) { succeeds = movePlayer(player_x, py); } return succeeds; } else { return false; } } void Game::kickUpDust(int x, int y, size_t chain) { Kickup dk; dk.x = x; dk.y = y; dk.chain = chain; dk.cur = 0; dk.radius = RADIUS + (chain + 1) * (chain + 1); dk.front.emplace(x, y); dk.done.emplace(x, y); kickups.push_back(dk); } void Game::popLamp(int x, int y, size_t chain) { muxer.playSoundAtPosition("pop", x, y); if (map.tile(x,y) == Tile::Lamp) { numLamps--; } map.tile(x,y) = Tile::Dust; map.at(x,y).dustLife = 2; numDust++; dirtyLighting = true; kickUpDust(x, y, chain); } void Game::processKickup() { for (Kickup& kickup : kickups) { kickup.cur++; std::set newFront; for (const coord& xy : kickup.front) { auto processDir = [&] (int x, int y) { coord c {x,y}; if (map.inBounds(x,y) && (map.tile(x,y) == Tile::Floor || map.tile(x,y) == Tile::Lamp) && !kickup.done.count(c)) { newFront.insert(c); kickup.done.insert(c); if (map.tile(x,y) == Tile::Floor) { map.tile(x,y) = Tile::Dust; map.at(x,y).dustLife = 2; numDust++; dirtyLighting = true; } else if (map.tile(x,y) == Tile::Lamp) { popLamp(x, y, kickup.chain + 1); } } }; processDir(std::get<0>(xy) - 1, std::get<1>(xy) ); processDir(std::get<0>(xy) + 1, std::get<1>(xy) ); processDir(std::get<0>(xy) , std::get<1>(xy) - 1); processDir(std::get<0>(xy) , std::get<1>(xy) + 1); if (std::bernoulli_distribution(0.5)(rng)) { processDir(std::get<0>(xy) - 1, std::get<1>(xy) - 1); } if (std::bernoulli_distribution(0.5)(rng)) { processDir(std::get<0>(xy) - 1, std::get<1>(xy) + 1); } if (std::bernoulli_distribution(0.5)(rng)) { processDir(std::get<0>(xy) + 1, std::get<1>(xy) - 1); } if (std::bernoulli_distribution(0.5)(rng)) { processDir(std::get<0>(xy) + 1, std::get<1>(xy) + 1); } } kickup.front.swap(newFront); } erase_if( kickups, [] (const Kickup& kickup) { return kickup.cur == kickup.radius; }); } void Game::loadMap() { double zoomBasis = getZoomBasis(); int newChunksHoriz = std::ceil(zoomBasis * ZOOM_X_FACTOR / CHUNK_WIDTH) + 4; int newChunksVert = std::ceil(zoomBasis * ZOOM_Y_FACTOR / CHUNK_HEIGHT) + 4; int curPlayerChunkX, curPlayerChunkY; toChunkPos(player_x, player_y, curPlayerChunkX, curPlayerChunkY); map.load( curPlayerChunkX - newChunksHoriz / 2, curPlayerChunkY - newChunksVert / 2, newChunksHoriz, newChunksVert, rng); ticksNeeded = 3; dirtyLighting = true; dirtyRender = true; } void Game::setZoom(size_t zoom) { if (zoom == curZoom || zooming) { return; } zoomProgress.start(62 * std::abs(static_cast(zoom) - curZoom) * TILE_WIDTH); oldZoom = curZoom; curZoom = zoom; zooming = true; loadMap(); int zoomLevel = getZoomLevel(*this); if (zoomLevel == 0) { muxer.setMusicLevel(0); } else if (zoomLevel < 3) { muxer.setMusicLevel(1); } else if (zoomLevel < 4) { muxer.setMusicLevel(2); } else if (zoomLevel < 6) { muxer.setMusicLevel(3); } else { muxer.setMusicLevel(4); } } void Game::performDash() { if (map.tile(player_x, player_y) == Tile::Floor) { std::vector freeSpaces; auto addIfFree = [&] (int x, int y) { if (map.inBounds(x,y) && map.tile(x,y) == Tile::Floor) { freeSpaces.emplace_back(x, y); } }; addIfFree(player_x - 1, player_y - 1); addIfFree(player_x , player_y - 1); addIfFree(player_x + 1, player_y - 1); addIfFree(player_x - 1, player_y ); addIfFree(player_x + 1, player_y ); addIfFree(player_x - 1, player_y + 1); addIfFree(player_x , player_y + 1); addIfFree(player_x + 1, player_y + 1); if (!freeSpaces.empty()) { map.tile(player_x, player_y) = Tile::Lamp; numLamps++; dirtyLighting = true; kickUpDust(player_x, player_y, 0); muxer.playSoundAtPosition("drop", player_x, player_y); if (firstInput) { for (int i = 0; i < 5; i++) { if (!processKeys(lastInput)) { std::uniform_int_distribution freeDist(0, freeSpaces.size() - 1); int freeIndex = freeDist(rng); coord& moveTo = freeSpaces[freeIndex]; movePlayer(std::get<0>(moveTo), std::get<1>(moveTo)); } tick( player_x - (RADIUS - 1), player_y - (RADIUS - 1), player_x + RADIUS, player_y + RADIUS); } } else { std::uniform_int_distribution freeDist(0, freeSpaces.size() - 1); int freeIndex = freeDist(rng); coord& moveTo = freeSpaces[freeIndex]; movePlayer(std::get<0>(moveTo), std::get<1>(moveTo)); } //muxer.playSoundAtPosition("dash", player_x, player_y); } } } void Game::updatePlaying(size_t frameTime) { SDL_Event e; while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) { quit = true; } else if (e.type == SDL_KEYDOWN) { switch (e.key.keysym.sym) { case SDLK_ESCAPE: { if (losing == LoseState::NewGame) { // Do nothing. } else if (losing != LoseState::None) { if (quitting) { quit = true; } else { losing = LoseState::Done; } } else { menu.open(*this); } break; } case SDLK_SPACE: { if (losing == LoseState::None) { auto [lookX, lookY] = coordInDirection(player_x, player_y, playerAnim.getDirection()); MapData& lookTile = map.at(lookX, lookY); if (moving) { if (!lookTile.sign) { queueDash = true; } } else if (lookTile.sign) { if (lookTile.text.empty()) { int lineToRead = nextSignIndex++; if (nextSignIndex >= signTexts.size()) { storyDone = true; } lookTile.text = signTexts[lineToRead]; } sign.displayMessage(lookTile.text); clearedSigns = false; } else { performDash(); } } break; } case SDLK_m: { muxer.toggleMute(); break; } } } } const Uint8* state = SDL_GetKeyboardState(NULL); keystate.left = state[SDL_SCANCODE_LEFT] || state[SDL_SCANCODE_A]; keystate.right = state[SDL_SCANCODE_RIGHT] || state[SDL_SCANCODE_D]; keystate.up = state[SDL_SCANCODE_UP] || state[SDL_SCANCODE_W]; keystate.down = state[SDL_SCANCODE_DOWN] || state[SDL_SCANCODE_S]; bumpCooldown.accumulate(frameTime); if (alreadyBumped && keystate != lastInput && bumpCooldown.step()) { alreadyBumped = false; } if (queueDash && !moving) { queueDash = false; performDash(); } if (keystate.left || keystate.right || keystate.up || keystate.down) { firstInput = true; lastInput = keystate; } else if (losing == LoseState::None) { playerAnim.setAnimation("still"); } dustTimer.accumulate(frameTime); inputTimer.accumulate(frameTime); while (dustTimer.step()) { numDust = 0; for (int y=map.getTop(); y> lamps; for (int y = map.getTop(); y < map.getBottom(); y++) { for (int x = map.getLeft(); x < map.getRight(); x++) { if (map.tile(x,y) == Tile::Lamp) { lamps.emplace_back(x, y); } } } if (lamps.empty()) { losing = LoseState::PoppingPlayer; } else { std::uniform_int_distribution lampDist(0, lamps.size() - 1); std::tuple popPos = lamps[lampDist(rng)]; popLamp(std::get<0>(popPos), std::get<1>(popPos), 1); } } } break; } case LoseState::PoppingPlayer: { losePopPlayerTimer.accumulate(frameTime); if (losePopPlayerTimer.step()) { popLamp(player_x, player_y, 10); renderPlayer = false; losing = LoseState::Outro; } break; } case LoseState::Outro: { if (numDust == 0) { if (quitting) { quit = true; } else { losing = LoseState::Done; } } break; } case LoseState::Done: { // Do nothing. break; } } switch (helpState) { case HelpState::PreWait: { helpProgress.tick(frameTime); if (helpProgress.isComplete()) { helpState = HelpState::FadeIn; helpProgress.start(1000); } break; } case HelpState::FadeIn: { helpProgress.tick(frameTime); if (helpProgress.isComplete()) { helpState = HelpState::Hold; helpProgress.start(3000); } break; } case HelpState::Hold: { helpProgress.tick(frameTime); if (helpProgress.isComplete()) { helpState = HelpState::FadeOut; helpProgress.start(1000); } break; } case HelpState::FadeOut: { helpProgress.tick(frameTime); if (helpProgress.isComplete()) { helpState = HelpState::Done; } break; } case HelpState::Done: { // Do nothing. break; } } switch (signInstructionState) { case SignInstructionState::Hidden: { auto [lookX, lookY] = coordInDirection(player_x, player_y, playerAnim.getDirection()); if (losing == LoseState::None && map.at(lookX, lookY).sign) { signInstructionState = SignInstructionState::FadingIn; signFade.start(1000); } break; } case SignInstructionState::FadingIn: { signFade.tick(frameTime); if (signFade.isComplete()) { signInstructionState = SignInstructionState::Visible; } break; } case SignInstructionState::Visible: { auto [lookX, lookY] = coordInDirection(player_x, player_y, playerAnim.getDirection()); if (!map.at(lookX, lookY).sign || losing != LoseState::None) { signInstructionState = SignInstructionState::FadingOut; signFade.start(1000); } break; } case SignInstructionState::FadingOut: { signFade.tick(frameTime); if (signFade.isComplete()) { signInstructionState = SignInstructionState::Hidden; } break; } } if (ticksNeeded > 0) { gradualTickTimer.accumulate(frameTime); if (gradualTickTimer.step()) { ticksNeeded--; tickOuter(); } } if (dirtyLighting) { recalculateLighting(); for (int y = map.getTop(); y < map.getBottom(); y++) { for (int x = map.getLeft(); x < map.getRight(); x++) { if (!map.at(x,y).lit && map.at(x,y).wasLit) { Tile oldTile = map.tile(x,y); if (std::bernoulli_distribution(0.5)(rng)) { map.tile(x,y) = Tile::Wall; } else { map.tile(x,y) = Tile::Floor; } if (map.tile(x,y) != oldTile) { map.at(x,y).dirtyRender = true; map.markDirtyAround(x,y); } } } } tickDirty(true); tickDirty(true); tickDirty(true); // TODO: better zoom algorithm setZoom(litSpots / 1500 + INIT_ZOOM); } if (dirtyRender) { recalculateRender(); } if (zooming) { zoomProgress.tick(frameTime); if (zoomProgress.isComplete()) { zooming = false; } } playerAnim.update(frameTime); } void Game::update(size_t frameTime) { if (menu.menuState != MenuState::Closed) { menu.update(frameTime, *this); } else if (sign.signDisplayState != SignInstructionState::Hidden) { sign.update(frameTime, *this); if (!clearedSigns && sign.signDisplayState == SignInstructionState::FadingOut) { for (int y = map.getTop(); y < map.getBottom(); y++) { for (int x = map.getLeft(); x < map.getRight(); x++) { if (map.at(x,y).sign && (storyDone || map.at(x,y).text.empty())) { map.at(x,y).sign = false; map.at(x,y).renderId = -1; } } } if (storyDone) { muxer.playSound("dash"); } clearedSigns = true; } } else { updatePlaying(frameTime); } }