- Replace procedural city with Room_window.jpg panoramic backdrop - Add volumetric violet godrays, floor ambient pools, and cyan monitor glow - Fix bed occlusion bug: reorder renderHub pipeline with rlDrawRenderBatchActive() - Darken hub materials (smoked walnut parquet, slate plaster, midnight quilt) - Enable cyber-noir postprocessing shader with vignette in hub room - Implement diagonal matrix pixel wave screen transitions (ScreenTransition.hpp) - Add tactical ammo palette (KIN, EMP, THM, ACD, GLT, NAN) & 3D combat laser tracers - Center [ 12:00 FIRING PIN ] indicator and add drawWrappedText word-wrapping in UI - Maintain strict zero-rounding constraint across the entire codebase
1189 lines
44 KiB
C++
1189 lines
44 KiB
C++
#include "Game.hpp"
|
|
#include "dungeon/DungeonGen.hpp"
|
|
#include "core/SoundManager.hpp"
|
|
#include <cstdlib>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
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<AmmoType>(data.chambers[i]));
|
|
}
|
|
|
|
// Restore stacked ammo counts
|
|
for (size_t i = 0; i < data.ammoCounts.size(); ++i) {
|
|
m_player.setAmmoCount(static_cast<AmmoType>(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<AmmoType>(i), data.ammoTiers[i]);
|
|
}
|
|
|
|
// Restore Neural Implants & Defibrillator state
|
|
std::vector<ImplantType> imps;
|
|
for (int val : data.implants) {
|
|
imps.push_back(static_cast<ImplantType>(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<float>(cx - 190), static_cast<float>(cy - 50), 380.0f, 48.0f };
|
|
Rectangle btnNewRun = { static_cast<float>(cx - 190), static_cast<float>(cy + 10), 380.0f, 48.0f };
|
|
Rectangle btnSettings = { static_cast<float>(cx - 190), static_cast<float>(cy + 70), 380.0f, 48.0f };
|
|
Rectangle btnExit = { static_cast<float>(cx - 190), static_cast<float>(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<int>(dirVec.x), static_cast<int>(dirVec.y));
|
|
}
|
|
if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) {
|
|
tryMoveHub(-static_cast<int>(dirVec.x), -static_cast<int>(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<int>(strafe.x), static_cast<int>(strafe.y));
|
|
}
|
|
if (IsKeyPressed(KEY_E)) {
|
|
if (!m_contextPrompt.empty()) {
|
|
tryInteractHub();
|
|
} else {
|
|
Vector2 strafe = { -dirVec.y, dirVec.x };
|
|
tryMoveHub(static_cast<int>(strafe.x), static_cast<int>(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<int>(dirVec.x), static_cast<int>(dirVec.y));
|
|
}
|
|
if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) {
|
|
tryMoveRestRoom(-static_cast<int>(dirVec.x), -static_cast<int>(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<int>(strafe.x), static_cast<int>(strafe.y));
|
|
}
|
|
if (IsKeyPressed(KEY_E)) {
|
|
if (!m_contextPrompt.empty()) {
|
|
tryInteractRestRoom();
|
|
} else {
|
|
Vector2 strafe = { -dirVec.y, dirVec.x };
|
|
tryMoveRestRoom(static_cast<int>(strafe.x), static_cast<int>(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<ImplantType> available;
|
|
for (int i = 0; i < 12; ++i) {
|
|
ImplantType t = static_cast<ImplantType>(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<float>(slX), static_cast<float>(slY), static_cast<float>(slotW), static_cast<float>(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<int>(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<int>(dirVec.x);
|
|
int frontY = m_player.getGridY() + static_cast<int>(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<int>(dirVec.x), static_cast<int>(dirVec.y));
|
|
}
|
|
if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) {
|
|
tryMoveDungeon(-static_cast<int>(dirVec.x), -static_cast<int>(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<int>(strafe.x), static_cast<int>(strafe.y));
|
|
}
|
|
if (IsKeyPressed(KEY_E)) {
|
|
if (!m_contextPrompt.empty()) {
|
|
tryInteractDungeon();
|
|
} else {
|
|
Vector2 strafe = { -dirVec.y, dirVec.x };
|
|
tryMoveDungeon(static_cast<int>(strafe.x), static_cast<int>(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<Enemy>(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<Enemy>(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<int>(dirVec.x);
|
|
int fy = m_player.getGridY() + static_cast<int>(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<Enemy>(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<float>(modalX + modalW / 2 - 120), static_cast<float>(modalY + modalH - 52), 240.0f, 38.0f };
|
|
|
|
int logX = 770;
|
|
int pvY = 180 + 210 + 12;
|
|
Rectangle btnCodexToggle = { static_cast<float>(logX + 460 - 95), static_cast<float>(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<float>(barX + 15), static_cast<float>(barY + 12), 370.0f, 96.0f };
|
|
Rectangle btnShield = { static_cast<float>(barX + 405), static_cast<float>(barY + 12), 370.0f, 96.0f };
|
|
Rectangle btnTriple = { static_cast<float>(barX + 795), static_cast<float>(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<float>(menuX + 30), static_cast<float>(startY), static_cast<float>(menuW - 60), 48.0f };
|
|
Rectangle btnSettings = { static_cast<float>(menuX + 30), static_cast<float>(startY + 58), static_cast<float>(menuW - 60), 48.0f };
|
|
Rectangle btnSave = { static_cast<float>(menuX + 30), static_cast<float>(startY + 58 * 2), static_cast<float>(menuW - 60), 48.0f };
|
|
Rectangle btnHub = { static_cast<float>(menuX + 30), static_cast<float>(startY + 58 * 3), static_cast<float>(menuW - 60), 48.0f };
|
|
Rectangle btnQuit = { static_cast<float>(menuX + 30), static_cast<float>(startY + 58 * 4), static_cast<float>(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<float>(menuX + (menuW - 240) / 2), static_cast<float>(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<float>(sliderX - 10), static_cast<float>(itemY - 6), static_cast<float>(sliderW + 20), static_cast<float>(sliderH + 12) };
|
|
if (CheckCollisionPointRec(mouse, sBar)) {
|
|
m_selectedSlider = i;
|
|
m_isDraggingSlider = true;
|
|
float ratio = std::clamp((mouse.x - sliderX) / static_cast<float>(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<float>(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
|