#include "Game.hpp" #include "dungeon/DungeonGen.hpp" #include "core/SoundManager.hpp" #include #include #include namespace dnr { Game::Game() = default; Game::~Game() { m_renderer.cleanup(); SoundManager::get().cleanup(); } void Game::init() { SoundManager::get().init(); m_renderer.init(); m_state = GameState::MAIN_MENU; SoundManager::get().playTrack(MusicTrack::MENU_HUB); } void Game::startNewRun() { m_dungeon.setFloor(1); m_player.reset(3, 4, Direction::NORTH); m_player.resetImplants(); m_showImplantDraft = false; m_showShrineUI = false; m_activeShrine = nullptr; m_isDefusingMine = false; m_activeMine = nullptr; m_hubRoom.init(m_player); m_state = GameState::HUB; SoundManager::get().playTrack(MusicTrack::MENU_HUB); SoundManager::get().playPickup(); } void Game::enterCyberspace() { int floor = m_dungeon.getFloor(); auto res = DungeonGen::generate(m_dungeon, floor); m_player.setGridPosition(res.startX, res.startY); m_player.setFacing(Direction::NORTH); m_dungeon.reveal(res.startX, res.startY, 4); m_state = GameState::EXPLORATION; SoundManager::get().playTrack(MusicTrack::EXPLORATION); SoundManager::get().playDoorOpen(); } void Game::enterRestRoom() { int completedFloor = m_dungeon.getFloor(); m_restRoom.init(m_player, completedFloor); m_showWorkbenchUI = false; m_state = GameState::REST_ROOM; SoundManager::get().playTrack(MusicTrack::REST_ROOM); SoundManager::get().playSkillGreat(); } void Game::nextFloor() { int nextF = m_dungeon.getFloor() + 1; m_dungeon.setFloor(nextF); enterCyberspace(); } void Game::saveCurrentProgress() { bool inHub = (m_state == GameState::HUB || m_previousState == GameState::HUB); SaveSystem::saveGame("savegame.dat", m_dungeon.getFloor(), inHub, m_player); m_saveFeedbackTimer = 2.0f; SoundManager::get().playSkillSuccess(); } bool Game::loadSavedProgress() { SaveData data; if (!SaveSystem::loadGame("savegame.dat", data)) return false; m_dungeon.setFloor(data.floor); m_player.setStats(data.hp, data.maxHp, data.shield, data.maxShield, data.picks); // Restore cylinder chambers auto& rev = m_player.getRevolver(); for (int i = 0; i < 6; ++i) { rev.setChamber(i, static_cast(data.chambers[i])); } // Restore stacked ammo counts for (size_t i = 0; i < data.ammoCounts.size(); ++i) { m_player.setAmmoCount(static_cast(i), data.ammoCounts[i]); } // Restore Core Chips & Cartridge upgrade tiers m_player.setTechScraps(data.techScraps); for (size_t i = 0; i < data.ammoTiers.size(); ++i) { m_player.setAmmoTier(static_cast(i), data.ammoTiers[i]); } // Restore Neural Implants & Defibrillator state std::vector imps; for (int val : data.implants) { imps.push_back(static_cast(val)); } m_player.setImplants(imps); m_player.setDefibrillatorUsed(data.defibrillatorUsed); m_showImplantDraft = false; m_showShrineUI = false; m_activeShrine = nullptr; m_isDefusingMine = false; m_activeMine = nullptr; if (data.inHub) { m_hubRoom.init(m_player); m_state = GameState::HUB; SoundManager::get().playTrack(MusicTrack::MENU_HUB); } else { auto res = DungeonGen::generate(m_dungeon, data.floor); m_player.reset(res.startX, res.startY, Direction::NORTH); m_dungeon.reveal(res.startX, res.startY, 4); m_state = GameState::EXPLORATION; SoundManager::get().playTrack(MusicTrack::EXPLORATION); } SoundManager::get().playPickup(); return true; } void Game::update(float dt) { m_animTime += dt; if (m_saveFeedbackTimer > 0.0f) { m_saveFeedbackTimer -= dt; } // Update screen transition wave m_transition.update(dt); if (m_transition.isBusy()) { return; } // Update dynamic music streaming engine every frame SoundManager::get().updateMusic(dt); switch (m_state) { case GameState::MAIN_MENU: updateMainMenu(dt); break; case GameState::HUB: updateHub(dt); break; case GameState::REST_ROOM: updateRestRoom(dt); break; case GameState::EXPLORATION: updateExploration(dt); break; case GameState::HACKING: updateHacking(dt); break; case GameState::COMBAT: updateCombat(dt); break; case GameState::INVENTORY: updateInventory(dt); break; case GameState::PAUSE_MENU: updatePauseMenu(dt); break; case GameState::SETTINGS: updateSettings(dt); break; case GameState::VICTORY_LEVEL: if (IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_ENTER)) { m_transition.start([this]() { enterRestRoom(); }); } break; case GameState::GAME_OVER: if (IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_ENTER)) { m_transition.start([this]() { startNewRun(); }); } break; } } void Game::updateMainMenu(float dt) { int cx = GetScreenWidth() / 2; int cy = GetScreenHeight() / 2; Vector2 mouse = GetMousePosition(); bool mouseClicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); Rectangle btnContinue = { static_cast(cx - 190), static_cast(cy - 50), 380.0f, 48.0f }; Rectangle btnNewRun = { static_cast(cx - 190), static_cast(cy + 10), 380.0f, 48.0f }; Rectangle btnSettings = { static_cast(cx - 190), static_cast(cy + 70), 380.0f, 48.0f }; Rectangle btnExit = { static_cast(cx - 190), static_cast(cy + 130), 380.0f, 48.0f }; bool hasSave = SaveSystem::hasSaveFile(); if ((IsKeyPressed(KEY_ONE) || (mouseClicked && CheckCollisionPointRec(mouse, btnContinue))) && hasSave) { m_transition.start([this]() { loadSavedProgress(); }); } else if (IsKeyPressed(KEY_TWO) || (mouseClicked && CheckCollisionPointRec(mouse, btnNewRun))) { m_transition.start([this]() { startNewRun(); }); } else if (IsKeyPressed(KEY_THREE) || (mouseClicked && CheckCollisionPointRec(mouse, btnSettings))) { m_previousState = GameState::MAIN_MENU; m_state = GameState::SETTINGS; SoundManager::get().playTumblerClick(); } else if (IsKeyPressed(KEY_FOUR) || (mouseClicked && CheckCollisionPointRec(mouse, btnExit))) { m_shouldQuit = true; } } void Game::updateHub(float dt) { m_hubRoom.update(dt, m_player); Vector2 dirVec = getDirectionOffset(m_player.getFacing()); // Check interaction context m_contextPrompt = ""; if (m_hubRoom.isFacingPortal(m_player)) { m_contextPrompt = "[SPACE / E] JACK IN // ENTER CYBERSPACE GATEWAY"; } else if (m_hubRoom.isFacingTerminal(m_player)) { m_contextPrompt = "[SPACE / E] DESK TERMINAL // SAVE RUN PROGRESS"; } if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_UP)) { tryMoveHub(static_cast(dirVec.x), static_cast(dirVec.y)); } if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) { tryMoveHub(-static_cast(dirVec.x), -static_cast(dirVec.y)); } if (IsKeyPressed(KEY_A) || IsKeyPressed(KEY_LEFT)) { m_player.turnLeft(); } if (IsKeyPressed(KEY_D) || IsKeyPressed(KEY_RIGHT)) { m_player.turnRight(); } if (IsKeyPressed(KEY_Q)) { Vector2 strafe = { dirVec.y, -dirVec.x }; tryMoveHub(static_cast(strafe.x), static_cast(strafe.y)); } if (IsKeyPressed(KEY_E)) { if (!m_contextPrompt.empty()) { tryInteractHub(); } else { Vector2 strafe = { -dirVec.y, dirVec.x }; tryMoveHub(static_cast(strafe.x), static_cast(strafe.y)); } } if (IsKeyPressed(KEY_SPACE) && !m_contextPrompt.empty()) { tryInteractHub(); } if (IsKeyPressed(KEY_TAB)) { m_previousState = GameState::HUB; m_state = GameState::INVENTORY; SoundManager::get().playTumblerClick(); } if (IsKeyPressed(KEY_ESCAPE)) { m_previousState = GameState::HUB; m_state = GameState::PAUSE_MENU; SoundManager::get().playTumblerClick(); } } void Game::tryMoveHub(int dx, int dy) { m_hubRoom.tryMove(dx, dy, m_player); } void Game::tryInteractHub() { if (m_hubRoom.isFacingPortal(m_player)) { m_transition.start([this]() { enterCyberspace(); }); } else if (m_hubRoom.isFacingTerminal(m_player)) { saveCurrentProgress(); } } void Game::updateRestRoom(float dt) { if (m_showWorkbenchUI) { int screenW = GetScreenWidth(); int screenH = GetScreenHeight(); Vector2 mouse = GetMousePosition(); bool mouseClicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); Rectangle btnClose = UIRenderer::getWorkbenchCloseButtonRect(screenW, screenH); if (IsKeyPressed(KEY_ESCAPE) || IsKeyPressed(KEY_E) || (mouseClicked && CheckCollisionPointRec(mouse, btnClose))) { m_showWorkbenchUI = false; SoundManager::get().playTumblerClick(); return; } // Check key shortcuts 1..6 or clicking card buttons AmmoType upgradeList[6] = { AmmoType::KINETIC, AmmoType::EMP, AmmoType::OVERHEAT, AmmoType::CORROSIVE, AmmoType::GLITCH, AmmoType::NANITE }; int triggeredIndex = -1; if (IsKeyPressed(KEY_ONE) || IsKeyPressed(KEY_KP_1)) triggeredIndex = 0; else if (IsKeyPressed(KEY_TWO) || IsKeyPressed(KEY_KP_2)) triggeredIndex = 1; else if (IsKeyPressed(KEY_THREE) || IsKeyPressed(KEY_KP_3)) triggeredIndex = 2; else if (IsKeyPressed(KEY_FOUR) || IsKeyPressed(KEY_KP_4)) triggeredIndex = 3; else if (IsKeyPressed(KEY_FIVE) || IsKeyPressed(KEY_KP_5)) triggeredIndex = 4; else if (IsKeyPressed(KEY_SIX) || IsKeyPressed(KEY_KP_6)) triggeredIndex = 5; if (mouseClicked) { for (int i = 0; i < 6; ++i) { Rectangle btnRect = UIRenderer::getWorkbenchUpgradeButtonRect(i, screenW, screenH); if (CheckCollisionPointRec(mouse, btnRect)) { triggeredIndex = i; break; } } } if (triggeredIndex >= 0 && triggeredIndex < 6) { AmmoType ammo = upgradeList[triggeredIndex]; if (m_player.canUpgradeAmmo(ammo)) { if (m_player.upgradeAmmoTier(ammo)) { SoundManager::get().playSkillSuccess(); } } else { SoundManager::get().playDamage(); } } return; } m_restRoom.update(dt, m_player); Vector2 dirVec = getDirectionOffset(m_player.getFacing()); // Check interaction context m_contextPrompt = ""; if (m_restRoom.isFacingGateway(m_player)) { m_contextPrompt = "[SPACE / E] DESCEND TO LAYER 0" + std::to_string(m_restRoom.getNextFloor()) + " // PALACE ARCHWAY"; } else if (m_restRoom.isFacingRechargeMatrix(m_player)) { if (!m_restRoom.isRecharged()) { m_contextPrompt = "[SPACE / E] NECTAR FOUNTAIN // DRINK (HP & SHIELD RESTORED)"; } else { m_contextPrompt = "NECTAR FOUNTAIN EMPTIED // VITALITY OPTIMAL"; } } else if (m_restRoom.isFacingTerminal(m_player)) { m_contextPrompt = "[SPACE / E] SCRIBE'S STELE // ENGRAVE PROGRESS (SAVE RUN)"; } else if (m_restRoom.isFacingWorkbench(m_player)) { m_contextPrompt = "[SPACE / E] CRAFTSMAN WORKBENCH // UPGRADE AMMO PROTOCOLS"; } if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_UP)) { tryMoveRestRoom(static_cast(dirVec.x), static_cast(dirVec.y)); } if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) { tryMoveRestRoom(-static_cast(dirVec.x), -static_cast(dirVec.y)); } if (IsKeyPressed(KEY_A) || IsKeyPressed(KEY_LEFT)) { m_player.turnLeft(); } if (IsKeyPressed(KEY_D) || IsKeyPressed(KEY_RIGHT)) { m_player.turnRight(); } if (IsKeyPressed(KEY_Q)) { Vector2 strafe = { dirVec.y, -dirVec.x }; tryMoveRestRoom(static_cast(strafe.x), static_cast(strafe.y)); } if (IsKeyPressed(KEY_E)) { if (!m_contextPrompt.empty()) { tryInteractRestRoom(); } else { Vector2 strafe = { -dirVec.y, dirVec.x }; tryMoveRestRoom(static_cast(strafe.x), static_cast(strafe.y)); } } if (IsKeyPressed(KEY_SPACE) && !m_contextPrompt.empty()) { tryInteractRestRoom(); } if (IsKeyPressed(KEY_TAB)) { m_previousState = GameState::REST_ROOM; m_state = GameState::INVENTORY; SoundManager::get().playTumblerClick(); } if (IsKeyPressed(KEY_ESCAPE)) { m_previousState = GameState::REST_ROOM; m_state = GameState::PAUSE_MENU; SoundManager::get().playTumblerClick(); } } void Game::tryMoveRestRoom(int dx, int dy) { m_restRoom.tryMove(dx, dy, m_player); } void Game::tryInteractRestRoom() { if (m_restRoom.isFacingGateway(m_player)) { m_transition.start([this]() { nextFloor(); }); } else if (m_restRoom.isFacingRechargeMatrix(m_player)) { if (!m_restRoom.isRecharged()) { m_restRoom.triggerRecharge(m_player); SoundManager::get().playPickup(); } } else if (m_restRoom.isFacingTerminal(m_player)) { saveCurrentProgress(); } else if (m_restRoom.isFacingWorkbench(m_player)) { m_showWorkbenchUI = true; SoundManager::get().playTumblerClick(); } } void Game::triggerImplantDraft() { std::vector available; for (int i = 0; i < 12; ++i) { ImplantType t = static_cast(i); if (!m_player.hasImplant(t)) { available.push_back(t); } } if (available.empty()) { m_player.addTechScraps(50); return; } for (size_t i = available.size() - 1; i > 0; --i) { size_t j = std::rand() % (i + 1); std::swap(available[i], available[j]); } m_draftChoices.clear(); for (size_t i = 0; i < available.size() && i < 3; ++i) { m_draftChoices.push_back(available[i]); } m_showImplantDraft = true; m_draftReplaceSlot = -1; SoundManager::get().playSkillGreat(); } void Game::handleImplantDraftInput() { Vector2 mouse = GetMousePosition(); bool mouseClicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); int screenW = GetScreenWidth(); int screenH = GetScreenHeight(); if (m_player.getImplants().size() >= 4) { int modalW = 960; int modalH = 550; int modalX = (screenW - modalW) / 2; int modalY = (screenH - modalH) / 2; int mountY = modalY + 332; int slotW = 212; int slotH = 68; int slotGap = 16; int startSlotX = modalX + 30; for (int s = 0; s < 4; ++s) { int slX = startSlotX + s * (slotW + slotGap); int slY = mountY + 20; Rectangle slotRect = { static_cast(slX), static_cast(slY), static_cast(slotW), static_cast(slotH) }; if (mouseClicked && CheckCollisionPointRec(mouse, slotRect)) { m_draftReplaceSlot = s; SoundManager::get().playTumblerClick(); return; } } } for (size_t i = 0; i < m_draftChoices.size() && i < 3; ++i) { Rectangle btn = UIRenderer::getDraftCardButtonRect(static_cast(i), screenW, screenH); bool keyPressed = (i == 0 && IsKeyPressed(KEY_ONE)) || (i == 1 && IsKeyPressed(KEY_TWO)) || (i == 2 && IsKeyPressed(KEY_THREE)); if (keyPressed || (mouseClicked && CheckCollisionPointRec(mouse, btn))) { ImplantType chosen = m_draftChoices[i]; if (m_player.getImplants().size() < 4) { m_player.addImplant(chosen); m_showImplantDraft = false; SoundManager::get().playPickup(); return; } else { if (m_draftReplaceSlot >= 0 && m_draftReplaceSlot < 4) { m_player.replaceImplant(m_draftReplaceSlot, chosen); m_showImplantDraft = false; m_draftReplaceSlot = -1; SoundManager::get().playPickup(); return; } else { SoundManager::get().playDamage(); } } } } Rectangle scrapBtn = UIRenderer::getDraftScrapButtonRect(screenW, screenH); if (IsKeyPressed(KEY_FOUR) || IsKeyPressed(KEY_ESCAPE) || (mouseClicked && CheckCollisionPointRec(mouse, scrapBtn))) { m_player.addTechScraps(35); m_showImplantDraft = false; m_draftReplaceSlot = -1; SoundManager::get().playTumblerClick(); } } void Game::handleShrineInput() { Vector2 mouse = GetMousePosition(); bool mouseClicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); int screenW = GetScreenWidth(); int screenH = GetScreenHeight(); Rectangle btn0 = UIRenderer::getShrineOptionButtonRect(0, screenW, screenH); if (IsKeyPressed(KEY_ONE) || (mouseClicked && CheckCollisionPointRec(mouse, btn0))) { if (m_player.getHp() > 25) { m_player.takeDamage(25); m_player.addTechScraps(60); m_player.addAmmo(AmmoType::KINETIC, 1); m_player.addAmmo(AmmoType::GLITCH, 1); if (m_activeShrine) m_activeShrine->used = true; m_showShrineUI = false; m_activeShrine = nullptr; SoundManager::get().playSkillGreat(); return; } else { SoundManager::get().playDamage(); } } Rectangle btn1 = UIRenderer::getShrineOptionButtonRect(1, screenW, screenH); if (IsKeyPressed(KEY_TWO) || (mouseClicked && CheckCollisionPointRec(mouse, btn1))) { if (m_player.getCyberPicks() >= 1) { m_player.usePick(); m_player.increaseMaxShield(15); m_player.rechargeShield(m_player.getMaxShield()); if (m_activeShrine) m_activeShrine->used = true; m_showShrineUI = false; m_activeShrine = nullptr; SoundManager::get().playSkillGreat(); return; } else { SoundManager::get().playDamage(); } } Rectangle btn2 = UIRenderer::getShrineOptionButtonRect(2, screenW, screenH); if (IsKeyPressed(KEY_THREE) || (mouseClicked && CheckCollisionPointRec(mouse, btn2))) { if (m_player.getAmmoCount(AmmoType::KINETIC) >= 2) { m_player.consumeAmmo(AmmoType::KINETIC); m_player.consumeAmmo(AmmoType::KINETIC); m_player.addAmmo(AmmoType::NANITE, 1); m_player.addAmmo(AmmoType::GLITCH, 1); if (m_activeShrine) m_activeShrine->used = true; m_showShrineUI = false; m_activeShrine = nullptr; SoundManager::get().playSkillGreat(); return; } else { SoundManager::get().playDamage(); } } Rectangle btnClose = UIRenderer::getShrineCloseButtonRect(screenW, screenH); if (IsKeyPressed(KEY_FOUR) || IsKeyPressed(KEY_ESCAPE) || (mouseClicked && CheckCollisionPointRec(mouse, btnClose))) { m_showShrineUI = false; m_activeShrine = nullptr; SoundManager::get().playTumblerClick(); } } void Game::updateExploration(float dt) { if (m_showImplantDraft) { handleImplantDraftInput(); return; } if (m_showShrineUI) { handleShrineInput(); return; } m_player.update(dt); m_dungeon.reveal(m_player.getGridX(), m_player.getGridY(), 3); // Update laser barrier timers for (auto& laser : m_dungeon.getLasers()) { if (laser.disarmed) continue; laser.timer += dt; if (laser.timer >= laser.period) { laser.timer -= laser.period; } laser.active = (laser.timer < 2.0f); } Vector2 dirVec = getDirectionOffset(m_player.getFacing()); int frontX = m_player.getGridX() + static_cast(dirVec.x); int frontY = m_player.getGridY() + static_cast(dirVec.y); m_contextPrompt = ""; if (m_dungeon.isDoorLocked(frontX, frontY)) { m_contextPrompt = "[SPACE / E] Bypass Door ICE [Picks: " + std::to_string(m_player.getCyberPicks()) + "]"; } else { LaserBarrier* frontLaser = m_dungeon.getLaserAt(frontX, frontY); ProximityMine* frontMine = m_dungeon.getMineAt(frontX, frontY); CyberShrine* frontShrine = m_dungeon.getShrineAt(frontX, frontY); if (!frontShrine) frontShrine = m_dungeon.getShrineAt(m_player.getGridX(), m_player.getGridY()); if (frontLaser && !frontLaser->disarmed) { m_contextPrompt = "[SPACE / E] Disarm Laser Barrier (Cost: 1 ICE Pick)"; } else if (frontMine && frontMine->isArmed()) { m_contextPrompt = "[SPACE / E] Defuse Proximity Mine (Quick DBD Timing Check)"; } else if (frontShrine && !frontShrine->used) { m_contextPrompt = "[SPACE / E] Access Darknet Cyber Shrine"; } else if (m_dungeon.getTile(frontX, frontY) == TileType::LOOT_CACHE || m_dungeon.getTile(m_player.getGridX(), m_player.getGridY()) == TileType::LOOT_CACHE) { m_contextPrompt = "[SPACE / E] Extract Data Cache"; } else if (m_dungeon.getTile(m_player.getGridX(), m_player.getGridY()) == TileType::EXIT_PORTAL || m_dungeon.getTile(frontX, frontY) == TileType::EXIT_PORTAL) { m_contextPrompt = "[SPACE / E] Jack Out To Safe Haven Rest Node"; } } if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_UP)) { tryMoveDungeon(static_cast(dirVec.x), static_cast(dirVec.y)); } if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) { tryMoveDungeon(-static_cast(dirVec.x), -static_cast(dirVec.y)); } if (IsKeyPressed(KEY_A) || IsKeyPressed(KEY_LEFT)) { m_player.turnLeft(); } if (IsKeyPressed(KEY_D) || IsKeyPressed(KEY_RIGHT)) { m_player.turnRight(); } if (IsKeyPressed(KEY_Q)) { Vector2 strafe = { dirVec.y, -dirVec.x }; tryMoveDungeon(static_cast(strafe.x), static_cast(strafe.y)); } if (IsKeyPressed(KEY_E)) { if (!m_contextPrompt.empty()) { tryInteractDungeon(); } else { Vector2 strafe = { -dirVec.y, dirVec.x }; tryMoveDungeon(static_cast(strafe.x), static_cast(strafe.y)); } } if (IsKeyPressed(KEY_SPACE) && !m_contextPrompt.empty()) { tryInteractDungeon(); } if (IsKeyPressed(KEY_TAB)) { m_previousState = GameState::EXPLORATION; m_state = GameState::INVENTORY; SoundManager::get().setInventoryDucked(true); SoundManager::get().playTumblerClick(); } if (IsKeyPressed(KEY_ESCAPE)) { m_previousState = GameState::EXPLORATION; m_state = GameState::PAUSE_MENU; SoundManager::get().playTumblerClick(); } // Step on enemy MapEnemy* me = m_dungeon.getEnemyAt(m_player.getGridX(), m_player.getGridY()); if (me && me->alive) { m_state = GameState::COMBAT; m_showCombatCodex = false; m_lastPlayerHp = m_player.getHp(); SoundManager::get().playTrack(MusicTrack::COMBAT); auto enemyPtr = std::make_unique(me->enemy.getType(), m_dungeon.getFloor()); if (me->enemy.isElite()) enemyPtr->setElite(true); m_combat.startCombat(m_player, std::move(enemyPtr)); m_dungeon.removeEnemyAt(me->x, me->y); } } void Game::tryMoveDungeon(int dx, int dy) { int nx = m_player.getGridX() + dx; int ny = m_player.getGridY() + dy; if (m_dungeon.isWalkable(nx, ny)) { m_player.setGridPosition(nx, ny); MapEnemy* me = m_dungeon.getEnemyAt(nx, ny); if (me && me->alive) { m_state = GameState::COMBAT; m_showCombatCodex = false; m_lastPlayerHp = m_player.getHp(); SoundManager::get().playTrack(MusicTrack::COMBAT); auto enemyPtr = std::make_unique(me->enemy.getType(), m_dungeon.getFloor()); if (me->enemy.isElite()) enemyPtr->setElite(true); m_combat.startCombat(m_player, std::move(enemyPtr)); m_dungeon.removeEnemyAt(nx, ny); return; } // Check if stepped on laser barrier LaserBarrier* lb = m_dungeon.getLaserAt(nx, ny); if (lb && !lb->disarmed && lb->active) { m_player.takeDamage(15, AmmoType::EMPTY); m_player.applyStatus(StatusEffect::GLITCHED, 2, 1); SoundManager::get().playDamage(); if (m_player.isDead()) { m_state = GameState::GAME_OVER; return; } } // Check if stepped on proximity mine ProximityMine* pm = m_dungeon.getMineAt(nx, ny); if (pm && pm->isArmed()) { pm->triggered = true; m_player.takeDamage(25, AmmoType::EMPTY); m_player.applyStatus(StatusEffect::GLITCHED, 2, 1); SoundManager::get().playDamage(); if (m_player.isDead()) { m_state = GameState::GAME_OVER; return; } } } else if (m_dungeon.isDoorLocked(nx, ny)) { m_targetDoorX = nx; m_targetDoorY = ny; } } void Game::tryInteractDungeon() { Vector2 dirVec = getDirectionOffset(m_player.getFacing()); int fx = m_player.getGridX() + static_cast(dirVec.x); int fy = m_player.getGridY() + static_cast(dirVec.y); int px = m_player.getGridX(); int py = m_player.getGridY(); if (m_dungeon.isDoorLocked(fx, fy)) { if (m_player.getCyberPicks() > 0) { m_targetDoorX = fx; m_targetDoorY = fy; DoorData* dd = m_dungeon.getDoorAt(fx, fy); int sec = dd ? dd->securityLevel : 1; bool expanded = m_player.hasImplant(ImplantType::LOGIC_OVERCLOCK); m_hacking.start(sec, expanded); m_state = GameState::HACKING; SoundManager::get().playTumblerClick(); } else { SoundManager::get().playDamage(); } return; } LaserBarrier* lb = m_dungeon.getLaserAt(fx, fy); if (lb && !lb->disarmed) { if (m_player.getCyberPicks() > 0) { m_player.usePick(); lb->disarmed = true; SoundManager::get().playDoorOpen(); } else { SoundManager::get().playDamage(); } return; } ProximityMine* pm = m_dungeon.getMineAt(fx, fy); if (pm && pm->isArmed()) { m_activeMine = pm; m_isDefusingMine = true; bool expanded = m_player.hasImplant(ImplantType::LOGIC_OVERCLOCK); m_hacking.startSkillCheckOnly(1, expanded); m_state = GameState::HACKING; SoundManager::get().playTumblerClick(); return; } CyberShrine* cs = m_dungeon.getShrineAt(fx, fy); if (!cs) cs = m_dungeon.getShrineAt(px, py); if (cs && !cs->used) { m_activeShrine = cs; m_showShrineUI = true; SoundManager::get().playSkillGreat(); return; } LootData* ld = m_dungeon.getLootAt(fx, fy); if (!ld) ld = m_dungeon.getLootAt(px, py); if (ld) { ld->looted = true; m_dungeon.setTile(ld->x, ld->y, TileType::FLOOR); for (auto a : ld->ammoRewards) { m_player.addAmmo(a, 1); } m_player.addPicks(ld->picksReward); m_player.addTechScraps(35 + (std::rand() % 25)); SoundManager::get().playPickup(); return; } if (m_dungeon.getTile(px, py) == TileType::EXIT_PORTAL || m_dungeon.getTile(fx, fy) == TileType::EXIT_PORTAL) { m_transition.start([this]() { enterRestRoom(); }); return; } } void Game::updateHacking(float dt) { if (IsKeyPressed(KEY_ESCAPE)) { m_previousState = GameState::HACKING; m_state = GameState::PAUSE_MENU; SoundManager::get().playTumblerClick(); return; } m_hacking.update(dt); m_hacking.handleInput(); if (m_hacking.isFinished() && m_hacking.getResultTimer() > 1.2f) { if (m_isDefusingMine) { m_isDefusingMine = false; if (m_hacking.isSuccess()) { if (m_activeMine) { m_activeMine->defused = true; } m_player.addTechScraps(20); m_player.addAmmo(AmmoType::KINETIC, 1); if (m_hacking.isCritical()) { m_player.addTechScraps(15); m_player.addAmmo(AmmoType::EMP, 1); } SoundManager::get().playPickup(); } else { if (m_activeMine) { m_activeMine->triggered = true; } m_player.takeDamage(25, AmmoType::EMPTY); m_player.applyStatus(StatusEffect::GLITCHED, 2, 1); SoundManager::get().playDamage(); if (m_player.isDead()) { m_activeMine = nullptr; m_state = GameState::GAME_OVER; return; } } m_activeMine = nullptr; m_state = GameState::EXPLORATION; return; } if (m_hacking.isSuccess()) { m_dungeon.unlockDoorAt(m_targetDoorX, m_targetDoorY); SoundManager::get().playDoorOpen(); if (m_hacking.isCritical()) { m_player.addAmmo(AmmoType::NANITE, 1); m_player.addPicks(1); m_player.addTechScraps(40); if (m_hacking.getSecurityLevel() >= 3) { triggerImplantDraft(); } } else { m_player.addTechScraps(15); } m_state = GameState::EXPLORATION; } else if (m_hacking.isAlarmTriggered()) { m_player.usePick(); m_state = GameState::COMBAT; m_showCombatCodex = false; m_lastPlayerHp = m_player.getHp(); SoundManager::get().playTrack(MusicTrack::COMBAT); m_combat.startCombat(m_player, std::make_unique(EnemyType::ICE_SENTINEL, m_dungeon.getFloor())); } } } void Game::updateCombat(float dt) { Vector2 mouse = GetMousePosition(); bool mouseClicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); int screenW = GetScreenWidth(); int screenH = GetScreenHeight(); int modalW = 980; int modalH = 580; int modalX = (screenW - modalW) / 2; int modalY = (screenH - modalH) / 2; Rectangle btnCloseCodex = { static_cast(modalX + modalW / 2 - 120), static_cast(modalY + modalH - 52), 240.0f, 38.0f }; int logX = 770; int pvY = 180 + 210 + 12; Rectangle btnCodexToggle = { static_cast(logX + 460 - 95), static_cast(pvY + 25), 85.0f, 40.0f }; // Toggle Codex Guide Modal if (m_showCombatCodex) { if (IsKeyPressed(KEY_H) || IsKeyPressed(KEY_ESCAPE) || (mouseClicked && CheckCollisionPointRec(mouse, btnCloseCodex))) { m_showCombatCodex = false; SoundManager::get().playTumblerClick(); } return; } if (IsKeyPressed(KEY_H) || (mouseClicked && CheckCollisionPointRec(mouse, btnCodexToggle))) { m_showCombatCodex = true; SoundManager::get().playTumblerClick(); return; } if (IsKeyPressed(KEY_ESCAPE) && m_combat.getPhase() == CombatPhase::PLAYER_TURN) { m_previousState = GameState::COMBAT; m_state = GameState::PAUSE_MENU; SoundManager::get().playTumblerClick(); return; } m_combat.update(dt, m_player); // Trigger glitch post-processing effect whenever player takes damage int curHp = m_player.getHp(); if (curHp < m_lastPlayerHp) { m_renderer.triggerGlitch(0.7f); } m_lastPlayerHp = curHp; int barX = 50; int barY = 548; Rectangle btnShoot = { static_cast(barX + 15), static_cast(barY + 12), 370.0f, 96.0f }; Rectangle btnShield = { static_cast(barX + 405), static_cast(barY + 12), 370.0f, 96.0f }; Rectangle btnTriple = { static_cast(barX + 795), static_cast(barY + 12), 370.0f, 96.0f }; if (m_combat.getPhase() == CombatPhase::PLAYER_TURN) { bool doShoot = IsKeyPressed(KEY_ONE) || IsKeyPressed(KEY_KP_1) || IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_ENTER) || (mouseClicked && CheckCollisionPointRec(mouse, btnShoot)); bool doShield = IsKeyPressed(KEY_TWO) || IsKeyPressed(KEY_KP_2) || (mouseClicked && CheckCollisionPointRec(mouse, btnShield)); bool doTriple = IsKeyPressed(KEY_THREE) || IsKeyPressed(KEY_KP_3) || (mouseClicked && CheckCollisionPointRec(mouse, btnTriple)); if (doShoot) { m_combat.actionSpinAndShoot(m_player); } else if (doShield) { m_combat.actionShield(m_player); } else if (doTriple && m_combat.getTripleCooldown() == 0) { m_combat.actionTripleShot(m_player); } } else if (m_combat.getPhase() == CombatPhase::VICTORY) { if (IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_ENTER) || mouseClicked) { bool wasElite = m_combat.wasElite(); m_player.heal(25); m_player.addAmmo(AmmoType::KINETIC, 1); m_player.addAmmo(AmmoType::EMP, 1); m_state = GameState::EXPLORATION; SoundManager::get().playTrack(MusicTrack::EXPLORATION, true); if (wasElite) { triggerImplantDraft(); } } } else if (m_combat.getPhase() == CombatPhase::DEFEAT) { if (IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_ENTER) || mouseClicked) { m_state = GameState::GAME_OVER; } } } void Game::updateInventory(float dt) { m_player.getRevolver().update(dt); m_inventoryUI.update(m_player); if (IsKeyPressed(KEY_TAB) || IsKeyPressed(KEY_ESCAPE)) { SoundManager::get().setInventoryDucked(false); m_state = m_previousState; SoundManager::get().playTumblerClick(); } } void Game::updatePauseMenu(float dt) { int menuW = 480; int menuH = 430; int menuX = (GetScreenWidth() - menuW) / 2; int menuY = (GetScreenHeight() - menuH) / 2; int startY = menuY + 90; Vector2 mouse = GetMousePosition(); bool mouseClicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); Rectangle btnResume = { static_cast(menuX + 30), static_cast(startY), static_cast(menuW - 60), 48.0f }; Rectangle btnSettings = { static_cast(menuX + 30), static_cast(startY + 58), static_cast(menuW - 60), 48.0f }; Rectangle btnSave = { static_cast(menuX + 30), static_cast(startY + 58 * 2), static_cast(menuW - 60), 48.0f }; Rectangle btnHub = { static_cast(menuX + 30), static_cast(startY + 58 * 3), static_cast(menuW - 60), 48.0f }; Rectangle btnQuit = { static_cast(menuX + 30), static_cast(startY + 58 * 4), static_cast(menuW - 60), 48.0f }; if (IsKeyPressed(KEY_ONE) || IsKeyPressed(KEY_ESCAPE) || (mouseClicked && CheckCollisionPointRec(mouse, btnResume))) { m_state = m_previousState; SoundManager::get().playTumblerClick(); } else if (IsKeyPressed(KEY_TWO) || (mouseClicked && CheckCollisionPointRec(mouse, btnSettings))) { m_previousState = GameState::PAUSE_MENU; m_state = GameState::SETTINGS; SoundManager::get().playTumblerClick(); } else if (IsKeyPressed(KEY_THREE) || (mouseClicked && CheckCollisionPointRec(mouse, btnSave))) { saveCurrentProgress(); } else if (IsKeyPressed(KEY_FOUR) || (mouseClicked && CheckCollisionPointRec(mouse, btnHub))) { m_hubRoom.init(m_player); m_state = GameState::HUB; SoundManager::get().playTrack(MusicTrack::MENU_HUB); SoundManager::get().playDoorOpen(); } else if (IsKeyPressed(KEY_FIVE) || (mouseClicked && CheckCollisionPointRec(mouse, btnQuit))) { m_shouldQuit = true; } } void Game::updateSettings(float dt) { auto& sm = SoundManager::get(); float mVol = sm.getMasterVolume(); float muVol = sm.getMusicVolume(); float sVol = sm.getSfxVolume(); // Keyboard selection & adjustment if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_UP)) { m_selectedSlider = (m_selectedSlider + 2) % 3; sm.playTumblerClick(); } if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) { m_selectedSlider = (m_selectedSlider + 1) % 3; sm.playTumblerClick(); } if (IsKeyPressed(KEY_A) || IsKeyPressed(KEY_LEFT)) { if (m_selectedSlider == 0) sm.setMasterVolume(mVol - 0.05f); else if (m_selectedSlider == 1) sm.setMusicVolume(muVol - 0.05f); else if (m_selectedSlider == 2) sm.setSfxVolume(sVol - 0.05f); sm.playTumblerClick(); } if (IsKeyPressed(KEY_D) || IsKeyPressed(KEY_RIGHT)) { if (m_selectedSlider == 0) sm.setMasterVolume(mVol + 0.05f); else if (m_selectedSlider == 1) sm.setMusicVolume(muVol + 0.05f); else if (m_selectedSlider == 2) sm.setSfxVolume(sVol + 0.05f); sm.playTumblerClick(); } // Mouse Interaction Vector2 mouse = GetMousePosition(); int menuW = 580; int menuH = 430; int menuX = (GetScreenWidth() - menuW) / 2; int menuY = (GetScreenHeight() - menuH) / 2; int startY = menuY + 85; int sliderW = 340; int sliderH = 26; int sliderX = menuX + 180; Rectangle btnBack = { static_cast(menuX + (menuW - 240) / 2), static_cast(menuY + menuH - 65), 240.0f, 44.0f }; if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { if (CheckCollisionPointRec(mouse, btnBack)) { m_state = m_previousState; sm.playTumblerClick(); return; } for (int i = 0; i < 3; ++i) { int itemY = startY + i * 78; Rectangle sBar = { static_cast(sliderX - 10), static_cast(itemY - 6), static_cast(sliderW + 20), static_cast(sliderH + 12) }; if (CheckCollisionPointRec(mouse, sBar)) { m_selectedSlider = i; m_isDraggingSlider = true; float ratio = std::clamp((mouse.x - sliderX) / static_cast(sliderW), 0.0f, 1.0f); if (i == 0) sm.setMasterVolume(ratio); else if (i == 1) sm.setMusicVolume(ratio); else if (i == 2) sm.setSfxVolume(ratio); sm.playTumblerClick(); break; } } } if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && m_isDraggingSlider) { float ratio = std::clamp((mouse.x - sliderX) / static_cast(sliderW), 0.0f, 1.0f); if (m_selectedSlider == 0) sm.setMasterVolume(ratio); else if (m_selectedSlider == 1) sm.setMusicVolume(ratio); else if (m_selectedSlider == 2) sm.setSfxVolume(ratio); } if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { m_isDraggingSlider = false; } if (IsKeyPressed(KEY_ESCAPE) || IsKeyPressed(KEY_ENTER)) { m_state = m_previousState; sm.playTumblerClick(); } } void Game::draw() { BeginDrawing(); ClearBackground(Color{ 4, 6, 12, 255 }); switch (m_state) { case GameState::MAIN_MENU: m_uiRenderer.drawMainMenu(SaveSystem::hasSaveFile()); break; case GameState::HUB: m_renderer.renderHub(m_hubRoom, m_player, m_animTime); m_uiRenderer.drawHubHUD(m_player, m_contextPrompt); break; case GameState::REST_ROOM: m_renderer.renderRestRoom(m_restRoom, m_player, m_animTime); m_uiRenderer.drawRestRoomHUD(m_restRoom.getNextFloor(), m_player, m_showWorkbenchUI ? "" : m_contextPrompt); if (m_showWorkbenchUI) { m_uiRenderer.drawWorkbenchModal(m_player); } m_uiRenderer.drawMatrixOverlay(m_animTime); break; case GameState::EXPLORATION: m_renderer.render3D(m_dungeon, m_player, m_animTime); m_uiRenderer.drawExplorationHUD(m_dungeon, m_player, m_contextPrompt); if (m_showImplantDraft) { m_uiRenderer.drawImplantDraftModal(m_draftChoices, m_player, m_draftReplaceSlot); } else if (m_showShrineUI) { m_uiRenderer.drawShrineModal(m_player); } m_uiRenderer.drawMatrixOverlay(m_animTime); break; case GameState::HACKING: m_renderer.render3D(m_dungeon, m_player, m_animTime); m_uiRenderer.drawHackingUI(m_hacking); m_uiRenderer.drawMatrixOverlay(m_animTime); break; case GameState::COMBAT: m_renderer.renderCombat3D(m_dungeon, m_player, m_combat, m_animTime); m_uiRenderer.drawCombatUI(m_combat, m_player, m_animTime, m_showCombatCodex); m_uiRenderer.drawMatrixOverlay(m_animTime); break; case GameState::INVENTORY: if (m_previousState == GameState::HUB) { m_renderer.renderHub(m_hubRoom, m_player, m_animTime); } else if (m_previousState == GameState::REST_ROOM) { m_renderer.renderRestRoom(m_restRoom, m_player, m_animTime); } else { m_renderer.render3D(m_dungeon, m_player, m_animTime); } m_inventoryUI.draw(m_player); break; case GameState::PAUSE_MENU: if (m_previousState == GameState::HUB) { m_renderer.renderHub(m_hubRoom, m_player, m_animTime); m_uiRenderer.drawHubHUD(m_player, m_contextPrompt); } else if (m_previousState == GameState::REST_ROOM) { m_renderer.renderRestRoom(m_restRoom, m_player, m_animTime); m_uiRenderer.drawRestRoomHUD(m_restRoom.getNextFloor(), m_player, m_contextPrompt); } else { m_renderer.render3D(m_dungeon, m_player, m_animTime); m_uiRenderer.drawExplorationHUD(m_dungeon, m_player, m_contextPrompt); } m_uiRenderer.drawPauseMenu(m_saveFeedbackTimer > 0.0f); break; case GameState::SETTINGS: if (m_previousState == GameState::MAIN_MENU) { m_uiRenderer.drawMainMenu(SaveSystem::hasSaveFile()); } else if (m_previousState == GameState::HUB) { m_renderer.renderHub(m_hubRoom, m_player, m_animTime); m_uiRenderer.drawHubHUD(m_player, m_contextPrompt); } else if (m_previousState == GameState::REST_ROOM) { m_renderer.renderRestRoom(m_restRoom, m_player, m_animTime); m_uiRenderer.drawRestRoomHUD(m_restRoom.getNextFloor(), m_player, m_contextPrompt); } else { m_renderer.render3D(m_dungeon, m_player, m_animTime); m_uiRenderer.drawExplorationHUD(m_dungeon, m_player, m_contextPrompt); } m_uiRenderer.drawSettingsMenu(SoundManager::get().getMasterVolume(), SoundManager::get().getMusicVolume(), SoundManager::get().getSfxVolume(), m_selectedSlider); break; case GameState::VICTORY_LEVEL: m_uiRenderer.drawLevelClearScreen(m_dungeon.getFloor()); break; case GameState::GAME_OVER: m_uiRenderer.drawGameOverScreen(); break; } // Render screen transition wave on top of all 2D/3D elements m_transition.draw(); EndDrawing(); } } // namespace dnr