From 4cad7192907e20b6b761d341ff01673bde40b495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=A4=D0=B5=D0=B4=D0=BE?= =?UTF-8?q?=D1=81=D0=B5=D0=B5=D0=B2?= Date: Tue, 6 Oct 2026 21:15:02 +0300 Subject: [PATCH] feat: Cyber-noir hub room lighting, pixel wave transitions, tactical UI & ammo overhaul - 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 --- assets/Pictures/Room_window.jpg | Bin 0 -> 157349 bytes assets/shaders/postprocess.fs | 61 +-- src/combat/CombatSystem.cpp | 3 + src/combat/CombatSystem.hpp | 4 + src/combat/Enemy.cpp | 8 +- src/core/Common.hpp | 91 ++-- src/core/Game.cpp | 23 +- src/core/Game.hpp | 2 + src/inventory/InventoryUI.cpp | 234 +++++---- src/render/CyberRenderer.cpp | 854 +++++++++++++++++++------------- src/render/CyberRenderer.hpp | 1 + src/render/ScreenTransition.hpp | 144 ++++++ src/render/UIRenderer.cpp | 574 ++++++++++++--------- src/render/UIRenderer.hpp | 1 + 14 files changed, 1230 insertions(+), 770 deletions(-) create mode 100644 assets/Pictures/Room_window.jpg create mode 100644 src/render/ScreenTransition.hpp diff --git a/assets/Pictures/Room_window.jpg b/assets/Pictures/Room_window.jpg new file mode 100644 index 0000000000000000000000000000000000000000..522f0cb3b9a6ae45e52beb647f9e966d25c258cd GIT binary patch literal 157349 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 RDbY%#ZzOZmeCqN?|JlW>jNkwO literal 0 HcmV?d00001 diff --git a/assets/shaders/postprocess.fs b/assets/shaders/postprocess.fs index a1ec93a..2f9d3a1 100644 --- a/assets/shaders/postprocess.fs +++ b/assets/shaders/postprocess.fs @@ -19,17 +19,17 @@ void main() { vec2 uv = fragTexCoord; - // 1. Digital Glitch / Chromatic Aberration + // 1. Digital Glitch / Tactical Chromatic Aberration float glitch = clamp(u_glitchIntensity, 0.0, 1.0); if (glitch > 0.001) { // Horizontal line slice displacement - float slice = step(0.65, sin(uv.y * 35.0 + u_time * 25.0)); - float sliceOffset = (slice * 2.0 - 1.0) * 0.018 * glitch; + float slice = step(0.68, sin(uv.y * 42.0 + u_time * 30.0)); + float sliceOffset = (slice * 2.0 - 1.0) * 0.015 * glitch; uv.x += sliceOffset; // Chromatic RGB separation - float split = 0.012 * glitch; + float split = 0.010 * glitch; float r = texture(texture0, vec2(uv.x + split, uv.y)).r; float g = texture(texture0, uv).g; float b = texture(texture0, vec2(uv.x - split, uv.y)).b; @@ -40,43 +40,34 @@ void main() finalColor = texture(texture0, uv); } - // 2. Neon Bloom / Emissive Halo - vec2 texel = 1.0 / u_resolution; - vec3 bloom = vec3(0.0); - float bloomWeight = 0.0; - - // 9-tap bright-pass sample - vec2 offsets[8] = vec2[]( - vec2(-2.0, 0.0), vec2( 2.0, 0.0), - vec2( 0.0, -2.0), vec2( 0.0, 2.0), - vec2(-1.5, -1.5), vec2( 1.5, -1.5), - vec2(-1.5, 1.5), vec2( 1.5, 1.5) - ); - - for (int i = 0; i < 8; ++i) + // 2. Tactical Color Grading (High Contrast Monochrome + Vivid Crimson Accent) + // Desaturate non-red tones toward tactical gunmetal/monochrome + float luma = dot(finalColor.rgb, vec3(0.299, 0.587, 0.114)); + + // Check if the color is an intentional tactical accent (Crimson, Cobalt EMP, Amber, Acid, Violet) + float maxC = max(finalColor.r, max(finalColor.g, finalColor.b)); + float minC = min(finalColor.r, min(finalColor.g, finalColor.b)); + float sat = (maxC > 0.05) ? ((maxC - minC) / maxC) : 0.0; + bool isTacticalAccent = (sat > 0.36) || (finalColor.r > 0.35 && finalColor.r > finalColor.g * 1.3); + + if (!isTacticalAccent) { - vec3 col = texture(texture0, uv + offsets[i] * texel * 1.8).rgb; - float brightness = dot(col, vec3(0.2126, 0.7152, 0.0722)); - if (brightness > 0.55) - { - bloom += (col - 0.45) * 1.4; - bloomWeight += 1.0; - } + // Pull low-saturation environmental surfaces toward high-contrast monochrome steel + vec3 mono = vec3(luma); + finalColor.rgb = mix(finalColor.rgb, mono, 0.80); } - if (bloomWeight > 0.0) - { - bloom /= 8.0; - finalColor.rgb += bloom * 0.75; - } + // Enhance contrast (crush deep blacks, crispen bright highlights) + finalColor.rgb = (finalColor.rgb - 0.5) * 1.18 + 0.5; + finalColor.rgb = clamp(finalColor.rgb, 0.0, 1.0); - // 3. Vignette (Smooth cinematic edge darkening) + // 3. Cinematic Corner Vignette (Tactical Helmet Visor Shadow) float d = distance(uv, vec2(0.5, 0.5)); - float vignette = smoothstep(0.82, 0.38, d); - finalColor.rgb *= mix(0.58, 1.0, vignette); + float vignette = smoothstep(0.78, 0.32, d); + finalColor.rgb *= mix(0.40, 1.0, vignette); - // 4. Subtle Cyber Scanlines - float scanline = sin(uv.y * u_resolution.y * 1.8) * 0.035; + // 4. Subtle Tactical CRT Scanlines + float scanline = sin(uv.y * u_resolution.y * 1.5) * 0.025; finalColor.rgb -= scanline; finalColor.a = 1.0; diff --git a/src/combat/CombatSystem.cpp b/src/combat/CombatSystem.cpp index 902554f..d695e6f 100644 --- a/src/combat/CombatSystem.cpp +++ b/src/combat/CombatSystem.cpp @@ -14,6 +14,7 @@ void CombatSystem::startCombat(Player& player, std::unique_ptr enemy) { m_ghostBonusDmg = false; m_defeatedEnemyWasElite = false; m_tripleCooldown = 0; + m_lastFiredAmmo = AmmoType::EMPTY; m_combatLog.clear(); m_floatingTexts.clear(); @@ -249,6 +250,7 @@ void CombatSystem::applyAmmoStatus(AmmoType chamberAmmo, int dealt, Player& play void CombatSystem::resolveBurstShot(Player& player, int shotNum) { AmmoType chamberAmmo = player.getRevolver().fire(); + m_lastFiredAmmo = chamberAmmo; const auto& info = getAmmoInfo(chamberAmmo); float offsetX = (shotNum - 2) * 45.0f; @@ -291,6 +293,7 @@ void CombatSystem::resolveBurstShot(Player& player, int shotNum) { void CombatSystem::resolveShotImpact(Player& player) { AmmoType chamberAmmo = player.getRevolver().fire(); + m_lastFiredAmmo = chamberAmmo; const auto& info = getAmmoInfo(chamberAmmo); if (chamberAmmo == AmmoType::EMPTY) { diff --git a/src/combat/CombatSystem.hpp b/src/combat/CombatSystem.hpp index e70332c..e663424 100644 --- a/src/combat/CombatSystem.hpp +++ b/src/combat/CombatSystem.hpp @@ -56,6 +56,9 @@ public: void addLog(const std::string& msg); void addFloatingText(const std::string& text, Vector2 pos, Color color); + AmmoType getLastFiredAmmo() const { return m_lastFiredAmmo; } + float getPhaseTimer() const { return m_phaseTimer; } + private: void resolveShotImpact(Player& player); void resolveBurstShot(Player& player, int shotNum); @@ -74,6 +77,7 @@ private: bool m_shieldBoosted = false; bool m_ghostBonusDmg = false; bool m_defeatedEnemyWasElite = false; + AmmoType m_lastFiredAmmo = AmmoType::EMPTY; std::vector m_combatLog; std::vector m_floatingTexts; diff --git a/src/combat/Enemy.cpp b/src/combat/Enemy.cpp index 5eb9990..e1d8c46 100644 --- a/src/combat/Enemy.cpp +++ b/src/combat/Enemy.cpp @@ -12,7 +12,7 @@ Enemy::Enemy(EnemyType type, int level) : m_type(type), m_level(level) { m_baseDamage = 12 + level * 3; m_attackElement = AmmoType::OVERHEAT; m_weakness = AmmoType::EMP; - m_color = CyberColors::NeonOrange; + m_color = TacticalColors::PureWhite; break; case EnemyType::ICE_SENTINEL: @@ -22,7 +22,7 @@ Enemy::Enemy(EnemyType type, int level) : m_type(type), m_level(level) { m_baseDamage = 14 + level * 3; m_attackElement = AmmoType::EMP; m_weakness = AmmoType::CORROSIVE; - m_color = CyberColors::NeonCyan; + m_color = TacticalColors::Crimson; break; case EnemyType::GLITCH_WRAITH: @@ -32,7 +32,7 @@ Enemy::Enemy(EnemyType type, int level) : m_type(type), m_level(level) { m_baseDamage = 18 + level * 4; m_attackElement = AmmoType::GLITCH; m_weakness = AmmoType::KINETIC; - m_color = CyberColors::NeonPurple; + m_color = TacticalColors::BrightRed; break; case EnemyType::VIRUS_CONSTRUCT: @@ -42,7 +42,7 @@ Enemy::Enemy(EnemyType type, int level) : m_type(type), m_level(level) { m_baseDamage = 15 + level * 3; m_attackElement = AmmoType::CORROSIVE; m_weakness = AmmoType::OVERHEAT; - m_color = CyberColors::NeonGreen; + m_color = TacticalColors::DarkRed; break; } diff --git a/src/core/Common.hpp b/src/core/Common.hpp index 51b831c..c0ea284 100644 --- a/src/core/Common.hpp +++ b/src/core/Common.hpp @@ -44,13 +44,13 @@ struct AmmoInfo { }; inline const AmmoInfo& getAmmoInfo(AmmoType type) { - static const AmmoInfo infoEmpty { AmmoType::EMPTY, "Empty Shell", "BLANK", "Empty chamber - no round.", DARKGRAY, 0, 0.0f }; - static const AmmoInfo infoKinetic { AmmoType::KINETIC, "Kinetic Pierce", "KINET", "Heavy kinetic sabot. High raw impact.", { 240, 240, 240, 255 }, 35, 0.25f }; - static const AmmoInfo infoEMP { AmmoType::EMP, "EMP Shock", "EMP", "Electro-magnetic pulse. Destroys shields, stuns machine.", { 0, 220, 255, 255 }, 20, 0.70f }; - static const AmmoInfo infoOverheat { AmmoType::OVERHEAT, "Overheat Core", "THERM", "Thermal incendiary round. Ignites target CPU for 3 turns.", { 255, 80, 20, 255 }, 18, 1.0f }; - static const AmmoInfo infoCorrosive { AmmoType::CORROSIVE, "Acid Corrosive", "ACID", "Sub-routine dissolver. Destroys armor, targets take +50% dmg.", { 120, 255, 0, 255 }, 15, 1.0f }; - static const AmmoInfo infoGlitch { AmmoType::GLITCH, "Glitch Cryo", "GLITCH", "Corrupting code payload. Drops enemy accuracy drastically.", { 220, 50, 255, 255 }, 22, 0.80f }; - static const AmmoInfo infoNanite { AmmoType::NANITE, "Nanite Siphon", "NANITE", "Vampiric micro-drones. Deals damage & restores Player HP/Shield.", { 0, 255, 180, 255 }, 16, 1.0f }; + static const AmmoInfo infoEmpty { AmmoType::EMPTY, "Empty Shell", "---", "Empty chamber - no round.", { 50, 60, 75, 255 }, 0, 0.0f }; + static const AmmoInfo infoKinetic { AmmoType::KINETIC, "Kinetic Pierce", "KIN", "Heavy kinetic sabot. High raw impact.", { 240, 245, 255, 255 }, 35, 0.25f }; + static const AmmoInfo infoEMP { AmmoType::EMP, "EMP Shock", "EMP", "Electro-magnetic pulse. Destroys shields, stuns machine.", { 70, 150, 255, 255 }, 20, 0.70f }; + static const AmmoInfo infoOverheat { AmmoType::OVERHEAT, "Overheat Core", "THM", "Thermal incendiary round. Ignites target CPU for 3 turns.", { 245, 130, 32, 255 }, 18, 1.0f }; + static const AmmoInfo infoCorrosive { AmmoType::CORROSIVE, "Acid Corrosive", "ACD", "Sub-routine dissolver. Destroys armor, targets take +50% dmg.", { 95, 185, 75, 255 }, 15, 1.0f }; + static const AmmoInfo infoGlitch { AmmoType::GLITCH, "Glitch Cryo", "GLT", "Corrupting code payload. Drops enemy accuracy drastically.", { 175, 95, 245, 255 }, 22, 0.80f }; + static const AmmoInfo infoNanite { AmmoType::NANITE, "Nanite Siphon", "NAN", "Vampiric micro-drones. Deals damage & restores Player HP/Shield.", { 225, 25, 55, 255 }, 16, 1.0f }; switch (type) { case AmmoType::KINETIC: return infoKinetic; @@ -246,58 +246,94 @@ struct ImplantData { std::string code; }; +// Tactical Military / Arasaka Brutalism color palette (Monochrome + Crimson Red) +namespace TacticalColors { + // Backgrounds & Base Panels + inline const Color PitchBlack = { 6, 8, 12, 255 }; // Deepest background + inline const Color DarkPanel = { 12, 16, 22, 240 }; // Modal & panel backgrounds + inline const Color SurfacePlate = { 20, 26, 36, 255 }; // Button & card backgrounds + inline const Color SteelMuted = { 45, 55, 70, 255 }; // Dividers, disabled frames + + // Monochrome Typography & Contours + inline const Color PureWhite = { 255, 255, 255, 255 }; // High-contrast headers, active labels + inline const Color SilverText = { 210, 218, 230, 255 }; // Normal body text + inline const Color MutedText = { 120, 132, 148, 255 }; // Subtitles, inactive hints + inline const Color BorderMuted = { 60, 72, 90, 255 }; // Unfocused border + inline const Color BorderWhite = { 220, 230, 245, 255 }; // Focused / hover border + + // Crimson & Tactical Red Accents + inline const Color Crimson = { 230, 25, 45, 255 }; // Primary red accent, reticles, striker + inline const Color BrightRed = { 255, 60, 75, 255 }; // Critical hit, warnings, alerts + inline const Color DarkRed = { 70, 15, 22, 255 }; // Warning backing, alarm panels + inline const Color CrimsonGlow = { 230, 25, 45, 90 }; // Low alpha red accent +} + +// Backward-compatibility alias namespace for gradual migration +namespace CyberColors { + inline const Color NeonCyan = TacticalColors::BorderWhite; + inline const Color NeonPink = TacticalColors::Crimson; + inline const Color NeonGreen = TacticalColors::PureWhite; + inline const Color NeonYellow = TacticalColors::Crimson; + inline const Color NeonOrange = TacticalColors::BrightRed; + inline const Color NeonPurple = TacticalColors::SilverText; + inline const Color DarkGrid = TacticalColors::PitchBlack; + inline const Color TerminalBg = TacticalColors::DarkPanel; + inline const Color WallBorder = TacticalColors::SteelMuted; + inline const Color WallGlow = TacticalColors::CrimsonGlow; +} + inline ImplantData getImplantData(ImplantType type) { switch (type) { case ImplantType::GHOST_CHAMBER: return { type, "GHOST CHAMBER", "ROULETTE", ImplantArchetype::CYLINDER, "Empty chamber gives +20 Shield and +50% damage on next shot.", - Color{ 0, 240, 255, 255 }, "[GHOST]", "GHOST" }; + TacticalColors::PureWhite, "[GHOST]", "GHOST" }; case ImplantType::ADRENALINE_DRUM: return { type, "ADRENALINE DRUM", "ROULETTE", ImplantArchetype::CYLINDER, "Killing a drone instantly loads +1 random ammo & grants a free turn.", - Color{ 255, 230, 0, 255 }, "[ADREN]", "ADREN" }; + TacticalColors::Crimson, "[ADREN]", "ADREN" }; case ImplantType::HEAVY_HAMMER: return { type, "HEAVY HAMMER", "ROULETTE", ImplantArchetype::CYLINDER, "Single shots deal +30% damage. Triple burst cooldown reduced to 1 turn.", - Color{ 255, 140, 30, 255 }, "[HAMMR]", "HAMMR" }; + TacticalColors::SilverText, "[HAMMR]", "HAMMR" }; case ImplantType::CATALYST_MATRIX: return { type, "CATALYST MATRIX", "ELEMENTAL", ImplantArchetype::ELEMENTAL, "Attacking Burned or Corroded foes detonates for +25 bonus direct damage.", - Color{ 255, 40, 130, 255 }, "[CATLY]", "CATLY" }; + TacticalColors::Crimson, "[CATLY]", "CATLY" }; case ImplantType::CONDUCTIVE_SALVO: return { type, "CONDUCTIVE SALVO", "ELEMENTAL", ImplantArchetype::ELEMENTAL, "EMP shots deal damage through enemy shields; Stun duration +1 turn.", - Color{ 80, 220, 255, 255 }, "[CONDC]", "CONDC" }; + TacticalColors::PureWhite, "[CONDC]", "CONDC" }; case ImplantType::CORROSIVE_LEECH: return { type, "CORROSIVE LEECH", "ELEMENTAL", ImplantArchetype::ELEMENTAL, "Nanite hits on Corroded targets restore 100% more HP & Shield.", - Color{ 57, 255, 20, 255 }, "[LEECH]", "LEECH" }; + TacticalColors::BrightRed, "[LEECH]", "LEECH" }; case ImplantType::DEFLECTOR_FEEDBACK: return { type, "DEFLECTOR FEEDBACK", "DEFENSE", ImplantArchetype::DEFENSE, "When Shield breaks, a shock pulse deals 20 retaliatory damage to drone.", - Color{ 0, 210, 255, 255 }, "[DEFLC]", "DEFLC" }; + TacticalColors::SilverText, "[DEFLC]", "DEFLC" }; case ImplantType::AUTO_DEFIBRILLATOR: return { type, "AUTO-DEFIBRILLATOR", "DEFENSE", ImplantArchetype::DEFENSE, "Once per run, lethal damage is prevented: grants 1 HP, 50 Shield, and stuns.", - Color{ 100, 255, 150, 255 }, "[DEFIB]", "DEFIB" }; + TacticalColors::PureWhite, "[DEFIB]", "DEFIB" }; case ImplantType::NANITE_WEAVER: return { type, "NANITE WEAVER", "DEFENSE", ImplantArchetype::DEFENSE, "Start of turn in combat: restore +5 HP and +8 Shield if enemy has debuffs.", - Color{ 0, 255, 180, 255 }, "[WEAVR]", "WEAVR" }; + TacticalColors::Crimson, "[WEAVR]", "WEAVR" }; case ImplantType::LOGIC_OVERCLOCK: return { type, "LOGIC OVERCLOCK", "HACKER", ImplantArchetype::HACKER, "Skill check Great zone is 2x wider; Critical hacking awards 2x Core Chips.", - Color{ 200, 80, 255, 255 }, "[LOGIC]", "LOGIC" }; + TacticalColors::BrightRed, "[LOGIC]", "LOGIC" }; case ImplantType::SALVAGE_PROTOCOL: return { type, "SALVAGE PROTOCOL", "HACKER", ImplantArchetype::HACKER, "+50% Core Chips dropped from all sources; 20% ammo chance on door bypass.", - Color{ 255, 210, 40, 255 }, "[SALVG]", "SALVG" }; + TacticalColors::PureWhite, "[SALVG]", "SALVG" }; case ImplantType::PRECOGNITION_OPTICS: return { type, "PRECOGNITION OPTICS", "HACKER", ImplantArchetype::HACKER, "Scans 5 tiles ahead, revealing Elites, Mines, Lasers & Caches on minimap.", - Color{ 120, 240, 255, 255 }, "[OPTIC]", "OPTIC" }; + TacticalColors::SilverText, "[OPTIC]", "OPTIC" }; default: - return { type, "UNKNOWN IMPLANT", "GENERAL", ImplantArchetype::DEFENSE, "Undocumented firmware.", WHITE, "[CHIP]", "CHIP" }; + return { type, "UNKNOWN IMPLANT", "GENERAL", ImplantArchetype::DEFENSE, "Undocumented firmware.", TacticalColors::PureWhite, "[CHIP]", "CHIP" }; } } @@ -328,19 +364,4 @@ struct CyberShrine { bool used = false; }; - -// Cyberpunk color palette -namespace CyberColors { - inline const Color NeonCyan = { 0, 240, 255, 255 }; - inline const Color NeonPink = { 255, 0, 128, 255 }; - inline const Color NeonGreen = { 57, 255, 20, 255 }; - inline const Color NeonYellow = { 255, 230, 0, 255 }; - inline const Color NeonOrange = { 255, 120, 0, 255 }; - inline const Color NeonPurple = { 180, 0, 255, 255 }; - inline const Color DarkGrid = { 10, 14, 26, 255 }; - inline const Color TerminalBg = { 6, 8, 15, 240 }; - inline const Color WallBorder = { 30, 70, 120, 255 }; - inline const Color WallGlow = { 0, 180, 255, 80 }; -} - } // namespace dnr diff --git a/src/core/Game.cpp b/src/core/Game.cpp index 4b86be6..a55d506 100644 --- a/src/core/Game.cpp +++ b/src/core/Game.cpp @@ -127,6 +127,12 @@ void Game::update(float dt) { 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); @@ -160,12 +166,12 @@ void Game::update(float dt) { break; case GameState::VICTORY_LEVEL: if (IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_ENTER)) { - enterRestRoom(); + m_transition.start([this]() { enterRestRoom(); }); } break; case GameState::GAME_OVER: if (IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_ENTER)) { - startNewRun(); + m_transition.start([this]() { startNewRun(); }); } break; } @@ -185,9 +191,9 @@ void Game::updateMainMenu(float dt) { bool hasSave = SaveSystem::hasSaveFile(); if ((IsKeyPressed(KEY_ONE) || (mouseClicked && CheckCollisionPointRec(mouse, btnContinue))) && hasSave) { - loadSavedProgress(); + m_transition.start([this]() { loadSavedProgress(); }); } else if (IsKeyPressed(KEY_TWO) || (mouseClicked && CheckCollisionPointRec(mouse, btnNewRun))) { - startNewRun(); + m_transition.start([this]() { startNewRun(); }); } else if (IsKeyPressed(KEY_THREE) || (mouseClicked && CheckCollisionPointRec(mouse, btnSettings))) { m_previousState = GameState::MAIN_MENU; m_state = GameState::SETTINGS; @@ -257,7 +263,7 @@ void Game::tryMoveHub(int dx, int dy) { void Game::tryInteractHub() { if (m_hubRoom.isFacingPortal(m_player)) { - enterCyberspace(); + m_transition.start([this]() { enterCyberspace(); }); } else if (m_hubRoom.isFacingTerminal(m_player)) { saveCurrentProgress(); } @@ -386,7 +392,7 @@ void Game::tryMoveRestRoom(int dx, int dy) { void Game::tryInteractRestRoom() { if (m_restRoom.isFacingGateway(m_player)) { - nextFloor(); + m_transition.start([this]() { nextFloor(); }); } else if (m_restRoom.isFacingRechargeMatrix(m_player)) { if (!m_restRoom.isRecharged()) { m_restRoom.triggerRecharge(m_player); @@ -783,7 +789,7 @@ void Game::tryInteractDungeon() { if (m_dungeon.getTile(px, py) == TileType::EXIT_PORTAL || m_dungeon.getTile(fx, fy) == TileType::EXIT_PORTAL) { - enterRestRoom(); + m_transition.start([this]() { enterRestRoom(); }); return; } } @@ -1174,6 +1180,9 @@ void Game::draw() { break; } + // Render screen transition wave on top of all 2D/3D elements + m_transition.draw(); + EndDrawing(); } diff --git a/src/core/Game.hpp b/src/core/Game.hpp index 62857d3..57f7a2b 100644 --- a/src/core/Game.hpp +++ b/src/core/Game.hpp @@ -8,6 +8,7 @@ #include "inventory/InventoryUI.hpp" #include "render/CyberRenderer.hpp" #include "render/UIRenderer.hpp" +#include "render/ScreenTransition.hpp" #include "hub/HubRoom.hpp" #include "hub/RestRoom.hpp" #include "core/SaveSystem.hpp" @@ -64,6 +65,7 @@ private: CyberRenderer m_renderer; UIRenderer m_uiRenderer; + ScreenTransition m_transition; float m_animTime = 0.0f; float m_saveFeedbackTimer = 0.0f; diff --git a/src/inventory/InventoryUI.cpp b/src/inventory/InventoryUI.cpp index eeda50b..88add45 100644 --- a/src/inventory/InventoryUI.cpp +++ b/src/inventory/InventoryUI.cpp @@ -1,4 +1,5 @@ #include "InventoryUI.hpp" +#include "render/UIRenderer.hpp" #include "core/SoundManager.hpp" #include @@ -117,65 +118,79 @@ void InventoryUI::draw(const Player& player) { Vector2 mouse = GetMousePosition(); // Dark semi-transparent cyber overlay - DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 8, 16, 245 }); + DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 5, 5, 5, 245 }); // Header - DrawRectangle(80, 35, GetScreenWidth() - 160, 52, Color{ 12, 22, 38, 255 }); - DrawRectangleLines(80, 35, GetScreenWidth() - 160, 52, CyberColors::NeonCyan); - DrawText("CYBER-WEAPON ARMORY // CYLINDER CONFIGURATION", 105, 50, 20, CyberColors::NeonCyan); - DrawText("[TAB / ESC] RETURN TO GAME", GetScreenWidth() - 340, 51, 16, CyberColors::NeonYellow); + DrawRectangle(80, 35, GetScreenWidth() - 160, 52, TacticalColors::DarkPanel); + DrawRectangleLines(80, 35, GetScreenWidth() - 160, 52, TacticalColors::BorderWhite); + DrawText("CYBER-WEAPON ARMORY // CYLINDER CONFIGURATION", 105, 50, 20, TacticalColors::PureWhite); + DrawText("[TAB / ESC] RETURN TO GAME", GetScreenWidth() - 340, 51, 16, TacticalColors::Crimson); - // Left Panel: Revolver Cylinder + // Left Panel: Revolver Cylinder (Strict 0-rounding) int cylCenterX = 350; int cylCenterY = 320; - float cylRadius = 115.0f; - float chamberOrbitR = cylRadius - 35.0f; - float chamberR = 24.0f; - - // 1. Heavy gunmetal outer housing - DrawCircle(cylCenterX, cylCenterY, cylRadius + 18.0f, Color{ 8, 14, 24, 250 }); - DrawCircleLines(cylCenterX, cylCenterY, cylRadius + 18.0f, Color{ 30, 50, 75, 255 }); - - // Metallic cylinder body - DrawCircle(cylCenterX, cylCenterY, cylRadius, Color{ 20, 28, 42, 255 }); - DrawCircleLines(cylCenterX, cylCenterY, cylRadius, CyberColors::NeonCyan); - - // 6 Decorative flutes between chambers + float chamberOrbitR = 58.0f; float visAngle = rev.getVisualAngle(); - for (int i = 0; i < 6; ++i) { - float fluteAngle = (-visAngle + i * 60.0f + 30.0f - 90.0f) * (3.14159265f / 180.0f); - int fluteX = cylCenterX + static_cast(std::cos(fluteAngle) * (cylRadius + 2.0f)); - int fluteY = cylCenterY + static_cast(std::sin(fluteAngle) * (cylRadius + 2.0f)); - DrawCircle(fluteX, fluteY, 14.0f, Color{ 8, 14, 24, 255 }); - DrawCircleLines(fluteX, fluteY, 14.0f, Color{ 40, 65, 95, 255 }); - } - // 2. Extractor Ratchet & Hub - DrawCircle(cylCenterX, cylCenterY, 36.0f, Color{ 14, 20, 32, 255 }); - DrawCircleLines(cylCenterX, cylCenterY, 36.0f, Color{ 70, 100, 140, 255 }); - DrawCircle(cylCenterX, cylCenterY, 16.0f, Color{ 220, 175, 45, 255 }); - DrawCircleLines(cylCenterX, cylCenterY, 16.0f, CyberColors::NeonYellow); - DrawText(".357", cylCenterX - 16, cylCenterY - 7, 13, CyberColors::NeonYellow); + // 1. Faceted Octagonal Heavy Outer Housing + DrawRectangle(cylCenterX - 110, cylCenterY - 110, 220, 220, TacticalColors::DarkPanel); + DrawRectangleLines(cylCenterX - 110, cylCenterY - 110, 220, 220, TacticalColors::BorderMuted); - // 3. Golden Striker Hammer / Firing Pin at 12:00 (-90 deg) - int strikerTipY = cylCenterY - static_cast(cylRadius) - 4; - int strikerBaseY = cylCenterY - static_cast(cylRadius) - 28; + // 45-degree chamfers on outer corners + DrawTriangle({ (float)(cylCenterX - 110), (float)(cylCenterY - 110) }, + { (float)(cylCenterX - 78), (float)(cylCenterY - 110) }, + { (float)(cylCenterX - 110), (float)(cylCenterY - 78) }, TacticalColors::PitchBlack); + DrawTriangle({ (float)(cylCenterX + 110), (float)(cylCenterY - 110) }, + { (float)(cylCenterX + 78), (float)(cylCenterY - 110) }, + { (float)(cylCenterX + 110), (float)(cylCenterY - 78) }, TacticalColors::PitchBlack); + DrawTriangle({ (float)(cylCenterX - 110), (float)(cylCenterY + 110) }, + { (float)(cylCenterX - 78), (float)(cylCenterY + 110) }, + { (float)(cylCenterX - 110), (float)(cylCenterY + 78) }, TacticalColors::PitchBlack); + DrawTriangle({ (float)(cylCenterX + 110), (float)(cylCenterY + 110) }, + { (float)(cylCenterX + 78), (float)(cylCenterY + 110) }, + { (float)(cylCenterX + 110), (float)(cylCenterY + 78) }, TacticalColors::PitchBlack); - DrawTriangle( - { static_cast(cylCenterX), static_cast(strikerTipY) }, - { static_cast(cylCenterX - 14), static_cast(strikerBaseY) }, - { static_cast(cylCenterX + 14), static_cast(strikerBaseY) }, - Color{ 255, 210, 50, 255 } - ); - DrawRectangle(cylCenterX - 20, strikerBaseY - 14, 40, 14, Color{ 210, 160, 30, 255 }); - DrawRectangleLines(cylCenterX - 20, strikerBaseY - 14, 40, 14, CyberColors::NeonYellow); - DrawCircle(cylCenterX, strikerTipY, 3.0f, CyberColors::NeonYellow); + // 2. Inner Chamfered Drum Plate + DrawRectangle(cylCenterX - 96, cylCenterY - 96, 192, 192, Color{ 16, 22, 32, 255 }); + DrawRectangleLines(cylCenterX - 96, cylCenterY - 96, 192, 192, TacticalColors::BorderWhite); + DrawTriangle({ (float)(cylCenterX - 96), (float)(cylCenterY - 96) }, + { (float)(cylCenterX - 70), (float)(cylCenterY - 96) }, + { (float)(cylCenterX - 96), (float)(cylCenterY - 70) }, TacticalColors::DarkPanel); + DrawTriangle({ (float)(cylCenterX + 96), (float)(cylCenterY - 96) }, + { (float)(cylCenterX + 70), (float)(cylCenterY - 96) }, + { (float)(cylCenterX + 96), (float)(cylCenterY - 70) }, TacticalColors::DarkPanel); + DrawTriangle({ (float)(cylCenterX - 96), (float)(cylCenterY + 96) }, + { (float)(cylCenterX - 70), (float)(cylCenterY + 96) }, + { (float)(cylCenterX - 96), (float)(cylCenterY + 70) }, TacticalColors::DarkPanel); + DrawTriangle({ (float)(cylCenterX + 96), (float)(cylCenterY + 96) }, + { (float)(cylCenterX + 70), (float)(cylCenterY + 96) }, + { (float)(cylCenterX + 96), (float)(cylCenterY + 70) }, TacticalColors::DarkPanel); - DrawRectangle(cylCenterX - 65, strikerBaseY - 32, 130, 16, Color{ 10, 16, 26, 230 }); - DrawRectangleLines(cylCenterX - 65, strikerBaseY - 32, 130, 16, Color{ 180, 150, 40, 200 }); - DrawText("FIRING PIN [12:00]", cylCenterX - 58, strikerBaseY - 30, 11, CyberColors::NeonYellow); + // 3. Center Extractor Ratchet & Hub + DrawRectangle(cylCenterX - 26, cylCenterY - 26, 52, 52, Color{ 12, 16, 24, 255 }); + DrawRectangleLines(cylCenterX - 26, cylCenterY - 26, 52, 52, TacticalColors::BorderMuted); + DrawRectangle(cylCenterX - 16, cylCenterY - 16, 32, 32, TacticalColors::SurfacePlate); + DrawRectangleLines(cylCenterX - 16, cylCenterY - 16, 32, 32, TacticalColors::Crimson); + DrawText(".357", cylCenterX - 15, cylCenterY - 7, 13, TacticalColors::PureWhite); - // 4. Six Chambers (Rotates with rev.getVisualAngle()) + // 4. Aggressive Red Striker Firing Pin at 12:00 (-90 deg / top) + int strikerW = 56; + int strikerH = 16; + int strikerX = cylCenterX - strikerW / 2; + int strikerBaseY = cylCenterY - 110 - strikerH - 6; + + DrawRectangle(strikerX, strikerBaseY, strikerW, strikerH, TacticalColors::DarkRed); + DrawRectangleLines(strikerX, strikerBaseY, strikerW, strikerH, TacticalColors::BrightRed); + DrawTriangle({ (float)cylCenterX, (float)(cylCenterY - 106) }, + { (float)(cylCenterX - 9), (float)(strikerBaseY + strikerH) }, + { (float)(cylCenterX + 9), (float)(strikerBaseY + strikerH) }, + TacticalColors::BrightRed); + + const char* pinTxt = "[ 12:00 FIRING PIN ]"; + int pinW = MeasureText(pinTxt, 11); + DrawText(pinTxt, cylCenterX - pinW / 2, strikerBaseY - 18, 11, TacticalColors::BrightRed); + + // 5. Six Chambers (Rotates with rev.getVisualAngle()) for (int i = 0; i < 6; ++i) { float angleRad = (-visAngle + i * 60.0f - 90.0f) * (3.14159265f / 180.0f); int chX = cylCenterX + static_cast(std::cos(angleRad) * chamberOrbitR); @@ -186,42 +201,45 @@ void InventoryUI::draw(const Player& player) { bool isSelected = (i == m_selectedChamber); bool isCurrentReady = (i == rev.getCurrentIndex()); - DrawCircle(chX, chY, chamberR, Color{ 16, 22, 34, 255 }); - DrawCircleLines(chX, chY, chamberR, isSelected ? CyberColors::NeonYellow : Color{ 55, 80, 120, 255 }); + int chW = 36; + int chH = 36; + Rectangle chRect = { (float)(chX - chW / 2), (float)(chY - chH / 2), (float)chW, (float)chH }; 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); + DrawRectangleRec(chRect, TacticalColors::PitchBlack); + DrawRectangleLinesEx(chRect, isSelected ? 2.0f : 1.0f, isSelected ? TacticalColors::Crimson : (isCurrentReady ? TacticalColors::BorderWhite : TacticalColors::BorderMuted)); + DrawText("---", chX - 9, chY - 5, 10, TacticalColors::MutedText); } 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 - 4, chY - 4, 3.0f, Color{ 255, 255, 255, 200 }); - DrawText(info.shortName.substr(0, 3).c_str(), chX - 9, chY - 5, 11, BLACK); + DrawRectangleRec(chRect, TacticalColors::DarkPanel); + DrawRectangleLinesEx(chRect, isSelected ? 2.0f : 1.0f, isSelected ? TacticalColors::Crimson : info.color); + // Tactical cartridge primer block + DrawRectangle(chX - 7, chY - 7, 14, 14, Color{ info.color.r, info.color.g, info.color.b, 200 }); + DrawRectangleLines(chX - 7, chY - 7, 14, 14, info.color); + DrawText(info.shortName.substr(0, 3).c_str(), chX - 10, chY - 5, 10, TacticalColors::PureWhite); } - // Selection Reticle + // Selection Reticle (Square brackets) if (isSelected) { - DrawRectangleLines(chX - static_cast(chamberR) - 4, chY - static_cast(chamberR) - 4, - static_cast(chamberR * 2) + 8, static_cast(chamberR * 2) + 8, - CyberColors::NeonYellow); + DrawRectangleLinesEx({ chRect.x - 4, chRect.y - 4, chRect.width + 8, chRect.height + 8 }, 2.0f, TacticalColors::Crimson); + DrawLine(chX, (int)chRect.y - 8, chX, (int)chRect.y - 3, TacticalColors::Crimson); + DrawLine(chX, (int)(chRect.y + chRect.height + 3), chX, (int)(chRect.y + chRect.height + 8), TacticalColors::Crimson); + DrawLine((int)chRect.x - 8, chY, (int)chRect.x - 3, chY, TacticalColors::Crimson); + DrawLine((int)(chRect.x + chRect.width + 3), chY, (int)(chRect.x + chRect.width + 8), chY, TacticalColors::Crimson); } // Active Chamber Marker (Under firing pin) if (isCurrentReady) { - DrawCircleLines(chX, chY, chamberR + 6.0f, CyberColors::NeonGreen); - DrawText("READY", chX - 16, chY + 28, 10, CyberColors::NeonGreen); + DrawText("READY", chX - 16, chY + 24, 10, TacticalColors::PureWhite); } } - // Selected chamber info box below cylinder + // Selected chamber info box below cylinder (Strict 0-rounding) int boxX = 110; int boxY = 480; int boxW = 480; int boxH = 100; - DrawRectangle(boxX, boxY, boxW, boxH, Color{ 10, 18, 30, 255 }); - DrawRectangleLines(boxX, boxY, boxW, boxH, CyberColors::NeonCyan); + DrawRectangle(boxX, boxY, boxW, boxH, TacticalColors::DarkPanel); + DrawRectangleLines(boxX, boxY, boxW, boxH, TacticalColors::BorderMuted); AmmoType selChamberAmmo = rev.getChamber(m_selectedChamber); const auto& selChamberInfo = getAmmoInfo(selChamberAmmo); @@ -230,17 +248,17 @@ void InventoryUI::draw(const Player& player) { if (m_selectedChamber == rev.getCurrentIndex()) { selHeader += " [ACTIVE @ 12:00 FIRING PIN]"; } - DrawText(selHeader.c_str(), boxX + 15, boxY + 12, 15, CyberColors::NeonYellow); + DrawText(selHeader.c_str(), boxX + 15, boxY + 12, 15, TacticalColors::Crimson); DrawText(("LOADED: " + selChamberInfo.name).c_str(), boxX + 15, boxY + 36, 15, selChamberInfo.color); - DrawText(("IMPACT: " + std::to_string(selChamberInfo.baseDamage) + " DMG // " + selChamberInfo.description).c_str(), boxX + 15, boxY + 58, 12, RAYWHITE); - DrawText("[1-6 / CLICK] SELECT | [U] UNLOAD | [R] SPIN ROULETTE", boxX + 15, boxY + 78, 11, CyberColors::NeonCyan); + DrawText(("IMPACT: " + std::to_string(selChamberInfo.baseDamage) + " DMG // " + selChamberInfo.description).c_str(), boxX + 15, boxY + 58, 12, TacticalColors::SilverText); + DrawText("[1-6 / CLICK] SELECT | [U] UNLOAD | [R] SPIN ROULETTE", boxX + 15, boxY + 78, 11, TacticalColors::MutedText); // Ammo Probability Breakdown under info box int oddsY = boxY + boxH + 12; int oddsH = 65; - DrawRectangle(boxX, oddsY, boxW, oddsH, Color{ 8, 14, 24, 255 }); - DrawRectangleLines(boxX, oddsY, boxW, oddsH, Color{ 35, 60, 95, 255 }); - DrawText("// AMMO PROBABILITY ROULETTE", boxX + 15, oddsY + 8, 10, GRAY); + DrawRectangle(boxX, oddsY, boxW, oddsH, TacticalColors::DarkPanel); + DrawRectangleLines(boxX, oddsY, boxW, oddsH, TacticalColors::BorderMuted); + DrawText("// AMMO PROBABILITY ROULETTE", boxX + 15, oddsY + 8, 10, TacticalColors::MutedText); int loadedCount = 0; int typeCounts[7] = { 0 }; @@ -253,10 +271,10 @@ void InventoryUI::draw(const Player& player) { int blankChance = 100 - hitChance; std::string oddsStr = "LOADED: " + std::to_string(loadedCount) + "/6 (" + std::to_string(hitChance) + "% HIT)"; - DrawText(oddsStr.c_str(), boxX + 15, oddsY + 24, 13, hitChance >= 50 ? CyberColors::NeonGreen : CyberColors::NeonYellow); + DrawText(oddsStr.c_str(), boxX + 15, oddsY + 24, 13, hitChance >= 50 ? TacticalColors::PureWhite : TacticalColors::Crimson); std::string blankStr = "MISFIRE RISK: " + std::to_string(blankChance) + "%"; - DrawText(blankStr.c_str(), boxX + 220, oddsY + 24, 13, blankChance == 0 ? CyberColors::NeonGreen : (blankChance > 33 ? RED : CyberColors::NeonYellow)); + DrawText(blankStr.c_str(), boxX + 220, oddsY + 24, 13, blankChance == 0 ? TacticalColors::PureWhite : TacticalColors::Crimson); std::string chipSummary = ""; for (int t = 1; t <= 6; ++t) { @@ -267,7 +285,7 @@ void InventoryUI::draw(const Player& player) { } } if (chipSummary.empty()) chipSummary = "ALL CHAMBERS EMPTY - LOAD CARTRIDGES FROM STACK"; - DrawText(chipSummary.c_str(), boxX + 15, oddsY + 44, 11, Color{ 170, 195, 225, 255 }); + DrawText(chipSummary.c_str(), boxX + 15, oddsY + 44, 11, TacticalColors::SilverText); // Right Panel: Ammo Pouch / Storage + Neural Implants Matrix int pouchX = 660; @@ -275,12 +293,12 @@ void InventoryUI::draw(const Player& player) { int pouchW = 520; int pouchH = 580; - DrawRectangle(pouchX, pouchY, pouchW, pouchH, Color{ 10, 16, 26, 255 }); - DrawRectangleLines(pouchX, pouchY, pouchW, pouchH, CyberColors::NeonCyan); - DrawText("TACTICAL CARTRIDGE STACK // INVENTORY", pouchX + 20, pouchY + 12, 16, CyberColors::NeonCyan); + DrawRectangle(pouchX, pouchY, pouchW, pouchH, TacticalColors::DarkPanel); + DrawRectangleLines(pouchX, pouchY, pouchW, pouchH, TacticalColors::BorderWhite); + DrawText("TACTICAL CARTRIDGE STACK // INVENTORY", pouchX + 20, pouchY + 12, 16, TacticalColors::PureWhite); DrawText(("[W/S / CLICK] SELECT [ENTER / E / SPACE] LOAD INTO CHAMBER #" + std::to_string(m_selectedChamber + 1)).c_str(), - pouchX + 20, pouchY + 36, 12, CyberColors::NeonYellow); + pouchX + 20, pouchY + 36, 12, TacticalColors::Crimson); int startY = pouchY + 58; for (int i = 0; i < 6; ++i) { @@ -292,29 +310,31 @@ void InventoryUI::draw(const Player& player) { Rectangle rowRect = { static_cast(pouchX + 15), static_cast(startY + i * 42), static_cast(pouchW - 30), 38.0f }; bool isHover = CheckCollisionPointRec(mouse, rowRect); - Color boxCol = isSel ? Color{ 30, 50, 80, 255 } : (isHover ? Color{ 20, 32, 52, 255 } : Color{ 14, 20, 32, 255 }); - Color borderCol = isSel ? CyberColors::NeonYellow : (isHover ? CyberColors::NeonCyan : Color{ 30, 50, 75, 255 }); + Color boxCol = isSel ? TacticalColors::SurfacePlate : (isHover ? Color{ 24, 24, 24, 255 } : TacticalColors::PitchBlack); + Color borderCol = isSel ? TacticalColors::Crimson : (isHover ? TacticalColors::BorderWhite : TacticalColors::BorderMuted); DrawRectangleRec(rowRect, boxCol); DrawRectangleLinesEx(rowRect, isSel ? 2.0f : 1.0f, borderCol); - // Icon - DrawCircle(pouchX + 32, startY + i * 42 + 19, 10.0f, count > 0 ? itemInfo.color : DARKGRAY); + // Tactical rectangular badge (Strict 0-rounding) + DrawRectangle(pouchX + 22, startY + i * 42 + 9, 22, 20, count > 0 ? TacticalColors::SurfacePlate : TacticalColors::PitchBlack); + DrawRectangleLines(pouchX + 22, startY + i * 42 + 9, 22, 20, count > 0 ? itemInfo.color : TacticalColors::BorderMuted); + DrawText(itemInfo.shortName.substr(0, 3).c_str(), pouchX + 24, startY + i * 42 + 14, 9, count > 0 ? itemInfo.color : TacticalColors::MutedText); // Name - DrawText(itemInfo.name.c_str(), pouchX + 50, startY + i * 42 + 11, 14, count > 0 ? (isSel ? CyberColors::NeonYellow : RAYWHITE) : GRAY); + DrawText(itemInfo.name.c_str(), pouchX + 50, startY + i * 42 + 11, 14, count > 0 ? (isSel ? TacticalColors::PureWhite : TacticalColors::SilverText) : TacticalColors::MutedText); // Tier badge int tier = player.getAmmoTier(aType); std::string tStr = tier == 1 ? "T1" : (tier == 2 ? "T2" : "T3"); - DrawText(tStr.c_str(), pouchX + 230, startY + i * 42 + 12, 12, tier >= 3 ? CyberColors::NeonYellow : (tier == 2 ? CyberColors::NeonGreen : GRAY)); + DrawText(tStr.c_str(), pouchX + 230, startY + i * 42 + 12, 12, tier >= 3 ? TacticalColors::Crimson : TacticalColors::SilverText); // Damage Tag - DrawText(("DMG: " + std::to_string(itemInfo.baseDamage)).c_str(), pouchX + 270, startY + i * 42 + 12, 13, CyberColors::NeonCyan); + DrawText(("DMG: " + std::to_string(itemInfo.baseDamage)).c_str(), pouchX + 270, startY + i * 42 + 12, 13, TacticalColors::PureWhite); // Stack Count std::string countStr = "[x " + std::to_string(count) + "]"; - Color countColor = (count > 0) ? (isSel ? CyberColors::NeonYellow : CyberColors::NeonGreen) : RED; + Color countColor = (count > 0) ? (isSel ? TacticalColors::PureWhite : TacticalColors::SilverText) : TacticalColors::Crimson; DrawText(countStr.c_str(), pouchX + 430, startY + i * 42 + 11, 15, countColor); } @@ -325,25 +345,25 @@ void InventoryUI::draw(const Player& player) { int detailY = startY + 6 * 42 + 4; int detailH = 64; - DrawRectangle(pouchX + 15, detailY, pouchW - 30, detailH, Color{ 14, 24, 40, 255 }); - DrawRectangleLines(pouchX + 15, detailY, pouchW - 30, detailH, CyberColors::NeonGreen); + DrawRectangle(pouchX + 15, detailY, pouchW - 30, detailH, TacticalColors::SurfacePlate); + DrawRectangleLines(pouchX + 15, detailY, pouchW - 30, detailH, TacticalColors::BorderWhite); DrawText(curSelInfo.name.c_str(), pouchX + 26, detailY + 8, 14, curSelInfo.color); - DrawText(("IN STOCK: " + std::to_string(curSelCount)).c_str(), pouchX + 370, detailY + 8, 12, curSelCount > 0 ? CyberColors::NeonGreen : RED); - DrawText(curSelInfo.description.c_str(), pouchX + 26, detailY + 28, 11, RAYWHITE); - DrawText("[ENTER / E / SPACE] INSERT INTO SELECTED CHAMBER", pouchX + 26, detailY + 46, 11, CyberColors::NeonYellow); + DrawText(("IN STOCK: " + std::to_string(curSelCount)).c_str(), pouchX + 370, detailY + 8, 12, curSelCount > 0 ? TacticalColors::PureWhite : TacticalColors::Crimson); + UIRenderer::drawWrappedText(curSelInfo.description, pouchX + 26, detailY + 28, pouchW - 55, 11, 2, TacticalColors::SilverText); + DrawText("[ENTER / E / SPACE] INSERT INTO SELECTED CHAMBER", pouchX + 26, detailY + 46, 11, TacticalColors::Crimson); // Neural Implants Matrix Panel (2x2 grid) int impPanelY = detailY + detailH + 8; int impPanelH = pouchY + pouchH - impPanelY - 10; - DrawRectangle(pouchX + 15, impPanelY, pouchW - 30, impPanelH, Color{ 12, 18, 30, 255 }); - DrawRectangleLines(pouchX + 15, impPanelY, pouchW - 30, impPanelH, CyberColors::NeonCyan); + DrawRectangle(pouchX + 15, impPanelY, pouchW - 30, impPanelH, TacticalColors::DarkPanel); + DrawRectangleLines(pouchX + 15, impPanelY, pouchW - 30, impPanelH, TacticalColors::BorderMuted); std::string impHeader = "// NEURAL MATRIX RIG // SLOTS [" + std::to_string(player.getImplants().size()) + " / 4]"; - DrawText(impHeader.c_str(), pouchX + 25, impPanelY + 8, 12, CyberColors::NeonCyan); + DrawText(impHeader.c_str(), pouchX + 25, impPanelY + 8, 12, TacticalColors::PureWhite); - std::string chipTxt = "[ ❖ " + std::to_string(player.getTechScraps()) + " ]"; - DrawText(chipTxt.c_str(), pouchX + pouchW - 95, impPanelY + 8, 12, CyberColors::NeonYellow); + std::string chipTxt = "[ SCRAP: " + std::to_string(player.getTechScraps()) + " ]"; + DrawText(chipTxt.c_str(), pouchX + pouchW - 120, impPanelY + 8, 12, TacticalColors::Crimson); const auto& implants = player.getImplants(); int slotW = 236; @@ -365,28 +385,28 @@ void InventoryUI::draw(const Player& player) { Color archCol = data.color; if (imp == ImplantType::AUTO_DEFIBRILLATOR && player.isDefibrillatorUsed()) { - archCol = RED; + archCol = TacticalColors::Crimson; } - DrawRectangleRec(sRect, Color{ 16, 24, 38, 255 }); - DrawRectangleLinesEx(sRect, isHoverImp ? 2.0f : 1.0f, isHoverImp ? WHITE : archCol); + DrawRectangleRec(sRect, TacticalColors::SurfacePlate); + DrawRectangleLinesEx(sRect, isHoverImp ? 2.0f : 1.0f, isHoverImp ? TacticalColors::PureWhite : archCol); std::string sTitle = "SLOT " + std::to_string(s + 1) + ": " + data.name; DrawText(sTitle.c_str(), sX + 8, sY + 6, 11, archCol); if (imp == ImplantType::AUTO_DEFIBRILLATOR && player.isDefibrillatorUsed()) { - DrawText("[STATUS: REVIVE DEPLETED]", sX + 8, sY + 24, 10, RED); + DrawText("[STATUS: REVIVE DEPLETED]", sX + 8, sY + 24, 10, TacticalColors::Crimson); } else { std::string shortDesc = data.description; if (shortDesc.length() > 32) shortDesc = shortDesc.substr(0, 30) + ".."; - DrawText(shortDesc.c_str(), sX + 8, sY + 24, 10, RAYWHITE); + DrawText(shortDesc.c_str(), sX + 8, sY + 24, 10, TacticalColors::SilverText); } } else { - DrawRectangleRec(sRect, Color{ 8, 12, 20, 200 }); - DrawRectangleLinesEx(sRect, 1.0f, Color{ 30, 45, 65, 200 }); + DrawRectangleRec(sRect, TacticalColors::PitchBlack); + DrawRectangleLinesEx(sRect, 1.0f, TacticalColors::BorderMuted); std::string emptyTitle = "SLOT " + std::to_string(s + 1) + ": [EMPTY SOCKET]"; - DrawText(emptyTitle.c_str(), sX + 8, sY + 8, 11, DARKGRAY); - DrawText("Draft from Elites / ICE", sX + 8, sY + 26, 10, Color{ 60, 80, 110, 255 }); + DrawText(emptyTitle.c_str(), sX + 8, sY + 8, 11, TacticalColors::MutedText); + DrawText("Draft from Elites / ICE", sX + 8, sY + 26, 10, TacticalColors::BorderMuted); } } } diff --git a/src/render/CyberRenderer.cpp b/src/render/CyberRenderer.cpp index 5d46474..2188dfb 100644 --- a/src/render/CyberRenderer.cpp +++ b/src/render/CyberRenderer.cpp @@ -91,11 +91,11 @@ void drawTexturedBox(Texture2D tex, Vector3 pos, Vector3 size, float uTile = 1.0 rlEnableBackfaceCulling(); } -// Procedural generator: Warm Oak Parquet Floor (256x256) +// Procedural generator: Dark Smoked Walnut Parquet Floor (256x256) Texture2D createParquetTexture() { const int w = 256; const int h = 256; - Image img = GenImageColor(w, h, Color{ 145, 95, 52, 255 }); + Image img = GenImageColor(w, h, Color{ 36, 28, 24, 255 }); Color* pixels = (Color*)img.data; const int plankHeight = 32; @@ -113,24 +113,24 @@ Texture2D createParquetTexture() { bool isVerticalSeam = (plankX == 0 || plankX == plankW - 1); if (isHorizontalSeam || isVerticalSeam) { - pixels[y * w + x] = Color{ 48, 28, 14, 255 }; + pixels[y * w + x] = Color{ 16, 12, 10, 255 }; continue; } int plankSeed = (row * 17 + plankCol * 31); - int toneOffset = (plankSeed % 31) - 15; + int toneOffset = (plankSeed % 19) - 9; - int baseR = 158 + toneOffset; - int baseG = 108 + (toneOffset * 3) / 4; - int baseB = 60 + toneOffset / 2; + int baseR = 52 + toneOffset; + int baseG = 42 + (toneOffset * 3) / 4; + int baseB = 36 + toneOffset / 2; float grainSin = std::sin((y * 4.0f + std::sin(x * 0.05f) * 6.0f) * 0.4f); - int grainVal = static_cast(grainSin * 14.0f); - int fiberNoise = ((x * 13 + y * 29) % 9) - 4; + int grainVal = static_cast(grainSin * 6.0f); + int fiberNoise = ((x * 13 + y * 29) % 7) - 3; - int finalR = std::clamp(baseR + grainVal + fiberNoise, 40, 240); - int finalG = std::clamp(baseG + (grainVal * 3) / 4 + fiberNoise, 25, 200); - int finalB = std::clamp(baseB + grainVal / 2 + fiberNoise, 10, 160); + int finalR = std::clamp(baseR + grainVal + fiberNoise, 20, 110); + int finalG = std::clamp(baseG + (grainVal * 3) / 4 + fiberNoise, 16, 95); + int finalB = std::clamp(baseB + grainVal / 2 + fiberNoise, 12, 85); pixels[y * w + x] = Color{ (unsigned char)finalR, (unsigned char)finalG, (unsigned char)finalB, 255 }; } @@ -157,20 +157,20 @@ Texture2D createRugTexture() { Color c = Color{ 24, 35, 58, 255 }; if (distEdge < 4) { - c = Color{ 210, 185, 130, 255 }; // Fringes + c = Color{ 120, 105, 80, 255 }; // Fringes } else if (distEdge >= 6 && distEdge <= 22) { // Wide ornate gold border - int pattern = ((x / 6 + y / 6) % 2 == 0) ? 35 : -15; - c = Color{ (unsigned char)std::clamp(185 + pattern, 0, 255), - (unsigned char)std::clamp(150 + pattern, 0, 255), - (unsigned char)std::clamp(75 + pattern, 0, 255), 255 }; + int pattern = ((x / 6 + y / 6) % 2 == 0) ? 20 : -10; + c = Color{ (unsigned char)std::clamp(115 + pattern, 0, 255), + (unsigned char)std::clamp(92 + pattern, 0, 255), + (unsigned char)std::clamp(48 + pattern, 0, 255), 255 }; } else if (distEdge == 24 || distEdge == 25) { - c = Color{ 165, 35, 45, 255 }; // Crimson accent piping + c = Color{ 110, 25, 35, 255 }; // Crimson accent piping } else if (distEdge >= 28 && distEdge <= 38) { if ((x / 4) % 2 == (y / 4) % 2) { - c = Color{ 125, 98, 58, 255 }; + c = Color{ 85, 68, 42, 255 }; } else { - c = Color{ 36, 52, 84, 255 }; + c = Color{ 22, 32, 54, 255 }; } } else { int cx = w / 2; @@ -181,19 +181,19 @@ Texture2D createRugTexture() { int eucDist = static_cast(std::sqrt(dx * dx + dy * dy)); if (manhattanDist < 60 && manhattanDist > 54) { - c = Color{ 215, 175, 85, 255 }; // Gold diamond outline + c = Color{ 135, 108, 55, 255 }; // Gold diamond outline } else if (manhattanDist <= 54 && manhattanDist >= 40) { - c = Color{ 150, 32, 48, 255 }; // Crimson medallion body + c = Color{ 105, 24, 36, 255 }; // Crimson medallion body } else if (eucDist < 25) { if (eucDist < 8) { - c = Color{ 245, 215, 125, 255 }; + c = Color{ 155, 135, 78, 255 }; } else if ((dx == dy) || dx == 0 || dy == 0) { - c = Color{ 215, 175, 85, 255 }; + c = Color{ 135, 108, 55, 255 }; } else { - c = Color{ 20, 32, 54, 255 }; + c = Color{ 16, 24, 40, 255 }; } } else if (manhattanDist < 85 && (dx < 12 || dy < 12)) { - c = Color{ 175, 145, 75, 255 }; + c = Color{ 115, 95, 52, 255 }; } } @@ -210,11 +210,11 @@ Texture2D createRugTexture() { return tex; } -// Procedural generator: Textured Apartment Stucco/Wallpaper (256x256) +// Procedural generator: Textured Dark Slate Plaster (256x256) Texture2D createWallTexture() { const int w = 256; const int h = 256; - Image img = GenImageColor(w, h, Color{ 78, 84, 94, 255 }); + Image img = GenImageColor(w, h, Color{ 34, 38, 46, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { @@ -225,19 +225,19 @@ Texture2D createWallTexture() { for (int x = 0; x < w; ++x) { int seamMod = x % 64; int seamShade = 0; - if (seamMod == 0) seamShade = -22; - else if (seamMod == 1) seamShade = +16; + if (seamMod == 0) seamShade = -14; + else if (seamMod == 1) seamShade = +10; - int stipple = ((x * 19 + y * 37 + (x ^ y) * 11) % 15) - 7; + int stipple = ((x * 19 + y * 37 + (x ^ y) * 11) % 11) - 5; - int baseR = static_cast((82 + stipple + seamShade) * vLighting); - int baseG = static_cast((88 + stipple + seamShade) * vLighting); - int baseB = static_cast((98 + stipple + seamShade) * vLighting); + int baseR = static_cast((36 + stipple + seamShade) * vLighting); + int baseG = static_cast((40 + stipple + seamShade) * vLighting); + int baseB = static_cast((48 + stipple + seamShade) * vLighting); pixels[y * w + x] = Color{ - (unsigned char)std::clamp(baseR, 0, 255), - (unsigned char)std::clamp(baseG, 0, 255), - (unsigned char)std::clamp(baseB, 0, 255), + (unsigned char)std::clamp(baseR, 12, 90), + (unsigned char)std::clamp(baseG, 14, 95), + (unsigned char)std::clamp(baseB, 18, 110), 255 }; } @@ -352,23 +352,24 @@ Texture2D createDeskWoodTexture() { return tex; } +// Procedural generator: Code Editor / IDE Monitor Screen (512x256) // Procedural generator: Code Editor / IDE Monitor Screen (512x256) Texture2D createMonitorCodeTexture() { const int w = 512; const int h = 256; - Image img = GenImageColor(w, h, Color{ 18, 20, 28, 255 }); + Image img = GenImageColor(w, h, Color{ 14, 18, 24, 255 }); - ImageDrawRectangle(&img, 0, 0, w, 22, Color{ 28, 32, 44, 255 }); - ImageDrawCircle(&img, 12, 11, 4, Color{ 255, 95, 86, 255 }); - ImageDrawCircle(&img, 26, 11, 4, Color{ 255, 189, 46, 255 }); - ImageDrawCircle(&img, 40, 11, 4, Color{ 39, 201, 63, 255 }); + ImageDrawRectangle(&img, 0, 0, w, 22, Color{ 22, 28, 38, 255 }); + ImageDrawRectangle(&img, 10, 7, 7, 7, TacticalColors::Crimson); + ImageDrawRectangle(&img, 22, 7, 7, 7, Color{ 120, 130, 145, 255 }); + ImageDrawRectangle(&img, 34, 7, 7, 7, TacticalColors::PureWhite); - ImageDrawText(&img, "neural_daemon.cpp - [ACTIVE SESSION]", 60, 6, 10, Color{ 160, 175, 200, 255 }); - ImageDrawRectangle(&img, 0, 22, 38, h - 38, Color{ 22, 25, 34, 255 }); + ImageDrawText(&img, "neural_daemon.cpp - [ACTIVE ARASAKA PROTOCOL]", 52, 6, 10, Color{ 210, 220, 235, 255 }); + ImageDrawRectangle(&img, 0, 22, 38, h - 38, Color{ 18, 22, 30, 255 }); - ImageDrawRectangle(&img, 0, h - 16, w, 16, Color{ 0, 122, 204, 255 }); - ImageDrawText(&img, " UTF-8 C++20 CYBER-LINK: 10 Gbps ICE_SECURITY: BYPASS READY", 8, h - 13, 10, WHITE); - ImageDrawRectangle(&img, w - 45, 22, 45, h - 38, Color{ 22, 24, 32, 255 }); + ImageDrawRectangle(&img, 0, h - 16, w, 16, Color{ 25, 32, 44, 255 }); + ImageDrawText(&img, " UTF-8 C++20 NET_LINK: 10 Gbps ICE_SECURITY: ARMED", 8, h - 13, 10, TacticalColors::SilverText); + ImageDrawRectangle(&img, w - 45, 22, 45, h - 38, Color{ 18, 22, 30, 255 }); const char* codeLines[] = { "#include ", @@ -399,21 +400,21 @@ Texture2D createMonitorCodeTexture() { snprintf(numBuf, sizeof(numBuf), "%02d", i + 1); ImageDrawText(&img, numBuf, 8, lineY, 10, Color{ 85, 95, 115, 255 }); - Color syntaxCol = Color{ 220, 225, 235, 255 }; + Color syntaxCol = Color{ 210, 218, 230, 255 }; std::string s = codeLines[i]; - if (s.rfind("#include", 0) == 0) syntaxCol = Color{ 210, 140, 230, 255 }; - else if (s.find("//") != std::string::npos) syntaxCol = Color{ 100, 210, 115, 255 }; - else if (s.find("class") != std::string::npos || s.find("namespace") != std::string::npos) syntaxCol = Color{ 86, 156, 214, 255 }; - else if (s.find("bool") != std::string::npos || s.find("uint64_t") != std::string::npos) syntaxCol = Color{ 78, 201, 176, 255 }; - else if (s.find("return") != std::string::npos) syntaxCol = Color{ 197, 134, 192, 255 }; - else if (s.find("\"") != std::string::npos) syntaxCol = Color{ 206, 145, 120, 255 }; - else if (s.find("injectPayload") != std::string::npos || s.find("decryptChamber") != std::string::npos) syntaxCol = Color{ 220, 220, 130, 255 }; + if (s.rfind("#include", 0) == 0) syntaxCol = TacticalColors::PureWhite; + else if (s.find("//") != std::string::npos) syntaxCol = Color{ 110, 120, 135, 255 }; + else if (s.find("class") != std::string::npos || s.find("namespace") != std::string::npos) syntaxCol = TacticalColors::SilverText; + else if (s.find("bool") != std::string::npos || s.find("uint64_t") != std::string::npos) syntaxCol = Color{ 180, 195, 210, 255 }; + else if (s.find("return") != std::string::npos) syntaxCol = TacticalColors::Crimson; + else if (s.find("\"") != std::string::npos) syntaxCol = Color{ 230, 230, 235, 255 }; + else if (s.find("injectPayload") != std::string::npos || s.find("decryptChamber") != std::string::npos) syntaxCol = TacticalColors::PureWhite; ImageDrawText(&img, codeLines[i], 46, lineY, 10, syntaxCol); ImageDrawRectangle(&img, w - 40, lineY, static_cast(s.length() * 0.8f), 2, syntaxCol); } - ImageDrawRectangle(&img, 46 + 220, 28 + 18 * 11, 7, 10, Color{ 0, 240, 255, 255 }); + ImageDrawRectangle(&img, 46 + 220, 28 + 18 * 11, 7, 10, TacticalColors::Crimson); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); @@ -425,20 +426,20 @@ Texture2D createMonitorCodeTexture() { Texture2D createMonitorRadarTexture() { const int w = 256; const int h = 256; - Image img = GenImageColor(w, h, Color{ 10, 16, 26, 255 }); + Image img = GenImageColor(w, h, Color{ 10, 14, 20, 255 }); - ImageDrawRectangle(&img, 0, 0, w, 20, Color{ 16, 26, 42, 255 }); - ImageDrawText(&img, "NET SCAN // SUBNET 192.168.0", 8, 5, 10, Color{ 0, 220, 255, 255 }); + ImageDrawRectangle(&img, 0, 0, w, 20, Color{ 18, 24, 34, 255 }); + ImageDrawText(&img, "TACTICAL RADAR // SECTOR SCAN", 8, 5, 10, TacticalColors::PureWhite); int cx = w / 2; int cy = h / 2 + 10; - ImageDrawCircleLines(&img, cx, cy, 30, Color{ 0, 140, 180, 180 }); - ImageDrawCircleLines(&img, cx, cy, 60, Color{ 0, 140, 180, 180 }); - ImageDrawCircleLines(&img, cx, cy, 90, Color{ 0, 140, 180, 180 }); + ImageDrawRectangleLines(&img, Rectangle{ (float)(cx - 30), (float)(cy - 30), 60.0f, 60.0f }, 1, Color{ 60, 75, 95, 180 }); + ImageDrawRectangleLines(&img, Rectangle{ (float)(cx - 60), (float)(cy - 60), 120.0f, 120.0f }, 1, Color{ 60, 75, 95, 140 }); + ImageDrawRectangleLines(&img, Rectangle{ (float)(cx - 90), (float)(cy - 90), 180.0f, 180.0f }, 1, Color{ 60, 75, 95, 100 }); - ImageDrawLine(&img, cx - 100, cy, cx + 100, cy, Color{ 0, 180, 220, 120 }); - ImageDrawLine(&img, cx, cy - 100, cx, cy + 100, Color{ 0, 180, 220, 120 }); - ImageDrawLine(&img, cx, cy, cx + 65, cy - 65, Color{ 0, 255, 180, 255 }); + ImageDrawLine(&img, cx - 100, cy, cx + 100, cy, Color{ 80, 95, 115, 140 }); + ImageDrawLine(&img, cx, cy - 100, cx, cy + 100, Color{ 80, 95, 115, 140 }); + ImageDrawLine(&img, cx, cy, cx + 65, cy - 65, TacticalColors::Crimson); Vector2 nodes[] = { { 70.0f, 90.0f }, @@ -448,13 +449,13 @@ Texture2D createMonitorRadarTexture() { { 128.0f, 138.0f } }; for (int i = 0; i < 4; ++i) { - ImageDrawLine(&img, (int)nodes[i].x, (int)nodes[i].y, (int)nodes[4].x, (int)nodes[4].y, Color{ 0, 200, 255, 160 }); - ImageDrawCircle(&img, (int)nodes[i].x, (int)nodes[i].y, 4, Color{ 255, 190, 0, 255 }); + ImageDrawLine(&img, (int)nodes[i].x, (int)nodes[i].y, (int)nodes[4].x, (int)nodes[4].y, Color{ 80, 95, 115, 120 }); + ImageDrawRectangle(&img, (int)nodes[i].x - 2, (int)nodes[i].y - 2, 5, 5, TacticalColors::Crimson); } - ImageDrawCircle(&img, (int)nodes[4].x, (int)nodes[4].y, 6, Color{ 0, 255, 120, 255 }); + ImageDrawRectangle(&img, (int)nodes[4].x - 3, (int)nodes[4].y - 3, 7, 7, TacticalColors::PureWhite); - ImageDrawText(&img, "GATEWAY STATUS: STANDBY", 10, h - 26, 10, Color{ 0, 255, 120, 255 }); - ImageDrawText(&img, "ICE BREAKER: READY", 10, h - 14, 10, Color{ 255, 200, 60, 255 }); + ImageDrawText(&img, "HOSTILE THREATS: DETECTED", 10, h - 26, 10, TacticalColors::Crimson); + ImageDrawText(&img, "COMBAT READY: ONLINE", 10, h - 14, 10, TacticalColors::SilverText); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); @@ -466,7 +467,7 @@ Texture2D createMonitorRadarTexture() { Texture2D createBedQuiltTexture() { const int w = 256; const int h = 256; - Image img = GenImageColor(w, h, Color{ 42, 54, 76, 255 }); + Image img = GenImageColor(w, h, Color{ 26, 32, 46, 255 }); Color* pixels = (Color*)img.data; const int gridSize = 32; @@ -480,19 +481,19 @@ Texture2D createBedQuiltTexture() { int distToSeam = std::min({ d1, gridSize - d1, d2, gridSize - d2 }); float puff = std::clamp(distToSeam / (gridSize * 0.5f), 0.0f, 1.0f); - int puffShade = static_cast(puff * 22.0f); - int weave = ((x ^ y) % 3) * 3 - 3; + int puffShade = static_cast(puff * 16.0f); + int weave = ((x ^ y) % 3) * 2 - 2; - int r = 40 + puffShade + weave; - int g = 52 + puffShade + weave; - int b = 74 + puffShade + weave; + int r = 26 + puffShade + weave; + int g = 32 + puffShade + weave; + int b = 48 + puffShade + weave; if (isSeam) { bool isStitch = ((x / 4 + y / 4) % 2 == 0); if (isStitch) { - r = 110; g = 135; b = 175; + r = 65; g = 80; b = 115; } else { - r = 24; g = 32; b = 46; + r = 16; g = 20; b = 30; } } @@ -557,7 +558,7 @@ Texture2D createCityNightTexture() { if (bh > 160) { ImageDrawLine(&img, bx + bw / 2, by - 25, bx + bw / 2, by, Color{ 60, 70, 90, 255 }); - ImageDrawCircle(&img, bx + bw / 2, by - 26, 2, Color{ 255, 40, 40, 255 }); + ImageDrawPixel(&img, bx + bw / 2, by - 26, Color{ 255, 40, 40, 255 }); } for (int wy = by + 12; wy < h - 8; wy += 8) { @@ -623,12 +624,12 @@ Texture2D createCyberGateTexture() { } } - ImageDrawRectangle(&img, 32, h / 2 - 12, w - 64, 24, Color{ 20, 26, 38, 255 }); - ImageDrawRectangleLines(&img, Rectangle{ 32.0f, h / 2.0f - 12.0f, (float)(w - 64), 24.0f }, 2, Color{ 0, 180, 220, 200 }); + ImageDrawRectangle(&img, 32, h / 2 - 12, w - 64, 24, TacticalColors::DarkPanel); + ImageDrawRectangleLines(&img, Rectangle{ 32.0f, h / 2.0f - 12.0f, (float)(w - 64), 24.0f }, 2, TacticalColors::BorderWhite); - ImageDrawText(&img, "NEURAL DIVER GATEWAY", 46, h / 2 - 8, 12, Color{ 0, 240, 255, 255 }); - ImageDrawText(&img, "CYBERSPACE ICE LEVEL 1+", 54, 40, 10, Color{ 240, 195, 25, 255 }); - ImageDrawText(&img, "AUTHORIZED RUNNERS ONLY", 50, h - 50, 10, Color{ 220, 50, 60, 255 }); + ImageDrawText(&img, "TACTICAL BUNKER ACCESS", 46, h / 2 - 8, 12, TacticalColors::PureWhite); + ImageDrawText(&img, "SECTOR ARCHIVE // LEVEL 1+", 54, 40, 10, TacticalColors::SilverText); + ImageDrawText(&img, "RESTRICTED PERSONNEL ONLY", 50, h - 50, 10, TacticalColors::Crimson); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); @@ -636,69 +637,62 @@ Texture2D createCyberGateTexture() { return tex; } +// Procedural generator: High-Tech Gunmetal Dungeon Wall (256x256) // Procedural generator: High-Tech Gunmetal Dungeon Wall (256x256) Texture2D createDungeonWallTexture() { const int w = 256; const int h = 256; - Image img = GenImageColor(w, h, Color{ 18, 24, 36, 255 }); + Image img = GenImageColor(w, h, Color{ 18, 22, 28, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - int n = ((x * 13 + y * 29) % 17) - 8; - int r = std::clamp(22 + n, 0, 255); - int g = std::clamp(28 + n, 0, 255); - int b = std::clamp(42 + n, 0, 255); + int n = ((x * 17 + y * 31) % 15) - 7; + int r = std::clamp(20 + n, 0, 255); + int g = std::clamp(24 + n, 0, 255); + int b = std::clamp(30 + n, 0, 255); Color c = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; - // Panel bevel seams - if (x < 4 || x >= w - 4 || y < 4 || y >= h - 4 || - y == 85 || y == 86 || y == 170 || y == 171 || - (x == 128 && (y < 85 || y > 170))) { - c = Color{ 8, 12, 18, 255 }; + // Heavy industrial plate bevel seams (0 rounding) + if (x < 3 || x >= w - 3 || y < 3 || y >= h - 3 || + y == 84 || y == 85 || y == 170 || y == 171 || + (x == 128 && (y < 84 || y > 170))) { + c = Color{ 8, 10, 14, 255 }; } // Hex bolts in corners - for (int bx : { 16, w - 16 }) { - for (int by : { 16, 96, 182, h - 16 }) { - if (std::abs(x - bx) <= 3 && std::abs(y - by) <= 3) { - c = Color{ 130, 160, 200, 255 }; + for (int bx : { 12, w - 12 }) { + for (int by : { 12, 94, 180, h - 12 }) { + if (std::abs(x - bx) <= 2 && std::abs(y - by) <= 2) { + c = Color{ 95, 105, 120, 255 }; } } } - // Vertical glowing cyan data fiber conduit - if (x >= 124 && x <= 132) { - int dist = std::abs(x - 128); - if (dist <= 1) { - c = Color{ 0, 240, 255, 255 }; - } else if (dist <= 3) { - c = Color{ 0, 160, 220, 255 }; - } else { - c = Color{ 0, 80, 140, 255 }; + // Dark recessed vertical conduit slot with subtle red status pip + if (x >= 126 && x <= 130) { + c = Color{ 10, 12, 16, 255 }; + if (y >= 124 && y <= 132) { + c = TacticalColors::Crimson; } } - // Hazard warning stripes at bottom - if (y >= h - 22 && y < h - 4) { - bool isYellow = (((x + y) / 10) % 2 == 0); - c = isYellow ? Color{ 230, 180, 25, 255 } : Color{ 15, 18, 24, 255 }; - } - - // Server status LEDs - if (x >= 40 && x <= 85 && y >= 110 && y <= 145) { - if (x % 12 < 4 && y % 10 < 4) { - c = ((x + y) % 3 == 0) ? CyberColors::NeonGreen : - (((x + y) % 3 == 1) ? CyberColors::NeonCyan : CyberColors::NeonOrange); - } else { - c = Color{ 12, 16, 24, 255 }; - } + // Red & dark gray tactical hazard stencil at bottom + if (y >= h - 18 && y < h - 4) { + bool isStripe = (((x + y) / 10) % 2 == 0); + c = isStripe ? TacticalColors::DarkRed : Color{ 12, 14, 18, 255 }; } pixels[y * w + x] = c; } } + // High-contrast stencil warning markings + ImageDrawRectangle(&img, 36, 102, 184, 24, Color{ 12, 14, 18, 240 }); + ImageDrawRectangleLines(&img, Rectangle{ 36.0f, 102.0f, 184.0f, 24.0f }, 1, TacticalColors::DarkRed); + ImageDrawText(&img, "[SEC-04 // RESTRICTED]", 46, 108, 10, TacticalColors::PureWhite); + ImageDrawText(&img, "HIGH VOLTAGE", 52, 138, 10, TacticalColors::Crimson); + Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); @@ -709,28 +703,29 @@ Texture2D createDungeonWallTexture() { Texture2D createDungeonFloorTexture() { const int w = 256; const int h = 256; - Image img = GenImageColor(w, h, Color{ 12, 16, 24, 255 }); + Image img = GenImageColor(w, h, Color{ 14, 17, 22, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - bool isMeshLine = (((x + y) % 16 == 0) || ((x - y + 512) % 16 == 0)); - bool isGrateHole = (((x + y) % 16 >= 6 && (x + y) % 16 <= 10) && - ((x - y + 512) % 16 >= 6 && (x - y + 512) % 16 <= 10)); + // Anti-slip diamond plate ridges + bool isDiamondRidge = (((x + y) % 16 <= 1) || ((x - y + 512) % 16 <= 1)); + bool isDrainSlot = (((x + y) % 16 >= 7 && (x + y) % 16 <= 9) && + ((x - y + 512) % 16 >= 7 && (x - y + 512) % 16 <= 9)); - Color c = Color{ 20, 28, 40, 255 }; + Color c = Color{ 22, 27, 34, 255 }; - if (isMeshLine) { - c = Color{ 70, 90, 120, 255 }; - } else if (isGrateHole) { - bool isCircuit = (x % 64 < 6 || y % 64 < 6); - c = isCircuit ? Color{ 0, 200, 240, 255 } : Color{ 6, 8, 14, 255 }; + if (isDiamondRidge) { + c = Color{ 48, 56, 70, 255 }; + } else if (isDrainSlot) { + c = Color{ 6, 8, 12, 255 }; // Deep sub-floor darkness } - if (x < 6 || x >= w - 6 || y < 6 || y >= h - 6) { - c = Color{ 35, 45, 60, 255 }; + // Heavy steel framing plate along edges + if (x < 5 || x >= w - 5 || y < 5 || y >= h - 5) { + c = Color{ 32, 38, 48, 255 }; if ((x % 32 < 4) && (y % 32 < 4)) { - c = Color{ 140, 165, 195, 255 }; + c = Color{ 110, 125, 145, 255 }; // Rivets } } @@ -744,29 +739,31 @@ Texture2D createDungeonFloorTexture() { return tex; } -// Procedural generator: Industrial Grid Ceiling with Neon Fixture (256x256) +// Procedural generator: Industrial Grid Ceiling with Dim Red Emergency Fixture (256x256) Texture2D createDungeonCeilTexture() { const int w = 256; const int h = 256; - Image img = GenImageColor(w, h, Color{ 10, 14, 20, 255 }); + Image img = GenImageColor(w, h, Color{ 10, 12, 16, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - Color c = Color{ 12, 16, 24, 255 }; + Color c = Color{ 12, 15, 20, 255 }; + // Structural I-beam ribs if (x % 64 < 8 || y % 64 < 8) { - c = Color{ 30, 40, 55, 255 }; + c = Color{ 26, 32, 42, 255 }; } - if (x >= 96 && x <= 160 && y >= 32 && y <= 224) { + // Central cable tray with dim emergency red light conduit + if (x >= 118 && x <= 138 && y >= 24 && y <= 232) { int distFromCenter = std::abs(x - 128); - if (distFromCenter <= 12) { - c = Color{ 245, 252, 255, 255 }; - } else if (distFromCenter <= 22) { - c = Color{ 0, 220, 255, 255 }; + if (distFromCenter <= 3) { + c = TacticalColors::Crimson; + } else if (distFromCenter <= 6) { + c = TacticalColors::DarkRed; } else { - c = Color{ 0, 80, 140, 255 }; + c = Color{ 18, 22, 28, 255 }; } } @@ -784,33 +781,35 @@ Texture2D createDungeonCeilTexture() { Texture2D createBulkheadDoorTexture() { const int w = 256; const int h = 256; - Image img = GenImageColor(w, h, Color{ 22, 30, 44, 255 }); + Image img = GenImageColor(w, h, Color{ 20, 24, 32, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - Color c = Color{ 26, 36, 52, 255 }; + Color c = Color{ 24, 30, 40, 255 }; - if (x < 6 || x >= w - 6 || y < 6 || y >= h - 6 || std::abs(x - 128) <= 2) { - c = Color{ 12, 16, 24, 255 }; + if (x < 5 || x >= w - 5 || y < 5 || y >= h - 5 || std::abs(x - 128) <= 2) { + c = Color{ 8, 10, 14, 255 }; } - if ((y >= 8 && y <= 32) || (y >= h - 34 && y <= h - 10)) { - bool isYellow = (((x + y) / 14) % 2 == 0); - c = isYellow ? Color{ 235, 185, 20, 255 } : Color{ 18, 20, 26, 255 }; + // Diagonal crimson/dark hazard stripes + if ((y >= 8 && y <= 30) || (y >= h - 32 && y <= h - 10)) { + bool isRed = (((x + y) / 14) % 2 == 0); + c = isRed ? TacticalColors::Crimson : Color{ 12, 14, 18, 255 }; } - if (x == 50 || x == 51 || x == 205 || x == 206) { - c = Color{ 60, 80, 110, 255 }; + if (x == 48 || x == 49 || x == 207 || x == 208) { + c = Color{ 55, 68, 85, 255 }; } + // Center hydraulic lock housing if (x >= 88 && x <= 168 && y >= 96 && y <= 160) { int dx = std::abs(x - 128); int dy = std::abs(y - 128); if (dx <= 32 && dy <= 24) { - c = Color{ 10, 14, 20, 255 }; - if (dx <= 20 && dy <= 12) { - c = Color{ 255, 60, 40, 255 }; + c = Color{ 10, 12, 16, 255 }; + if (dx <= 18 && dy <= 10) { + c = TacticalColors::Crimson; } } } @@ -819,7 +818,7 @@ Texture2D createBulkheadDoorTexture() { } } - ImageDrawText(&img, "RESTRICTED ICE", 70, 44, 12, Color{ 0, 220, 255, 255 }); + ImageDrawText(&img, "[RESTRICTED // BLAST BULKHEAD]", 44, 42, 10, TacticalColors::PureWhite); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); @@ -831,27 +830,27 @@ Texture2D createBulkheadDoorTexture() { Texture2D createDoorFrameTexture() { const int w = 128; const int h = 256; - Image img = GenImageColor(w, h, Color{ 30, 40, 58, 255 }); + Image img = GenImageColor(w, h, Color{ 24, 30, 40, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { - Color c = Color{ 28, 38, 54, 255 }; + Color c = Color{ 22, 28, 38, 255 }; - if (x < 6 || x >= w - 6) { - c = Color{ 14, 18, 26, 255 }; + if (x < 5 || x >= w - 5) { + c = Color{ 10, 12, 16, 255 }; } - if (x >= 40 && x <= 88) { - c = Color{ 55, 75, 100, 255 }; - if (x >= 56 && x <= 72) { - c = Color{ 160, 185, 215, 255 }; + if (x >= 38 && x <= 90) { + c = Color{ 45, 55, 72, 255 }; + if (x >= 54 && x <= 74) { + c = Color{ 120, 135, 155, 255 }; } } - if (x >= w - 18 && x <= w - 6) { - bool isYellow = (((x + y) / 10) % 2 == 0); - c = isYellow ? Color{ 230, 180, 25, 255 } : Color{ 16, 20, 28, 255 }; + if (x >= w - 16 && x <= w - 5) { + bool isRed = (((x + y) / 10) % 2 == 0); + c = isRed ? TacticalColors::Crimson : Color{ 12, 14, 18, 255 }; } pixels[y * w + x] = c; @@ -1048,7 +1047,7 @@ void drawAncientAmphora(Vector3 pos, float height, Color clayColor) { DrawCylinder({ pos.x, pos.y + height * 0.40f, pos.z }, height * 0.18f, height * 0.38f, height * 0.60f, 12, clayColor); DrawCylinder({ pos.x, pos.y + height * 0.72f, pos.z }, height * 0.38f, height * 0.16f, height * 0.20f, 12, clayColor); DrawCylinder({ pos.x, pos.y + height * 0.88f, pos.z }, height * 0.16f, height * 0.18f, height * 0.20f, 12, clayColor); - DrawCircle3D({ pos.x, pos.y + height, pos.z }, height * 0.20f, { 1.0f, 0.0f, 0.0f }, 90.0f, clayColor); + DrawCylinderWires({ pos.x, pos.y + height, pos.z }, height * 0.20f, height * 0.20f, 0.02f, 12, clayColor); // Twin loop handles DrawCube({ pos.x - height * 0.26f, pos.y + height * 0.62f, pos.z }, height * 0.06f, height * 0.28f, height * 0.06f, clayColor); @@ -1084,7 +1083,7 @@ void drawMarbleNectarFountain(Vector3 pos, bool recharged, float animTime) { // Spouting urn in center DrawCylinder({ pos.x, pos.y + 1.25f, pos.z }, 0.22f, 0.14f, 0.55f, 12, Color{ 185, 110, 70, 255 }); - DrawCircle3D({ pos.x, pos.y + 1.05f + pulse * 0.12f, pos.z }, 0.55f, { 1.0f, 0.0f, 0.0f }, 90.0f, nectarCol); + DrawCylinderWires({ pos.x, pos.y + 1.05f + pulse * 0.12f, pos.z }, 0.55f, 0.55f, 0.02f, 16, nectarCol); DrawSphere({ pos.x, pos.y + 1.55f, pos.z }, 0.12f + pulse * 0.04f, WHITE); } @@ -1203,6 +1202,26 @@ void CyberRenderer::generateTextures() { m_texCityNight = createCityNightTexture(); m_texCyberGate = createCyberGateTexture(); + // Load Room_window.jpg picture for window backdrop + std::vector winPaths = { + "assets/Pictures/Room_window.jpg", + "../assets/Pictures/Room_window.jpg", + "Pictures/Room_window.jpg", + "assets/Room_window.jpg" + }; + bool loadedWin = false; + for (const auto& wp : winPaths) { + if (FileExists(wp.c_str())) { + m_texWindowView = LoadTexture(wp.c_str()); + SetTextureFilter(m_texWindowView, TEXTURE_FILTER_BILINEAR); + loadedWin = true; + break; + } + } + if (!loadedWin) { + m_texWindowView = m_texCityNight; + } + m_texAncientWall = createAncientStoneWallTexture(); m_texAncientFloor = createAncientFloorTexture(); m_texAncientCeil = createAncientCeilTexture(); @@ -1229,6 +1248,9 @@ void CyberRenderer::unloadTextures() { UnloadTexture(m_texMonitorRadar); UnloadTexture(m_texBedQuilt); UnloadTexture(m_texCityNight); + if (m_texWindowView.id != m_texCityNight.id && m_texWindowView.id != 0) { + UnloadTexture(m_texWindowView); + } UnloadTexture(m_texCyberGate); UnloadTexture(m_texAncientWall); @@ -1282,33 +1304,31 @@ void CyberRenderer::renderCyberDrone(Vector3 pos, Color coreColor, float scale, basePos.y += shakeY; } - // Elite Aura and Orbiting Corona + // Elite Tactical Armor Shrouds and Orbiting Blades (0 rounding) if (isElite) { float haloY = basePos.y + 0.48f * scale + 0.04f * std::sin(animTime * 4.0f); - DrawCircle3D({ basePos.x, haloY, basePos.z }, 0.55f * scale, { 1.0f, 0.0f, 0.0f }, 90.0f, CyberColors::NeonPurple); - DrawCircle3D({ basePos.x, haloY, basePos.z }, 0.40f * scale, { 1.0f, 0.0f, 0.0f }, 90.0f, CyberColors::NeonYellow); for (int k = 0; k < 4; ++k) { float spAngle = animTime * 2.5f + k * (3.14159265f * 0.5f); Vector3 spPos = { basePos.x + std::cos(spAngle) * 0.48f * scale, haloY, basePos.z + std::sin(spAngle) * 0.48f * scale }; - DrawCube(spPos, 0.08f * scale, 0.18f * scale, 0.08f * scale, CyberColors::NeonYellow); - DrawCubeWires(spPos, 0.09f * scale, 0.19f * scale, 0.09f * scale, CyberColors::NeonPurple); + DrawCube(spPos, 0.08f * scale, 0.18f * scale, 0.08f * scale, Color{ 30, 36, 48, 255 }); + DrawCubeWires(spPos, 0.09f * scale, 0.19f * scale, 0.09f * scale, TacticalColors::Crimson); } } // 1. Armored Faceted Chassis - DrawSphere(basePos, 0.42f * scale, Color{ 22, 28, 42, 255 }); - DrawSphereWires(basePos, 0.44f * scale, 8, 8, Color{ 45, 60, 90, 255 }); + DrawSphere(basePos, 0.42f * scale, Color{ 22, 28, 36, 255 }); + DrawSphereWires(basePos, 0.44f * scale, 8, 8, Color{ 55, 65, 80, 255 }); - // 2. Glowing Elemental Core / Reactor + // 2. Tactical Sensor Core / Reactor float pulse = 0.5f + 0.5f * std::sin(animTime * (isAttacking ? 14.0f : 5.0f)); - Color pulseCol = isHit ? WHITE : (isAttacking ? RED : coreColor); + Color pulseCol = isHit ? WHITE : (isAttacking ? TacticalColors::BrightRed : coreColor); DrawSphere(basePos, 0.28f * scale, pulseCol); // 3. Forward Sensory Laser Visor Vector3 visorPos = { basePos.x, basePos.y + 0.05f * scale, basePos.z - 0.38f * scale }; - DrawCube(visorPos, 0.38f * scale, 0.12f * scale, 0.12f * scale, Color{ 14, 18, 26, 255 }); - DrawCube(visorPos, 0.34f * scale, 0.08f * scale, 0.14f * scale, isAttacking ? RED : coreColor); - DrawCubeWires(visorPos, 0.36f * scale, 0.10f * scale, 0.14f * scale, WHITE); + DrawCube(visorPos, 0.38f * scale, 0.12f * scale, 0.12f * scale, Color{ 14, 18, 24, 255 }); + DrawCube(visorPos, 0.34f * scale, 0.08f * scale, 0.14f * scale, isAttacking ? TacticalColors::BrightRed : coreColor); + DrawCubeWires(visorPos, 0.36f * scale, 0.10f * scale, 0.14f * scale, TacticalColors::PureWhite); // 4. Orbiting Deflection Shields (4 plates rotating around core) float rot = animTime * 1.6f; @@ -1320,17 +1340,17 @@ void CyberRenderer::renderCyberDrone(Vector3 pos, Color coreColor, float scale, basePos.y + std::sin(angle * 2.0f) * 0.10f * scale, basePos.z + std::sin(angle) * dist }; - DrawCube(shieldPos, 0.18f * scale, 0.38f * scale, 0.06f * scale, Color{ 30, 42, 62, 255 }); - DrawCubeWires(shieldPos, 0.19f * scale, 0.39f * scale, 0.07f * scale, coreColor); + DrawCube(shieldPos, 0.18f * scale, 0.38f * scale, 0.06f * scale, Color{ 28, 34, 46, 255 }); + DrawCubeWires(shieldPos, 0.19f * scale, 0.39f * scale, 0.07f * scale, isElite ? TacticalColors::Crimson : TacticalColors::BorderMuted); } // 5. Plasma Thruster & Exhaust Plume Vector3 nozzlePos = { basePos.x, basePos.y - 0.40f * scale, basePos.z }; - DrawCylinder(nozzlePos, 0.14f * scale, 0.18f * scale, 0.15f * scale, 8, Color{ 35, 45, 60, 255 }); - float flameH = 0.25f + pulse * 0.15f; + DrawCylinder(nozzlePos, 0.14f * scale, 0.18f * scale, 0.15f * scale, 8, Color{ 35, 42, 54, 255 }); + float flameH = 0.22f + pulse * 0.12f; Vector3 flamePos = { basePos.x, basePos.y - (0.45f + flameH * 0.5f) * scale, basePos.z }; - DrawCylinder(flamePos, 0.10f * scale, 0.01f * scale, flameH * scale, 8, CyberColors::NeonCyan); - DrawSphere({ basePos.x, basePos.y - 0.45f * scale, basePos.z }, 0.08f * scale, WHITE); + DrawCylinder(flamePos, 0.10f * scale, 0.01f * scale, flameH * scale, 8, TacticalColors::Crimson); + DrawSphere({ basePos.x, basePos.y - 0.45f * scale, basePos.z }, 0.08f * scale, TacticalColors::PureWhite); } void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float animTime) { @@ -1368,10 +1388,19 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float Vector3 center = { x * tileSize, 0.0f, y * tileSize }; TileType tile = dungeon.getTile(x, y); - // Atmospheric depth fog - float dist = std::sqrt(static_cast((x - pX) * (x - pX) + (y - pY) * (y - pY))); - float fog = std::clamp((dist - 1.2f) / (renderDist - 1.2f), 0.0f, 1.0f); - float b = 1.0f - fog * 0.85f; + // Tactical Helmet Spotlight / Flashlight Cone Falloff + float dx = (center.x - eye.x); + float dz = (center.z - eye.z); + float dist = std::sqrt(dx * dx + dz * dz); + float b = 0.12f; // Deep atmospheric shadow baseline + if (dist < 0.4f) { + b = 1.0f; + } else { + float dot = (dx * dirX + dz * dirZ) / dist; + float cone = std::clamp((dot - 0.20f) / 0.80f, 0.0f, 1.0f); + float distFall = std::clamp(1.0f - (dist / (renderDist * 2.0f)), 0.0f, 1.0f); + b = std::clamp(0.12f + cone * distFall * 0.88f, 0.08f, 1.0f); + } Color tileTint = Color{ static_cast(255 * b), static_cast(255 * b), static_cast(255 * b), 255 }; if (tile == TileType::WALL) { @@ -1379,12 +1408,8 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float Vector3 wallSize = { tileSize, wallHeight, tileSize }; drawTexturedBox(m_texDungeonWall, wallCenter, wallSize, 1.0f, 1.0f, tileTint); - - Vector3 accentCenter = { center.x, wallHeight * 0.5f, center.z }; - DrawCubeWires(accentCenter, wallSize.x * 1.008f, 0.06f, wallSize.z * 1.008f, - Color{ 0, static_cast(220 * b), static_cast(255 * b), 255 }); } else { - // Textured Floor + // Textured Industrial Grate Floor drawTexturedQuad(m_texDungeonFloor, { center.x - tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f }, { center.x - tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f }, @@ -1392,7 +1417,7 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float { center.x + tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f }, 1.0f, 1.0f, tileTint); - // Textured Ceiling + // Textured Beam Ceiling with Dim Red Fixtures drawTexturedQuad(m_texDungeonCeil, { center.x + tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f }, { center.x + tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f }, @@ -1419,10 +1444,10 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float drawTexturedBox(m_texDungeonDoor, doorBody, { 1.6f, wallHeight - 0.2f, 0.2f }, 1.0f, 1.0f, tileTint); float pulse = 0.5f + 0.5f * std::sin(animTime * 6.0f); - Color lockCol = (pulse > 0.5f) ? CyberColors::NeonOrange : RED; + Color lockCol = (pulse > 0.5f) ? TacticalColors::Crimson : TacticalColors::DarkRed; Vector3 lockCore = { center.x, 0.9f, center.z }; - DrawCube(lockCore, 0.4f, 0.25f, 0.24f, lockCol); - DrawCubeWires(lockCore, 0.42f, 0.27f, 0.26f, CyberColors::NeonYellow); + DrawCube(lockCore, 0.38f, 0.22f, 0.24f, lockCol); + DrawCubeWires(lockCore, 0.40f, 0.24f, 0.26f, TacticalColors::BrightRed); } else { Vector3 leftSlab = { center.x - 0.7f, (wallHeight - 0.2f) * 0.5f, center.z }; Vector3 rightSlab = { center.x + 0.7f, (wallHeight - 0.2f) * 0.5f, center.z }; @@ -1444,10 +1469,10 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float drawTexturedBox(m_texDungeonDoor, doorBody, { 0.2f, wallHeight - 0.2f, 1.6f }, 1.0f, 1.0f, tileTint); float pulse = 0.5f + 0.5f * std::sin(animTime * 6.0f); - Color lockCol = (pulse > 0.5f) ? CyberColors::NeonOrange : RED; + Color lockCol = (pulse > 0.5f) ? TacticalColors::Crimson : TacticalColors::DarkRed; Vector3 lockCore = { center.x, 0.9f, center.z }; - DrawCube(lockCore, 0.24f, 0.25f, 0.4f, lockCol); - DrawCubeWires(lockCore, 0.26f, 0.27f, 0.42f, CyberColors::NeonYellow); + DrawCube(lockCore, 0.24f, 0.22f, 0.38f, lockCol); + DrawCubeWires(lockCore, 0.26f, 0.24f, 0.40f, TacticalColors::BrightRed); } else { Vector3 northSlab = { center.x, (wallHeight - 0.2f) * 0.5f, center.z - 0.7f }; Vector3 southSlab = { center.x, (wallHeight - 0.2f) * 0.5f, center.z + 0.7f }; @@ -1456,20 +1481,20 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float } } } else if (tile == TileType::LOOT_CACHE) { - float floatY = 0.6f + std::sin(animTime * 3.0f + x) * 0.12f; + float floatY = 0.55f + std::sin(animTime * 3.0f + x) * 0.08f; Vector3 lootPos = { center.x, floatY, center.z }; - DrawCubeWires(lootPos, 0.6f, 0.6f, 0.6f, CyberColors::NeonYellow); - DrawCube(lootPos, 0.35f, 0.35f, 0.35f, Color{ 255, 230, 0, 180 }); + DrawCube(lootPos, 0.52f, 0.40f, 0.52f, Color{ 24, 30, 40, 255 }); + DrawCubeWires(lootPos, 0.54f, 0.42f, 0.54f, TacticalColors::Crimson); + DrawCube({ lootPos.x, lootPos.y + 0.12f, lootPos.z }, 0.18f, 0.08f, 0.18f, TacticalColors::BrightRed); } else if (tile == TileType::EXIT_PORTAL) { float pulse = 0.5f + 0.5f * std::sin(animTime * 4.0f); - // 1. Raised Octagonal Platform Dais with hazard glow + // Tactical Extraction Terminal Platform Vector3 basePos = { center.x, 0.10f, center.z }; - DrawCylinder(basePos, 0.95f, 1.0f, 0.20f, 8, Color{ 22, 30, 44, 255 }); - DrawCylinderWires(basePos, 0.96f, 1.01f, 0.20f, 8, CyberColors::NeonCyan); - DrawCircle3D({ center.x, 0.21f, center.z }, 0.90f, { 1.0f, 0.0f, 0.0f }, 90.0f, Color{ 0, 240, 255, 120 }); + DrawCylinder(basePos, 0.95f, 1.0f, 0.20f, 8, Color{ 22, 28, 38, 255 }); + DrawCylinderWires(basePos, 0.96f, 1.01f, 0.20f, 8, TacticalColors::Crimson); - // 2. Four Corner Energy Emitter Pylons with glowing caps + // 4 Corner Pylons with Crimson Warning Beacons float pylonDist = 0.72f; Vector3 pylons[4] = { { center.x - pylonDist, 0.42f, center.z - pylonDist }, @@ -1478,34 +1503,20 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float { center.x + pylonDist, 0.42f, center.z + pylonDist } }; for (int p = 0; p < 4; ++p) { - DrawCylinder(pylons[p], 0.06f, 0.08f, 0.65f, 8, Color{ 36, 46, 66, 255 }); - DrawCylinderWires(pylons[p], 0.065f, 0.085f, 0.65f, 8, CyberColors::NeonCyan); - DrawSphere({ pylons[p].x, 0.76f, pylons[p].z }, 0.08f, Color{ 0, 255, 200, 255 }); + DrawCylinder(pylons[p], 0.06f, 0.08f, 0.65f, 8, Color{ 34, 42, 56, 255 }); + DrawCylinderWires(pylons[p], 0.065f, 0.085f, 0.65f, 8, TacticalColors::DarkRed); + DrawCube({ pylons[p].x, 0.76f, pylons[p].z }, 0.12f, 0.12f, 0.12f, TacticalColors::Crimson); } - // 3. Three Concentric Levitating Counter-Rotating Neon Rings - float ring1Y = 0.42f + 0.05f * std::sin(animTime * 3.0f); - float ring2Y = 0.88f + 0.07f * std::sin(animTime * 3.5f + 1.2f); - float ring3Y = 1.34f + 0.05f * std::sin(animTime * 2.8f + 2.4f); - - DrawCircle3D({ center.x, ring1Y, center.z }, 0.75f, { 1.0f, 0.0f, 0.0f }, 90.0f, - Color{ 0, 255, 180, static_cast(160 + pulse * 95) }); - DrawCircle3D({ center.x, ring2Y, center.z }, 0.55f, { 1.0f, 0.0f, 0.0f }, 90.0f, - Color{ 0, 240, 255, static_cast(180 + pulse * 75) }); - DrawCircle3D({ center.x, ring3Y, center.z }, 0.35f, { 1.0f, 0.0f, 0.0f }, 90.0f, - Color{ 180, 0, 255, static_cast(160 + pulse * 95) }); - - // 4. Central Swirling Singularity Core + // Central Quantum Extraction Beacon Vector3 corePos = { center.x, 0.85f + pulse * 0.05f, center.z }; - DrawCylinder(corePos, 0.20f + pulse * 0.03f, 0.20f + pulse * 0.03f, 1.3f, 16, - Color{ 0, 230, 255, static_cast(130 + pulse * 80) }); - DrawCylinderWires(corePos, 0.23f, 0.23f, 1.32f, 8, CyberColors::NeonGreen); - DrawSphere(corePos, 0.18f + pulse * 0.04f, Color{ 255, 255, 255, 220 }); + DrawCube(corePos, 0.32f, 1.25f, 0.32f, Color{ 230, 25, 45, static_cast(120 + pulse * 70) }); + DrawCubeWires(corePos, 0.34f, 1.27f, 0.34f, TacticalColors::PureWhite); - // 5. Overhead Holographic Beacon Frame + // Overhead Extraction Airlock Frame Vector3 signPos = { center.x, wallHeight - 0.20f, center.z }; - DrawCube(signPos, 1.3f, 0.18f, 0.06f, Color{ 10, 16, 26, 240 }); - DrawCubeWires(signPos, 1.32f, 0.20f, 0.08f, CyberColors::NeonCyan); + DrawCube(signPos, 1.3f, 0.18f, 0.06f, Color{ 14, 18, 26, 240 }); + DrawCubeWires(signPos, 1.32f, 0.20f, 0.08f, TacticalColors::Crimson); } } } @@ -1518,59 +1529,59 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float Vector3 lCenter = { l.x * tileSize, 0.9f, l.y * tileSize }; if (l.vertical) { - DrawCube({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, 0.25f, 0.25f, 0.10f, Color{ 60, 65, 80, 255 }); - DrawCube({ lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.25f, 0.25f, 0.10f, Color{ 60, 65, 80, 255 }); + DrawCube({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, 0.25f, 0.25f, 0.10f, Color{ 45, 52, 65, 255 }); + DrawCube({ lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.25f, 0.25f, 0.10f, Color{ 45, 52, 65, 255 }); if (l.active) { float pulse = 0.5f + 0.5f * std::sin(animTime * 12.0f); - DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.04f, 0.04f, 8, RED); - DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.12f, 0.12f, 8, Color{ 255, 0, 50, static_cast(70 + pulse * 60) }); + DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.04f, 0.04f, 8, TacticalColors::BrightRed); + DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.10f, 0.10f, 8, Color{ 230, 25, 45, static_cast(70 + pulse * 60) }); } else { - DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.02f, 0.02f, 6, Color{ 0, 255, 120, 90 }); + DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.02f, 0.02f, 6, Color{ 80, 90, 105, 70 }); } } else { - DrawCube({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, 0.10f, 0.25f, 0.25f, Color{ 60, 65, 80, 255 }); - DrawCube({ lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.10f, 0.25f, 0.25f, Color{ 60, 65, 80, 255 }); + DrawCube({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, 0.10f, 0.25f, 0.25f, Color{ 45, 52, 65, 255 }); + DrawCube({ lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.10f, 0.25f, 0.25f, Color{ 45, 52, 65, 255 }); if (l.active) { float pulse = 0.5f + 0.5f * std::sin(animTime * 12.0f); - DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.04f, 0.04f, 8, RED); - DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.12f, 0.12f, 8, Color{ 255, 0, 50, static_cast(70 + pulse * 60) }); + DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.04f, 0.04f, 8, TacticalColors::BrightRed); + DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.10f, 0.10f, 8, Color{ 230, 25, 45, static_cast(70 + pulse * 60) }); } else { - DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.02f, 0.02f, 6, Color{ 0, 255, 120, 90 }); + DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.02f, 0.02f, 6, Color{ 80, 90, 105, 70 }); } } } - // Render Proximity Mines + // Render Proximity Mines (Zero-rounding: rectangular plate with flashing sensor) for (const auto& m : dungeon.getMines()) { if (m.defused || m.triggered) continue; if (std::abs(m.x - pX) > renderDist || std::abs(m.y - pY) > renderDist) continue; Vector3 mCenter = { m.x * tileSize, 0.04f, m.y * tileSize }; - DrawCylinder(mCenter, 0.38f, 0.42f, 0.08f, 16, Color{ 32, 38, 50, 255 }); - DrawCylinderWires(mCenter, 0.39f, 0.43f, 0.08f, 16, Color{ 80, 90, 110, 255 }); + DrawCube(mCenter, 0.44f, 0.08f, 0.44f, Color{ 30, 36, 46, 255 }); + DrawCubeWires(mCenter, 0.45f, 0.09f, 0.45f, TacticalColors::DarkRed); float blink = 0.5f + 0.5f * std::sin(animTime * 10.0f); - Color ledCol = (blink > 0.4f) ? RED : Color{ 120, 20, 30, 255 }; - DrawSphere({ mCenter.x, 0.12f, mCenter.z }, 0.10f, ledCol); - DrawCircle3D({ mCenter.x, 0.09f, mCenter.z }, 0.28f, { 1.0f, 0.0f, 0.0f }, 90.0f, Color{ 255, 120, 0, static_cast(100 + blink * 100) }); + Color ledCol = (blink > 0.4f) ? TacticalColors::BrightRed : TacticalColors::DarkRed; + DrawCube({ mCenter.x, 0.10f, mCenter.z }, 0.14f, 0.08f, 0.14f, ledCol); + DrawCubeWires({ mCenter.x, 0.10f, mCenter.z }, 0.15f, 0.09f, 0.15f, TacticalColors::PureWhite); } - // Render Cyber Shrines + // Render Cyber Shrines (Monolithic Arasaka Server Terminal) for (const auto& s : dungeon.getShrines()) { if (std::abs(s.x - pX) > renderDist || std::abs(s.y - pY) > renderDist) continue; Vector3 sCenter = { s.x * tileSize, 0.0f, s.y * tileSize }; - DrawCylinder({ sCenter.x, 0.10f, sCenter.z }, 0.9f, 1.0f, 0.20f, 8, Color{ 20, 14, 30, 255 }); - DrawCylinderWires({ sCenter.x, 0.10f, sCenter.z }, 0.92f, 1.02f, 0.20f, 8, CyberColors::NeonPurple); + DrawCube({ sCenter.x, 0.10f, sCenter.z }, 1.1f, 0.20f, 1.1f, Color{ 22, 28, 38, 255 }); + DrawCubeWires({ sCenter.x, 0.10f, sCenter.z }, 1.12f, 0.22f, 1.12f, TacticalColors::Crimson); - DrawCube({ sCenter.x, 0.95f, sCenter.z }, 0.55f, 1.50f, 0.55f, Color{ 14, 10, 22, 255 }); - DrawCubeWires({ sCenter.x, 0.95f, sCenter.z }, 0.57f, 1.52f, 0.57f, s.used ? GRAY : CyberColors::NeonPurple); + DrawCube({ sCenter.x, 0.95f, sCenter.z }, 0.58f, 1.50f, 0.58f, Color{ 14, 18, 26, 255 }); + DrawCubeWires({ sCenter.x, 0.95f, sCenter.z }, 0.60f, 1.52f, 0.60f, s.used ? TacticalColors::SteelMuted : TacticalColors::Crimson); float pulse = 0.5f + 0.5f * std::sin(animTime * 3.5f); - Vector3 runePos = { sCenter.x, 1.95f + pulse * 0.10f, sCenter.z }; - Color runeCol = s.used ? GRAY : Color{ 255, 40, 160, static_cast(180 + pulse * 75) }; - DrawSphere(runePos, 0.18f + pulse * 0.04f, runeCol); - DrawCircle3D(runePos, 0.40f, { 1.0f, 0.0f, 0.0f }, 90.0f, runeCol); + Vector3 terminalPos = { sCenter.x, 1.85f + pulse * 0.04f, sCenter.z }; + Color termCol = s.used ? TacticalColors::SteelMuted : TacticalColors::BrightRed; + DrawCube(terminalPos, 0.24f, 0.24f, 0.24f, termCol); + DrawCubeWires(terminalPos, 0.26f, 0.26f, 0.26f, TacticalColors::PureWhite); } // Render Corridor Patrol Enemies as 3D Cyber-Drones @@ -1623,9 +1634,18 @@ void CyberRenderer::renderCombat3D(const Dungeon& dungeon, const Player& player, Vector3 center = { x * tileSize, 0.0f, y * tileSize }; TileType tile = dungeon.getTile(x, y); - float dist = std::sqrt(static_cast((x - pX) * (x - pX) + (y - pY) * (y - pY))); - float fog = std::clamp((dist - 1.0f) / 4.0f, 0.0f, 1.0f); - float b = 1.0f - fog * 0.85f; + float dx = (center.x - eye.x); + float dz = (center.z - eye.z); + float dist = std::sqrt(dx * dx + dz * dz); + float b = 0.15f; + if (dist < 0.4f) { + b = 1.0f; + } else { + float dot = (dx * dirX + dz * dirZ) / dist; + float cone = std::clamp((dot - 0.20f) / 0.80f, 0.0f, 1.0f); + float distFall = std::clamp(1.0f - (dist / 10.0f), 0.0f, 1.0f); + b = std::clamp(0.15f + cone * distFall * 0.85f, 0.10f, 1.0f); + } Color tileTint = Color{ static_cast(255 * b), static_cast(255 * b), static_cast(255 * b), 255 }; if (tile == TileType::WALL) { @@ -1662,6 +1682,39 @@ void CyberRenderer::renderCombat3D(const Dungeon& dungeon, const Player& player, bool isAttacking = (combat.getPhase() == CombatPhase::ENEMY_ACTION_ANIM); renderCyberDrone(combatDronePos, enemy->getColor(), 1.5f, animTime, isHit, isAttacking, enemy->isElite()); + + // 3. Tactical Muzzle Flash & High-Velocity Projectile Tracer FX (Strict 0-rounding) + if (isHit && combat.getLastFiredAmmo() != AmmoType::EMPTY) { + AmmoType shotType = combat.getLastFiredAmmo(); + Color shotColor = getAmmoInfo(shotType).color; + + float rightX = -dirZ; + float rightZ = dirX; + Vector3 muzzlePos = { + eye.x + dirX * 0.45f + rightX * 0.22f, + eye.y - 0.18f, + eye.z + dirZ * 0.45f + rightZ * 0.22f + }; + + // Muzzle flash: faceted diamond prism + DrawCube(muzzlePos, 0.09f, 0.09f, 0.09f, shotColor); + DrawCubeWires(muzzlePos, 0.12f, 0.12f, 0.12f, TacticalColors::PureWhite); + DrawCube({ muzzlePos.x + dirX * 0.05f, muzzlePos.y, muzzlePos.z + dirZ * 0.05f }, + 0.06f, 0.06f, 0.14f, Color{ shotColor.r, shotColor.g, shotColor.b, 180 }); + + // High-velocity projectile tracer beam (Straight beam from muzzle to drone) + DrawCylinderEx(muzzlePos, combatDronePos, 0.035f, 0.035f, 6, shotColor); + DrawCylinderEx(muzzlePos, combatDronePos, 0.015f, 0.015f, 6, TacticalColors::PureWhite); + + // Impact sparks on target drone chassis + Vector3 impactPos = { + combatDronePos.x - dirX * 0.18f, + combatDronePos.y, + combatDronePos.z - dirZ * 0.18f + }; + DrawCube(impactPos, 0.11f, 0.11f, 0.11f, shotColor); + DrawCubeWires(impactPos, 0.15f, 0.15f, 0.15f, TacticalColors::PureWhite); + } } EndMode3D(); @@ -1674,6 +1727,8 @@ void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float an generateTextures(); } + beginPostProcess(); + Vector3 eye = player.getVisualPosition(); m_camera.position = eye; @@ -1690,13 +1745,18 @@ void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float an const float minCoord = 0.6f; const float maxCoord = 11.4f; - // 1. FLOOR: Warm Oak Parquet Floor (snug room = 10.8m x 10.8m) + // ========================================================================= + // PHASE 1: OPAQUE SCENE GEOMETRY (Writes to both Color and Depth buffers) + // ========================================================================= + + // 1. FLOOR: Dark Smoked Walnut Parquet Floor (snug room = 10.8m x 10.8m) drawTexturedQuad(m_texParquet, { minCoord, 0.0f, minCoord }, { minCoord, 0.0f, maxCoord }, { maxCoord, 0.0f, maxCoord }, { maxCoord, 0.0f, minCoord }, - 5.5f, 5.5f); + 5.5f, 5.5f, + Color{ 180, 180, 200, 255 }); // 2. LUXURY RUG: Centered in room (6.0m, 6.0m), size 4.0m x 4.0m Vector3 rugCenter = { 6.0f, 0.02f, 6.0f }; @@ -1707,75 +1767,68 @@ void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float an { rugCenter.x - rw, 0.02f, rugCenter.z + rl }, { rugCenter.x + rw, 0.02f, rugCenter.z + rl }, { rugCenter.x + rw, 0.02f, rugCenter.z - rl }, - 1.0f, 1.0f); + 1.0f, 1.0f, + Color{ 180, 180, 200, 255 }); - // 3. CEILING: Solid ceiling covering the entire room and walls - DrawCube({ 6.0f, roomHeight + 0.10f, 6.0f }, 11.6f, 0.20f, 11.6f, Color{ 32, 36, 44, 255 }); - DrawCube({ 6.0f, roomHeight - 0.02f, 6.0f }, 2.4f, 0.04f, 2.4f, Color{ 255, 245, 220, 255 }); // Warm LED ceiling lamp - DrawCubeWires({ 6.0f, roomHeight - 0.02f, 6.0f }, 2.42f, 0.04f, 2.42f, Color{ 180, 140, 80, 255 }); // Brass fixture + // 3. CEILING: Moody dark matte ceiling + DrawCube({ 6.0f, roomHeight + 0.10f, 6.0f }, 11.6f, 0.20f, 11.6f, Color{ 14, 16, 22, 255 }); // 4. SURROUNDING WALLS (Solid textured boxes with 0.24m thickness) const float wallThick = 0.24f; const float halfThick = wallThick * 0.5f; + Color wallTint = Color{ 170, 175, 195, 255 }; // South Wall (Z = maxCoord = 11.4f) drawTexturedBox(m_texPlasterWall, { 6.0f, roomHeight * 0.5f, maxCoord + halfThick }, { 10.8f + wallThick * 2.0f, roomHeight, wallThick }, - 5.5f, 1.4f); + 5.5f, 1.4f, wallTint); // West Wall (X = minCoord = 0.6f) drawTexturedBox(m_texPlasterWall, { minCoord - halfThick, roomHeight * 0.5f, 6.0f }, { wallThick, roomHeight, 10.8f + wallThick * 2.0f }, - 5.5f, 1.4f); + 5.5f, 1.4f, wallTint); // North Wall: Split around Gateway (Gateway is at X = 6.0f, width 2.6f: X in [4.7, 7.3]) - // Left Segment: X from minCoord (0.6f) to 4.7f => length 4.1f, center 2.65f drawTexturedBox(m_texPlasterWall, { 2.65f, roomHeight * 0.5f, minCoord - halfThick }, { 4.1f + wallThick, roomHeight, wallThick }, - 2.1f, 1.4f); - // Right Segment: X from 7.3f to maxCoord (11.4f) => length 4.1f, center 9.35f + 2.1f, 1.4f, wallTint); drawTexturedBox(m_texPlasterWall, { 9.35f, roomHeight * 0.5f, minCoord - halfThick }, { 4.1f + wallThick, roomHeight, wallThick }, - 2.1f, 1.4f); - // Lintel above Gateway: X in [4.7f, 7.3f], Y from 2.4f to roomHeight (0.4f height) + 2.1f, 1.4f, wallTint); drawTexturedBox(m_texPlasterWall, { 6.0f, 2.6f, minCoord - halfThick }, { 2.6f, 0.4f, wallThick }, - 1.3f, 0.2f); + 1.3f, 0.2f, wallTint); // East Wall: Split around Window (Window at Z in [3.6f, 8.4f], Y in [0.8f, 2.4f]) - // North Segment: Z from minCoord (0.6f) to 3.6f => length 3.0f, center 2.1f drawTexturedBox(m_texPlasterWall, { maxCoord + halfThick, roomHeight * 0.5f, 2.1f }, { wallThick, roomHeight, 3.0f + wallThick }, - 1.5f, 1.4f); - // South Segment: Z from 8.4f to maxCoord (11.4f) => length 3.0f, center 9.9f + 1.5f, 1.4f, wallTint); drawTexturedBox(m_texPlasterWall, { maxCoord + halfThick, roomHeight * 0.5f, 9.9f }, { wallThick, roomHeight, 3.0f + wallThick }, - 1.5f, 1.4f); - // Below Window: Z in [3.6f, 8.4f] (length 4.8f), Y from 0.0f to 0.8f (height 0.8f, center 0.4f) + 1.5f, 1.4f, wallTint); drawTexturedBox(m_texPlasterWall, { maxCoord + halfThick, 0.4f, 6.0f }, { wallThick, 0.8f, 4.8f }, - 2.4f, 0.4f); - // Above Window: Z in [3.6f, 8.4f] (length 4.8f), Y from 2.4f to roomHeight (height 0.4f, center 2.6f) + 2.4f, 0.4f, wallTint); drawTexturedBox(m_texPlasterWall, { maxCoord + halfThick, 2.6f, 6.0f }, { wallThick, 0.4f, 4.8f }, - 2.4f, 0.2f); + 2.4f, 0.2f, wallTint); // Baseboards along bottom of walls inside room - DrawCube({ 6.0f, 0.08f, maxCoord - 0.04f }, 10.8f, 0.16f, 0.08f, Color{ 75, 48, 30, 255 }); - DrawCube({ minCoord + 0.04f, 0.08f, 6.0f }, 0.08f, 0.16f, 10.8f, Color{ 75, 48, 30, 255 }); - DrawCube({ 2.65f, 0.08f, minCoord + 0.04f }, 4.1f, 0.16f, 0.08f, Color{ 75, 48, 30, 255 }); - DrawCube({ 9.35f, 0.08f, minCoord + 0.04f }, 4.1f, 0.16f, 0.08f, Color{ 75, 48, 30, 255 }); - DrawCube({ maxCoord - 0.04f, 0.08f, 2.1f }, 0.08f, 0.16f, 3.0f, Color{ 75, 48, 30, 255 }); - DrawCube({ maxCoord - 0.04f, 0.08f, 9.9f }, 0.08f, 0.16f, 3.0f, Color{ 75, 48, 30, 255 }); + DrawCube({ 6.0f, 0.08f, maxCoord - 0.04f }, 10.8f, 0.16f, 0.08f, Color{ 36, 24, 18, 255 }); + DrawCube({ minCoord + 0.04f, 0.08f, 6.0f }, 0.08f, 0.16f, 10.8f, Color{ 36, 24, 18, 255 }); + DrawCube({ 2.65f, 0.08f, minCoord + 0.04f }, 4.1f, 0.16f, 0.08f, Color{ 36, 24, 18, 255 }); + DrawCube({ 9.35f, 0.08f, minCoord + 0.04f }, 4.1f, 0.16f, 0.08f, Color{ 36, 24, 18, 255 }); + DrawCube({ maxCoord - 0.04f, 0.08f, 2.1f }, 0.08f, 0.16f, 3.0f, Color{ 36, 24, 18, 255 }); + DrawCube({ maxCoord - 0.04f, 0.08f, 9.9f }, 0.08f, 0.16f, 3.0f, Color{ 36, 24, 18, 255 }); // 5. FRAMED SYNTHWAVE POSTER: South Wall (Z = maxCoord - 0.01f, centered at X = 3.8f, Y = 1.55f) drawTexturedQuad(m_texPoster, @@ -1786,55 +1839,33 @@ void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float an 1.0f, 1.0f); DrawCubeWires({ 3.8f, 1.55f, maxCoord - 0.005f }, 1.62f, 1.62f, 0.02f, Color{ 20, 20, 25, 255 }); - // 6. PANORAMIC WINDOW & NIGHT CITY SKYLINE (East Wall, X = maxCoord = 11.4f) - // Step A: Outdoor Megacity Backdrop Quad (rendered first so depth test passes for everything in front) - drawTexturedQuad(m_texCityNight, - { 16.5f, 6.0f, -2.0f }, - { 16.5f, -2.0f, -2.0f }, - { 16.5f, -2.0f, 14.0f }, - { 16.5f, 6.0f, 14.0f }, + // 6. PANORAMIC WINDOW OPAQUE STRUCTURE (East Wall, X = maxCoord = 11.4f) + // Exterior Room_window backdrop quad (sized to 16:9 ratio, seamlessly framed by window opening) + drawTexturedQuad(m_texWindowView, + { 11.85f, 3.90f, 2.0f }, + { 11.85f, -0.70f, 2.0f }, + { 11.85f, -0.70f, 10.0f }, + { 11.85f, 3.90f, 10.0f }, 1.0f, 1.0f); - // Step B: 3D Skyscraper buildings outside window (X = 13.5 .. 14.8) - Vector3 bldPositions[4] = { - { 14.0f, 1.2f, 4.4f }, - { 14.6f, 1.7f, 5.8f }, - { 14.2f, 2.1f, 7.0f }, - { 14.5f, 1.1f, 8.0f } - }; - for (int i = 0; i < 4; ++i) { - float bldH = 3.4f + (i % 3) * 1.2f; - Vector3 bldPos = { bldPositions[i].x, bldH * 0.5f - 0.5f, bldPositions[i].z }; - DrawCube(bldPos, 1.6f, bldH, 1.4f, Color{ 12, 16, 28, 255 }); - DrawCubeWires(bldPos, 1.6f, bldH, 1.4f, (i % 2 == 0) ? CyberColors::NeonCyan : CyberColors::NeonPink); - // Roof antenna beacon - DrawLine3D({ bldPos.x, bldH - 0.5f, bldPos.z }, { bldPos.x, bldH + 0.45f, bldPos.z }, Color{ 140, 150, 170, 255 }); - DrawSphere({ bldPos.x, bldH + 0.48f, bldPos.z }, 0.06f, Color{ 255, 40, 40, 255 }); - } - - // Step C: Window frame, mullions, and wooden sill in the wall opening + // Window frame, mullions, and wooden sill in the wall opening DrawCubeWires({ maxCoord, 1.6f, 6.0f }, 0.28f, 1.62f, 4.82f, Color{ 15, 20, 30, 255 }); DrawCube({ maxCoord, 1.6f, 6.0f }, 0.08f, 1.6f, 0.06f, Color{ 22, 28, 40, 255 }); // Center vertical mullion DrawCube({ maxCoord, 1.6f, 6.0f }, 0.08f, 0.06f, 4.8f, Color{ 22, 28, 40, 255 }); // Center horizontal mullion - DrawCube({ maxCoord - 0.12f, 0.78f, 6.0f }, 0.32f, 0.06f, 5.0f, Color{ 85, 55, 35, 255 }); // Interior wooden sill - - // Step D: Translucent Glass Reflections (depth mask DISABLED so city behind is NEVER occluded!) - rlDisableDepthMask(); - DrawCube({ maxCoord, 1.6f, 6.0f }, 0.01f, 1.56f, 4.74f, Color{ 0, 160, 230, 20 }); - DrawLine3D({ maxCoord - 0.01f, 2.25f, 4.3f }, { maxCoord - 0.01f, 0.95f, 5.6f }, Color{ 255, 255, 255, 55 }); - DrawLine3D({ maxCoord - 0.01f, 2.15f, 6.5f }, { maxCoord - 0.01f, 0.95f, 7.7f }, Color{ 255, 255, 255, 45 }); - rlEnableDepthMask(); + DrawCube({ maxCoord - 0.12f, 0.78f, 6.0f }, 0.32f, 0.06f, 5.0f, Color{ 55, 38, 28, 255 }); // Interior wooden sill + // Violet rim highlight on wooden sill reflecting exterior neon + DrawCube({ maxCoord - 0.12f, 0.812f, 6.0f }, 0.28f, 0.005f, 4.8f, Color{ 150, 90, 225, 75 }); // 7. COMPUTER WORKSTATION DESK (West Wall: against X = minCoord = 0.6f, Z = 5.0f) Vector3 deskPos = { 1.05f, 0.75f, 5.0f }; Vector3 deskSize = { 0.85f, 0.08f, 2.2f }; - drawTexturedBox(m_texDeskWood, deskPos, deskSize, 1.0f, 2.0f); + drawTexturedBox(m_texDeskWood, deskPos, deskSize, 1.0f, 2.0f, Color{ 180, 180, 195, 255 }); // Metal legs - DrawCube({ 0.70f, 0.36f, 4.0f }, 0.08f, 0.72f, 0.08f, Color{ 40, 44, 52, 255 }); - DrawCube({ 1.40f, 0.36f, 4.0f }, 0.08f, 0.72f, 0.08f, Color{ 40, 44, 52, 255 }); - DrawCube({ 0.70f, 0.36f, 6.0f }, 0.08f, 0.72f, 0.08f, Color{ 40, 44, 52, 255 }); - DrawCube({ 1.40f, 0.36f, 6.0f }, 0.08f, 0.72f, 0.08f, Color{ 40, 44, 52, 255 }); + DrawCube({ 0.70f, 0.36f, 4.0f }, 0.08f, 0.72f, 0.08f, Color{ 36, 40, 48, 255 }); + DrawCube({ 1.40f, 0.36f, 4.0f }, 0.08f, 0.72f, 0.08f, Color{ 36, 40, 48, 255 }); + DrawCube({ 0.70f, 0.36f, 6.0f }, 0.08f, 0.72f, 0.08f, Color{ 36, 40, 48, 255 }); + DrawCube({ 1.40f, 0.36f, 6.0f }, 0.08f, 0.72f, 0.08f, Color{ 36, 40, 48, 255 }); // Primary Monitor (IDE code editor): facing East (+X) toward the player Vector3 mon1Pos = { 0.90f, 1.25f, 4.5f }; @@ -1880,33 +1911,182 @@ void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float an // 8. BED (South-East corner: X in [8.5f, 11.2f], Z in [8.8f, 11.2f]) Vector3 bedPos = { 9.8f, 0.30f, 10.0f }; Vector3 bedSize = { 2.3f, 0.45f, 2.2f }; - DrawCube({ bedPos.x, 0.08f, bedPos.z }, bedSize.x + 0.1f, 0.16f, bedSize.z + 0.1f, Color{ 55, 35, 20, 255 }); + DrawCube({ bedPos.x, 0.08f, bedPos.z }, bedSize.x + 0.1f, 0.16f, bedSize.z + 0.1f, Color{ 38, 26, 18, 255 }); drawTexturedBox(m_texBedQuilt, bedPos, bedSize, 2.0f, 2.0f); // Bed Headboard against South wall - DrawCube({ bedPos.x, 0.75f, maxCoord - 0.08f }, bedSize.x + 0.1f, 1.1f, 0.10f, Color{ 55, 35, 20, 255 }); + DrawCube({ bedPos.x, 0.75f, maxCoord - 0.08f }, bedSize.x + 0.1f, 1.1f, 0.10f, Color{ 38, 26, 18, 255 }); // Two soft pillows at head of bed Vector3 pillow1 = { 9.2f, 0.58f, 10.7f }; Vector3 pillow2 = { 10.4f, 0.58f, 10.7f }; - DrawCube(pillow1, 0.9f, 0.12f, 0.50f, Color{ 235, 238, 246, 255 }); - DrawCube(pillow2, 0.9f, 0.12f, 0.50f, Color{ 235, 238, 246, 255 }); + DrawCube(pillow1, 0.9f, 0.12f, 0.50f, Color{ 160, 165, 180, 255 }); + DrawCube(pillow2, 0.9f, 0.12f, 0.50f, Color{ 160, 165, 180, 255 }); // 9. CYBERSPACE GATEWAY DOOR (North wall at X = 6.0f, Z = minCoord) Vector3 gateFramePos = { 6.0f, roomHeight * 0.5f, minCoord + 0.05f }; drawTexturedBox(m_texCyberGate, gateFramePos, { 2.6f, roomHeight, 0.30f }, 1.0f, 1.0f); float pulse = 0.5f + 0.5f * std::sin(animTime * 4.0f); - Vector3 portalField = { 6.0f, (roomHeight - 0.4f) * 0.5f, minCoord + 0.06f }; - DrawCube(portalField, 1.8f, roomHeight - 0.4f, 0.08f, Color{ 0, 230, 160, 140 }); - DrawCubeWires(portalField, 1.82f, roomHeight - 0.38f, 0.10f, + Vector3 portalWire = { 6.0f, (roomHeight - 0.4f) * 0.5f, minCoord + 0.06f }; + DrawCubeWires(portalWire, 1.82f, roomHeight - 0.38f, 0.10f, Color{ 0, 255, 120, static_cast(180 + pulse * 75) }); Vector3 signPos = { 6.0f, roomHeight - 0.22f, minCoord + 0.24f }; DrawCube(signPos, 1.6f, 0.25f, 0.06f, Color{ 10, 16, 26, 255 }); DrawCubeWires(signPos, 1.6f, 0.25f, 0.06f, CyberColors::NeonCyan); + // ========================================================================= + // PHASE 2: FLUSH OPAQUE BATCH TO GPU BEFORE TRANSLUCENT PASSES + // ========================================================================= + rlDrawRenderBatchActive(); + + // ========================================================================= + // PHASE 3: TRANSLUCENT LIGHTING & ATMOSPHERE PASSES + // Reads depth from Phase 1 (so bed/walls occlude rays properly), + // but does NOT write to depth mask (so godrays never erase/culled scene objects). + // ========================================================================= + BeginBlendMode(BLEND_ALPHA); + rlDisableDepthMask(); + rlDisableBackfaceCulling(); + + // A. Window Glass Reflections & Diagonal Rain Streaks + DrawCube({ maxCoord, 1.6f, 6.0f }, 0.01f, 1.56f, 4.74f, Color{ 90, 50, 160, 22 }); + DrawLine3D({ maxCoord - 0.01f, 2.25f, 4.3f }, { maxCoord - 0.01f, 0.95f, 5.3f }, Color{ 200, 180, 255, 45 }); + DrawLine3D({ maxCoord - 0.01f, 2.15f, 6.5f }, { maxCoord - 0.01f, 0.95f, 7.5f }, Color{ 200, 180, 255, 40 }); + + // B. Soft Violet Floor Ambient Light Pools from Window + rlBegin(RL_QUADS); + // Outer broad twilight pool on parquet (fading West into room) + rlColor4ub(130, 75, 210, 35); + rlVertex3f(11.35f, 0.012f, 3.2f); + rlColor4ub(90, 45, 170, 0); + rlVertex3f(6.5f, 0.012f, 3.2f); + rlColor4ub(90, 45, 170, 0); + rlVertex3f(6.5f, 0.012f, 8.8f); + rlColor4ub(130, 75, 210, 35); + rlVertex3f(11.35f, 0.012f, 8.8f); + + // Inner brighter pool right beneath window + rlColor4ub(160, 95, 235, 55); + rlVertex3f(11.35f, 0.015f, 4.0f); + rlColor4ub(120, 65, 200, 0); + rlVertex3f(8.8f, 0.015f, 4.0f); + rlColor4ub(120, 65, 200, 0); + rlVertex3f(8.8f, 0.015f, 8.0f); + rlColor4ub(160, 95, 235, 55); + rlVertex3f(11.35f, 0.015f, 8.0f); + + // C. Volumetric Godrays streaming through Window Mullions + float rayPulse = 0.85f + 0.15f * std::sin(animTime * 1.6f); + unsigned char topAlpha = static_cast(38.0f * rayPulse); + unsigned char midAlpha = static_cast(18.0f * rayPulse); + + // Shaft 1 (Left pane: Z in [3.9f, 5.2f]) + rlColor4ub(165, 105, 245, topAlpha); + rlVertex3f(11.35f, 2.32f, 3.9f); + rlColor4ub(165, 105, 245, topAlpha); + rlVertex3f(11.35f, 2.32f, 5.2f); + rlColor4ub(120, 65, 210, 0); + rlVertex3f(6.8f, 0.02f, 5.6f); + rlColor4ub(120, 65, 210, 0); + rlVertex3f(6.8f, 0.02f, 4.3f); + + // Shaft 1 cross-sheet + rlColor4ub(150, 90, 235, midAlpha); + rlVertex3f(11.35f, 1.80f, 4.55f); + rlColor4ub(150, 90, 235, midAlpha); + rlVertex3f(11.35f, 1.10f, 4.55f); + rlColor4ub(110, 55, 195, 0); + rlVertex3f(7.2f, 0.02f, 4.95f); + rlColor4ub(110, 55, 195, 0); + rlVertex3f(7.2f, 0.40f, 4.95f); + + // Shaft 2 (Center pane: Z in [5.4f, 6.7f]) + rlColor4ub(175, 115, 255, topAlpha); + rlVertex3f(11.35f, 2.35f, 5.4f); + rlColor4ub(175, 115, 255, topAlpha); + rlVertex3f(11.35f, 2.35f, 6.7f); + rlColor4ub(130, 70, 220, 0); + rlVertex3f(6.5f, 0.02f, 7.1f); + rlColor4ub(130, 70, 220, 0); + rlVertex3f(6.5f, 0.02f, 5.8f); + + // Shaft 2 cross-sheet + rlColor4ub(160, 100, 245, midAlpha); + rlVertex3f(11.35f, 1.85f, 6.05f); + rlColor4ub(160, 100, 245, midAlpha); + rlVertex3f(11.35f, 1.15f, 6.05f); + rlColor4ub(120, 65, 205, 0); + rlVertex3f(6.9f, 0.02f, 6.45f); + rlColor4ub(120, 65, 205, 0); + rlVertex3f(6.9f, 0.42f, 6.45f); + + // Shaft 3 (Right pane: Z in [6.9f, 8.1f]) + rlColor4ub(165, 105, 245, topAlpha); + rlVertex3f(11.35f, 2.32f, 6.9f); + rlColor4ub(165, 105, 245, topAlpha); + rlVertex3f(11.35f, 2.32f, 8.1f); + rlColor4ub(120, 65, 210, 0); + rlVertex3f(6.8f, 0.02f, 8.5f); + rlColor4ub(120, 65, 210, 0); + rlVertex3f(6.8f, 0.02f, 7.3f); + + // Shaft 3 cross-sheet + rlColor4ub(150, 90, 235, midAlpha); + rlVertex3f(11.35f, 1.80f, 7.50f); + rlColor4ub(150, 90, 235, midAlpha); + rlVertex3f(11.35f, 1.10f, 7.50f); + rlColor4ub(110, 55, 195, 0); + rlVertex3f(7.2f, 0.02f, 7.90f); + rlColor4ub(110, 55, 195, 0); + rlVertex3f(7.2f, 0.40f, 7.90f); + + // D. Intimate Workstation Monitor Glow & Cast Screen Light + // Desk surface glow under monitors + rlColor4ub(0, 185, 240, 50); + rlVertex3f(0.95f, 0.795f, 4.05f); + rlColor4ub(0, 185, 240, 50); + rlVertex3f(0.95f, 0.795f, 5.95f); + rlColor4ub(0, 120, 210, 10); + rlVertex3f(1.45f, 0.795f, 5.95f); + rlColor4ub(0, 120, 210, 10); + rlVertex3f(1.45f, 0.795f, 4.05f); + + // Floor glow cone cast eastward from monitors into room + rlColor4ub(0, 175, 240, 36); + rlVertex3f(1.10f, 0.015f, 4.0f); + rlColor4ub(0, 175, 240, 36); + rlVertex3f(1.10f, 0.015f, 6.0f); + rlColor4ub(0, 90, 180, 0); + rlVertex3f(3.20f, 0.015f, 6.6f); + rlColor4ub(0, 90, 180, 0); + rlVertex3f(3.20f, 0.015f, 3.4f); + + // Subtle volumetric cone of screen light in the air in front of monitors + rlColor4ub(0, 200, 255, 18); + rlVertex3f(1.02f, 1.45f, 4.2f); + rlColor4ub(0, 200, 255, 18); + rlVertex3f(1.02f, 1.45f, 5.8f); + rlColor4ub(0, 120, 220, 0); + rlVertex3f(2.40f, 1.30f, 6.2f); + rlColor4ub(0, 120, 220, 0); + rlVertex3f(2.40f, 1.30f, 3.8f); + rlEnd(); + + // E. Gateway Portal Energy Field + Vector3 portalField = { 6.0f, (roomHeight - 0.4f) * 0.5f, minCoord + 0.06f }; + DrawCube(portalField, 1.8f, roomHeight - 0.4f, 0.08f, Color{ 0, 230, 160, 140 }); + + // Flush translucent batch + rlDrawRenderBatchActive(); + rlEnableBackfaceCulling(); + rlEnableDepthMask(); + EndBlendMode(); + EndMode3D(); + + endPostProcess(animTime); } void CyberRenderer::renderRestRoom(const RestRoom& restRoom, const Player& player, float animTime) { diff --git a/src/render/CyberRenderer.hpp b/src/render/CyberRenderer.hpp index c06bb06..6356544 100644 --- a/src/render/CyberRenderer.hpp +++ b/src/render/CyberRenderer.hpp @@ -63,6 +63,7 @@ private: Texture2D m_texMonitorRadar{}; Texture2D m_texBedQuilt{}; Texture2D m_texCityNight{}; + Texture2D m_texWindowView{}; Texture2D m_texCyberGate{}; // Ancient Palace Storeroom Procedural Textures diff --git a/src/render/ScreenTransition.hpp b/src/render/ScreenTransition.hpp new file mode 100644 index 0000000..742e154 --- /dev/null +++ b/src/render/ScreenTransition.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "raylib.h" +#include "core/Common.hpp" +#include +#include +#include + +namespace dnr { + +class ScreenTransition { +public: + enum class Phase { + IDLE, + WIPE_IN, + HOLD_MIDPOINT, + WIPE_OUT + }; + + ScreenTransition() = default; + + void start(std::function onMidpoint) { + m_onMidpoint = onMidpoint; + m_phase = Phase::WIPE_IN; + m_timer = 0.0f; + } + + bool isBusy() const { + return m_phase != Phase::IDLE; + } + + void update(float dt) { + if (m_phase == Phase::IDLE) return; + + m_timer += dt; + + const float wipeInDuration = 0.35f; + const float holdDuration = 0.05f; + const float wipeOutDuration = 0.35f; + + if (m_phase == Phase::WIPE_IN) { + if (m_timer >= wipeInDuration) { + m_phase = Phase::HOLD_MIDPOINT; + m_timer = 0.0f; + if (m_onMidpoint) { + m_onMidpoint(); + } + } + } else if (m_phase == Phase::HOLD_MIDPOINT) { + if (m_timer >= holdDuration) { + m_phase = Phase::WIPE_OUT; + m_timer = 0.0f; + } + } else if (m_phase == Phase::WIPE_OUT) { + if (m_timer >= wipeOutDuration) { + m_phase = Phase::IDLE; + m_timer = 0.0f; + m_onMidpoint = nullptr; + } + } + } + + void draw() { + if (m_phase == Phase::IDLE) return; + + int screenW = GetScreenWidth(); + int screenH = GetScreenHeight(); + const int blockSize = 32; + int cols = (screenW + blockSize - 1) / blockSize; + int rows = (screenH + blockSize - 1) / blockSize; + int maxDist = (cols - 1) + (rows - 1); + if (maxDist <= 0) maxDist = 1; + + if (m_phase == Phase::HOLD_MIDPOINT) { + // Full opaque PitchBlack screen + DrawRectangle(0, 0, screenW, screenH, TacticalColors::PitchBlack); + return; + } + + const float wipeInDuration = 0.35f; + const float wipeOutDuration = 0.35f; + const float waveSpread = 0.32f; + + bool isWipeIn = (m_phase == Phase::WIPE_IN); + float progress = isWipeIn ? std::clamp(m_timer / wipeInDuration, 0.0f, 1.0f) + : std::clamp(m_timer / wipeOutDuration, 0.0f, 1.0f); + + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < cols; ++c) { + float dist = static_cast(c + r) / static_cast(maxDist); + float cellProgress = 0.0f; + + if (isWipeIn) { + // Blocks expand along wave: starts when progress >= dist * (1 - waveSpread) + float startThreshold = dist * (1.0f - waveSpread); + cellProgress = std::clamp((progress - startThreshold) / waveSpread, 0.0f, 1.0f); + } else { + // Blocks shrink along wave: dissolved as progress advances + float startThreshold = dist * (1.0f - waveSpread); + float dissolve = std::clamp((progress - startThreshold) / waveSpread, 0.0f, 1.0f); + cellProgress = 1.0f - dissolve; + } + + if (cellProgress <= 0.001f) continue; + + int posX = c * blockSize; + int posY = r * blockSize; + + if (cellProgress >= 0.999f) { + // Full solid black block (Strict 0-rounding) + DrawRectangle(posX, posY, blockSize, blockSize, TacticalColors::PitchBlack); + } else { + // Partial block growing / shrinking from center + int curSize = static_cast(blockSize * cellProgress); + int offset = (blockSize - curSize) / 2; + Rectangle bRect = { + static_cast(posX + offset), + static_cast(posY + offset), + static_cast(curSize), + static_cast(curSize) + }; + + DrawRectangleRec(bRect, TacticalColors::PitchBlack); + + // Tactical Crimson Edge on wave frontier + DrawRectangleLinesEx(bRect, 1.0f, TacticalColors::Crimson); + + // Glitch binary glyph on active frontline + if (curSize > 14 && ((c * 7 + r * 13) % 4 == 0)) { + const char* glyph = ((c + r) % 2 == 0) ? "1" : "0"; + DrawText(glyph, posX + blockSize / 2 - 3, posY + blockSize / 2 - 5, 10, TacticalColors::Crimson); + } + } + } + } + } + +private: + Phase m_phase = Phase::IDLE; + float m_timer = 0.0f; + std::function m_onMidpoint; +}; + +} // namespace dnr diff --git a/src/render/UIRenderer.cpp b/src/render/UIRenderer.cpp index 02a8955..553eba4 100644 --- a/src/render/UIRenderer.cpp +++ b/src/render/UIRenderer.cpp @@ -1,6 +1,7 @@ #include "UIRenderer.hpp" #include #include +#include namespace dnr { @@ -13,15 +14,36 @@ void UIRenderer::drawProgressBar(int x, int y, int width, int height, float rati DrawRectangleLines(x, y, width, height, Color{ 40, 80, 120, 255 }); } +void UIRenderer::drawWrappedText(const std::string& text, int posX, int posY, int maxW, int fontSize, int lineSpacing, Color color) { + std::istringstream words(text); + std::string word; + std::string currentLine = ""; + int curY = posY; + + while (words >> word) { + std::string testLine = currentLine.empty() ? word : currentLine + " " + word; + if (MeasureText(testLine.c_str(), fontSize) > maxW && !currentLine.empty()) { + DrawText(currentLine.c_str(), posX, curY, fontSize, color); + curY += fontSize + lineSpacing; + currentLine = word; + } else { + currentLine = testLine; + } + } + if (!currentLine.empty()) { + DrawText(currentLine.c_str(), posX, curY, fontSize, color); + } +} + 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(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); + // Frame (Strict 0-rounding) + DrawRectangle(startX - 4, startY - 4, size + 8, size + 8, TacticalColors::DarkPanel); + DrawRectangleLines(startX - 4, startY - 4, size + 8, size + 8, TacticalColors::BorderMuted); + DrawText("TACTICAL MAP // 2D", startX, startY - 18, 11, TacticalColors::PureWhite); bool hasPrecog = player.hasImplant(ImplantType::PRECOGNITION_OPTICS); @@ -38,22 +60,22 @@ void UIRenderer::drawMinimap(const Dungeon& dungeon, const Player& player, int s TileType t = dungeon.getTile(x, y); switch (t) { case TileType::WALL: - DrawRectangle(px, py, cs, cs, Color{ 25, 45, 75, 255 }); + DrawRectangle(px, py, cs, cs, Color{ 30, 36, 48, 255 }); break; case TileType::FLOOR: - DrawRectangle(px, py, cs, cs, Color{ 10, 20, 35, 255 }); + DrawRectangle(px, py, cs, cs, Color{ 10, 14, 20, 255 }); break; case TileType::DOOR_LOCKED: - DrawRectangle(px, py, cs, cs, CyberColors::NeonOrange); + DrawRectangle(px, py, cs, cs, TacticalColors::Crimson); break; case TileType::DOOR_UNLOCKED: - DrawRectangle(px, py, cs, cs, CyberColors::NeonGreen); + DrawRectangle(px, py, cs, cs, TacticalColors::PureWhite); break; case TileType::LOOT_CACHE: - DrawRectangle(px, py, cs, cs, CyberColors::NeonYellow); + DrawRectangle(px, py, cs, cs, TacticalColors::BorderWhite); break; case TileType::EXIT_PORTAL: - DrawRectangle(px, py, cs, cs, CyberColors::NeonCyan); + DrawRectangle(px, py, cs, cs, TacticalColors::Crimson); break; default: break; @@ -68,34 +90,35 @@ void UIRenderer::drawMinimap(const Dungeon& dungeon, const Player& player, int s int lx = startX + static_cast(laser.x * cellSize); int ly = startY + static_cast(laser.y * cellSize); int cs = static_cast(cellSize) + 1; - Color lCol = laser.disarmed ? DARKGRAY : (laser.active ? RED : CyberColors::NeonCyan); + Color lCol = laser.disarmed ? DARKGRAY : (laser.active ? TacticalColors::BrightRed : TacticalColors::BorderMuted); DrawRectangle(lx + 1, ly + 1, std::max(2, cs - 2), std::max(2, cs - 2), lCol); } } - // Mines on minimap + // Mines on minimap (Sharp square) for (const auto& mine : dungeon.getMines()) { int dist = std::abs(mine.x - player.getGridX()) + std::abs(mine.y - player.getGridY()); if (dungeon.isExplored(mine.x, mine.y) || (hasPrecog && dist <= 5)) { int mx = startX + static_cast((mine.x + 0.5f) * cellSize); int my = startY + static_cast((mine.y + 0.5f) * cellSize); - Color mCol = mine.defused ? DARKGRAY : (mine.isArmed() ? CyberColors::NeonOrange : RED); - DrawCircle(mx, my, std::max(2.0f, cellSize * 0.35f), mCol); + Color mCol = mine.defused ? DARKGRAY : (mine.isArmed() ? TacticalColors::Crimson : TacticalColors::BrightRed); + DrawRectangle(mx - 2, my - 2, 5, 5, mCol); } } - // Cyber Shrines on minimap + // Cyber Shrines on minimap (Sharp square mark) for (const auto& shrine : dungeon.getShrines()) { int dist = std::abs(shrine.x - player.getGridX()) + std::abs(shrine.y - player.getGridY()); if (dungeon.isExplored(shrine.x, shrine.y) || (hasPrecog && dist <= 5)) { int sx = startX + static_cast((shrine.x + 0.5f) * cellSize); int sy = startY + static_cast((shrine.y + 0.5f) * cellSize); - Color sCol = shrine.used ? DARKGRAY : CyberColors::NeonPink; - DrawCircle(sx, sy, std::max(2.5f, cellSize * 0.45f), sCol); + Color sCol = shrine.used ? DARKGRAY : TacticalColors::PureWhite; + DrawRectangle(sx - 3, sy - 3, 6, 6, sCol); + DrawRectangleLines(sx - 3, sy - 3, 6, 6, TacticalColors::Crimson); } } - // Enemies on minimap + // Enemies on minimap (Sharp tactical diamonds/squares) for (const auto& me : dungeon.getEnemies()) { if (!me.alive) continue; int dist = std::abs(me.x - player.getGridX()) + std::abs(me.y - player.getGridY()); @@ -103,21 +126,26 @@ void UIRenderer::drawMinimap(const Dungeon& dungeon, const Player& player, int s int ex = startX + static_cast((me.x + 0.5f) * cellSize); int ey = startY + static_cast((me.y + 0.5f) * cellSize); if (me.enemy.isElite()) { - DrawCircle(ex, ey, std::max(3.0f, cellSize * 0.5f), CyberColors::NeonYellow); - DrawCircleLines(ex, ey, std::max(4.0f, cellSize * 0.65f), CyberColors::NeonPink); + DrawRectangle(ex - 4, ey - 4, 9, 9, TacticalColors::Crimson); + DrawRectangle(ex - 2, ey - 2, 5, 5, TacticalColors::PureWhite); } else { - DrawCircle(ex, ey, std::max(2.0f, cellSize * 0.35f), RED); + DrawRectangle(ex - 2, ey - 2, 5, 5, TacticalColors::BrightRed); } } } - // Player icon and direction on minimap + // Player Chevron Arrow on minimap (Strict 0-rounding) int plX = startX + static_cast((player.getGridX() + 0.5f) * cellSize); int plY = startY + static_cast((player.getGridY() + 0.5f) * cellSize); - DrawCircle(plX, plY, cellSize * 0.7f, CyberColors::NeonYellow); - Vector2 dirVec = getDirectionOffset(player.getFacing()); - DrawLine(plX, plY, plX + static_cast(dirVec.x * cellSize * 1.5f), plY + static_cast(dirVec.y * cellSize * 1.5f), CyberColors::NeonGreen); + float pSize = cellSize * 0.75f; + Vector2 tip = { plX + dirVec.x * pSize, plY + dirVec.y * pSize }; + Vector2 left = { plX - dirVec.y * (pSize * 0.55f) - dirVec.x * (pSize * 0.35f), + plY + dirVec.x * (pSize * 0.55f) - dirVec.y * (pSize * 0.35f) }; + Vector2 right = { plX + dirVec.y * (pSize * 0.55f) - dirVec.x * (pSize * 0.35f), + plY - dirVec.x * (pSize * 0.55f) - dirVec.y * (pSize * 0.35f) }; + DrawTriangle(tip, left, right, TacticalColors::PureWhite); + DrawRectangle(plX - 1, plY - 1, 3, 3, TacticalColors::Crimson); } void UIRenderer::drawMatrixDigitalRain(float animTime) { @@ -131,65 +159,75 @@ void UIRenderer::drawMatrixDigitalRain(float animTime) { int xPos = c * colWidth + 10; const char* txt = glyphs[(c + static_cast(animTime * 2.0f)) % 12]; - DrawText(txt, xPos, static_cast(yPos), 12, Color{ 0, 255, 100, 35 }); - DrawText(txt, xPos, static_cast(yPos - 20), 11, Color{ 0, 255, 100, 18 }); + DrawText(txt, xPos, static_cast(yPos), 12, Color{ 140, 150, 165, 30 }); + DrawText(txt, xPos, static_cast(yPos - 20), 11, Color{ 140, 150, 165, 15 }); } } void UIRenderer::drawMatrixOverlay(float animTime) { - // Subtle Matrix CRT scanlines + // Subtle 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 }); + DrawLine(0, y, screenW, y, Color{ 180, 195, 215, 10 }); } // 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 }); + DrawText("[SYS::TACTICAL_OS] [ARASAKA_NET] [PROTOCOL::357_MAGNUM] [SECURITY::ACTIVE]", + 15, screenH - 18, 11, TacticalColors::MutedText); } void UIRenderer::drawExplorationHUD(const Dungeon& dungeon, const Player& player, const std::string& prompt) { - // Top-Left: Player Vital Stats + // Top-Left: Player Vital Stats (Strict 0-rounding) int hudX = 20; int hudY = 20; - DrawRectangle(hudX, hudY, 330, 145, Color{ 6, 12, 22, 220 }); - DrawRectangleLines(hudX, hudY, 330, 145, CyberColors::NeonCyan); + DrawRectangle(hudX, hudY, 330, 145, TacticalColors::DarkPanel); + DrawRectangleLines(hudX, hudY, 330, 145, TacticalColors::BorderMuted); - DrawText(("SECTOR LAYER // " + std::to_string(dungeon.getFloor())).c_str(), hudX + 15, hudY + 12, 16, CyberColors::NeonCyan); + // Tactical corner bracket accents + DrawRectangle(hudX, hudY, 6, 2, TacticalColors::Crimson); + DrawRectangle(hudX, hudY, 2, 6, TacticalColors::Crimson); + DrawRectangle(hudX + 324, hudY, 6, 2, TacticalColors::Crimson); + DrawRectangle(hudX + 328, hudY, 2, 6, TacticalColors::Crimson); + DrawRectangle(hudX, hudY + 143, 6, 2, TacticalColors::Crimson); + DrawRectangle(hudX, hudY + 139, 2, 6, TacticalColors::Crimson); + DrawRectangle(hudX + 324, hudY + 143, 6, 2, TacticalColors::Crimson); + DrawRectangle(hudX + 328, hudY + 139, 2, 6, TacticalColors::Crimson); - // HP Bar + DrawText(("SECTOR LAYER // " + std::to_string(dungeon.getFloor())).c_str(), hudX + 15, hudY + 12, 16, TacticalColors::PureWhite); + + // HP Bar (Crimson fill) float hpRatio = static_cast(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); + drawProgressBar(hudX + 60, hudY + 40, 250, 18, hpRatio, TacticalColors::Crimson, TacticalColors::DarkRed); + DrawText("CORE", hudX + 15, hudY + 40, 14, TacticalColors::Crimson); + DrawText((std::to_string(player.getHp()) + " / " + std::to_string(player.getMaxHp())).c_str(), hudX + 145, hudY + 42, 13, TacticalColors::PureWhite); - // Shield Bar + // Shield Bar (High-contrast pure white / silver) float shRatio = static_cast(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); + drawProgressBar(hudX + 60, hudY + 68, 250, 18, shRatio, TacticalColors::PureWhite, Color{ 24, 30, 42, 255 }); + DrawText("SHLD", hudX + 15, hudY + 68, 14, TacticalColors::PureWhite); + DrawText((std::to_string(player.getShield()) + " / " + std::to_string(player.getMaxShield())).c_str(), hudX + 145, hudY + 70, 13, TacticalColors::PitchBlack); - // Cyber-Picks & Statuses - DrawText(("ICE PICKS: " + std::to_string(player.getCyberPicks())).c_str(), hudX + 15, hudY + 102, 15, CyberColors::NeonYellow); + // ICE-Picks & Statuses + DrawText(("ICE PICKS: " + std::to_string(player.getCyberPicks())).c_str(), hudX + 15, hudY + 102, 14, TacticalColors::SilverText); std::string statStr = "STATUS: "; if (player.getStatuses().empty()) { statStr += "SECURE"; - DrawText(statStr.c_str(), hudX + 160, hudY + 102, 14, CyberColors::NeonGreen); + DrawText(statStr.c_str(), hudX + 160, hudY + 102, 14, TacticalColors::PureWhite); } else { - statStr += "MALWARE_DEBUFF"; - DrawText(statStr.c_str(), hudX + 160, hudY + 102, 14, RED); + statStr += "HOSTILE_DEBUFF"; + DrawText(statStr.c_str(), hudX + 160, hudY + 102, 14, TacticalColors::BrightRed); } - // Neural Implants HUD Bar + // Neural Implants HUD Bar (Strict 0-rounding) int impBarX = hudX; int impBarY = hudY + 152; int impBarW = 330; int impBarH = 34; - DrawRectangle(impBarX, impBarY, impBarW, impBarH, Color{ 6, 12, 22, 220 }); - DrawRectangleLines(impBarX, impBarY, impBarW, impBarH, CyberColors::NeonCyan); + DrawRectangle(impBarX, impBarY, impBarW, impBarH, TacticalColors::DarkPanel); + DrawRectangleLines(impBarX, impBarY, impBarW, impBarH, TacticalColors::BorderMuted); const auto& implants = player.getImplants(); Vector2 mousePos = GetMousePosition(); @@ -207,11 +245,11 @@ void UIRenderer::drawExplorationHUD(const Dungeon& dungeon, const Player& player Color archCol = data.color; if (imp == ImplantType::AUTO_DEFIBRILLATOR && player.isDefibrillatorUsed()) { - archCol = RED; + archCol = TacticalColors::DarkRed; } - DrawRectangleRec(bRect, Color{ 12, 20, 32, 240 }); - DrawRectangleLinesEx(bRect, isHover ? 2.0f : 1.0f, isHover ? WHITE : archCol); + DrawRectangleRec(bRect, TacticalColors::SurfacePlate); + DrawRectangleLinesEx(bRect, isHover ? 2.0f : 1.0f, isHover ? TacticalColors::Crimson : archCol); std::string code = data.code; if (imp == ImplantType::AUTO_DEFIBRILLATOR && player.isDefibrillatorUsed()) { @@ -222,9 +260,9 @@ void UIRenderer::drawExplorationHUD(const Dungeon& dungeon, const Player& player if (isHover) hoveredImpIdx = s; } else { - DrawRectangleRec(bRect, Color{ 8, 14, 22, 180 }); - DrawRectangleLinesEx(bRect, 1.0f, Color{ 40, 60, 85, 180 }); - DrawText("[--]", bX + 27, bY + 6, 11, DARKGRAY); + DrawRectangleRec(bRect, Color{ 10, 14, 20, 200 }); + DrawRectangleLinesEx(bRect, 1.0f, TacticalColors::BorderMuted); + DrawText("[--]", bX + 27, bY + 6, 11, TacticalColors::MutedText); } } @@ -234,67 +272,83 @@ void UIRenderer::drawExplorationHUD(const Dungeon& dungeon, const Player& player int tipY = impBarY + impBarH + 6; int tipW = 330; int tipH = 50; - DrawRectangle(tipX, tipY, tipW, tipH, Color{ 10, 16, 26, 250 }); - DrawRectangleLines(tipX, tipY, tipW, tipH, CyberColors::NeonYellow); - DrawText(tipData.name.c_str(), tipX + 10, tipY + 8, 12, CyberColors::NeonYellow); - DrawText(tipData.description.c_str(), tipX + 10, tipY + 26, 10, RAYWHITE); + DrawRectangle(tipX, tipY, tipW, tipH, TacticalColors::DarkPanel); + DrawRectangleLines(tipX, tipY, tipW, tipH, TacticalColors::Crimson); + DrawText(tipData.name.c_str(), tipX + 10, tipY + 8, 12, TacticalColors::PureWhite); + DrawText(tipData.description.c_str(), tipX + 10, tipY + 26, 10, TacticalColors::SilverText); } - // Top-Right: Holographic Minimap + // Top-Right: Tactical Minimap drawMinimap(dungeon, player, GetScreenWidth() - 200, 20, 180); - // Bottom-Right: Revolver Ready Ammo preview + // Bottom-Right: Revolver Ready Ammo preview (Strict 0-rounding) 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); + DrawRectangle(revX, revY, 240, 90, TacticalColors::DarkPanel); + DrawRectangleLines(revX, revY, 240, 90, TacticalColors::BorderMuted); 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("REVOLVER READY", revX + 15, revY + 10, 12, TacticalColors::MutedText); - DrawText(("NEXT: " + nextInfo.shortName).c_str(), revX + 15, revY + 65, 12, GRAY); - DrawText("[TAB] ARMORY", revX + 140, revY + 65, 12, CyberColors::NeonYellow); + // Tactical ammo badge (Rectangular, 0-rounding) + DrawRectangle(revX + 15, revY + 34, 46, 22, TacticalColors::SurfacePlate); + DrawRectangleLines(revX + 15, revY + 34, 46, 22, TacticalColors::Crimson); + DrawText(curInfo.shortName.substr(0, 3).c_str(), revX + 21, revY + 39, 12, curInfo.color); - // Bottom-Center: Context Action Prompt + DrawText(curInfo.name.c_str(), revX + 68, revY + 37, 14, curInfo.color); + + DrawText(("NEXT: [" + nextInfo.shortName + "]").c_str(), revX + 15, revY + 65, 12, TacticalColors::MutedText); + DrawText("[TAB] ARMORY", revX + 140, revY + 65, 12, TacticalColors::PureWhite); + + // Bottom-Center: Context Action Prompt (Strict 0-rounding) if (!prompt.empty()) { - int promptW = MeasureText(prompt.c_str(), 18) + 40; + int promptW = MeasureText(prompt.c_str(), 16) + 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); + DrawRectangle(promptX, promptY, promptW, 44, TacticalColors::DarkPanel); + DrawRectangleLines(promptX, promptY, promptW, 44, TacticalColors::Crimson); + DrawText(prompt.c_str(), promptX + 20, promptY + 14, 16, TacticalColors::PureWhite); } // 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 }); + 20, GetScreenHeight() - 35, 13, TacticalColors::MutedText); } void UIRenderer::drawHackingUI(const HackingGame& hacking) { - DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 8, 16, 235 }); + DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 6, 10, 240 }); 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); + DrawRectangle(panelX, panelY, panelW, panelH, TacticalColors::DarkPanel); + DrawRectangleLines(panelX, panelY, panelW, panelH, TacticalColors::BorderMuted); - // Header - DrawRectangle(panelX, panelY, panelW, 50, Color{ 14, 24, 42, 255 }); + // Tactical corner bracket accents + DrawRectangle(panelX, panelY, 8, 2, TacticalColors::Crimson); + DrawRectangle(panelX, panelY, 2, 8, TacticalColors::Crimson); + DrawRectangle(panelX + panelW - 8, panelY, 8, 2, TacticalColors::Crimson); + DrawRectangle(panelX + panelW - 2, panelY, 2, 8, TacticalColors::Crimson); + DrawRectangle(panelX, panelY + panelH - 2, 8, 2, TacticalColors::Crimson); + DrawRectangle(panelX, panelY + panelH - 8, 2, 8, TacticalColors::Crimson); + DrawRectangle(panelX + panelW - 8, panelY + panelH - 2, 8, 2, TacticalColors::Crimson); + DrawRectangle(panelX + panelW - 2, panelY + panelH - 8, 2, 8, TacticalColors::Crimson); + + // Header (Strict 0-rounding) + DrawRectangle(panelX, panelY, panelW, 50, TacticalColors::SurfacePlate); + DrawRectangleLines(panelX, panelY, panelW, 50, TacticalColors::BorderMuted); DrawText(("SECURITY ICE BYPASS TERMINAL // DEFENSE LEVEL 0" + std::to_string(hacking.getSecurityLevel())).c_str(), - panelX + 25, panelY + 16, 18, CyberColors::NeonCyan); + panelX + 25, panelY + 16, 17, TacticalColors::PureWhite); 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); + DrawText("PHASE 1: FREQUENCY TUMBLER ALIGNMENT", panelX + 25, panelY + 65, 16, TacticalColors::PureWhite); + DrawText("Tune each pin into the sweet spot frequency band, then lock it into place.", panelX + 25, panelY + 90, 13, TacticalColors::SilverText); const auto& tumbler = hacking.getTumbler(); int pinCount = tumbler.getPinCount(); @@ -310,150 +364,194 @@ void UIRenderer::drawHackingUI(const HackingGame& hacking) { 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 }); + DrawRectangle(px, trackY, pinW, trackH, Color{ 14, 18, 26, 255 }); + DrawRectangleLines(px, trackY, pinW, trackH, isSelected ? TacticalColors::Crimson : TacticalColors::BorderMuted); // Sweet spot target zone int sweetY = trackY + static_cast((1.0f - pin.targetHeight) * trackH); int sweetSpan = static_cast(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); + DrawRectangle(px + 2, sweetY - sweetSpan / 2, pinW - 4, sweetSpan, Color{ 220, 230, 245, 45 }); + DrawLine(px, sweetY, px + pinW, sweetY, TacticalColors::PureWhite); int pinH = 40; int pinCurrY = trackY + static_cast((1.0f - pin.currentHeight) * (trackH - pinH)); - Color pinColor = pin.locked ? CyberColors::NeonGreen : - (pin.isInSweetSpot() ? CyberColors::NeonYellow : Color{ 70, 110, 160, 255 }); + Color pinColor = pin.locked ? TacticalColors::PureWhite : + (pin.isInSweetSpot() ? TacticalColors::Crimson : Color{ 50, 62, 80, 255 }); DrawRectangle(px + 4, pinCurrY, pinW - 8, pinH, pinColor); - DrawRectangleLines(px + 4, pinCurrY, pinW - 8, pinH, WHITE); + DrawRectangleLines(px + 4, pinCurrY, pinW - 8, pinH, TacticalColors::BorderWhite); if (pin.locked) { - DrawText("LOCKED", px + 8, trackY + trackH + 12, 12, CyberColors::NeonGreen); + DrawText("LOCKED", px + 8, trackY + trackH + 12, 12, TacticalColors::PureWhite); } else if (pin.isInSweetSpot()) { - DrawText("SWEET", px + 12, trackY + trackH + 12, 12, CyberColors::NeonYellow); + DrawText("SWEET", px + 12, trackY + trackH + 12, 12, TacticalColors::Crimson); } else { - DrawText(std::to_string(i + 1).c_str(), px + 24, trackY + trackH + 12, 14, GRAY); + DrawText(std::to_string(i + 1).c_str(), px + 24, trackY + trackH + 12, 14, TacticalColors::MutedText); } } - DrawRectangle(panelX + 20, panelY + panelH - 65, panelW - 40, 45, Color{ 12, 18, 30, 255 }); + DrawRectangle(panelX + 20, panelY + panelH - 65, panelW - 40, 45, TacticalColors::SurfacePlate); + DrawRectangleLines(panelX + 20, panelY + panelH - 65, panelW - 40, 45, TacticalColors::BorderMuted); if (tumbler.isAllPinsLocked()) { - DrawText("[ENTER / SPACE] ALL PINS LOCKED! INITIATE DEAD-LOCK TIMING CHECK", panelX + 70, panelY + panelH - 52, 16, CyberColors::NeonGreen); + DrawText("[ENTER / SPACE] ALL PINS LOCKED! INITIATE DEAD-LOCK TIMING CHECK", panelX + 70, panelY + panelH - 52, 15, TacticalColors::PureWhite); } else { - DrawText("[A/D] Select Pin [W/S] Tune Frequency [SPACE] Lock Pin", panelX + 130, panelY + panelH - 52, 14, CyberColors::NeonCyan); + DrawText("[A/D] Select Pin [W/S] Tune Frequency [SPACE] Lock Pin", panelX + 130, panelY + panelH - 52, 14, TacticalColors::SilverText); } } 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); + DrawText("PHASE 2: DEAD-LOCK TIMING INJECTION (CALIBRATION RAIL)", panelX + 25, panelY + 65, 16, TacticalColors::Crimson); + DrawText("STRIKE [SPACE] WHEN THE NEEDLE HITS THE TARGET WINDOW!", panelX + 25, panelY + 90, 14, TacticalColors::PureWhite); const auto& sc = hacking.getSkillCheck(); - int dialCenterX = panelX + panelW / 2; - int dialCenterY = panelY + panelH / 2 + 10; - float dialRadius = 130.0f; + int railW = 600; + int railH = 54; + int railX = panelX + (panelW - railW) / 2; + int railY = panelY + panelH / 2 - 20; - 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 }); + // Outer housing (Strict 0-rounding) + DrawRectangle(railX - 8, railY - 14, railW + 16, railH + 28, Color{ 14, 18, 26, 255 }); + DrawRectangleLines(railX - 8, railY - 14, railW + 16, railH + 28, TacticalColors::BorderMuted); - // Success zone - DrawCircleSectorLines({ static_cast(dialCenterX), static_cast(dialCenterY) }, - dialRadius, - sc.getSuccessStart() - 90.0f, - sc.getSuccessEnd() - 90.0f, - 32, RAYWHITE); - DrawCircleSector({ static_cast(dialCenterX), static_cast(dialCenterY) }, - dialRadius, - sc.getSuccessStart() - 90.0f, - sc.getSuccessEnd() - 90.0f, - 32, Color{ 220, 220, 220, 120 }); + // Rail background + DrawRectangle(railX, railY, railW, railH, Color{ 18, 24, 34, 255 }); + DrawRectangleLines(railX, railY, railW, railH, TacticalColors::BorderMuted); - // Great success zone - DrawCircleSector({ static_cast(dialCenterX), static_cast(dialCenterY) }, - dialRadius, - sc.getGreatStart() - 90.0f, - sc.getGreatEnd() - 90.0f, - 16, CyberColors::NeonGreen); + // Tick marks every 10% + for (int t = 0; t <= 10; ++t) { + int tx = railX + static_cast(t * (railW / 10.0f)); + DrawLine(tx, railY, tx, railY + 8, TacticalColors::BorderMuted); + DrawLine(tx, railY + railH - 8, tx, railY + railH, TacticalColors::BorderMuted); + } - // Rotating needle - float needleRad = (sc.getNeedleAngle() - 90.0f) * (3.14159265f / 180.0f); - int needleEndX = dialCenterX + static_cast(std::cos(needleRad) * (dialRadius + 8.0f)); - int needleEndY = dialCenterY + static_cast(std::sin(needleRad) * (dialRadius + 8.0f)); + // Linear Target Window (Success Zone) + float succStartRatio = std::clamp(sc.getSuccessStart() / 360.0f, 0.0f, 1.0f); + float succEndRatio = std::clamp(sc.getSuccessEnd() / 360.0f, 0.0f, 1.0f); + int sX = railX + static_cast(succStartRatio * railW); + int sW = static_cast((succEndRatio - succStartRatio) * railW); - DrawLineEx({ static_cast(dialCenterX), static_cast(dialCenterY) }, - { static_cast(needleEndX), static_cast(needleEndY) }, - 4.0f, RED); - DrawCircle(dialCenterX, dialCenterY, 14, RED); + DrawRectangle(sX, railY + 2, sW, railH - 4, Color{ 220, 230, 245, 50 }); + DrawRectangleLines(sX, railY + 2, sW, railH - 4, TacticalColors::PureWhite); - DrawText("HIT [ SPACE ]", dialCenterX - 60, dialCenterY + static_cast(dialRadius) + 30, 20, CyberColors::NeonYellow); + // Critical Hit Zone + float greatStartRatio = std::clamp(sc.getGreatStart() / 360.0f, 0.0f, 1.0f); + float greatEndRatio = std::clamp(sc.getGreatEnd() / 360.0f, 0.0f, 1.0f); + int gX = railX + static_cast(greatStartRatio * railW); + int gW = std::max(8, static_cast((greatEndRatio - greatStartRatio) * railW)); + + DrawRectangle(gX, railY + 2, gW, railH - 4, TacticalColors::Crimson); + DrawRectangleLines(gX, railY + 2, gW, railH - 4, TacticalColors::BrightRed); + DrawText("CRIT", gX + (gW - MeasureText("CRIT", 10)) / 2, railY + (railH - 10) / 2, 10, TacticalColors::PureWhite); + + // Rotating Needle mapped linearly across the rail + float needleRatio = std::clamp(sc.getNeedleAngle() / 360.0f, 0.0f, 1.0f); + int curX = railX + static_cast(needleRatio * railW); + + // Vertical reticle cursor (Strict 0-rounding) + DrawLineEx({ static_cast(curX), static_cast(railY - 8) }, + { static_cast(curX), static_cast(railY + railH + 8) }, + 3.0f, TacticalColors::BrightRed); + DrawTriangle({ (float)curX, (float)(railY - 2) }, + { (float)(curX - 7), (float)(railY - 12) }, + { (float)(curX + 7), (float)(railY - 12) }, + TacticalColors::BrightRed); + DrawTriangle({ (float)curX, (float)(railY + railH + 2) }, + { (float)(curX + 7), (float)(railY + railH + 12) }, + { (float)(curX - 7), (float)(railY + railH + 12) }, + TacticalColors::BrightRed); + + // Range telemetry labels + DrawText("[ 0.00 MS ]", railX, railY - 26, 11, TacticalColors::MutedText); + DrawText("[ TARGET WINDOW ]", sX, railY - 26, 11, TacticalColors::PureWhite); + DrawText("[ 1000 MS ]", railX + railW - MeasureText("[ 1000 MS ]", 11), railY - 26, 11, TacticalColors::MutedText); + + // Action Prompt Box + int promptBoxW = 280; + int promptBoxH = 42; + int promptBoxX = panelX + (panelW - promptBoxW) / 2; + int promptBoxY = railY + railH + 34; + DrawRectangle(promptBoxX, promptBoxY, promptBoxW, promptBoxH, TacticalColors::SurfacePlate); + DrawRectangleLines(promptBoxX, promptBoxY, promptBoxW, promptBoxH, TacticalColors::Crimson); + DrawText("STRIKE [ SPACE ] OVERRIDE", promptBoxX + 24, promptBoxY + 12, 16, TacticalColors::PureWhite); } 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); + DrawText(">>> CRITICAL OVERRIDE SUCCESS! <<<", cx - 210, cy - 40, 24, TacticalColors::Crimson); + DrawText("Subroutines penetrated. Bonus cyber-cartridge recovered!", cx - 200, cy + 10, 16, TacticalColors::PureWhite); } 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); + DrawText(">>> ACCESS GRANTED <<<", cx - 140, cy - 40, 26, TacticalColors::PureWhite); + DrawText("Security locks bypassed successfully.", cx - 150, cy + 10, 16, TacticalColors::SilverText); } 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); + DrawText("!!! ICE ALARM TRIGGERED !!!", cx - 180, cy - 40, 26, TacticalColors::BrightRed); + DrawText("Intrusion countermeasures activated. Defense Sentinel deployed!", cx - 240, cy + 10, 16, TacticalColors::Crimson); } } } 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 chamberOrbitR = 56.0f; 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(std::cos(fluteAngle) * (cylinderR + 2.0f)); - int fluteY = centerY + static_cast(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); + // 1. Faceted Octagonal Heavy Outer Housing (Strict 0-rounding) + DrawRectangle(centerX - 100, centerY - 100, 200, 200, TacticalColors::DarkPanel); + DrawRectangleLines(centerX - 100, centerY - 100, 200, 200, TacticalColors::BorderMuted); - // 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(cylinderR) - 4; - int strikerBaseY = centerY - static_cast(cylinderR) - 26; + // 45-degree chamfers on outer corners + DrawTriangle({ (float)(centerX - 100), (float)(centerY - 100) }, + { (float)(centerX - 72), (float)(centerY - 100) }, + { (float)(centerX - 100), (float)(centerY - 72) }, TacticalColors::PitchBlack); + DrawTriangle({ (float)(centerX + 100), (float)(centerY - 100) }, + { (float)(centerX + 72), (float)(centerY - 100) }, + { (float)(centerX + 100), (float)(centerY - 72) }, TacticalColors::PitchBlack); + DrawTriangle({ (float)(centerX - 100), (float)(centerY + 100) }, + { (float)(centerX - 72), (float)(centerY + 100) }, + { (float)(centerX - 100), (float)(centerY + 72) }, TacticalColors::PitchBlack); + DrawTriangle({ (float)(centerX + 100), (float)(centerY + 100) }, + { (float)(centerX + 72), (float)(centerY + 100) }, + { (float)(centerX + 100), (float)(centerY + 72) }, TacticalColors::PitchBlack); - DrawTriangle( - { static_cast(centerX), static_cast(strikerTipY) }, - { static_cast(centerX - 12), static_cast(strikerBaseY) }, - { static_cast(centerX + 12), static_cast(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); + // 2. Inner Chamfered Drum Plate + DrawRectangle(centerX - 88, centerY - 88, 176, 176, Color{ 16, 22, 32, 255 }); + DrawRectangleLines(centerX - 88, centerY - 88, 176, 176, TacticalColors::BorderWhite); + DrawTriangle({ (float)(centerX - 88), (float)(centerY - 88) }, + { (float)(centerX - 64), (float)(centerY - 88) }, + { (float)(centerX - 88), (float)(centerY - 64) }, TacticalColors::DarkPanel); + DrawTriangle({ (float)(centerX + 88), (float)(centerY - 88) }, + { (float)(centerX + 64), (float)(centerY - 88) }, + { (float)(centerX + 88), (float)(centerY - 64) }, TacticalColors::DarkPanel); + DrawTriangle({ (float)(centerX - 88), (float)(centerY + 88) }, + { (float)(centerX - 64), (float)(centerY + 88) }, + { (float)(centerX - 88), (float)(centerY + 64) }, TacticalColors::DarkPanel); + DrawTriangle({ (float)(centerX + 88), (float)(centerY + 88) }, + { (float)(centerX + 64), (float)(centerY + 88) }, + { (float)(centerX + 88), (float)(centerY + 64) }, TacticalColors::DarkPanel); - // 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); + // 3. Center Hub & Caliber Stamp + DrawRectangle(centerX - 24, centerY - 24, 48, 48, Color{ 12, 16, 24, 255 }); + DrawRectangleLines(centerX - 24, centerY - 24, 48, 48, TacticalColors::BorderMuted); + DrawRectangle(centerX - 14, centerY - 14, 28, 28, TacticalColors::SurfacePlate); + DrawRectangleLines(centerX - 14, centerY - 14, 28, 28, TacticalColors::Crimson); + DrawText(".357", centerX - 14, centerY - 6, 12, TacticalColors::PureWhite); - // 4. Six Chambers (Top chamber at 12:00 aligns with striker when visualAngle == index * 60) + // 4. Aggressive Red Striker Firing Pin at 12:00 (-90 deg / top) + int strikerW = 54; + int strikerH = 16; + int strikerX = centerX - strikerW / 2; + int strikerY = centerY - 100 - strikerH - 6; + + DrawRectangle(strikerX, strikerY, strikerW, strikerH, TacticalColors::DarkRed); + DrawRectangleLines(strikerX, strikerY, strikerW, strikerH, TacticalColors::BrightRed); + DrawTriangle({ (float)centerX, (float)(centerY - 96) }, + { (float)(centerX - 8), (float)(strikerY + strikerH) }, + { (float)(centerX + 8), (float)(strikerY + strikerH) }, + TacticalColors::BrightRed); + + const char* pinTxt = "[ 12:00 FIRING PIN ]"; + int pinW = MeasureText(pinTxt, 11); + DrawText(pinTxt, centerX - pinW / 2, strikerY - 18, 11, TacticalColors::BrightRed); + + // 5. Six Hard-Surface Chambers (Strict 0-rounding) for (int i = 0; i < 6; ++i) { float angleRad = (-visAngle + i * 60.0f - 90.0f) * (3.14159265f / 180.0f); int chX = centerX + static_cast(std::cos(angleRad) * chamberOrbitR); @@ -463,28 +561,30 @@ void UIRenderer::drawRevolverCylinderCombat(const Revolver& revolver, int center 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 }); + int chW = 34; + int chH = 34; + Rectangle chRect = { (float)(chX - chW / 2), (float)(chY - chH / 2), (float)chW, (float)chH }; 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); + DrawRectangleRec(chRect, TacticalColors::PitchBlack); + DrawRectangleLinesEx(chRect, isCurrent ? 2.0f : 1.0f, isCurrent ? TacticalColors::BrightRed : TacticalColors::BorderMuted); + DrawText("---", chX - 9, chY - 5, 10, TacticalColors::MutedText); } 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); + DrawRectangleRec(chRect, TacticalColors::DarkPanel); + DrawRectangleLinesEx(chRect, isCurrent ? 2.0f : 1.0f, isCurrent ? TacticalColors::BrightRed : info.color); + // Tactical cartridge primer block + DrawRectangle(chX - 7, chY - 7, 14, 14, Color{ info.color.r, info.color.g, info.color.b, 200 }); + DrawRectangleLines(chX - 7, chY - 7, 14, 14, info.color); + DrawText(info.shortName.substr(0, 3).c_str(), chX - 10, chY - 5, 10, TacticalColors::PureWhite); } + // Active Chamber Bracket (12:00 lock) if (isCurrent) { - float reticleR = chamberR + 5.0f + 2.0f * std::sin(animTime * 6.0f); - DrawCircleLines(chX, chY, reticleR, CyberColors::NeonYellow); - DrawLine(chX - static_cast(reticleR) - 3, chY, chX - static_cast(reticleR) + 3, chY, CyberColors::NeonYellow); - DrawLine(chX + static_cast(reticleR) - 3, chY, chX + static_cast(reticleR) + 3, chY, CyberColors::NeonYellow); - DrawLine(chX, chY - static_cast(reticleR) - 3, chX, chY - static_cast(reticleR) + 3, CyberColors::NeonYellow); - DrawLine(chX, chY + static_cast(reticleR) - 3, chX, chY + static_cast(reticleR) + 3, CyberColors::NeonYellow); + DrawRectangleLinesEx({ chRect.x - 4, chRect.y - 4, chRect.width + 8, chRect.height + 8 }, 2.0f, TacticalColors::BrightRed); + DrawLine(chX, (int)chRect.y - 8, chX, (int)chRect.y - 3, TacticalColors::BrightRed); + DrawLine(chX, (int)(chRect.y + chRect.height + 3), chX, (int)(chRect.y + chRect.height + 8), TacticalColors::BrightRed); + DrawLine((int)chRect.x - 8, chY, (int)chRect.x - 3, chY, TacticalColors::BrightRed); + DrawLine((int)(chRect.x + chRect.width + 3), chY, (int)(chRect.x + chRect.width + 8), chY, TacticalColors::BrightRed); } } } @@ -532,18 +632,19 @@ void UIRenderer::drawCodexModal() { 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); + DrawRectangle(leftX, cardY, 455, 46, TacticalColors::SurfacePlate); + DrawRectangleLines(leftX, cardY, 455, 46, (ammos[i] == AmmoType::EMPTY) ? TacticalColors::BorderMuted : TacticalColors::BorderWhite); - 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); + DrawRectangle(leftX + 10, cardY + 12, 40, 22, TacticalColors::DarkPanel); + DrawRectangleLines(leftX + 10, cardY + 12, 40, 22, (ammos[i] == AmmoType::EMPTY) ? TacticalColors::BorderMuted : TacticalColors::Crimson); + DrawText(inf.shortName.substr(0, 3).c_str(), leftX + 15, cardY + 17, 11, inf.color); 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); + DrawText(title.c_str(), leftX + 58, cardY + 8, 13, inf.color); + DrawText(inf.description.c_str(), leftX + 58, cardY + 25, 11, TacticalColors::SilverText); cardY += 52; } @@ -648,15 +749,15 @@ void UIRenderer::drawCombatUI(const CombatSystem& combat, const Player& player, 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); + DrawText("WEAKNESS:", scanX + 160, scanY + 112, 12, TacticalColors::MutedText); + DrawRectangle(scanX + 235, scanY + 107, 240, 24, Color{ 14, 18, 26, 240 }); + DrawRectangleLines(scanX + 235, scanY + 107, 240, 24, wknInfo.color); + std::string wknText = "[" + wknInfo.shortName + "] -> +100% CRIT SURGE!"; + DrawText(wknText.c_str(), scanX + 242, scanY + 112, 12, wknInfo.color); 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; + Color badgeCol = (surgePulse > 0.5f) ? TacticalColors::Crimson : wknInfo.color; DrawText("[SURGE PRIMED!]", scanX + 482, scanY + 112, 12, badgeCol); } @@ -862,13 +963,13 @@ void UIRenderer::drawCombatUI(const CombatSystem& combat, const Player& player, int advW = 370; int advH = 46; - DrawRectangle(advX, advY, advW, advH, Color{ 8, 16, 30, 240 }); - DrawRectangleLines(advX, advY, advW, advH, CyberColors::NeonCyan); + DrawRectangle(advX, advY, advW, advH, TacticalColors::DarkPanel); + DrawRectangleLines(advX, advY, advW, advH, TacticalColors::BorderMuted); 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); + DrawRectangle(advX + 14, advY + 16, 10, 10, (aiPulse > 0.4f) ? TacticalColors::Crimson : TacticalColors::DarkRed); + DrawRectangleLines(advX + 14, advY + 16, 10, 10, TacticalColors::BrightRed); + DrawText("TACTICAL INTEL", advX + 32, advY + 8, 10, TacticalColors::Crimson); std::string advisorMsg = ""; if (rev.getCurrentAmmo() == enemy->getWeakness()) { @@ -1144,22 +1245,23 @@ void UIRenderer::drawWorkbenchModal(const Player& player) { 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); + // Card container (Strict 0-rounding) + DrawRectangle(cardX, cardY, cardW, cardH, TacticalColors::SurfacePlate); + DrawRectangleLines(cardX, cardY, cardW, cardH, isHoverBtn ? TacticalColors::Crimson : TacticalColors::BorderMuted); - // Header inside card: Ammo icon & Name - DrawCircle(cardX + 22, cardY + 24, 10.0f, info.color); - DrawCircleLines(cardX + 22, cardY + 24, 10.0f, WHITE); + // Header inside card: Ammo icon badge & Name + DrawRectangle(cardX + 12, cardY + 14, 38, 20, TacticalColors::DarkPanel); + DrawRectangleLines(cardX + 12, cardY + 14, 38, 20, TacticalColors::Crimson); + DrawText(info.shortName.substr(0, 3).c_str(), cardX + 17, cardY + 18, 11, info.color); std::string nameTxt = info.name + " [" + info.shortName + "]"; - DrawText(nameTxt.c_str(), cardX + 40, cardY + 15, 13, info.color); + DrawText(nameTxt.c_str(), cardX + 58, cardY + 16, 13, TacticalColors::PureWhite); // Tier badge - DrawText(curTierData.tierBadge.c_str(), cardX + cardW - 75, cardY + 15, 13, Color{ 255, 215, 60, 255 }); + DrawText(curTierData.tierBadge.c_str(), cardX + cardW - 75, cardY + 16, 13, TacticalColors::PureWhite); // Tier Title (e.g. TIER I: BASE SPEC) - DrawText(curTierData.tierTitle.c_str(), cardX + 16, cardY + 44, 12, Color{ 220, 190, 120, 255 }); + DrawText(curTierData.tierTitle.c_str(), cardX + 16, cardY + 44, 12, TacticalColors::SilverText); // Damage display int effDmg = player.getEffectiveDamage(ammo); @@ -1175,17 +1277,8 @@ void UIRenderer::drawWorkbenchModal(const Player& player) { 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); - } + DrawText("PERK:", cardX + 18, cardY + 90, 10, TacticalColors::MutedText); + drawWrappedText(curTierData.perkDesc, cardX + 18, cardY + 104, cardW - 36, 11, 3, TacticalColors::SilverText); // Bottom Action Button / Status if (curTier >= 3) { @@ -1328,17 +1421,8 @@ void UIRenderer::drawImplantDraftModal(const std::vector& choices, DrawLine(cardX + 12, cardY + 68, cardX + cardW - 12, cardY + 68, Color{ 35, 55, 80, 255 }); - // Description text wrapped into lines - std::string desc = data.description; - size_t breakPos = desc.find(". "); - if (breakPos != std::string::npos && breakPos < 35) { - std::string line1 = desc.substr(0, breakPos + 1); - std::string line2 = desc.substr(breakPos + 2); - DrawText(line1.c_str(), cardX + 18, cardY + 78, 12, archCol); - DrawText(line2.c_str(), cardX + 18, cardY + 98, 11, RAYWHITE); - } else { - DrawText(desc.c_str(), cardX + 18, cardY + 78, 11, RAYWHITE); - } + // Description text cleanly wrapped into card boundaries (Fixes Screen 1 overflow) + drawWrappedText(data.description, cardX + 18, cardY + 76, cardW - 36, 12, 4, TacticalColors::SilverText); // Install / Replace button Color btnBg = isHoverBtn ? Color{ 20, 55, 75, 255 } : Color{ 14, 35, 50, 255 }; diff --git a/src/render/UIRenderer.hpp b/src/render/UIRenderer.hpp index 6daa700..adf0128 100644 --- a/src/render/UIRenderer.hpp +++ b/src/render/UIRenderer.hpp @@ -18,6 +18,7 @@ public: void drawWorkbenchModal(const Player& player); static Rectangle getWorkbenchUpgradeButtonRect(int ammoIndex, int screenW, int screenH); static Rectangle getWorkbenchCloseButtonRect(int screenW, int screenH); + static void drawWrappedText(const std::string& text, int posX, int posY, int maxW, int fontSize, int lineSpacing, Color color); void drawImplantDraftModal(const std::vector& choices, const Player& player, int selectedReplaceSlot = -1); static Rectangle getDraftCardButtonRect(int cardIndex, int screenW, int screenH);