1223 lines
61 KiB
C++
1223 lines
61 KiB
C++
#include "UIRenderer.hpp"
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
namespace dnr {
|
|
|
|
UIRenderer::UIRenderer() = default;
|
|
|
|
void UIRenderer::drawProgressBar(int x, int y, int width, int height, float ratio, Color barColor, Color bgColor) {
|
|
ratio = std::clamp(ratio, 0.0f, 1.0f);
|
|
DrawRectangle(x, y, width, height, bgColor);
|
|
DrawRectangle(x, y, static_cast<int>(width * ratio), height, barColor);
|
|
DrawRectangleLines(x, y, width, height, Color{ 40, 80, 120, 255 });
|
|
}
|
|
|
|
void UIRenderer::drawMinimap(const Dungeon& dungeon, const Player& player, int startX, int startY, int size) {
|
|
int w = dungeon.getWidth();
|
|
int h = dungeon.getHeight();
|
|
float cellSize = static_cast<float>(size) / std::max(w, h);
|
|
|
|
// Frame
|
|
DrawRectangle(startX - 4, startY - 4, size + 8, size + 8, Color{ 4, 10, 18, 230 });
|
|
DrawRectangleLines(startX - 4, startY - 4, size + 8, size + 8, CyberColors::NeonCyan);
|
|
DrawText("NET_MAP // 2.5D", startX, startY - 18, 11, CyberColors::NeonCyan);
|
|
|
|
for (int y = 0; y < h; ++y) {
|
|
for (int x = 0; x < w; ++x) {
|
|
if (!dungeon.isExplored(x, y)) continue;
|
|
|
|
int px = startX + static_cast<int>(x * cellSize);
|
|
int py = startY + static_cast<int>(y * cellSize);
|
|
int cs = static_cast<int>(cellSize) + 1;
|
|
|
|
TileType t = dungeon.getTile(x, y);
|
|
switch (t) {
|
|
case TileType::WALL:
|
|
DrawRectangle(px, py, cs, cs, Color{ 25, 45, 75, 255 });
|
|
break;
|
|
case TileType::FLOOR:
|
|
DrawRectangle(px, py, cs, cs, Color{ 10, 20, 35, 255 });
|
|
break;
|
|
case TileType::DOOR_LOCKED:
|
|
DrawRectangle(px, py, cs, cs, CyberColors::NeonOrange);
|
|
break;
|
|
case TileType::DOOR_UNLOCKED:
|
|
DrawRectangle(px, py, cs, cs, CyberColors::NeonGreen);
|
|
break;
|
|
case TileType::LOOT_CACHE:
|
|
DrawRectangle(px, py, cs, cs, CyberColors::NeonYellow);
|
|
break;
|
|
case TileType::EXIT_PORTAL:
|
|
DrawRectangle(px, py, cs, cs, CyberColors::NeonCyan);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Player icon and direction on minimap
|
|
int plX = startX + static_cast<int>((player.getGridX() + 0.5f) * cellSize);
|
|
int plY = startY + static_cast<int>((player.getGridY() + 0.5f) * cellSize);
|
|
DrawCircle(plX, plY, cellSize * 0.7f, CyberColors::NeonYellow);
|
|
|
|
Vector2 dirVec = getDirectionOffset(player.getFacing());
|
|
DrawLine(plX, plY, plX + static_cast<int>(dirVec.x * cellSize * 1.5f), plY + static_cast<int>(dirVec.y * cellSize * 1.5f), CyberColors::NeonGreen);
|
|
}
|
|
|
|
void UIRenderer::drawMatrixDigitalRain(float animTime) {
|
|
static const char* glyphs[] = { "01", "FF", "A7", "EXE", "NULL", "ACK", "SYN", "ICE", "ERR", "CPU", "357", "7F" };
|
|
int colWidth = 55;
|
|
int cols = GetScreenWidth() / colWidth;
|
|
|
|
for (int c = 0; c < cols; ++c) {
|
|
float speed = 120.0f + (c % 5) * 40.0f;
|
|
float yPos = std::fmod(animTime * speed + c * 80.0f, static_cast<float>(GetScreenHeight() + 100)) - 50.0f;
|
|
int xPos = c * colWidth + 10;
|
|
|
|
const char* txt = glyphs[(c + static_cast<int>(animTime * 2.0f)) % 12];
|
|
DrawText(txt, xPos, static_cast<int>(yPos), 12, Color{ 0, 255, 100, 35 });
|
|
DrawText(txt, xPos, static_cast<int>(yPos - 20), 11, Color{ 0, 255, 100, 18 });
|
|
}
|
|
}
|
|
|
|
void UIRenderer::drawMatrixOverlay(float animTime) {
|
|
// Subtle Matrix CRT scanlines
|
|
int screenH = GetScreenHeight();
|
|
int screenW = GetScreenWidth();
|
|
|
|
for (int y = 0; y < screenH; y += 3) {
|
|
DrawLine(0, y, screenW, y, Color{ 0, 255, 120, 14 });
|
|
}
|
|
|
|
// Top terminal status bar
|
|
DrawText("[SYS::MATRIX_ONLINE] [SUBNET::0x7F00] [PROTOCOL::CYBER_REVOLVER] [SECURITY::ICE_ACTIVE]",
|
|
15, screenH - 18, 11, Color{ 0, 240, 160, 160 });
|
|
}
|
|
|
|
void UIRenderer::drawExplorationHUD(const Dungeon& dungeon, const Player& player, const std::string& prompt) {
|
|
// Top-Left: Player Vital Stats
|
|
int hudX = 20;
|
|
int hudY = 20;
|
|
DrawRectangle(hudX, hudY, 330, 145, Color{ 6, 12, 22, 220 });
|
|
DrawRectangleLines(hudX, hudY, 330, 145, CyberColors::NeonCyan);
|
|
|
|
DrawText(("SECTOR LAYER // " + std::to_string(dungeon.getFloor())).c_str(), hudX + 15, hudY + 12, 16, CyberColors::NeonCyan);
|
|
|
|
// HP Bar
|
|
float hpRatio = static_cast<float>(player.getHp()) / player.getMaxHp();
|
|
drawProgressBar(hudX + 60, hudY + 40, 250, 18, hpRatio, CyberColors::NeonPink, Color{ 30, 10, 20, 255 });
|
|
DrawText("CORE", hudX + 15, hudY + 40, 14, CyberColors::NeonPink);
|
|
DrawText((std::to_string(player.getHp()) + " / " + std::to_string(player.getMaxHp())).c_str(), hudX + 145, hudY + 42, 13, RAYWHITE);
|
|
|
|
// Shield Bar
|
|
float shRatio = static_cast<float>(player.getShield()) / player.getMaxShield();
|
|
drawProgressBar(hudX + 60, hudY + 68, 250, 18, shRatio, CyberColors::NeonCyan, Color{ 10, 25, 45, 255 });
|
|
DrawText("SHLD", hudX + 15, hudY + 68, 14, CyberColors::NeonCyan);
|
|
DrawText((std::to_string(player.getShield()) + " / " + std::to_string(player.getMaxShield())).c_str(), hudX + 145, hudY + 70, 13, RAYWHITE);
|
|
|
|
// Cyber-Picks & Statuses
|
|
DrawText(("ICE PICKS: " + std::to_string(player.getCyberPicks())).c_str(), hudX + 15, hudY + 102, 15, CyberColors::NeonYellow);
|
|
|
|
std::string statStr = "STATUS: ";
|
|
if (player.getStatuses().empty()) {
|
|
statStr += "SECURE";
|
|
DrawText(statStr.c_str(), hudX + 160, hudY + 102, 14, CyberColors::NeonGreen);
|
|
} else {
|
|
statStr += "MALWARE_DEBUFF";
|
|
DrawText(statStr.c_str(), hudX + 160, hudY + 102, 14, RED);
|
|
}
|
|
|
|
// Top-Right: Holographic Minimap
|
|
drawMinimap(dungeon, player, GetScreenWidth() - 200, 20, 180);
|
|
|
|
// Bottom-Right: Revolver Ready Ammo preview
|
|
int revX = GetScreenWidth() - 260;
|
|
int revY = GetScreenHeight() - 110;
|
|
DrawRectangle(revX, revY, 240, 90, Color{ 6, 12, 22, 220 });
|
|
DrawRectangleLines(revX, revY, 240, 90, CyberColors::NeonCyan);
|
|
|
|
AmmoType curAmmo = player.getRevolver().getCurrentAmmo();
|
|
AmmoType nextAmmo = player.getRevolver().getNextAmmo();
|
|
const auto& curInfo = getAmmoInfo(curAmmo);
|
|
const auto& nextInfo = getAmmoInfo(nextAmmo);
|
|
|
|
DrawText("REVOLVER READY", revX + 15, revY + 10, 13, CyberColors::NeonCyan);
|
|
DrawCircle(revX + 25, revY + 42, 10, curInfo.color);
|
|
DrawText(curInfo.name.c_str(), revX + 45, revY + 34, 15, curInfo.color);
|
|
|
|
DrawText(("NEXT: " + nextInfo.shortName).c_str(), revX + 15, revY + 65, 12, GRAY);
|
|
DrawText("[TAB] ARMORY", revX + 140, revY + 65, 12, CyberColors::NeonYellow);
|
|
|
|
// Bottom-Center: Context Action Prompt
|
|
if (!prompt.empty()) {
|
|
int promptW = MeasureText(prompt.c_str(), 18) + 40;
|
|
int promptX = (GetScreenWidth() - promptW) / 2;
|
|
int promptY = GetScreenHeight() - 70;
|
|
DrawRectangle(promptX, promptY, promptW, 45, Color{ 10, 18, 32, 240 });
|
|
DrawRectangleLines(promptX, promptY, promptW, 45, CyberColors::NeonYellow);
|
|
DrawText(prompt.c_str(), promptX + 20, promptY + 13, 18, CyberColors::NeonYellow);
|
|
}
|
|
|
|
// Controls hints in bottom-left
|
|
DrawText("[W/S] WALK | [A/D] TURN 90 | [Q/E] STRAFE | [SPACE/E] INTERACT | [TAB] ARMORY",
|
|
20, GetScreenHeight() - 35, 13, Color{ 120, 160, 200, 220 });
|
|
}
|
|
|
|
void UIRenderer::drawHackingUI(const HackingGame& hacking) {
|
|
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 8, 16, 235 });
|
|
|
|
int panelW = 760;
|
|
int panelH = 520;
|
|
int panelX = (GetScreenWidth() - panelW) / 2;
|
|
int panelY = (GetScreenHeight() - panelH) / 2;
|
|
|
|
DrawRectangle(panelX, panelY, panelW, panelH, Color{ 8, 14, 26, 255 });
|
|
DrawRectangleLines(panelX, panelY, panelW, panelH, CyberColors::NeonCyan);
|
|
|
|
// Header
|
|
DrawRectangle(panelX, panelY, panelW, 50, Color{ 14, 24, 42, 255 });
|
|
DrawText(("SECURITY ICE BYPASS TERMINAL // DEFENSE LEVEL 0" + std::to_string(hacking.getSecurityLevel())).c_str(),
|
|
panelX + 25, panelY + 16, 18, CyberColors::NeonCyan);
|
|
|
|
if (hacking.getPhase() == HackingPhase::PHASE_1_TUMBLER) {
|
|
DrawText("PHASE 1: FREQUENCY TUMBLER ALIGNMENT", panelX + 25, panelY + 65, 16, CyberColors::NeonYellow);
|
|
DrawText("Tune each pin into the sweet spot frequency band, then lock it into place.", panelX + 25, panelY + 90, 13, RAYWHITE);
|
|
|
|
const auto& tumbler = hacking.getTumbler();
|
|
int pinCount = tumbler.getPinCount();
|
|
int pinW = 60;
|
|
int pinGap = 40;
|
|
int totalPinsW = pinCount * pinW + (pinCount - 1) * pinGap;
|
|
int startPinX = panelX + (panelW - totalPinsW) / 2;
|
|
int trackY = panelY + 130;
|
|
int trackH = 260;
|
|
|
|
for (int i = 0; i < pinCount; ++i) {
|
|
const auto& pin = tumbler.getPin(i);
|
|
int px = startPinX + i * (pinW + pinGap);
|
|
bool isSelected = (i == tumbler.getSelectedPin());
|
|
|
|
DrawRectangle(px, trackY, pinW, trackH, Color{ 14, 20, 32, 255 });
|
|
DrawRectangleLines(px, trackY, pinW, trackH, isSelected ? CyberColors::NeonYellow : Color{ 40, 60, 90, 255 });
|
|
|
|
// Sweet spot target zone
|
|
int sweetY = trackY + static_cast<int>((1.0f - pin.targetHeight) * trackH);
|
|
int sweetSpan = static_cast<int>(pin.tolerance * 2.0f * trackH);
|
|
DrawRectangle(px + 2, sweetY - sweetSpan / 2, pinW - 4, sweetSpan, Color{ 0, 240, 255, 60 });
|
|
DrawLine(px, sweetY, px + pinW, sweetY, CyberColors::NeonCyan);
|
|
|
|
int pinH = 40;
|
|
int pinCurrY = trackY + static_cast<int>((1.0f - pin.currentHeight) * (trackH - pinH));
|
|
|
|
Color pinColor = pin.locked ? CyberColors::NeonGreen :
|
|
(pin.isInSweetSpot() ? CyberColors::NeonYellow : Color{ 70, 110, 160, 255 });
|
|
|
|
DrawRectangle(px + 4, pinCurrY, pinW - 8, pinH, pinColor);
|
|
DrawRectangleLines(px + 4, pinCurrY, pinW - 8, pinH, WHITE);
|
|
|
|
if (pin.locked) {
|
|
DrawText("LOCKED", px + 8, trackY + trackH + 12, 12, CyberColors::NeonGreen);
|
|
} else if (pin.isInSweetSpot()) {
|
|
DrawText("SWEET", px + 12, trackY + trackH + 12, 12, CyberColors::NeonYellow);
|
|
} else {
|
|
DrawText(std::to_string(i + 1).c_str(), px + 24, trackY + trackH + 12, 14, GRAY);
|
|
}
|
|
}
|
|
|
|
DrawRectangle(panelX + 20, panelY + panelH - 65, panelW - 40, 45, Color{ 12, 18, 30, 255 });
|
|
if (tumbler.isAllPinsLocked()) {
|
|
DrawText("[ENTER / SPACE] ALL PINS LOCKED! INITIATE DEAD-LOCK TIMING CHECK", panelX + 70, panelY + panelH - 52, 16, CyberColors::NeonGreen);
|
|
} else {
|
|
DrawText("[A/D] Select Pin [W/S] Tune Frequency [SPACE] Lock Pin", panelX + 130, panelY + panelH - 52, 14, CyberColors::NeonCyan);
|
|
}
|
|
} else if (hacking.getPhase() == HackingPhase::PHASE_2_SKILLCHECK) {
|
|
DrawText("PHASE 2: DEAD-LOCK TIMING INJECTION (DBD SKILL CHECK)", panelX + 25, panelY + 65, 16, CyberColors::NeonPink);
|
|
DrawText("STRIKE [SPACE] WHEN THE NEEDLE HITS THE TARGET ZONE!", panelX + 25, panelY + 90, 14, CyberColors::NeonYellow);
|
|
|
|
const auto& sc = hacking.getSkillCheck();
|
|
int dialCenterX = panelX + panelW / 2;
|
|
int dialCenterY = panelY + panelH / 2 + 10;
|
|
float dialRadius = 130.0f;
|
|
|
|
DrawCircle(dialCenterX, dialCenterY, dialRadius + 12.0f, Color{ 10, 16, 26, 255 });
|
|
DrawCircleLines(dialCenterX, dialCenterY, dialRadius + 12.0f, CyberColors::NeonCyan);
|
|
DrawCircle(dialCenterX, dialCenterY, dialRadius - 20.0f, Color{ 6, 10, 18, 255 });
|
|
|
|
// Success zone
|
|
DrawCircleSectorLines({ static_cast<float>(dialCenterX), static_cast<float>(dialCenterY) },
|
|
dialRadius,
|
|
sc.getSuccessStart() - 90.0f,
|
|
sc.getSuccessEnd() - 90.0f,
|
|
32, RAYWHITE);
|
|
DrawCircleSector({ static_cast<float>(dialCenterX), static_cast<float>(dialCenterY) },
|
|
dialRadius,
|
|
sc.getSuccessStart() - 90.0f,
|
|
sc.getSuccessEnd() - 90.0f,
|
|
32, Color{ 220, 220, 220, 120 });
|
|
|
|
// Great success zone
|
|
DrawCircleSector({ static_cast<float>(dialCenterX), static_cast<float>(dialCenterY) },
|
|
dialRadius,
|
|
sc.getGreatStart() - 90.0f,
|
|
sc.getGreatEnd() - 90.0f,
|
|
16, CyberColors::NeonGreen);
|
|
|
|
// Rotating needle
|
|
float needleRad = (sc.getNeedleAngle() - 90.0f) * (3.14159265f / 180.0f);
|
|
int needleEndX = dialCenterX + static_cast<int>(std::cos(needleRad) * (dialRadius + 8.0f));
|
|
int needleEndY = dialCenterY + static_cast<int>(std::sin(needleRad) * (dialRadius + 8.0f));
|
|
|
|
DrawLineEx({ static_cast<float>(dialCenterX), static_cast<float>(dialCenterY) },
|
|
{ static_cast<float>(needleEndX), static_cast<float>(needleEndY) },
|
|
4.0f, RED);
|
|
DrawCircle(dialCenterX, dialCenterY, 14, RED);
|
|
|
|
DrawText("HIT [ SPACE ]", dialCenterX - 60, dialCenterY + static_cast<int>(dialRadius) + 30, 20, CyberColors::NeonYellow);
|
|
} else {
|
|
int cx = panelX + panelW / 2;
|
|
int cy = panelY + panelH / 2;
|
|
|
|
if (hacking.getPhase() == HackingPhase::RESULT_CRITICAL) {
|
|
DrawText(">>> CRITICAL OVERRIDE SUCCESS! <<<", cx - 210, cy - 40, 24, CyberColors::NeonGreen);
|
|
DrawText("Subroutines penetrated. Bonus cyber-cartridge recovered!", cx - 200, cy + 10, 16, RAYWHITE);
|
|
} else if (hacking.getPhase() == HackingPhase::RESULT_SUCCESS) {
|
|
DrawText(">>> ACCESS GRANTED <<<", cx - 140, cy - 40, 26, CyberColors::NeonCyan);
|
|
DrawText("Security locks bypassed successfully.", cx - 150, cy + 10, 16, RAYWHITE);
|
|
} else {
|
|
DrawText("!!! ICE ALARM TRIGGERED !!!", cx - 180, cy - 40, 26, RED);
|
|
DrawText("Intrusion countermeasures activated. Defense Sentinel deployed!", cx - 240, cy + 10, 16, CyberColors::NeonOrange);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UIRenderer::drawRevolverCylinderCombat(const Revolver& revolver, int centerX, int centerY, float animTime, bool isSpinning) {
|
|
float cylinderR = 95.0f;
|
|
float chamberOrbitR = cylinderR - 30.0f;
|
|
float chamberR = 20.0f;
|
|
|
|
// 1. Heavy gunmetal outer housing
|
|
DrawCircle(centerX, centerY, cylinderR + 14.0f, Color{ 8, 14, 24, 250 });
|
|
DrawCircleLines(centerX, centerY, cylinderR + 14.0f, Color{ 30, 50, 75, 255 });
|
|
|
|
// Metallic cylinder body
|
|
DrawCircle(centerX, centerY, cylinderR, Color{ 20, 28, 42, 255 });
|
|
DrawCircleLines(centerX, centerY, cylinderR, CyberColors::NeonCyan);
|
|
|
|
// Decorative flutes/grooves between chambers (6 radial outer notches)
|
|
float visAngle = revolver.getVisualAngle();
|
|
for (int i = 0; i < 6; ++i) {
|
|
float fluteAngle = (-visAngle + i * 60.0f + 30.0f - 90.0f) * (3.14159265f / 180.0f);
|
|
int fluteX = centerX + static_cast<int>(std::cos(fluteAngle) * (cylinderR + 2.0f));
|
|
int fluteY = centerY + static_cast<int>(std::sin(fluteAngle) * (cylinderR + 2.0f));
|
|
DrawCircle(fluteX, fluteY, 12.0f, Color{ 8, 14, 24, 255 });
|
|
DrawCircleLines(fluteX, fluteY, 12.0f, Color{ 40, 65, 95, 255 });
|
|
}
|
|
|
|
// 2. Center extractor ratchet & hub
|
|
DrawCircle(centerX, centerY, 34.0f, Color{ 14, 20, 32, 255 });
|
|
DrawCircleLines(centerX, centerY, 34.0f, Color{ 70, 100, 140, 255 });
|
|
DrawCircle(centerX, centerY, 14.0f, Color{ 220, 175, 45, 255 });
|
|
DrawCircleLines(centerX, centerY, 14.0f, CyberColors::NeonYellow);
|
|
DrawText(".357", centerX - 15, centerY - 6, 12, CyberColors::NeonYellow);
|
|
|
|
// 3. Golden Striker Hammer / Firing Pin at 12 o'clock (-90 deg / top)
|
|
float strikerPulse = 0.5f + 0.5f * std::sin(animTime * 6.0f);
|
|
int strikerTipY = centerY - static_cast<int>(cylinderR) - 4;
|
|
int strikerBaseY = centerY - static_cast<int>(cylinderR) - 26;
|
|
|
|
DrawTriangle(
|
|
{ static_cast<float>(centerX), static_cast<float>(strikerTipY) },
|
|
{ static_cast<float>(centerX - 12), static_cast<float>(strikerBaseY) },
|
|
{ static_cast<float>(centerX + 12), static_cast<float>(strikerBaseY) },
|
|
Color{ 255, 210, 50, 255 }
|
|
);
|
|
DrawRectangle(centerX - 18, strikerBaseY - 14, 36, 14, Color{ 210, 160, 30, 255 });
|
|
DrawRectangleLines(centerX - 18, strikerBaseY - 14, 36, 14, CyberColors::NeonYellow);
|
|
DrawCircle(centerX, strikerTipY, 2.5f + strikerPulse * 1.5f, CyberColors::NeonYellow);
|
|
|
|
// Striker tag
|
|
DrawRectangle(centerX - 65, strikerBaseY - 32, 130, 16, Color{ 10, 16, 26, 230 });
|
|
DrawRectangleLines(centerX - 65, strikerBaseY - 32, 130, 16, Color{ 180, 150, 40, 200 });
|
|
DrawText("FIRING PIN [12:00]", centerX - 58, strikerBaseY - 30, 11, CyberColors::NeonYellow);
|
|
|
|
// 4. Six Chambers (Top chamber at 12:00 aligns with striker when visualAngle == index * 60)
|
|
for (int i = 0; i < 6; ++i) {
|
|
float angleRad = (-visAngle + i * 60.0f - 90.0f) * (3.14159265f / 180.0f);
|
|
int chX = centerX + static_cast<int>(std::cos(angleRad) * chamberOrbitR);
|
|
int chY = centerY + static_cast<int>(std::sin(angleRad) * chamberOrbitR);
|
|
|
|
AmmoType ammo = revolver.getChamber(i);
|
|
const auto& info = getAmmoInfo(ammo);
|
|
bool isCurrent = (i == revolver.getCurrentIndex());
|
|
|
|
DrawCircle(chX, chY, chamberR, Color{ 16, 22, 34, 255 });
|
|
DrawCircleLines(chX, chY, chamberR, isCurrent ? CyberColors::NeonYellow : Color{ 55, 80, 120, 255 });
|
|
|
|
if (ammo == AmmoType::EMPTY) {
|
|
DrawCircle(chX, chY, chamberR - 4.0f, Color{ 8, 12, 18, 255 });
|
|
DrawCircleLines(chX, chY, chamberR - 4.0f, Color{ 60, 60, 60, 255 });
|
|
DrawText("0", chX - 4, chY - 6, 12, DARKGRAY);
|
|
} else {
|
|
DrawCircle(chX, chY, chamberR - 4.0f, Color{ 34, 46, 62, 255 });
|
|
DrawCircleLines(chX, chY, chamberR - 4.0f, Color{ 180, 140, 50, 255 });
|
|
DrawCircle(chX, chY, chamberR - 8.0f, info.color);
|
|
DrawCircle(chX - 3, chY - 3, 2.5f, Color{ 255, 255, 255, 200 });
|
|
DrawText(info.shortName.substr(0, 3).c_str(), chX - 9, chY - 5, 10, BLACK);
|
|
}
|
|
|
|
if (isCurrent) {
|
|
float reticleR = chamberR + 5.0f + 2.0f * std::sin(animTime * 6.0f);
|
|
DrawCircleLines(chX, chY, reticleR, CyberColors::NeonYellow);
|
|
DrawLine(chX - static_cast<int>(reticleR) - 3, chY, chX - static_cast<int>(reticleR) + 3, chY, CyberColors::NeonYellow);
|
|
DrawLine(chX + static_cast<int>(reticleR) - 3, chY, chX + static_cast<int>(reticleR) + 3, chY, CyberColors::NeonYellow);
|
|
DrawLine(chX, chY - static_cast<int>(reticleR) - 3, chX, chY - static_cast<int>(reticleR) + 3, CyberColors::NeonYellow);
|
|
DrawLine(chX, chY + static_cast<int>(reticleR) - 3, chX, chY + static_cast<int>(reticleR) + 3, CyberColors::NeonYellow);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UIRenderer::drawCodexModal() {
|
|
int screenW = GetScreenWidth();
|
|
int screenH = GetScreenHeight();
|
|
|
|
DrawRectangle(0, 0, screenW, screenH, Color{ 4, 8, 16, 235 });
|
|
|
|
int modalW = 980;
|
|
int modalH = 580;
|
|
int modalX = (screenW - modalW) / 2;
|
|
int modalY = (screenH - modalH) / 2;
|
|
|
|
DrawRectangle(modalX, modalY, modalW, modalH, Color{ 8, 14, 26, 255 });
|
|
DrawRectangleLines(modalX, modalY, modalW, modalH, CyberColors::NeonCyan);
|
|
|
|
// Decorative corners
|
|
DrawRectangle(modalX - 2, modalY - 2, 8, 8, CyberColors::NeonCyan);
|
|
DrawRectangle(modalX + modalW - 6, modalY - 2, 8, 8, CyberColors::NeonCyan);
|
|
DrawRectangle(modalX - 2, modalY + modalH - 6, 8, 8, CyberColors::NeonCyan);
|
|
DrawRectangle(modalX + modalW - 6, modalY + modalH - 6, 8, 8, CyberColors::NeonCyan);
|
|
|
|
// Header banner
|
|
DrawRectangle(modalX, modalY, modalW, 48, Color{ 14, 24, 42, 255 });
|
|
DrawText("// TACTICAL AMMUNITION & COMBAT CODEX // DATABASE 404", modalX + 25, modalY + 15, 17, CyberColors::NeonCyan);
|
|
DrawText("[H] / [ESC] TO CLOSE", modalX + modalW - 190, modalY + 16, 13, CyberColors::NeonYellow);
|
|
|
|
// Left column: 7 Ammo Types breakdown
|
|
int leftX = modalX + 25;
|
|
int colY = modalY + 62;
|
|
DrawText("AMMUNITION CARTRIDGES (7 PROTOCOLS)", leftX, colY, 14, CyberColors::NeonYellow);
|
|
|
|
AmmoType ammos[7] = {
|
|
AmmoType::KINETIC,
|
|
AmmoType::EMP,
|
|
AmmoType::OVERHEAT,
|
|
AmmoType::CORROSIVE,
|
|
AmmoType::GLITCH,
|
|
AmmoType::NANITE,
|
|
AmmoType::EMPTY
|
|
};
|
|
|
|
int cardY = colY + 24;
|
|
for (int i = 0; i < 7; ++i) {
|
|
const auto& inf = getAmmoInfo(ammos[i]);
|
|
DrawRectangle(leftX, cardY, 455, 46, Color{ 12, 18, 30, 255 });
|
|
DrawRectangleLines(leftX, cardY, 455, 46, (ammos[i] == AmmoType::EMPTY) ? DARKGRAY : inf.color);
|
|
|
|
DrawCircle(leftX + 20, cardY + 23, 10.0f, (ammos[i] == AmmoType::EMPTY) ? Color{ 25, 25, 25, 255 } : inf.color);
|
|
DrawCircleLines(leftX + 20, cardY + 23, 10.0f, WHITE);
|
|
|
|
std::string title = inf.name + " [" + inf.shortName + "]";
|
|
if (ammos[i] != AmmoType::EMPTY) {
|
|
title += " // DMG: " + std::to_string(inf.baseDamage);
|
|
}
|
|
DrawText(title.c_str(), leftX + 40, cardY + 8, 13, inf.color);
|
|
DrawText(inf.description.c_str(), leftX + 40, cardY + 25, 11, RAYWHITE);
|
|
|
|
cardY += 52;
|
|
}
|
|
|
|
// Right column: Combat Rules & Tactical Intel
|
|
int rightX = modalX + 505;
|
|
DrawText("TACTICAL COMBAT MECHANICS & PROTOCOLS", rightX, colY, 14, CyberColors::NeonYellow);
|
|
|
|
struct GuideRule {
|
|
std::string title;
|
|
std::string desc1;
|
|
std::string desc2;
|
|
Color color;
|
|
};
|
|
GuideRule rules[4] = {
|
|
{ "1. THE 12:00 FIRING PIN RULE",
|
|
"The cylinder spins roulette-style. The chamber aligned at 12 o'clock",
|
|
"directly beneath the golden striker is the round that detonates.",
|
|
CyberColors::NeonYellow },
|
|
{ "2. CRITICAL SURGE WEAKNESS (+100% DMG)",
|
|
"Scan the hostile unit data scanner at top. Matching target weakness",
|
|
"immediately triggers a Critical Surge dealing +100% double damage!",
|
|
CyberColors::NeonGreen },
|
|
{ "3. DEFENSE MATRIX OVERCLOCK",
|
|
"Use [2] Overclock Shield when facing heavy retaliation or low ammo.",
|
|
"Instantly grants +25 shield and cuts incoming damage by 50% for 1 turn.",
|
|
CyberColors::NeonCyan },
|
|
{ "4. FAN THE HAMMER (3-ROUND BURST)",
|
|
"Protocol [3] discharges 3 consecutive chambers in rapid succession.",
|
|
"Verify your cylinder is filled in [TAB] Armory for devastating bursts!",
|
|
CyberColors::NeonPink }
|
|
};
|
|
|
|
int ruleY = colY + 24;
|
|
for (int i = 0; i < 4; ++i) {
|
|
DrawRectangle(rightX, ruleY, 450, 84, Color{ 12, 18, 30, 255 });
|
|
DrawRectangleLines(rightX, ruleY, 450, 84, Color{ 40, 65, 100, 255 });
|
|
|
|
DrawText(rules[i].title.c_str(), rightX + 15, ruleY + 10, 13, rules[i].color);
|
|
DrawText(rules[i].desc1.c_str(), rightX + 15, ruleY + 34, 11, RAYWHITE);
|
|
DrawText(rules[i].desc2.c_str(), rightX + 15, ruleY + 52, 11, RAYWHITE);
|
|
|
|
ruleY += 92;
|
|
}
|
|
|
|
// Close Button at bottom
|
|
Rectangle btnClose = { static_cast<float>(modalX + modalW / 2 - 120), static_cast<float>(modalY + modalH - 52), 240.0f, 38.0f };
|
|
Vector2 mousePos = GetMousePosition();
|
|
bool hoverClose = CheckCollisionPointRec(mousePos, btnClose);
|
|
DrawRectangleRec(btnClose, hoverClose ? Color{ 40, 80, 120, 255 } : Color{ 20, 36, 60, 255 });
|
|
DrawRectangleLinesEx(btnClose, hoverClose ? 2.0f : 1.0f, hoverClose ? CyberColors::NeonYellow : CyberColors::NeonCyan);
|
|
DrawText("[ CLOSE CODEX (PRESS H) ]", modalX + modalW / 2 - 95, modalY + modalH - 40, 13, hoverClose ? CyberColors::NeonYellow : WHITE);
|
|
}
|
|
|
|
void UIRenderer::drawCombatUI(const CombatSystem& combat, const Player& player, float animTime, bool showCodex) {
|
|
const Enemy* enemy = combat.getEnemy();
|
|
if (!enemy) return;
|
|
|
|
// Digital matrix rain in combat background
|
|
drawMatrixDigitalRain(animTime);
|
|
|
|
// 1. Top: Tactical Target Scanner HUD
|
|
int scanW = 760;
|
|
int scanH = 145;
|
|
int scanX = (GetScreenWidth() - scanW) / 2;
|
|
int scanY = 18;
|
|
|
|
DrawRectangle(scanX, scanY, scanW, scanH, Color{ 8, 14, 26, 240 });
|
|
DrawRectangleLines(scanX, scanY, scanW, scanH, enemy->getColor());
|
|
|
|
// Corner decorative accents
|
|
DrawRectangle(scanX - 2, scanY - 2, 8, 8, enemy->getColor());
|
|
DrawRectangle(scanX + scanW - 6, scanY - 2, 8, 8, enemy->getColor());
|
|
DrawRectangle(scanX - 2, scanY + scanH - 6, 8, 8, enemy->getColor());
|
|
DrawRectangle(scanX + scanW - 6, scanY + scanH - 6, 8, 8, enemy->getColor());
|
|
|
|
DrawText("// TACTICAL TARGET SCANNER // HOSTILE_UNIT_DATA", scanX + 25, scanY + 10, 11, CyberColors::NeonCyan);
|
|
DrawText(enemy->getName().c_str(), scanX + 25, scanY + 26, 22, enemy->getColor());
|
|
|
|
// Core HP Segmented Bar
|
|
float eHpRatio = static_cast<float>(enemy->getHp()) / enemy->getMaxHp();
|
|
int hpBarW = scanW - 220;
|
|
drawProgressBar(scanX + 105, scanY + 54, hpBarW, 20, eHpRatio, enemy->getColor(), Color{ 36, 12, 18, 255 });
|
|
DrawText("CORE HP", scanX + 25, scanY + 56, 14, RAYWHITE);
|
|
std::string hpTxt = std::to_string(enemy->getHp()) + " / " + std::to_string(enemy->getMaxHp()) +
|
|
" (" + std::to_string(static_cast<int>(eHpRatio * 100)) + "%)";
|
|
DrawText(hpTxt.c_str(), scanX + scanW / 2 - 40, scanY + 56, 13, WHITE);
|
|
|
|
// Shield Segmented Bar
|
|
float eShRatio = (enemy->getMaxShield() > 0) ? (static_cast<float>(enemy->getShield()) / enemy->getMaxShield()) : 0.0f;
|
|
drawProgressBar(scanX + 105, scanY + 80, hpBarW, 16, eShRatio, CyberColors::NeonCyan, Color{ 14, 24, 44, 255 });
|
|
DrawText("SHIELD", scanX + 25, scanY + 81, 14, CyberColors::NeonCyan);
|
|
std::string shTxt = std::to_string(enemy->getShield()) + " / " + std::to_string(enemy->getMaxShield()) +
|
|
" (" + std::to_string(static_cast<int>(eShRatio * 100)) + "%)";
|
|
DrawText(shTxt.c_str(), scanX + scanW / 2 - 35, scanY + 81, 12, WHITE);
|
|
|
|
// Weakness & Weapon Tags Row
|
|
const auto& atkInfo = getAmmoInfo(enemy->getAttackElement());
|
|
const auto& wknInfo = getAmmoInfo(enemy->getWeakness());
|
|
const auto& rev = player.getRevolver();
|
|
|
|
DrawText("WEAPON:", scanX + 25, scanY + 112, 12, GRAY);
|
|
DrawText(atkInfo.shortName.c_str(), scanX + 85, scanY + 112, 12, atkInfo.color);
|
|
|
|
DrawText("WEAKNESS:", scanX + 160, scanY + 112, 12, GRAY);
|
|
DrawRectangle(scanX + 235, scanY + 107, 240, 24, Color{ 35, 28, 12, 240 });
|
|
DrawRectangleLines(scanX + 235, scanY + 107, 240, 24, CyberColors::NeonYellow);
|
|
std::string wknText = wknInfo.shortName + " -> +100% CRIT SURGE!";
|
|
DrawText(wknText.c_str(), scanX + 242, scanY + 112, 12, CyberColors::NeonYellow);
|
|
|
|
if (rev.getCurrentAmmo() == enemy->getWeakness()) {
|
|
float surgePulse = 0.5f + 0.5f * std::sin(animTime * 10.0f);
|
|
Color badgeCol = (surgePulse > 0.5f) ? CyberColors::NeonGreen : CyberColors::NeonYellow;
|
|
DrawText("[SURGE PRIMED!]", scanX + 482, scanY + 112, 12, badgeCol);
|
|
}
|
|
|
|
// Active Status Chips
|
|
int chipX = scanX + scanW - 20;
|
|
const auto& statuses = enemy->getStatuses();
|
|
if (statuses.empty()) {
|
|
int w = MeasureText("[STATUS: NOMINAL]", 11);
|
|
DrawText("[STATUS: NOMINAL]", chipX - w, scanY + 112, 11, Color{ 120, 150, 180, 255 });
|
|
} else {
|
|
int curRight = chipX;
|
|
for (const auto& st : statuses) {
|
|
std::string stStr = "";
|
|
Color stCol = WHITE;
|
|
switch (st.effect) {
|
|
case StatusEffect::BURNING:
|
|
stStr = "BURN " + std::to_string(st.durationTurns) + "T";
|
|
stCol = CyberColors::NeonOrange;
|
|
break;
|
|
case StatusEffect::SHOCKED:
|
|
stStr = "STUN " + std::to_string(st.durationTurns) + "T";
|
|
stCol = CyberColors::NeonCyan;
|
|
break;
|
|
case StatusEffect::CORRODED:
|
|
stStr = "ACID +50% " + std::to_string(st.durationTurns) + "T";
|
|
stCol = CyberColors::NeonGreen;
|
|
break;
|
|
case StatusEffect::GLITCHED:
|
|
stStr = "GLITCH " + std::to_string(st.durationTurns) + "T";
|
|
stCol = CyberColors::NeonPurple;
|
|
break;
|
|
default: break;
|
|
}
|
|
if (!stStr.empty()) {
|
|
int stW = MeasureText(stStr.c_str(), 11) + 12;
|
|
curRight -= (stW + 6);
|
|
DrawRectangle(curRight, scanY + 108, stW, 20, Color{ 15, 22, 35, 230 });
|
|
DrawRectangleLines(curRight, scanY + 108, stW, 20, stCol);
|
|
DrawText(stStr.c_str(), curRight + 6, scanY + 112, 11, stCol);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Center-Left: Revolver Cylinder & Under-cylinder Odds breakdown
|
|
int drumCenterX = 210;
|
|
int drumCenterY = 320;
|
|
drawRevolverCylinderCombat(rev, drumCenterX, drumCenterY, animTime, rev.isSpinning());
|
|
|
|
AmmoType curAmmo = rev.getCurrentAmmo();
|
|
const auto& curInfo = getAmmoInfo(curAmmo);
|
|
int activeW = 310;
|
|
int activeX = drumCenterX - activeW / 2;
|
|
int activeY = drumCenterY + 115;
|
|
|
|
DrawRectangle(activeX, activeY, activeW, 46, Color{ 10, 16, 28, 240 });
|
|
DrawRectangleLines(activeX, activeY, activeW, 46, curAmmo == AmmoType::EMPTY ? RED : curInfo.color);
|
|
DrawText("12:00 CHAMBER // PRIMED TO FIRE", activeX + 12, activeY + 6, 10, CyberColors::NeonCyan);
|
|
|
|
if (curAmmo == AmmoType::EMPTY) {
|
|
DrawText("BLANK // DRY-FIRE MISFIRE (0 DMG)", activeX + 12, activeY + 22, 14, RED);
|
|
} else {
|
|
std::string actTxt = curInfo.name + " (" + std::to_string(curInfo.baseDamage) + " DMG)";
|
|
DrawText(actTxt.c_str(), activeX + 12, activeY + 22, 14, curInfo.color);
|
|
}
|
|
|
|
int statsY = activeY + 52;
|
|
int statsH = 68;
|
|
DrawRectangle(activeX, statsY, activeW, statsH, Color{ 8, 14, 24, 240 });
|
|
DrawRectangleLines(activeX, statsY, activeW, statsH, Color{ 35, 60, 95, 255 });
|
|
DrawText("// AMMO PROBABILITY ROULETTE", activeX + 12, statsY + 6, 10, GRAY);
|
|
|
|
int loadedCount = 0;
|
|
int typeCounts[7] = { 0 };
|
|
for (int i = 0; i < 6; ++i) {
|
|
AmmoType a = rev.getChamber(i);
|
|
typeCounts[static_cast<int>(a)]++;
|
|
if (a != AmmoType::EMPTY) loadedCount++;
|
|
}
|
|
int hitChance = (loadedCount * 100) / 6;
|
|
int blankChance = 100 - hitChance;
|
|
|
|
std::string oddsStr = "LOADED: " + std::to_string(loadedCount) + "/6 (" + std::to_string(hitChance) + "% HIT)";
|
|
DrawText(oddsStr.c_str(), activeX + 12, statsY + 22, 13, hitChance >= 50 ? CyberColors::NeonGreen : CyberColors::NeonYellow);
|
|
|
|
std::string blankStr = "MISFIRE RISK: " + std::to_string(blankChance) + "%";
|
|
DrawText(blankStr.c_str(), activeX + 175, statsY + 22, 13, blankChance == 0 ? CyberColors::NeonGreen : (blankChance > 33 ? RED : CyberColors::NeonYellow));
|
|
|
|
std::string chipSummary = "";
|
|
for (int t = 1; t <= 6; ++t) {
|
|
if (typeCounts[t] > 0) {
|
|
const auto& inf = getAmmoInfo(static_cast<AmmoType>(t));
|
|
if (!chipSummary.empty()) chipSummary += " | ";
|
|
chipSummary += inf.shortName + "x" + std::to_string(typeCounts[t]);
|
|
}
|
|
}
|
|
if (chipSummary.empty()) chipSummary = "ALL CHAMBERS EMPTY - RELOAD IN ARMORY";
|
|
DrawText(chipSummary.c_str(), activeX + 12, statsY + 44, 11, Color{ 170, 195, 225, 255 });
|
|
|
|
// 3. Center-Right: Combat Data Stream & Operator Vitals
|
|
int logX = 770;
|
|
int logY = 180;
|
|
int logW = 460;
|
|
int logH = 210;
|
|
|
|
DrawRectangle(logX, logY, logW, logH, Color{ 6, 12, 22, 245 });
|
|
DrawRectangleLines(logX, logY, logW, logH, Color{ 30, 55, 90, 255 });
|
|
DrawText("// COMBAT_DATA_STREAM // REAL-TIME LOG", logX + 15, logY + 10, 11, CyberColors::NeonCyan);
|
|
|
|
const auto& logs = combat.getLog();
|
|
int logTextY = logY + 30;
|
|
for (const auto& line : logs) {
|
|
Color logCol = RAYWHITE;
|
|
if (line.find("CRITICAL") != std::string::npos || line.find("SURGE") != std::string::npos) logCol = CyberColors::NeonYellow;
|
|
else if (line.find("FLATLINE") != std::string::npos || line.find("attacks") != std::string::npos) logCol = Color{ 255, 120, 120, 255 };
|
|
else if (line.find("STUNNED") != std::string::npos || line.find("Nanites") != std::string::npos) logCol = CyberColors::NeonGreen;
|
|
else if (line.find("FAN THE HAMMER") != std::string::npos) logCol = CyberColors::NeonPink;
|
|
|
|
DrawText(("> " + line).c_str(), logX + 15, logTextY, 13, logCol);
|
|
logTextY += 21;
|
|
}
|
|
|
|
int pvY = logY + logH + 12;
|
|
int pvH = 78;
|
|
DrawRectangle(logX, pvY, logW, pvH, Color{ 8, 14, 26, 245 });
|
|
DrawRectangleLines(logX, pvY, logW, pvH, CyberColors::NeonGreen);
|
|
DrawText("// OPERATOR VITALS // NEURAL SYNC", logX + 15, pvY + 8, 10, CyberColors::NeonGreen);
|
|
|
|
float pHpRatio = static_cast<float>(player.getHp()) / player.getMaxHp();
|
|
drawProgressBar(logX + 80, pvY + 26, logW - 190, 18, pHpRatio, CyberColors::NeonGreen, Color{ 35, 12, 15, 255 });
|
|
DrawText("HULL HP", logX + 15, pvY + 28, 12, RAYWHITE);
|
|
std::string pHpStr = std::to_string(player.getHp()) + "/" + std::to_string(player.getMaxHp());
|
|
DrawText(pHpStr.c_str(), logX + 85 + (logW - 190) / 2 - 25, pvY + 28, 12, WHITE);
|
|
|
|
float pShRatio = (player.getMaxShield() > 0) ? (static_cast<float>(player.getShield()) / player.getMaxShield()) : 0.0f;
|
|
drawProgressBar(logX + 80, pvY + 50, logW - 190, 16, pShRatio, CyberColors::NeonCyan, Color{ 12, 22, 40, 255 });
|
|
DrawText("SHIELD", logX + 15, pvY + 51, 12, CyberColors::NeonCyan);
|
|
std::string pShStr = std::to_string(player.getShield()) + "/" + std::to_string(player.getMaxShield());
|
|
DrawText(pShStr.c_str(), logX + 85 + (logW - 190) / 2 - 20, pvY + 51, 11, WHITE);
|
|
|
|
Rectangle btnCodex = { static_cast<float>(logX + logW - 95), static_cast<float>(pvY + 25), 85.0f, 40.0f };
|
|
Vector2 mousePos = GetMousePosition();
|
|
bool hoverCodex = CheckCollisionPointRec(mousePos, btnCodex);
|
|
DrawRectangleRec(btnCodex, hoverCodex ? Color{ 40, 65, 100, 255 } : Color{ 16, 28, 48, 255 });
|
|
DrawRectangleLinesEx(btnCodex, hoverCodex ? 2.0f : 1.0f, hoverCodex ? CyberColors::NeonYellow : CyberColors::NeonCyan);
|
|
DrawText("[H] CODEX", logX + logW - 87, pvY + 38, 12, hoverCodex ? CyberColors::NeonYellow : RAYWHITE);
|
|
|
|
// 4. Center: Tactical AI Advisor Hologram Ticker
|
|
int advX = 380;
|
|
int advY = 485;
|
|
int advW = 370;
|
|
int advH = 46;
|
|
|
|
DrawRectangle(advX, advY, advW, advH, Color{ 8, 16, 30, 240 });
|
|
DrawRectangleLines(advX, advY, advW, advH, CyberColors::NeonCyan);
|
|
|
|
float aiPulse = 0.5f + 0.5f * std::sin(animTime * 5.0f);
|
|
DrawCircle(advX + 18, advY + 23, 6.0f + aiPulse * 2.0f, CyberColors::NeonCyan);
|
|
DrawCircleLines(advX + 18, advY + 23, 10.0f, CyberColors::NeonYellow);
|
|
DrawText("AI ADVISOR", advX + 32, advY + 8, 10, CyberColors::NeonYellow);
|
|
|
|
std::string advisorMsg = "";
|
|
if (rev.getCurrentAmmo() == enemy->getWeakness()) {
|
|
advisorMsg = "WEAKNESS MATCH! Striking now triggers +100% Critical Surge!";
|
|
} else if (rev.getCurrentAmmo() == AmmoType::EMPTY) {
|
|
advisorMsg = "WARNING: 12:00 chamber is EMPTY! Will misfire. Spin again or shield!";
|
|
} else if (enemy->getShield() > 0 && rev.getCurrentAmmo() == AmmoType::EMP) {
|
|
advisorMsg = "EMP PRIMED: Discharge to collapse enemy barrier and stun!";
|
|
} else if (player.getHp() < 35) {
|
|
advisorMsg = "CRITICAL: Hull integrity low! Overclock Shield [2] recommended!";
|
|
} else if (combat.getTripleCooldown() == 0 && loadedCount >= 3) {
|
|
advisorMsg = "OPPORTUNITY: Fan the Hammer [3] ready for 3-round rapid burst!";
|
|
} else if (enemy->getShield() > 0) {
|
|
advisorMsg = "Hostile shield online. Kinetic sabots or EMP advised.";
|
|
} else {
|
|
advisorMsg = "Spin cylinder [1] to strike, or press [H] to review Ammunition Codex.";
|
|
}
|
|
DrawText(advisorMsg.c_str(), advX + 32, advY + 24, 11, RAYWHITE);
|
|
|
|
// 5. Bottom Action Bar: Cyber Ability Cards
|
|
int barX = 50;
|
|
int barY = 548;
|
|
int barW = GetScreenWidth() - 100;
|
|
int barH = 120;
|
|
|
|
DrawRectangle(barX, barY, barW, barH, Color{ 8, 14, 24, 245 });
|
|
DrawRectangleLines(barX, barY, barW, barH, CyberColors::NeonCyan);
|
|
|
|
if (combat.getPhase() == CombatPhase::PLAYER_TURN) {
|
|
// Card 1: [1 / SPACE] SPIN & SHOOT
|
|
Rectangle btnShoot = { static_cast<float>(barX + 15), static_cast<float>(barY + 12), 370.0f, 96.0f };
|
|
bool hoverShoot = CheckCollisionPointRec(mousePos, btnShoot);
|
|
|
|
DrawRectangleRec(btnShoot, hoverShoot ? Color{ 25, 45, 75, 255 } : Color{ 14, 24, 42, 255 });
|
|
DrawRectangleLinesEx(btnShoot, hoverShoot ? 2.0f : 1.0f, hoverShoot ? CyberColors::NeonGreen : CyberColors::NeonYellow);
|
|
DrawText("// PROTOCOL 01: ROULETTE", barX + 30, barY + 20, 10, CyberColors::NeonCyan);
|
|
DrawText("[1 / SPACE] SPIN & SHOOT", barX + 30, barY + 36, 18, hoverShoot ? CyberColors::NeonGreen : CyberColors::NeonYellow);
|
|
DrawText("Spins cylinder randomly & strikes 12:00 chamber.", barX + 30, barY + 62, 12, RAYWHITE);
|
|
std::string oddsBadge = "ODDS: " + std::to_string(hitChance) + "% HIT / " + std::to_string(blankChance) + "% BLANK";
|
|
DrawText(oddsBadge.c_str(), barX + 30, barY + 82, 12, hitChance >= 50 ? CyberColors::NeonGreen : CyberColors::NeonOrange);
|
|
|
|
// Card 2: [2] OVERCLOCK SHIELD
|
|
Rectangle btnShield = { static_cast<float>(barX + 405), static_cast<float>(barY + 12), 370.0f, 96.0f };
|
|
bool hoverShield = CheckCollisionPointRec(mousePos, btnShield);
|
|
|
|
DrawRectangleRec(btnShield, hoverShield ? Color{ 25, 45, 75, 255 } : Color{ 14, 24, 42, 255 });
|
|
DrawRectangleLinesEx(btnShield, hoverShield ? 2.0f : 1.0f, hoverShield ? CyberColors::NeonGreen : CyberColors::NeonCyan);
|
|
DrawText("// PROTOCOL 02: DEFENSE MATRIX", barX + 420, barY + 20, 10, CyberColors::NeonCyan);
|
|
DrawText("[2] OVERCLOCK SHIELD", barX + 420, barY + 36, 18, hoverShield ? CyberColors::NeonGreen : CyberColors::NeonCyan);
|
|
DrawText("Instantly adds +25 Shield & absorbs 50% damage this turn.", barX + 420, barY + 62, 12, RAYWHITE);
|
|
DrawText("ACTIVE MITIGATION // ALWAYS READY", barX + 420, barY + 82, 12, CyberColors::NeonGreen);
|
|
|
|
// Card 3: [3] FAN THE HAMMER
|
|
int cd = combat.getTripleCooldown();
|
|
Rectangle btnTriple = { static_cast<float>(barX + 795), static_cast<float>(barY + 12), 370.0f, 96.0f };
|
|
bool hoverTriple = CheckCollisionPointRec(mousePos, btnTriple) && (cd == 0);
|
|
|
|
Color tripleBg = (cd == 0) ? (hoverTriple ? Color{ 60, 25, 55, 255 } : Color{ 32, 16, 32, 255 }) : Color{ 14, 16, 22, 255 };
|
|
Color tripleBorder = (cd == 0) ? (hoverTriple ? CyberColors::NeonPink : Color{ 220, 70, 150, 255 }) : Color{ 45, 50, 65, 255 };
|
|
Color tripleTitle = (cd == 0) ? (hoverTriple ? WHITE : CyberColors::NeonPink) : GRAY;
|
|
|
|
DrawRectangleRec(btnTriple, tripleBg);
|
|
DrawRectangleLinesEx(btnTriple, hoverTriple ? 2.0f : 1.0f, tripleBorder);
|
|
DrawText("// PROTOCOL 03: BURST SURGE", barX + 810, barY + 20, 10, (cd == 0) ? CyberColors::NeonPink : GRAY);
|
|
DrawText("[3] FAN THE HAMMER", barX + 810, barY + 36, 18, tripleTitle);
|
|
DrawText("Rapid-fires 3 consecutive chambers in continuous sequence.", barX + 810, barY + 62, 12, (cd == 0) ? RAYWHITE : GRAY);
|
|
|
|
if (cd == 0) {
|
|
DrawText("READY TO FIRE! (Requires 3 rounds in cylinder)", barX + 810, barY + 82, 12, CyberColors::NeonYellow);
|
|
} else {
|
|
DrawText(("RECHARGING [COOLDOWN: " + std::to_string(cd) + " TURNS LEFT]").c_str(), barX + 810, barY + 82, 12, Color{ 150, 155, 170, 255 });
|
|
}
|
|
} else if (combat.getPhase() == CombatPhase::SPINNING_SHOT) {
|
|
float pulse = 0.5f + 0.5f * std::sin(animTime * 10.0f);
|
|
Color spinCol = (pulse > 0.5f) ? CyberColors::NeonYellow : CyberColors::NeonPink;
|
|
DrawText(">>> ROULETTE SPINNING... DECELERATING TO TARGET CHAMBER <<<", barX + 220, barY + 45, 22, spinCol);
|
|
} else if (combat.getPhase() == CombatPhase::TRIPLE_SHOT_BURST) {
|
|
float pulse = 0.5f + 0.5f * std::sin(animTime * 14.0f);
|
|
Color burstCol = (pulse > 0.5f) ? CyberColors::NeonPink : CyberColors::NeonYellow;
|
|
DrawText(">>> FANNING THE HAMMER! 3-ROUND CYBER-BURST RAPID FIRE! <<<", barX + 210, barY + 45, 22, burstCol);
|
|
} else if (combat.getPhase() == CombatPhase::VICTORY) {
|
|
DrawText(">>> HOSTILE SENTINEL TERMINATED! PRESS [SPACE / CLICK] TO ADVANCE <<<", barX + 160, barY + 45, 22, CyberColors::NeonGreen);
|
|
} else if (combat.getPhase() == CombatPhase::DEFEAT) {
|
|
DrawText(">>> SYSTEM COLLAPSE: PLAYER DEFEATED! PRESS [SPACE / CLICK] TO REBOOT <<<", barX + 140, barY + 45, 22, RED);
|
|
} else {
|
|
DrawText("CALCULATING COMBAT RESOLUTION DATA...", barX + 360, barY + 45, 20, CyberColors::NeonCyan);
|
|
}
|
|
|
|
// Floating combat numbers
|
|
for (const auto& ft : combat.getFloatingTexts()) {
|
|
DrawText(ft.text.c_str(), static_cast<int>(ft.pos.x), static_cast<int>(ft.pos.y), 22, ft.color);
|
|
}
|
|
|
|
// 6. Overlay Codex Modal if active
|
|
if (showCodex) {
|
|
drawCodexModal();
|
|
}
|
|
}
|
|
|
|
void UIRenderer::drawLevelClearScreen(int floor) {
|
|
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 8, 16, 235 });
|
|
int cx = GetScreenWidth() / 2;
|
|
int cy = GetScreenHeight() / 2;
|
|
|
|
DrawText(">>> SECTOR SECURED <<<", cx - 180, cy - 60, 30, CyberColors::NeonGreen);
|
|
DrawText(("FLOOR " + std::to_string(floor) + " CLEARED. DATA DOWNLOAD COMPLETE.").c_str(), cx - 220, cy - 10, 18, RAYWHITE);
|
|
DrawText("PRESS [ SPACE ] TO DESCEND TO NEXT DEEPER CYBERSPACE LAYER", cx - 290, cy + 40, 18, CyberColors::NeonYellow);
|
|
}
|
|
|
|
void UIRenderer::drawGameOverScreen() {
|
|
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 6, 12, 245 });
|
|
int cx = GetScreenWidth() / 2;
|
|
int cy = GetScreenHeight() / 2;
|
|
|
|
DrawText(">>> CONNECTION TERMINATED <<<", cx - 240, cy - 60, 32, RED);
|
|
DrawText("FLATLINE: NEURAL INTERFACE DISCONNECTED", cx - 210, cy - 10, 18, GRAY);
|
|
DrawText("PRESS [ SPACE / ENTER ] TO RECONNECT AND START NEW RUN", cx - 260, cy + 50, 18, CyberColors::NeonCyan);
|
|
}
|
|
|
|
void UIRenderer::drawHubHUD(const Player& player, const std::string& prompt) {
|
|
int hudX = 20;
|
|
int hudY = 20;
|
|
DrawRectangle(hudX, hudY, 320, 80, Color{ 16, 22, 30, 220 });
|
|
DrawRectangleLines(hudX, hudY, 320, 80, Color{ 180, 150, 100, 255 });
|
|
|
|
DrawText("PHYSICAL REALITY // ROOM 404", hudX + 15, hudY + 12, 16, Color{ 240, 210, 160, 255 });
|
|
DrawText("NEURAL DECK: ONLINE & READY", hudX + 15, hudY + 38, 14, CyberColors::NeonGreen);
|
|
DrawText("STATUS: SAFE APARTMENT", hudX + 15, hudY + 56, 12, GRAY);
|
|
|
|
if (!prompt.empty()) {
|
|
int promptW = MeasureText(prompt.c_str(), 18) + 40;
|
|
int promptX = (GetScreenWidth() - promptW) / 2;
|
|
int promptY = GetScreenHeight() - 70;
|
|
DrawRectangle(promptX, promptY, promptW, 45, Color{ 18, 25, 36, 240 });
|
|
DrawRectangleLines(promptX, promptY, promptW, 45, CyberColors::NeonGreen);
|
|
DrawText(prompt.c_str(), promptX + 20, promptY + 13, 18, CyberColors::NeonGreen);
|
|
}
|
|
|
|
DrawText("[W/S] WALK | [A/D] TURN 90 | [SPACE/E] INTERACT | [TAB] ARMORY | [ESC] PAUSE MENU",
|
|
20, GetScreenHeight() - 35, 13, Color{ 180, 190, 200, 220 });
|
|
}
|
|
|
|
void UIRenderer::drawRestRoomHUD(int nextFloor, const Player& player, const std::string& prompt) {
|
|
int hudX = 20;
|
|
int hudY = 20;
|
|
DrawRectangle(hudX, hudY, 350, 142, Color{ 14, 18, 28, 230 });
|
|
DrawRectangleLines(hudX, hudY, 350, 142, Color{ 210, 170, 70, 255 });
|
|
|
|
DrawText("PALACE STOREROOM // SAFE REFUGE", hudX + 15, hudY + 10, 15, Color{ 240, 210, 140, 255 });
|
|
DrawText(("NEXT DEPTH: LAYER 0" + std::to_string(nextFloor)).c_str(), hudX + 15, hudY + 30, 13, CyberColors::NeonYellow);
|
|
|
|
// HP Bar
|
|
float hpRatio = static_cast<float>(player.getHp()) / player.getMaxHp();
|
|
drawProgressBar(hudX + 55, hudY + 52, 270, 16, hpRatio, CyberColors::NeonGreen, Color{ 40, 15, 15, 255 });
|
|
DrawText("HP", hudX + 15, hudY + 52, 13, CyberColors::NeonGreen);
|
|
DrawText((std::to_string(player.getHp()) + "/" + std::to_string(player.getMaxHp())).c_str(), hudX + 155, hudY + 53, 12, RAYWHITE);
|
|
|
|
// Shield Bar
|
|
float shRatio = static_cast<float>(player.getShield()) / player.getMaxShield();
|
|
drawProgressBar(hudX + 55, hudY + 74, 270, 16, shRatio, CyberColors::NeonCyan, Color{ 10, 25, 45, 255 });
|
|
DrawText("SHLD", hudX + 15, hudY + 74, 13, CyberColors::NeonCyan);
|
|
DrawText((std::to_string(player.getShield()) + "/" + std::to_string(player.getMaxShield())).c_str(), hudX + 155, hudY + 75, 12, RAYWHITE);
|
|
|
|
DrawText(("PICKS: " + std::to_string(player.getCyberPicks())).c_str(), hudX + 15, hudY + 98, 13, CyberColors::NeonYellow);
|
|
DrawText(("CORE CHIPS: " + std::to_string(player.getTechScraps())).c_str(), hudX + 120, hudY + 98, 13, CyberColors::NeonCyan);
|
|
DrawText("STATUS: PALACE SANCTUARY", hudX + 15, hudY + 118, 12, CyberColors::NeonGreen);
|
|
|
|
// Context Action Prompt
|
|
if (!prompt.empty()) {
|
|
int promptW = MeasureText(prompt.c_str(), 18) + 40;
|
|
int promptX = (GetScreenWidth() - promptW) / 2;
|
|
int promptY = GetScreenHeight() - 70;
|
|
DrawRectangle(promptX, promptY, promptW, 45, Color{ 14, 20, 32, 240 });
|
|
DrawRectangleLines(promptX, promptY, promptW, 45, Color{ 220, 180, 80, 255 });
|
|
DrawText(prompt.c_str(), promptX + 20, promptY + 13, 18, Color{ 255, 220, 100, 255 });
|
|
}
|
|
|
|
DrawText("[W/S] WALK | [A/D] TURN 90 | [SPACE/E] INTERACT | [TAB] ARMORY | [ESC] PAUSE MENU",
|
|
20, GetScreenHeight() - 35, 13, Color{ 180, 190, 200, 220 });
|
|
}
|
|
|
|
Rectangle UIRenderer::getWorkbenchUpgradeButtonRect(int ammoIndex, int screenW, int screenH) {
|
|
int modalW = 1020;
|
|
int modalH = 600;
|
|
int modalX = (screenW - modalW) / 2;
|
|
int modalY = (screenH - modalH) / 2;
|
|
|
|
int col = ammoIndex % 3;
|
|
int row = ammoIndex / 3;
|
|
int cardW = 310;
|
|
int cardH = 220;
|
|
int startX = modalX + 30;
|
|
int startY = modalY + 68;
|
|
int gapX = 15;
|
|
int gapY = 16;
|
|
int cardX = startX + col * (cardW + gapX);
|
|
int cardY = startY + row * (cardH + gapY);
|
|
|
|
return Rectangle{ static_cast<float>(cardX + 12), static_cast<float>(cardY + cardH - 42), static_cast<float>(cardW - 24), 32.0f };
|
|
}
|
|
|
|
Rectangle UIRenderer::getWorkbenchCloseButtonRect(int screenW, int screenH) {
|
|
int modalW = 1020;
|
|
int modalH = 600;
|
|
int modalX = (screenW - modalW) / 2;
|
|
int modalY = (screenH - modalH) / 2;
|
|
return Rectangle{ static_cast<float>(modalX + modalW - 140), static_cast<float>(modalY + modalH - 42), 115.0f, 32.0f };
|
|
}
|
|
|
|
void UIRenderer::drawWorkbenchModal(const Player& player) {
|
|
int screenW = GetScreenWidth();
|
|
int screenH = GetScreenHeight();
|
|
|
|
DrawRectangle(0, 0, screenW, screenH, Color{ 4, 8, 16, 235 });
|
|
|
|
int modalW = 1020;
|
|
int modalH = 600;
|
|
int modalX = (screenW - modalW) / 2;
|
|
int modalY = (screenH - modalH) / 2;
|
|
|
|
// Outer background and classical bronze border
|
|
DrawRectangle(modalX, modalY, modalW, modalH, Color{ 10, 16, 26, 255 });
|
|
DrawRectangleLines(modalX, modalY, modalW, modalH, Color{ 200, 160, 60, 255 });
|
|
|
|
// Decorative corners
|
|
DrawRectangle(modalX - 2, modalY - 2, 8, 8, Color{ 220, 180, 80, 255 });
|
|
DrawRectangle(modalX + modalW - 6, modalY - 2, 8, 8, Color{ 220, 180, 80, 255 });
|
|
DrawRectangle(modalX - 2, modalY + modalH - 6, 8, 8, Color{ 220, 180, 80, 255 });
|
|
DrawRectangle(modalX + modalW - 6, modalY + modalH - 6, 8, 8, Color{ 220, 180, 80, 255 });
|
|
|
|
// Header Banner
|
|
DrawRectangle(modalX, modalY, modalW, 52, Color{ 18, 24, 38, 255 });
|
|
DrawRectangleLines(modalX, modalY, modalW, 52, Color{ 140, 110, 50, 255 });
|
|
|
|
DrawText("// CRAFTSMAN WORKBENCH // RUN CARTRIDGE FORGE & OVERCLOCK", modalX + 25, modalY + 16, 17, Color{ 255, 215, 80, 255 });
|
|
|
|
// Live Core Chips Balance badge in header
|
|
std::string scrapBadge = "[ ❖ CORE CHIPS: " + std::to_string(player.getTechScraps()) + " ]";
|
|
int badgeW = MeasureText(scrapBadge.c_str(), 16);
|
|
DrawRectangle(modalX + modalW - badgeW - 40, modalY + 12, badgeW + 20, 28, Color{ 8, 28, 38, 255 });
|
|
DrawRectangleLines(modalX + modalW - badgeW - 40, modalY + 12, badgeW + 20, 28, CyberColors::NeonCyan);
|
|
DrawText(scrapBadge.c_str(), modalX + modalW - badgeW - 30, modalY + 18, 16, CyberColors::NeonCyan);
|
|
|
|
Vector2 mouse = GetMousePosition();
|
|
|
|
AmmoType ammos[6] = {
|
|
AmmoType::KINETIC,
|
|
AmmoType::EMP,
|
|
AmmoType::OVERHEAT,
|
|
AmmoType::CORROSIVE,
|
|
AmmoType::GLITCH,
|
|
AmmoType::NANITE
|
|
};
|
|
|
|
int startX = modalX + 30;
|
|
int startY = modalY + 68;
|
|
int cardW = 310;
|
|
int cardH = 220;
|
|
int gapX = 15;
|
|
int gapY = 16;
|
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
AmmoType ammo = ammos[i];
|
|
const auto& info = getAmmoInfo(ammo);
|
|
int curTier = player.getAmmoTier(ammo);
|
|
const auto& curTierData = getAmmoTierData(ammo, curTier);
|
|
|
|
int col = i % 3;
|
|
int row = i / 3;
|
|
int cardX = startX + col * (cardW + gapX);
|
|
int cardY = startY + row * (cardH + gapY);
|
|
|
|
Rectangle btnRect = getWorkbenchUpgradeButtonRect(i, screenW, screenH);
|
|
bool isHoverBtn = CheckCollisionPointRec(mouse, btnRect);
|
|
|
|
// Card container
|
|
DrawRectangle(cardX, cardY, cardW, cardH, Color{ 14, 20, 32, 255 });
|
|
DrawRectangleLines(cardX, cardY, cardW, cardH, isHoverBtn ? CyberColors::NeonYellow : info.color);
|
|
|
|
// Header inside card: Ammo icon & Name
|
|
DrawCircle(cardX + 22, cardY + 24, 10.0f, info.color);
|
|
DrawCircleLines(cardX + 22, cardY + 24, 10.0f, WHITE);
|
|
|
|
std::string nameTxt = info.name + " [" + info.shortName + "]";
|
|
DrawText(nameTxt.c_str(), cardX + 40, cardY + 15, 13, info.color);
|
|
|
|
// Tier badge
|
|
DrawText(curTierData.tierBadge.c_str(), cardX + cardW - 75, cardY + 15, 13, Color{ 255, 215, 60, 255 });
|
|
|
|
// Tier Title (e.g. TIER I: BASE SPEC)
|
|
DrawText(curTierData.tierTitle.c_str(), cardX + 16, cardY + 44, 12, Color{ 220, 190, 120, 255 });
|
|
|
|
// Damage display
|
|
int effDmg = player.getEffectiveDamage(ammo);
|
|
std::string dmgStr = "EFFECTIVE DAMAGE: " + std::to_string(effDmg);
|
|
if (curTier < 3) {
|
|
int nextDmg = getAmmoTierData(ammo, curTier + 1).damage;
|
|
dmgStr += " -> " + std::to_string(nextDmg);
|
|
}
|
|
DrawText(dmgStr.c_str(), cardX + 16, cardY + 64, 12, RAYWHITE);
|
|
|
|
// Current Tier Perk & Effects
|
|
DrawRectangle(cardX + 12, cardY + 84, cardW - 24, 84, Color{ 10, 14, 24, 255 });
|
|
DrawRectangleLines(cardX + 12, cardY + 84, cardW - 24, 84, Color{ 35, 45, 65, 255 });
|
|
|
|
// Format perk text wrapping
|
|
DrawText("PERK:", cardX + 18, cardY + 90, 10, GRAY);
|
|
std::string perk = curTierData.perkDesc;
|
|
size_t mid1 = perk.find(". ");
|
|
if (mid1 != std::string::npos && mid1 < perk.length() - 2) {
|
|
std::string line1 = perk.substr(0, mid1 + 1);
|
|
std::string line2 = perk.substr(mid1 + 2);
|
|
DrawText(line1.c_str(), cardX + 18, cardY + 104, 11, CyberColors::NeonCyan);
|
|
DrawText(line2.c_str(), cardX + 18, cardY + 122, 11, RAYWHITE);
|
|
} else {
|
|
DrawText(perk.c_str(), cardX + 18, cardY + 106, 11, CyberColors::NeonCyan);
|
|
}
|
|
|
|
// Bottom Action Button / Status
|
|
if (curTier >= 3) {
|
|
DrawRectangleRec(btnRect, Color{ 40, 34, 14, 255 });
|
|
DrawRectangleLinesEx(btnRect, 1.0f, Color{ 220, 180, 50, 255 });
|
|
DrawText("[ ★ MASTER SPEC REACHED ★ ]", cardX + 32, static_cast<int>(btnRect.y + 9), 12, Color{ 255, 215, 60, 255 });
|
|
} else {
|
|
int nextTier = curTier + 1;
|
|
const auto& nextData = getAmmoTierData(ammo, nextTier);
|
|
int cost = nextData.upgradeCost;
|
|
bool canAfford = player.canUpgradeAmmo(ammo);
|
|
|
|
Color btnBg = canAfford ? (isHoverBtn ? Color{ 25, 65, 45, 255 } : Color{ 16, 42, 30, 255 })
|
|
: Color{ 30, 18, 22, 255 };
|
|
Color btnLine = canAfford ? (isHoverBtn ? CyberColors::NeonYellow : CyberColors::NeonGreen)
|
|
: Color{ 120, 50, 60, 255 };
|
|
Color btnText = canAfford ? (isHoverBtn ? CyberColors::NeonYellow : CyberColors::NeonGreen)
|
|
: Color{ 190, 110, 120, 255 };
|
|
|
|
DrawRectangleRec(btnRect, btnBg);
|
|
DrawRectangleLinesEx(btnRect, isHoverBtn ? 2.0f : 1.0f, btnLine);
|
|
|
|
std::string btnLabel = "[" + std::to_string(i + 1) + "] UPGRADE TO TIER " + (nextTier == 2 ? "II" : "III") + " (" + std::to_string(cost) + " ❖)";
|
|
if (!canAfford) {
|
|
btnLabel = "[" + std::to_string(i + 1) + "] NEED " + std::to_string(cost) + " ❖ (LACK " + std::to_string(cost - player.getTechScraps()) + ")";
|
|
}
|
|
|
|
int labelW = MeasureText(btnLabel.c_str(), 12);
|
|
DrawText(btnLabel.c_str(), cardX + (cardW - labelW) / 2, static_cast<int>(btnRect.y + 9), 12, btnText);
|
|
}
|
|
}
|
|
|
|
// Footer Info & Close Button
|
|
DrawText("[1 - 6] OR CLICK BUTTON TO UPGRADE CARTRIDGE | [ESC / E] CLOSE WORKBENCH",
|
|
modalX + 30, modalY + modalH - 32, 13, RAYWHITE);
|
|
|
|
Rectangle btnClose = getWorkbenchCloseButtonRect(screenW, screenH);
|
|
bool isHoverClose = CheckCollisionPointRec(mouse, btnClose);
|
|
DrawRectangleRec(btnClose, isHoverClose ? Color{ 45, 25, 30, 255 } : Color{ 24, 16, 20, 255 });
|
|
DrawRectangleLinesEx(btnClose, isHoverClose ? 2.0f : 1.0f, isHoverClose ? RED : Color{ 180, 80, 90, 255 });
|
|
DrawText("[ESC] CLOSE", static_cast<int>(btnClose.x + 18), static_cast<int>(btnClose.y + 9), 13, isHoverClose ? RED : RAYWHITE);
|
|
}
|
|
|
|
|
|
void UIRenderer::drawPauseMenu(bool hasSavedMessage) {
|
|
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 8, 16, 225 });
|
|
|
|
int menuW = 480;
|
|
int menuH = 430;
|
|
int menuX = (GetScreenWidth() - menuW) / 2;
|
|
int menuY = (GetScreenHeight() - menuH) / 2;
|
|
|
|
DrawRectangle(menuX, menuY, menuW, menuH, Color{ 10, 16, 28, 255 });
|
|
DrawRectangleLines(menuX, menuY, menuW, menuH, CyberColors::NeonCyan);
|
|
|
|
DrawRectangle(menuX, menuY, menuW, 50, Color{ 16, 26, 44, 255 });
|
|
DrawText("// PROTOCOL PAUSED", menuX + 25, menuY + 16, 20, CyberColors::NeonCyan);
|
|
|
|
if (hasSavedMessage) {
|
|
DrawText(">>> RUN SAVED TO DISK (savegame.dat) <<<", menuX + 45, menuY + 62, 14, CyberColors::NeonGreen);
|
|
} else {
|
|
DrawText("SESSION SUSPENDED // SELECT ACTION", menuX + 60, menuY + 62, 13, GRAY);
|
|
}
|
|
|
|
Vector2 mouse = GetMousePosition();
|
|
|
|
const char* labels[] = {
|
|
"[1 / ESC] RESUME SESSION",
|
|
"[2] AUDIO & MUSIC SETTINGS",
|
|
"[3] SAVE PROGRESS TO DISK",
|
|
"[4] RETURN TO ROOM HUB",
|
|
"[5] EXIT TO DESKTOP"
|
|
};
|
|
|
|
int startY = menuY + 90;
|
|
for (int i = 0; i < 5; ++i) {
|
|
Rectangle btnRect = { static_cast<float>(menuX + 30), static_cast<float>(startY + i * 58), static_cast<float>(menuW - 60), 48.0f };
|
|
bool isHover = CheckCollisionPointRec(mouse, btnRect);
|
|
|
|
DrawRectangleRec(btnRect, isHover ? Color{ 30, 55, 90, 255 } : Color{ 16, 25, 40, 255 });
|
|
DrawRectangleLinesEx(btnRect, isHover ? 2.0f : 1.0f, isHover ? CyberColors::NeonGreen : (i == 4 ? RED : CyberColors::NeonCyan));
|
|
|
|
DrawText(labels[i], menuX + 50, startY + i * 58 + 15, 16, isHover ? CyberColors::NeonYellow : (i == 4 ? Color{ 255, 100, 100, 255 } : RAYWHITE));
|
|
}
|
|
}
|
|
|
|
void UIRenderer::drawMainMenu(bool canContinue) {
|
|
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 8, 16, 255 });
|
|
|
|
drawMatrixDigitalRain(static_cast<float>(GetTime()));
|
|
|
|
int cx = GetScreenWidth() / 2;
|
|
int cy = GetScreenHeight() / 2;
|
|
|
|
DrawText("DOORNROLL", cx - 180, cy - 200, 54, CyberColors::NeonCyan);
|
|
DrawText("// CYBERSPACE PROTOCOL & PHYSICAL REALITY", cx - 210, cy - 135, 16, CyberColors::NeonGreen);
|
|
|
|
Vector2 mouse = GetMousePosition();
|
|
|
|
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 hoverCont = CheckCollisionPointRec(mouse, btnContinue) && canContinue;
|
|
bool hoverNew = CheckCollisionPointRec(mouse, btnNewRun);
|
|
bool hoverSet = CheckCollisionPointRec(mouse, btnSettings);
|
|
bool hoverExit = CheckCollisionPointRec(mouse, btnExit);
|
|
|
|
if (canContinue) {
|
|
DrawRectangleRec(btnContinue, hoverCont ? Color{ 30, 60, 95, 255 } : Color{ 16, 28, 48, 255 });
|
|
DrawRectangleLinesEx(btnContinue, hoverCont ? 2.0f : 1.0f, hoverCont ? CyberColors::NeonGreen : CyberColors::NeonCyan);
|
|
DrawText("[1] CONTINUE SAVED RUN", cx - 130, cy - 35, 18, hoverCont ? CyberColors::NeonYellow : RAYWHITE);
|
|
} else {
|
|
DrawRectangleRec(btnContinue, Color{ 12, 18, 28, 255 });
|
|
DrawRectangleLinesEx(btnContinue, 1.0f, DARKGRAY);
|
|
DrawText("CONTINUE (NO SAVE DATA)", cx - 130, cy - 35, 16, DARKGRAY);
|
|
}
|
|
|
|
DrawRectangleRec(btnNewRun, hoverNew ? Color{ 30, 60, 95, 255 } : Color{ 16, 28, 48, 255 });
|
|
DrawRectangleLinesEx(btnNewRun, hoverNew ? 2.0f : 1.0f, hoverNew ? CyberColors::NeonGreen : CyberColors::NeonCyan);
|
|
DrawText("[2] NEW RUN // REALITY ROOM", cx - 145, cy + 25, 18, hoverNew ? CyberColors::NeonYellow : RAYWHITE);
|
|
|
|
DrawRectangleRec(btnSettings, hoverSet ? Color{ 30, 60, 95, 255 } : Color{ 16, 28, 48, 255 });
|
|
DrawRectangleLinesEx(btnSettings, hoverSet ? 2.0f : 1.0f, hoverSet ? CyberColors::NeonGreen : CyberColors::NeonCyan);
|
|
DrawText("[3] AUDIO & MUSIC SETTINGS", cx - 145, cy + 85, 18, hoverSet ? CyberColors::NeonYellow : RAYWHITE);
|
|
|
|
DrawRectangleRec(btnExit, hoverExit ? Color{ 50, 20, 30, 255 } : Color{ 20, 14, 22, 255 });
|
|
DrawRectangleLinesEx(btnExit, hoverExit ? 2.0f : 1.0f, hoverExit ? RED : Color{ 120, 40, 50, 255 });
|
|
DrawText("[4] QUIT TO DESKTOP", cx - 105, cy + 145, 18, hoverExit ? RED : GRAY);
|
|
|
|
drawMatrixOverlay(static_cast<float>(GetTime()));
|
|
}
|
|
|
|
void UIRenderer::drawSettingsMenu(float masterVol, float musicVol, float sfxVol, int selectedSlider) {
|
|
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 8, 16, 230 });
|
|
|
|
int menuW = 580;
|
|
int menuH = 430;
|
|
int menuX = (GetScreenWidth() - menuW) / 2;
|
|
int menuY = (GetScreenHeight() - menuH) / 2;
|
|
|
|
DrawRectangle(menuX, menuY, menuW, menuH, Color{ 10, 16, 28, 255 });
|
|
DrawRectangleLines(menuX, menuY, menuW, menuH, CyberColors::NeonCyan);
|
|
|
|
// Title Bar
|
|
DrawRectangle(menuX, menuY, menuW, 52, Color{ 16, 26, 44, 255 });
|
|
DrawText("// AUDIO CONFIGURATION PROTOCOL", menuX + 25, menuY + 16, 20, CyberColors::NeonCyan);
|
|
|
|
Vector2 mouse = GetMousePosition();
|
|
|
|
const char* titles[] = { "MASTER VOLUME", "MUSIC VOLUME", "SFX VOLUME" };
|
|
float vols[] = { masterVol, musicVol, sfxVol };
|
|
|
|
int startY = menuY + 85;
|
|
int sliderW = 340;
|
|
int sliderH = 26;
|
|
int sliderX = menuX + 180;
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
int itemY = startY + i * 78;
|
|
bool isSel = (i == selectedSlider);
|
|
|
|
Rectangle itemBox = { static_cast<float>(menuX + 20), static_cast<float>(itemY - 10), static_cast<float>(menuW - 40), 65.0f };
|
|
bool isHover = CheckCollisionPointRec(mouse, itemBox);
|
|
|
|
if (isSel || isHover) {
|
|
DrawRectangleRec(itemBox, Color{ 20, 32, 52, 140 });
|
|
DrawRectangleLinesEx(itemBox, 1.0f, isSel ? CyberColors::NeonYellow : Color{ 40, 70, 110, 255 });
|
|
}
|
|
|
|
DrawText(titles[i], menuX + 30, itemY + 4, 15, isSel ? CyberColors::NeonYellow : RAYWHITE);
|
|
|
|
Rectangle sBar = { static_cast<float>(sliderX), static_cast<float>(itemY), static_cast<float>(sliderW), static_cast<float>(sliderH) };
|
|
DrawRectangleRec(sBar, Color{ 14, 20, 32, 255 });
|
|
DrawRectangleLinesEx(sBar, 1.0f, isSel ? CyberColors::NeonGreen : Color{ 50, 75, 110, 255 });
|
|
|
|
int fillW = static_cast<int>(vols[i] * sliderW);
|
|
Color fillCol = (i == 0) ? CyberColors::NeonCyan : ((i == 1) ? CyberColors::NeonPurple : CyberColors::NeonGreen);
|
|
DrawRectangle(sliderX + 2, itemY + 2, fillW - 4 > 0 ? fillW - 4 : 0, sliderH - 4, fillCol);
|
|
|
|
int knobX = sliderX + fillW;
|
|
DrawRectangle(knobX - 4, itemY - 3, 8, sliderH + 6, WHITE);
|
|
|
|
std::string pctStr = std::to_string(static_cast<int>(std::round(vols[i] * 100.0f))) + "%";
|
|
DrawText(pctStr.c_str(), sliderX + sliderW + 15, itemY + 4, 16, isSel ? CyberColors::NeonYellow : RAYWHITE);
|
|
}
|
|
|
|
Rectangle btnBack = { static_cast<float>(menuX + (menuW - 240) / 2), static_cast<float>(menuY + menuH - 65), 240.0f, 44.0f };
|
|
bool hoverBack = CheckCollisionPointRec(mouse, btnBack);
|
|
DrawRectangleRec(btnBack, hoverBack ? Color{ 30, 60, 95, 255 } : Color{ 16, 26, 42, 255 });
|
|
DrawRectangleLinesEx(btnBack, hoverBack ? 2.0f : 1.0f, hoverBack ? CyberColors::NeonGreen : CyberColors::NeonCyan);
|
|
DrawText("[ESC / ENTER] SAVE & RETURN", static_cast<int>(btnBack.x) + 18, static_cast<int>(btnBack.y) + 13, 14, hoverBack ? CyberColors::NeonYellow : RAYWHITE);
|
|
|
|
DrawText("[W/S] SELECT SLIDER | [A/D] ADJUST +/-5% | CLICK & DRAG SLIDERS",
|
|
menuX + 40, menuY + menuH - 18, 12, GRAY);
|
|
}
|
|
|
|
} // namespace dnr
|