#include "Dungeon.hpp" #include #include #include "olcPixelGameEngine.hpp" #include "olcPGEX_AnimatedSprite.hpp" #include "Collisions.hpp" #include "Things.hpp" #include "Camera.hpp" #include "Logger.hpp" void PerlinNoise1D(int nCount, float *fSeed, int nOctaves, float fBias, float *fOutput) { for (int x = 0; x < nCount; x++) { float fNoise = 0.0f; float fScaleAcc = 0.0f; float fScale = 1.0f / fBias; for (int o = 0; o < nOctaves; o++) { int nPitch = nCount >> o; int nSample1 = (x / nPitch) * nPitch; int nSample2 = (nSample1 + nPitch) % nCount; float fBlend = (float)(x - nSample1) / (float)nPitch; float fSample = (1.0f - fBlend) * fSeed[nSample1] + fBlend * fSeed[nSample2]; fScaleAcc += fScale; fNoise += fSample * fScale; // fScale = fScale / fBias; } // Scale to seed range fOutput[x] = fNoise / fScaleAcc; } } static int perlinSize = 10000; static float* perlinSeed; static float* perlinOutput; void SetupPerlin() { perlinSeed = (float*)malloc(sizeof(float) * perlinSize); perlinOutput = (float*)malloc(sizeof(float) * perlinSize); for (int i = 0; i < perlinSize; i++) perlinSeed[i] = static_cast(rand()) / static_cast(RAND_MAX); PerlinNoise1D(perlinSize, perlinSeed, 8, 2.0f, perlinOutput); } float GetNextPerlin() { static int i = 0; i++; if (i > perlinSize) i = 0; return perlinOutput[i]; } Dungeon::Dungeon() : _Logger(Logger::getInstance()) { SetupPerlin(); ActiveCamera = new Camera(); ActiveCamera->Coords = { 0, 0 }; ActiveCamera->ViewPort = { 1280, 720 }; Player = new Playable(); Player->Coords = { 2, 2 }; Player->Type = EEntity::Type::Player; // Relative to player TL corner // not really used ? lol Player->HitBox = new Collider{ 0, 0, 28, 36 }; EnemyRenderable = new olc::Renderable(); EnemyRenderable->Load("res/player.png"); EnemyAnimator = new olc::AnimatedSprite(); EnemyAnimator->mode = olc::AnimatedSprite::SPRITE_MODE::SINGLE; EnemyAnimator->type = olc::AnimatedSprite::SPRITE_TYPE::DECAL; EnemyAnimator->spriteSheet = EnemyRenderable; EnemyAnimator->SetSpriteSize({28, 36}); EnemyAnimator->AddState("idle", 0.127f, olc::AnimatedSprite::PLAY_MODE::LOOP, std::vector{ {28, 0} }); EnemyAnimator->SetState("idle"); Player->Renderable = new olc::Renderable(); Player->Renderable->Load("res/player.png"); Player->Animator = new olc::AnimatedSprite(); Player->Animator->mode = olc::AnimatedSprite::SPRITE_MODE::SINGLE; Player->Animator->type = olc::AnimatedSprite::SPRITE_TYPE::DECAL; Player->Animator->spriteSheet = Player->Renderable; Player->Animator->SetSpriteSize({28, 36}); Player->Animator->AddState("idle", 0.127f, olc::AnimatedSprite::PLAY_MODE::LOOP, std::vector{ {28, 0} }); Player->Animator->AddState("north", 0.127f, olc::AnimatedSprite::PLAY_MODE::LOOP, std::vector{ {0, 110}, {28, 110}, {56, 110}, {84, 110} }); Player->Animator->AddState("east", 0.127f, olc::AnimatedSprite::PLAY_MODE::LOOP, std::vector{ {0, 36}, {28, 36}, {56, 36}, {84, 36} }); Player->Animator->AddState("south", 0.127f, olc::AnimatedSprite::PLAY_MODE::LOOP, std::vector{ {0, 0}, {28, 0}, {56, 0}, {84, 0} }); Player->Animator->AddState("west", 0.127f, olc::AnimatedSprite::PLAY_MODE::LOOP, std::vector{ {0, 72}, {28, 72}, {56, 72}, {84, 72} }); Player->Animator->SetState("idle"); ActiveCamera->TrackEntity(Player); ActiveCamera->Update(0.0f); TileSetDictionary = new TileDictionary(); TileSetDictionary->Register(); TileSet = new olc::Renderable(); TileSet->Load("res/dungeon_tileset.png"); _Logger.Debug("Texture Loaded: ", TileSet, " ", TileSet->Sprite()->width, " ", TileSet->Sprite()->height); FireOverlay = new olc::Renderable(); FireOverlay->Load("res/torch.png"); SoundBufferFireLighting.loadFromFile("res/fire_start.wav"); SoundFireLighting.setBuffer(SoundBufferFireLighting); SoundFireLighting.setLoop(false); SoundFireLighting.setVolume(50.f); SoundBufferFire.loadFromFile("res/fire.wav"); SoundFire.setBuffer(SoundBufferFire); SoundFire.setLoop(true); SoundFire.setVolume(100.f); SoundBufferAmbient.loadFromFile("res/ambient.ogg"); SoundAmbient.setBuffer(SoundBufferAmbient); SoundAmbient.setLoop(true); SoundAmbient.play(); SoundBufferFootsteps.loadFromFile("res/run.ogg"); SoundFootsteps.setBuffer(SoundBufferFootsteps); SoundFootsteps.setLoop(true); SoundFootsteps.setVolume(20.f); } void Dungeon::Generate() { srand(time(NULL)); DungeonWidth = 0; DungeonHeight = 0; // Generate a dungeon // Loosely follows the algorithm in section 3.3 "Agent Based Growing" // http://pcgbook.com/wp-content/uploads/chapter03.pdf struct Room { int x, y, w, h; }; auto randomRoom = [](int x, int y) { return new Room{ x, y, (rand() % 7) + 3, (rand() % 7) + 3 }; }; std::vector tiles; // Starting at 0,0 int directionChance = 5; int roomChance = 5; int dungeonMinSize = 3000; struct Agent { // 0 up 1 right 2 down 3 left int x, y, direction; }; Agent* agent = new Agent(); agent->x = 0; agent->y = 0; agent->direction = rand() % 4; _Logger.Debug("Agent ", agent->x, " ", agent->y, " ", agent->direction); auto addTile = [&](int x, int y) { for (auto i : tiles) if (i.x == x && i.y == y) return; tiles.push_back({ x, y }); }; addTile(0, 0); while (tiles.size() < dungeonMinSize) { switch (agent->direction) { case 0: agent->y -= 1; break; case 1: agent->x += 1; break; case 2: agent->y += 1; break; case 3: agent->x -= 1; break; } addTile(agent->x, agent->y); if (rand() % 200 < directionChance) { agent->direction = rand() % 4; directionChance = 0; } else { directionChance += 5; } if (rand() % 300 < roomChance) { Room* room = randomRoom(agent->x, agent->y); for (int x = room->x; x < room->w + room->x; x++) for (int y = room->y; y < room->h + room->y; y++) addTile(x, y); delete room; roomChance = 0; } else { roomChance += 5; } } for (auto& tile : tiles) { if (DungeonWidth <= tile.x) DungeonWidth = tile.x; if (DungeonHeight <= tile.y) DungeonHeight = tile.y; ETile::Type tileType = rand() % 100 > 1 ? ETile::Type::Floor : ETile::Type::FloorV1; auto DoesTileExist = [&](olc::vi2d tile) { return std::find(tiles.begin(), tiles.end(), tile) != tiles.end(); }; bool tileBelow = DoesTileExist({ tile.x, tile.y + 1 }); bool tileAbove = DoesTileExist({ tile.x, tile.y - 1 }); bool tileLeft = DoesTileExist({ tile.x - 1, tile.y }); bool tileRight = DoesTileExist({ tile.x + 1, tile.y }); // Order here is very important! stop fucking it up if (!tileAbove) tileType = ETile::Type::WallU; if (!tileRight) tileType = ETile::Type::WallR; if (!tileBelow) tileType = ETile::Type::WallD; if (!tileLeft) tileType = ETile::Type::WallL; if (!tileAbove && !tileBelow) tileType = ETile::Type::PathAcross; if (!tileRight && !tileLeft) tileType = ETile::Type::PathUp; if (!tileAbove && !tileLeft) tileType = ETile::Type::WallTLCorner; if (!tileAbove && !tileRight) tileType = ETile::Type::WallTRCorner; if (!tileBelow && !tileLeft) tileType = ETile::Type::WallBLCorner; if (!tileBelow && !tileRight) tileType = ETile::Type::WallBRCorner; if (!tileAbove && !tileBelow && !tileRight) tileType = ETile::Type::PathRight; if (!tileAbove && !tileBelow && !tileLeft) tileType = ETile::Type::PathLeft; if (!tileRight && !tileLeft && !tileAbove) tileType = ETile::Type::PathTop; if (!tileRight && !tileLeft && !tileBelow) tileType = ETile::Type::PathBottom; // Is the tile below free? if so, make it 3D if (!tileBelow) { Tile* psuedo3DTile = new Tile({ tile.x, tile.y + 1 }, ETile::Type::ThreeDStandard, ETile::State::Default); DungeonTiles[psuedo3DTile->Coords] = psuedo3DTile; } Tile* t = new Tile(tile, tileType, ETile::State::Default); DungeonTiles[t->Coords] = t; } } void Dungeon::Input(olc::PixelGameEngine* engine, float fTime) { olc::vf2d oldCoords = Player->Coords; static std::string state = "idle"; static std::string lastState = "idle"; static bool WasMoving = false; bool IsMoving = false; if (engine->GetKey(olc::W).bHeld) { Player->Coords.y -= static_cast(TileSize) * (fTime * Player->Speed); if (state != "north") state = "north"; IsMoving = true; } if (engine->GetKey(olc::A).bHeld) { Player->Coords.x -= static_cast(TileSize) * (fTime * Player->Speed); if (state != "west") state = "west"; IsMoving = true; } if (engine->GetKey(olc::S).bHeld) { Player->Coords.y += static_cast(TileSize) * (fTime * Player->Speed); if (state != "south") state = "south"; IsMoving = true; } if (engine->GetKey(olc::D).bHeld) { Player->Coords.x += static_cast(TileSize) * (fTime * Player->Speed); if (state != "east") state = "east"; IsMoving = true; } if (engine->GetKey(olc::W).bHeld && engine->GetKey(olc::A).bHeld) { Player->Coords.y += static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); Player->Coords.x += static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); if (state != "west") state = "west"; } if (engine->GetKey(olc::W).bHeld && engine->GetKey(olc::D).bHeld) { Player->Coords.y += static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); Player->Coords.x -= static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); if (state != "east") state = "east"; } if (engine->GetKey(olc::S).bHeld && engine->GetKey(olc::D).bHeld) { Player->Coords.y -= static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); Player->Coords.x -= static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); if (state != "east") state = "east"; } if (engine->GetKey(olc::S).bHeld && engine->GetKey(olc::A).bHeld) { Player->Coords.y -= static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); Player->Coords.x += static_cast(TileSize) * (fTime * (Player->Speed / 3.0f)); if (state != "west") state = "west"; } if (!WasMoving && IsMoving) { SoundFootsteps.setPlayingOffset(sf::seconds(0.05f)); SoundFootsteps.play(); } if (WasMoving && !IsMoving) { SoundFootsteps.stop(); } WasMoving = IsMoving; if (oldCoords == Player->Coords) { state = "idle"; SoundFootsteps.pause(); } if (state != lastState) Player->Animator->SetState(state); lastState = state; // Map collisions olc::vi2d currentTile = { static_cast(Player->Coords.x / TileSize), static_cast(Player->Coords.y / TileSize) }; static olc::vi2d lastTile; if (lastTile != currentTile) _Logger.Debug("Player Tile: ", currentTile.x, " ", currentTile.y); auto IsMapMember = [&] (int x, int y) { std::unordered_map::const_iterator found = DungeonTiles.find({x, y}); return found != DungeonTiles.end(); }; auto IsNeighbouringMapMember = [&] (int x, int y) { if (IsMapMember(x + 1, y)) return true; if (IsMapMember(x - 1, y)) return true; if (IsMapMember(x, y + 1)) return true; if (IsMapMember(x, y - 1)) return true; return false; }; // get nearby collidables std::vector nearbyCollidables; for (int x = currentTile.x - 1; x <= currentTile.x + 1; x++) for (int y = currentTile.y - 1; y <= currentTile.y + 1; y++) { //_Logger.Debug("IsMapMember ", x, " ", y, " ", IsMapMember(x,y)); //_Logger.Debug("IsNeighbouringMapMember ", x, " ", y, " ", IsNeighbouringMapMember(x,y)); // If isnt in the map but borders a map tile, add a void type for collision if (IsMapMember(x,y)) { nearbyCollidables.push_back(DungeonTiles[{x, y}]); } else { if (IsNeighbouringMapMember(x,y)) nearbyCollidables.push_back(new Tile({x, y}, ETile::Type::Void, ETile::State::Default)); } } // Passes ownership back CollisionInfo collisionInfo; bool colliding = EntityCollide(Player, nearbyCollidables, TileSize, &collisionInfo, engine); // collision response if (colliding) Player->Coords = oldCoords; //if (collisionInfo.CollidingY) //Player->Coords.y = oldCoords.y; //if (collisionInfo.CollidingX) //Player->Coords.x = oldCoords.x; // delete nearbyCollidables that are void for (int i = 0; i < nearbyCollidables.size(); i++) { if (nearbyCollidables[i]->Type == ETile::Type::Void) delete nearbyCollidables[i]; } lastTile = currentTile; } template float vecDistance(T v1, T v2) { return sqrt(pow(v2.x - v1.x, 2.0f) + pow(v2.y - v1.y, 2.0f) * 1.0f); } void Dungeon::Update(olc::PixelGameEngine* engine, float fTime) { ActiveCamera->Update(fTime); if (!HasBegun) return; // spawn enemies if (rand() % 100 < 1) { Enemy* enemy = new Enemy(); enemy->Type = EEntity::Type::Enemy; enemy->Renderable = EnemyRenderable; enemy->Animator = EnemyAnimator; enemy->Coords = { static_cast(((float)rand() / (float)RAND_MAX) * 1280.0f), static_cast(((float)rand() / (float)RAND_MAX) * 720.0f) }; enemy->dxdy = { static_cast(rand() % 10 - 5), static_cast(rand() % 10 - 5) }; float distanceFromPlayer = vecDistance(Player->Coords, enemy->Coords); _Logger.Debug(enemy->Coords.x, " ", enemy->Coords.y); if (distanceFromPlayer > 100) { enemy->HitBox = new Collider{ 0, 0, 28, 36 }; Enemies.push_back(enemy); } else { delete enemy; } } olc::vf2d desiredLocation = Player->Coords; for (auto enemy : Enemies) { enemy->dxdy = static_cast(TileSize) * (fTime * (Player->Speed / 1.5f) * olc::vf2d(desiredLocation - enemy->Coords).norm()); enemy->Coords += enemy->dxdy; } } olc::Pixel pixelMultiply(const int x, const int y, const olc::Pixel& pSource, const olc::Pixel& pDest) { olc::Pixel ret; ret.r = pDest.r * pSource.r; ret.g = pDest.g * pSource.g; ret.b = pDest.b * pSource.b; ret.a = pDest.a * pSource.a; return ret; } void Dungeon::Draw(olc::PixelGameEngine* engine, float fTime) { // Maps not gonna be big enough for me to care about optimistaion // maybe i should care? i don't // Entities are always (tilesize / 3) * 2 // Dungeon Layer engine->SetDrawTarget(4); engine->Clear({38, 36, 40}); engine->SetPixelMode(olc::Pixel::ALPHA); for (std::pair tile : DungeonTiles) engine->DrawPartialDecal({ static_cast((tile.first.x * TileSize) - ActiveCamera->Coords.x), static_cast((tile.first.y * TileSize) - ActiveCamera->Coords.y) }, { static_cast(TileSize), static_cast(TileSize) }, TileSet->Decal(), TileSetDictionary->Dictionary[tile.second->Type], { 16, 16 }); // Entity Layer engine->SetDrawTarget(3); engine->Clear(olc::BLANK); // Draw character Player->Animator->Draw(fTime, {Player->Coords.x - ActiveCamera->Coords.x, Player->Coords.y - ActiveCamera->Coords.y}); for (int i = 0; i < Enemies.size(); i++) { Enemies[i]->Animator->SetState("idle"); Enemies[i]->Animator->Draw(fTime, {Enemies[i]->Coords.x - ActiveCamera->Coords.x, Enemies[i]->Coords.y - ActiveCamera->Coords.y}); } // Lighting layers engine->SetDrawTarget(2); engine->Clear(olc::BLANK); static bool WasFireLit = false; static bool HasFireLit = false; static float FireAccumilator = 0.0f; if (engine->GetKey(olc::F).bPressed) IsFireLit = true; if (!IsLightOn && !HasFireLit) { engine->FillRectDecal({0.0f, 0.0f}, {static_cast(engine->ScreenWidth()), static_cast(engine->ScreenHeight())}, olc::Pixel(0,0,0)); } if (!IsFireLit) return; // Wait for fire to get going to render if (WasFireLit != IsFireLit) { SoundFireLighting.setPlayingOffset(sf::seconds(0.3f)); SoundFireLighting.play(); } WasFireLit = true; static bool LastPlayFire = false; static bool PlayFire = false; FireAccumilator += fTime; if (FireAccumilator > 1.4f) PlayFire = true; if (FireAccumilator < 1.4f) return; if (LastPlayFire != PlayFire) { SoundFire.play(); LastPlayFire = true; HasFireLit = true; } HasBegun = true; static std::function fPixelMultiply = pixelMultiply; // loads to make it more chaotic float lightScale = 1.2f + GetNextPerlin(); GetNextPerlin(); GetNextPerlin(); GetNextPerlin(); GetNextPerlin(); float lightX = static_cast(Player->Coords.x - ActiveCamera->Coords.x) - ((FireOverlay->Sprite()->width * lightScale) / 2.0f) + static_cast(Player->HitBox->w) / 2.0f; float lightY = static_cast(Player->Coords.y - ActiveCamera->Coords.y) - ((FireOverlay->Sprite()->height * lightScale) / 2.0f) + static_cast(Player->HitBox->h) / 2.0f; float lightLeft = lightX + 1.0f; float lightRight = lightX + FireOverlay->Sprite()->width * lightScale - 1.0f; float lightTop = lightY + 1.0f; float lightBottom = lightY + FireOverlay->Sprite()->height * lightScale - 1.0f; // orange tint engine->FillRectDecal({0.0f, 0.0f}, {static_cast(engine->ScreenWidth()), static_cast(engine->ScreenHeight())}, olc::Pixel(255, 123, 0, 30)); engine->SetPixelMode(fPixelMultiply); engine->DrawDecal({ lightX, lightY }, FireOverlay->Decal(), {lightScale, lightScale}, olc::RED); engine->SetPixelMode(olc::Pixel::ALPHA); // top engine->FillRectDecal({0.0f, 0.0f}, {static_cast(engine->ScreenWidth()), lightTop}, olc::BLACK); // right engine->FillRectDecal({lightRight, 0.0f}, {static_cast(engine->ScreenWidth()) - lightRight, static_cast(engine->ScreenHeight())}, olc::BLACK); // bottom engine->FillRectDecal({0.0f, lightBottom}, {static_cast(engine->ScreenWidth()), static_cast(engine->ScreenHeight()) - lightBottom}, olc::BLACK); // left engine->FillRectDecal({0.0f, 0.0f}, {lightLeft, static_cast(engine->ScreenHeight())}, olc::BLACK); // UI Layer engine->SetDrawTarget(1); engine->Clear(olc::BLANK); engine->DrawString({15, 15}, std::to_string(Enemies.size()), olc::WHITE, 5); } Dungeon::~Dungeon() { delete Player->HitBox; delete Player->Renderable; delete Player->Animator; delete Player; delete ActiveCamera; delete TileSetDictionary; delete TileSet; for (std::pair tile : DungeonTiles) delete tile.second; for (auto enemy : Enemies) { delete enemy->HitBox; delete enemy; } free(perlinSeed); free(perlinOutput); }