#include "game.h"
#include <vector>
#include <fov.h>
#include <iostream>
#include <fstream>
#include <set>
#include <deque>
#include "util.h"
#include "renderer.h"
#include "consts.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);
do {
loadMap();
tick();
tick();
tick();
for (int y = -1; y <= 1; y++)
{
for (int x = -1; x <= 1; x++)
{
map.tile(x,y) = Tile::Floor;
}
}
tick();
} while (!isInitialCaveBigEnough());
std::ifstream textFile("../res/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<Tile>& 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<coord> 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<coord> accessible;
std::deque<coord> 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<map.getBottom(); y++) {
for (int x=map.getLeft(); x<map.getRight(); x++) {
map.at(x,y).wasLit = map.at(x,y).lit;
map.at(x,y).lit = false;
map.at(x,y).litTiles.clear();
if (map.tile(x,y) == Tile::Wall) {
map.at(x,y).dirtyRender = true;
}
}
}
fov_settings_type fov;
fov_settings_init(&fov);
fov_settings_set_opacity_test_function(
&fov,
[] (void* data, int x, int y) {
Game& game = *static_cast<Game*>(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<Game*>(data);
if (game.map.inBounds(x, y))
{
MapData& sourceData = *static_cast<MapData*>(source);
double lightRadius = static_cast<double>(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;
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<void*>(this),
static_cast<void*>(&map.at(x,y)),
x,
y,
lightRadius);
map.at(x,y).lit = true;
}
}
}
litSpots += map.getUnloadedLitTiles();
dirtyLighting = false;
}
void Game::recalculateRender() {
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;
map.at(x,y).renderId = -1;
if (map.tile(x,y) == Tile::Floor) {
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) {
map.at(x,y).renderId = TilesetIndex(24, 13);
} else if (std::bernoulli_distribution(0.05)(rng)) {
static const std::vector<int> furnishings {
TilesetIndex(20, 16),
TilesetIndex(21, 2),
TilesetIndex(22, 2),
TilesetIndex(21, 3),
TilesetIndex(22, 3)};
if (/*renderDesc == 0 &&*/ !(x == player_x && y == player_y) && std::bernoulli_distribution(0.005)(rng)) {
map.at(x,y).renderId = TilesetIndex(24, 13);
map.at(x,y).sign = true;
} else {
map.at(x,y).renderId = furnishings.at(std::uniform_int_distribution<int>(0, furnishings.size()-1)(rng));
map.at(x,y).sign = false;
}
} else {
map.at(x,y).sign = false;
}
} else if (map.tile(x,y) == Tile::Wall) {
static bool initWalls = false;
static std::vector<int> 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[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[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[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[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;*/
}
}
}
}
}
dirtyRender = false;
}
bool Game::processKeys(const Input& keystate)
{
int px = player_x;
int py = player_y;
Direction dir = Direction::up;
if (keystate.up)
{
py--;
}
if (keystate.down)
{
py++;
dir = Direction::down;
}
if (keystate.left)
{
px--;
dir = Direction::left;
}
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<coord> 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<long>(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 < 5) {
muxer.setMusicLevel(2);
} else if (zoomLevel < 7) {
muxer.setMusicLevel(3);
} else {
muxer.setMusicLevel(4);
}
}
void Game::performDash() {
if (map.tile(player_x, player_y) == Tile::Floor) {
std::vector<coord> 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<int> 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<int> 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()) {
nextSignIndex = 0;
}
lookTile.text = signTexts[lineToRead];
}
sign.displayMessage(lookTile.text);
} else {
performDash();
}
}
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<map.getBottom(); y++) {
for (int x=map.getLeft(); x<map.getRight(); x++) {
if (map.tile(x,y) == Tile::Dust)
{
map.at(x,y).dustLife--;
if (map.at(x,y).dustLife <= 0)
{
map.tile(x,y) = Tile::Floor;
dirtyLighting = true;
} else {
numDust++;
}
}
}
}
processKickup();
}
switch (losing)
{
case LoseState::NewGame:
{
if (!startedMusic) {
startedMusic = true;
muxer.startMusic();
}
initialFade.tick(frameTime);
if (initialFade.isComplete()) {
losing = LoseState::None;
}
break;
}
case LoseState::None:
{
if (moving) {
moveProgress.tick(frameTime);
if (moveProgress.isComplete()) {
moving = false;
}
}
while (inputTimer.step())
{
if (!moving) {
processKeys(keystate);
}
}
break;
}
case LoseState::PoppingLamps:
{
if (numLamps == 0)
{
if (numDust == 0)
{
losing = LoseState::PoppingPlayer;
}
} else {
losePopLampTimer.accumulate(frameTime);
while (losePopLampTimer.step())
{
std::vector<std::tuple<int, int>> 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);
}
}
}
std::uniform_int_distribution<int> lampDist(0, lamps.size() - 1);
std::tuple<int, int> 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 (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();
}
/*zoomTimer.accumulate(frameTime);
while (zoomTimer.step())
{
if (zooming)
{
zoomProgress++;
if (zoomProgress == zoomLength)
{
zooming = false;
}
}
}*/
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);
} else {
updatePlaying(frameTime);
}
}