#include "theArchanist.h" #include <random> #include "gui/gameOver.h" #include "gui/hud.h" #include "gui/pause.h" // Static variables std::shared_ptr<State> theArchanist::m_thisState{nullptr}; theArchanistSounds theArchanist::m_sounds{}; theArchanistLights theArchanist::m_light{}; gameMap theArchanist::m_map{}; GameWorld theArchanist::m_WorldController{}; GameInfo theArchanist::m_gameInfo{}; Player theArchanist::m_player{}; std::list<std::pair<Object, glm::vec3>> theArchanist::m_collisionObjects{}; std::shared_ptr<State> theArchanist::init(Resources& engineResources, int seed, int levels, int difficulty) { m_thisState = std::make_shared<State>(draw, handleEvents, destroy, pause, update, engineResources); setCamera(); pause(false); m_thisState->audioManager.playSound(m_sounds.music); m_gameInfo.seed = seed; if (levels > 0) { m_gameInfo.lastLevel = levels; } m_gameInfo.difficulty = (Difficulty)difficulty; setDifficultySettings(); newLevel(10); m_thisState->skybox.setModel( m_thisState->resources.modelManager.getModel("as2/spaceSkybox.obj")); return m_thisState; } void theArchanist::destroy() { pause(true); m_WorldController = GameWorld{}; m_thisState = nullptr; m_gameInfo = GameInfo{}; m_collisionObjects.clear(); m_player = Player{}; } void theArchanist::pause(bool pause) { if (pause) { Light::disableLighting(); m_thisState->audioManager.pauseSounds(true); ARCH_GUI::resetPauseState(); m_gameInfo.paused = false; } else { glClearColor(0.1f, 0.1f, 0.1f, 1.f); Light::enableLighting(); m_thisState->lights[m_light.L1].setPosition(0, 10, 0); m_thisState->lights[m_light.L2].setPosition(10, 10, 10); m_thisState->lights[m_light.L1].enable(); m_thisState->lights[m_light.L2].enable(); m_thisState->camera.sensitivity = m_thisState->resources.settings .mouseSens; m_thisState->audioManager.setMasterVolume( m_thisState->resources.settings.volume); m_thisState->audioManager.setMute(m_thisState->resources.settings.mute); // Set camera perspective m_thisState->camera .setPerspective(45.f, (float)m_thisState->resources.settings.width / (float)m_thisState->resources.settings.height, 0.1f, 10000.f); m_thisState->audioManager.pauseSounds(false); } } // testing globals bool drawRoomDebug{false}; bool drawAIDebug{false}; float testMana{100}; void theArchanist::handleEvents(SDL_Event& event) { if (m_gameInfo.paused) { m_thisState->camera.lockMouse(false); if (ARCH_GUI::pauseState.resume) { ARCH_GUI::resetPauseState(); m_thisState->camera.lockMouse(true); m_gameInfo.paused = false; } if (ARCH_GUI::pauseState.exit) { ARCH_GUI::resetPauseState(); m_thisState->stop = true; } while (SDL_PollEvent(&event)) { if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) { ARCH_GUI::resetPauseState(); m_gameInfo.paused = !m_gameInfo.paused; break; } ImGui_ImplSDL2_ProcessEvent(&event); } } else { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: exit(0); case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_SPACE) { m_thisState->camera.lockMouse( !m_thisState->camera.isLocked()); } else if (event.key.keysym.sym == SDLK_ESCAPE) { m_gameInfo.paused = true; break; } else if (event.key.keysym.sym == SDLK_e) { std::cout << m_thisState->camera.position.x << ' ' << m_thisState->camera.position.y << ' ' << m_thisState->camera.position.z << '\n'; } else if (event.key.keysym.sym == SDLK_q) { m_map.m_debug = !m_map.m_debug; drawRoomDebug = !drawRoomDebug; drawAIDebug = !drawAIDebug; } else if (event.key.keysym.sym == SDLK_n) { AITest::spawnDefaultBot(); } break; case SDL_MOUSEWHEEL: { if (event.wheel.y < 0 && event.wheel.y > -3) { m_player.setZoom(m_player.getZoom() + 0.25f * (float)-event.wheel.y); } else if (event.wheel.y > 0 && event.wheel.y < 3) { m_player.setZoom(m_player.getZoom() - 0.25f * (float)event.wheel.y); } break; } case SDL_MOUSEBUTTONDOWN: { switch (event.button.button) { case SDL_BUTTON_RIGHT: { if (testMana > 20) { testMana -= 20; AITest::shootTarget(clickWorldCoordinates()); // play shot sound std::string name{"Player"}; auto obj = m_thisState->objects.find(name); m_sounds.magicShot.position = obj->second .location; m_thisState->audioManager.playSound( m_sounds.magicShot); break; } } default: break; } } default: break; } } // For input that needs very fast updates const Uint8* keyStates = SDL_GetKeyboardState(nullptr); if (keyStates[SDL_SCANCODE_W]) { m_player.moveUp(); } if (keyStates[SDL_SCANCODE_A]) { m_player.moveLeft(); } if (keyStates[SDL_SCANCODE_S]) { m_player.moveDown(); } if (keyStates[SDL_SCANCODE_D]) { m_player.moveRight(); } if (keyStates[SDL_SCANCODE_LSHIFT]) { m_player.setMoveSpeed(5.f); } else { m_player.setMoveSpeed(2.5f); } } } void theArchanist::update(float deltaTime) { if (isGameOver()) { if (ARCH_GAME_OVER::mainMenu) { m_thisState->stop = true; } return; } if (!m_gameInfo.paused) { testMana += 12.f * deltaTime; if (testMana > 100) { testMana = 100; } m_gameInfo.time += deltaTime; trapDoorUpdate(); auto playerObj = m_thisState->objects.find("Player")->second; Camera_ temp = m_thisState->camera; m_player.update(playerObj, temp, deltaTime); glm::vec2 newPos{playerObj.location.x, playerObj.location.z}; if (m_WorldController.playerMoveCheck(newPos)) { m_WorldController.m_Vehicles.front()->setPosition( {playerObj.location.x, playerObj.location.z}); m_thisState->objects.find("Player")->second = playerObj; m_thisState->camera = temp; updatePlayerObject(deltaTime); } m_thisState->audioManager.setListener(m_thisState->camera.position, m_thisState->camera.direction); m_WorldController.Update(deltaTime); updateEnemyObjects(deltaTime); updateProjectileObjects(); updateCollisionParticles(deltaTime); } if (ARCH_GUI::click.occurred) { m_thisState->audioManager.playSound(m_sounds.click); ARCH_GUI::click.occurred = false; } } void theArchanist::draw() { Light::disableLighting(); m_thisState->skybox.draw(m_thisState->camera.direction, m_thisState->camera.up); Light::enableLighting(); m_thisState->lights[m_light.L2].enable(); m_thisState->lights[m_light.L1].enable(); float drawDistance = 23 + m_player.getZoom(); m_map.draw(m_thisState->camera.position, drawDistance); float camOffset{m_thisState->camera.position.z + 10}; auto camPos{m_thisState->camera.position}; for (auto& particle : m_collisionObjects) { if (camPos.z < camOffset && glm::distance(camPos, particle.first.location) < drawDistance) { particle.first.draw(); } } if (!drawAIDebug) { for (auto& obj : m_thisState->objects) { if (!isTrapDoor(obj.first) && camPos.z < camOffset && glm::distance(camPos, obj.second.location) < drawDistance) { obj.second.draw(); } } drawTrapDoor(); } else { AITest::drawDebug(); } m_thisState->lights[m_light.L2].draw(); m_thisState->lights[m_light.L1].draw(); if (isGameOver()) { ARCH_GAME_OVER::drawGameOver(m_gameInfo.time, m_gameInfo.currentLevel, m_gameInfo.lastLevel, (int)m_gameInfo.difficulty, m_gameInfo.enemiesKilled, m_gameInfo.victory, m_gameInfo.seed); } else if (m_gameInfo.paused) { ARCH_GUI::pauseMenu(m_thisState->resources); } else { // Example usage ARCH_HUD::drawHud(m_gameInfo.time, (float)m_WorldController.m_Vehicles.front() ->getHealth() / (float)m_WorldController.m_Vehicles.front() ->getMaxHealth(), testMana / 100, m_gameInfo.currentLevel, m_gameInfo.lastLevel, m_gameInfo.difficulty, (int)m_WorldController.m_Vehicles.size() - 1); if (m_gameInfo.allEnemiesDead) { ARCH_HUD::drawTrapdoorMsg(); } } } void theArchanist::setCamera() { // Set camera perspective m_thisState->camera .setPerspective(45.f, (float)m_thisState->resources.settings.width / (float)m_thisState->resources.settings.height, 0.1f, 10000.f); m_thisState->camera.pitch += 45; // Aligned at centre for new top down camera m_thisState->camera.position = glm::vec3{28, 5, 33}; m_thisState->camera.lockMouse(false); } glm::vec3 theArchanist::clickWorldCoordinates() { int width, height, mouseX, mouseY; glm::vec3 addPos(0.0f); SDL_GetMouseState(&mouseX, &mouseY); SDL_GetWindowSize(SDL_GL_GetCurrentWindow(), &width, &height); float x = (2.0f * (float)mouseX) / (float)width - 1.0f; float y = 1.0f - (2.0f * (float)mouseY) / (float)height; float z = 1.0f; glm::vec3 ray_nds = glm::vec3(x, y, z); glm::vec4 ray_clip = glm::vec4(ray_nds.x, ray_nds.y, -1.0, 1.0); glm::vec4 ray_eye = glm::inverse(m_thisState->camera.perspectiveMatrix) * ray_clip; ray_eye = glm::vec4(ray_eye.x, ray_eye.y, -1.0, 0.0); glm::vec4 ray_wor = (glm::inverse(m_thisState->camera.viewMatrix) * ray_eye); glm::vec3 ray(ray_wor.x, ray_wor.y, ray_wor.z); ray = glm::normalize(ray); float distance; if (glm::intersectRayPlane(m_thisState->camera.position, ray, glm::vec3(0.f), glm::vec3(0.f, 1.0f, 0.f), distance)) { glm::vec3 point(ray.x, ray.y, ray.z); point *= glm::vec3(distance); addPos = m_thisState->camera.position + point; } return addPos; } void theArchanist::createPlayerObject() { auto& player = m_WorldController.m_Vehicles.front(); std::string name{"Player"}; glm::vec3 location{player->getPosition().x, 0.7f, player->getPosition().y}; glm::vec3 scale{0.5f}, rotation{0.f}; std::shared_ptr<Model> model = m_thisState->resources.modelManager.getModel( "as2/robe.obj"); Object object{name, model, location, rotation, scale}; m_thisState->objects.emplace(name, object); createShadow(player->getID(), 0.25f); } // temp animation bool playerUp{true}; void theArchanist::updatePlayerObject(float deltaTime) { auto& player = m_WorldController.m_Vehicles.front(); auto obj = m_thisState->objects.find("Player"); glm::vec3 location{player->getPosition().x, 0.7f, player->getPosition().y}; if (obj != m_thisState->objects.end()) { if (obj->second.location.y > 0.8f) { playerUp = false; } else if (obj->second.location.y < 0.6f) { playerUp = true; } location.y = (playerUp) ? obj->second.location.y + 0.2f * deltaTime : obj->second.location.y - 0.2f * deltaTime; obj->second.location = location; updateShadow(player->getID(), location); } } void theArchanist::createShadow(int id, float scale) { std::string name = "Shadow_" + std::to_string(id); auto model = m_thisState->resources.modelManager.getModel("as2/shadow.obj"); glm::vec3 location{0}, rotation{0}, shadowScale{scale}; Object shadow{name, model, location, rotation, shadowScale}; m_thisState->objects.emplace(name, shadow); } void theArchanist::updateShadow(int id, const glm::vec3& location) { auto shadow = m_thisState->objects.find("Shadow_" + std::to_string(id)); if (shadow != m_thisState->objects.end()) { shadow->second.location = location; shadow->second.location.y = 0.08f; } } std::shared_ptr<Model> theArchanist::getRandomEnemyModel() { std::string modelName[] = {"as2/Bat.obj", "as2/Dragon.obj", "as2/Wasp.obj"}; std::random_device rd{}; std::mt19937 gen{rd()}; std::uniform_int_distribution<> index{0, 2}; return m_thisState->resources.modelManager.getModel(modelName[index(gen)]); } // temp animation vector std::vector<bool> isUp; void theArchanist::createEnemyObject(int id, const glm::vec3& location, const glm::vec3& rotation, const glm::vec3& scale) { auto model = getRandomEnemyModel(); std::string name{"Enemy_" + std::to_string(id)}; Object object{name, model, location, rotation, scale}; m_thisState->objects.emplace(name, object); createShadow(id, 0.25f); // Animation bool isUp.push_back(true); } void theArchanist::updateEnemyObjects(float deltaTime) { bool isPlayer{true}; std::size_t count{0}; for (auto& enemy : m_WorldController.m_Vehicles) { if (isPlayer) { isPlayer = false; continue; } glm::vec3 location{enemy->getPosition().x, 1, enemy->getPosition().y}; float angle = getHeadingAngle(enemy->getHeadingVec()); auto obj = m_thisState->objects.find("Enemy_" + std::to_string(enemy->getID())); if (obj != m_thisState->objects.end()) { if (obj->second.location.y > 1.15f) { isUp[count] = false; } else if (obj->second.location.y < 0.8f) { isUp[count] = true; } location.y = (isUp[count]) ? obj->second.location.y + 0.6f * deltaTime : obj->second.location.y - 0.6f * deltaTime; obj->second.location = location; obj->second.rotation.y = angle; updateShadow(enemy->getID(), location); ++count; } else { createEnemyObject(enemy->getID(), location, glm::vec3{0, angle, 0}, glm::vec3{0.2f}); } } updateDeadEnemyObjects(); } void theArchanist::updateDeadEnemyObjects() { for (auto& dead : m_WorldController.m_deadVehicles) { auto obj = m_thisState->objects.find("Enemy_" + std::to_string(dead)); obj->second.rotation.x = 90; obj->second.rotation.y = 0; obj->second.location.y = 0; m_sounds.batDeath.position = obj->second.location; m_thisState->audioManager.playSound(m_sounds.batDeath); m_thisState->objects.erase("Shadow_" + std::to_string(dead)); ++m_gameInfo.enemiesKilled; } m_WorldController.m_deadVehicles.clear(); } float theArchanist::getHeadingAngle(const glm::vec2& heading) { float dot = glm::dot(glm::vec2{0, -1}, heading); float det = heading.x; return -glm::degrees(std::atan2(det, dot)); } void theArchanist::createProjectileObj(int id, const glm::vec3& location, const glm::vec3& rotation, const glm::vec3& scale) { std::shared_ptr<Model> model = m_thisState->resources.modelManager.getModel( "as2/" "debugSphere.obj"); std::string name{"Projectile_" + std::to_string(id)}; Object object{name, model, location, rotation, scale}; m_thisState->objects.emplace(name, object); createShadow(id, 0.15f); m_sounds.magicShot.position = location; m_thisState->audioManager.playSound(m_sounds.magicShot); } void theArchanist::updateProjectileObjects() { if (!m_WorldController.m_Projectiles.empty()) { for (auto& projectile : m_WorldController.m_Projectiles) { float angle = getHeadingAngle(projectile->getHeadingVec()); glm::vec3 location{projectile->getPosition().x, 0.75f, projectile->getPosition().y}; auto obj = m_thisState->objects.find( "Projectile_" + std::to_string(projectile->getID())); if (obj != m_thisState->objects.end()) { obj->second.location = location; obj->second.rotation.y = angle; updateShadow(projectile->getID(), location); } else { createProjectileObj(projectile->getID(), location, glm::vec3{0, angle, 0}, glm::vec3{0.1f}); } } } destroyProjectileObjs(); } void theArchanist::destroyProjectileObjs() { // remove dead projectiles float speed{0.5f}; int amount{10}; for (auto& deadProj : m_WorldController.m_deadProjectiles) { auto projectile = m_thisState->objects .find("Projectile_" + std::to_string(deadProj.first)) ->second; createCollisionParticles(deadProj.first, projectile.location, projectile.scale, deadProj.second, projectile.model, amount, speed); m_sounds.explosion.position = projectile.location; m_thisState->audioManager.playSound(m_sounds.explosion); m_thisState->objects.erase("Projectile_" + std::to_string(deadProj.first)); m_thisState->objects.erase("Shadow_" + std::to_string(deadProj.first)); } m_WorldController.m_deadProjectiles.clear(); } void theArchanist::drawTrapDoor() { if (m_WorldController.m_Vehicles.size() != 1) { m_thisState->objects.find("Trapdoor")->second.draw(); } else { m_thisState->objects.find("Trapdoor_open")->second.draw(); m_thisState->objects.find("Trapdoor_portal")->second.draw(); } } void theArchanist::loadTrapDoorObj() { std::shared_ptr<Model> model = m_thisState->resources.modelManager.getModel( "as2/Trapdoor.obj"); glm::vec3 rotation{0, 0, 0}; glm::vec3 scale{0.75f}; glm::vec3 location{28.5f, 0.1f, 28.5f}; std::string name{"Trapdoor"}; Object object{name, model, location, rotation, scale}; m_thisState->objects.emplace(name, object); model = m_thisState->resources.modelManager.getModel( "as2/Trapdoor_open.obj"); name = "Trapdoor_open"; object = Object{name, model, location, rotation, scale}; m_thisState->objects.emplace(name, object); model = m_thisState->resources.modelManager.getModel( "as2/Trapdoor_portal.obj"); name = "Trapdoor_portal"; object = Object{name, model, location, rotation, scale}; m_thisState->objects.emplace(name, object); } bool theArchanist::isTrapDoor(const std::string& name) { return name == "Trapdoor" || name == "Trapdoor_portal" || name == "Trapdoor_open"; } void theArchanist::trapDoorUpdate() { if (m_WorldController.m_Vehicles.size() == 1) { m_gameInfo.allEnemiesDead = true; auto& playerObj = m_thisState->objects.find("Player")->second; if (playerObj.location.x < 29 && playerObj.location.x > 28 && playerObj.location.z < 29 && playerObj.location.z > 28) { m_gameInfo.playerHealth -= 50; m_gameInfo.spawnRate += 0.01f; newLevel(10); } } } void theArchanist::newLevel(int rooms) { std::string modelsFile{"../res/preload/roomModels.txt"}; m_map = gameMap(rooms, m_thisState->resources, modelsFile, m_gameInfo.seed + m_gameInfo.currentLevel * 113); ++m_gameInfo.currentLevel; m_gameInfo.allEnemiesDead = false; m_WorldController = GameWorld{}; m_thisState->objects.clear(); m_WorldController.init(m_map); AITest::Init(&m_WorldController); AITest::loadPlayer(m_gameInfo.playerHealth); AITest::loadBots(m_gameInfo.spawnRate, m_gameInfo.seed); createPlayerObject(); loadTrapDoorObj(); } void theArchanist::setDifficultySettings() { switch (m_gameInfo.difficulty) { case easy: m_gameInfo.spawnRate = 0.01f; m_gameInfo.playerHealth = 1000; break; case normal: m_gameInfo.spawnRate = 0.018f; m_gameInfo.playerHealth = 750; break; case hard: m_gameInfo.spawnRate = 0.025f; m_gameInfo.playerHealth = 500; break; } } bool theArchanist::isGameOver() { // TODO check boss is dead for victory, temp is level trans if (m_WorldController.m_Vehicles.front()->isDead()) { m_gameInfo.victory = false; return true; } else if (m_gameInfo.currentLevel > m_gameInfo.lastLevel) { m_gameInfo.victory = true; return true; } return false; } void theArchanist::createCollisionParticles(int id, const glm::vec3& location, const glm::vec3& scale, const glm::vec2& heading, std::shared_ptr<Model>& model, int amount, float speed) { std::random_device rd{}; std::mt19937 gen{rd()}; std::uniform_int_distribution<> pos{-3, 7}; std::uniform_int_distribution<> neg{-7, 3}; std::uniform_int_distribution<> yDist{-7, 4}; for (int i = 0; i < amount; ++i) { int newId = id - 5000 - i; std::string name{"Particle_" + std::to_string(newId)}; glm::vec3 velocity{heading.x, 1, heading.y}; velocity.x += (heading.x > 0) ? (float)pos(gen) : (float)neg(gen); velocity.y += (float)yDist(gen); velocity.z += (heading.y > 0) ? (float)pos(gen) : (float)neg(gen); glm::normalize(velocity); velocity *= speed; Object particle{name, model, location, glm::vec3{0, 0, 0}, scale / 4.f}; m_collisionObjects.emplace_back(particle, velocity); createShadow(newId, 0.05f); } } void theArchanist::updateCollisionParticles(float deltaTime) { float gravity = deltaTime * 9.8f; glm::vec3 resistance{0.95f, 0, 0.95f}; auto particleItr = m_collisionObjects.begin(); while (particleItr != m_collisionObjects.end()) { auto& particle = *particleItr; if (particle.first.location.y < 0.05f || !m_WorldController.currentLevel.isInBoundsTight( {particle.first.location.x, particle.first.location.z})) { m_thisState->objects.erase( "Shadow_" + particle.first.name.substr(9, particle.first.name.size() - 1)); m_collisionObjects.erase(particleItr++); } else { particle.first.location += deltaTime * particle.second; particle.second.y -= gravity; particle.second -= particle.second * resistance * deltaTime; updateShadow(std::stoi( particle.first.name .substr(9, particle.first.name.size() - 1)), particle.first.location); ++particleItr; } } }