diff --git a/assets/Pictures/Room_window.jpg b/assets/Pictures/Room_window.jpg new file mode 100644 index 0000000..522f0cb Binary files /dev/null and b/assets/Pictures/Room_window.jpg differ 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);