feat: Cyber-noir hub room lighting, pixel wave transitions, tactical UI & ammo overhaul
- Replace procedural city with Room_window.jpg panoramic backdrop - Add volumetric violet godrays, floor ambient pools, and cyan monitor glow - Fix bed occlusion bug: reorder renderHub pipeline with rlDrawRenderBatchActive() - Darken hub materials (smoked walnut parquet, slate plaster, midnight quilt) - Enable cyber-noir postprocessing shader with vignette in hub room - Implement diagonal matrix pixel wave screen transitions (ScreenTransition.hpp) - Add tactical ammo palette (KIN, EMP, THM, ACD, GLT, NAN) & 3D combat laser tracers - Center [ 12:00 FIRING PIN ] indicator and add drawWrappedText word-wrapping in UI - Maintain strict zero-rounding constraint across the entire codebase
This commit is contained in:
parent
1e3b05d5c8
commit
4cad719290
14 changed files with 1230 additions and 770 deletions
BIN
assets/Pictures/Room_window.jpg
Normal file
BIN
assets/Pictures/Room_window.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ void CombatSystem::startCombat(Player& player, std::unique_ptr<Enemy> 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) {
|
||||
|
|
|
|||
|
|
@ -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<std::string> m_combatLog;
|
||||
std::vector<FloatingText> m_floatingTexts;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "InventoryUI.hpp"
|
||||
#include "render/UIRenderer.hpp"
|
||||
#include "core/SoundManager.hpp"
|
||||
#include <cmath>
|
||||
|
||||
|
|
@ -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<int>(std::cos(fluteAngle) * (cylRadius + 2.0f));
|
||||
int fluteY = cylCenterY + static_cast<int>(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<int>(cylRadius) - 4;
|
||||
int strikerBaseY = cylCenterY - static_cast<int>(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<float>(cylCenterX), static_cast<float>(strikerTipY) },
|
||||
{ static_cast<float>(cylCenterX - 14), static_cast<float>(strikerBaseY) },
|
||||
{ static_cast<float>(cylCenterX + 14), static_cast<float>(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<int>(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<int>(chamberR) - 4, chY - static_cast<int>(chamberR) - 4,
|
||||
static_cast<int>(chamberR * 2) + 8, static_cast<int>(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<float>(pouchX + 15), static_cast<float>(startY + i * 42), static_cast<float>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
144
src/render/ScreenTransition.hpp
Normal file
144
src/render/ScreenTransition.hpp
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#pragma once
|
||||
|
||||
#include "raylib.h"
|
||||
#include "core/Common.hpp"
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace dnr {
|
||||
|
||||
class ScreenTransition {
|
||||
public:
|
||||
enum class Phase {
|
||||
IDLE,
|
||||
WIPE_IN,
|
||||
HOLD_MIDPOINT,
|
||||
WIPE_OUT
|
||||
};
|
||||
|
||||
ScreenTransition() = default;
|
||||
|
||||
void start(std::function<void()> 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<float>(c + r) / static_cast<float>(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<int>(blockSize * cellProgress);
|
||||
int offset = (blockSize - curSize) / 2;
|
||||
Rectangle bRect = {
|
||||
static_cast<float>(posX + offset),
|
||||
static_cast<float>(posY + offset),
|
||||
static_cast<float>(curSize),
|
||||
static_cast<float>(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<void()> m_onMidpoint;
|
||||
};
|
||||
|
||||
} // namespace dnr
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include "UIRenderer.hpp"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
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<float>(size) / std::max(w, h);
|
||||
|
||||
// Frame
|
||||
DrawRectangle(startX - 4, startY - 4, size + 8, size + 8, Color{ 4, 10, 18, 230 });
|
||||
DrawRectangleLines(startX - 4, startY - 4, size + 8, size + 8, CyberColors::NeonCyan);
|
||||
DrawText("NET_MAP // 2.5D", startX, startY - 18, 11, CyberColors::NeonCyan);
|
||||
// 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<int>(laser.x * cellSize);
|
||||
int ly = startY + static_cast<int>(laser.y * cellSize);
|
||||
int cs = static_cast<int>(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<int>((mine.x + 0.5f) * cellSize);
|
||||
int my = startY + static_cast<int>((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<int>((shrine.x + 0.5f) * cellSize);
|
||||
int sy = startY + static_cast<int>((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<int>((me.x + 0.5f) * cellSize);
|
||||
int ey = startY + static_cast<int>((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<int>((player.getGridX() + 0.5f) * cellSize);
|
||||
int plY = startY + static_cast<int>((player.getGridY() + 0.5f) * cellSize);
|
||||
DrawCircle(plX, plY, cellSize * 0.7f, CyberColors::NeonYellow);
|
||||
|
||||
Vector2 dirVec = getDirectionOffset(player.getFacing());
|
||||
DrawLine(plX, plY, plX + static_cast<int>(dirVec.x * cellSize * 1.5f), plY + static_cast<int>(dirVec.y * cellSize * 1.5f), CyberColors::NeonGreen);
|
||||
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<int>(animTime * 2.0f)) % 12];
|
||||
DrawText(txt, xPos, static_cast<int>(yPos), 12, Color{ 0, 255, 100, 35 });
|
||||
DrawText(txt, xPos, static_cast<int>(yPos - 20), 11, Color{ 0, 255, 100, 18 });
|
||||
DrawText(txt, xPos, static_cast<int>(yPos), 12, Color{ 140, 150, 165, 30 });
|
||||
DrawText(txt, xPos, static_cast<int>(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<float>(player.getHp()) / player.getMaxHp();
|
||||
drawProgressBar(hudX + 60, hudY + 40, 250, 18, hpRatio, CyberColors::NeonPink, Color{ 30, 10, 20, 255 });
|
||||
DrawText("CORE", hudX + 15, hudY + 40, 14, CyberColors::NeonPink);
|
||||
DrawText((std::to_string(player.getHp()) + " / " + std::to_string(player.getMaxHp())).c_str(), hudX + 145, hudY + 42, 13, RAYWHITE);
|
||||
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<float>(player.getShield()) / player.getMaxShield();
|
||||
drawProgressBar(hudX + 60, hudY + 68, 250, 18, shRatio, CyberColors::NeonCyan, Color{ 10, 25, 45, 255 });
|
||||
DrawText("SHLD", hudX + 15, hudY + 68, 14, CyberColors::NeonCyan);
|
||||
DrawText((std::to_string(player.getShield()) + " / " + std::to_string(player.getMaxShield())).c_str(), hudX + 145, hudY + 70, 13, RAYWHITE);
|
||||
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<int>((1.0f - pin.targetHeight) * trackH);
|
||||
int sweetSpan = static_cast<int>(pin.tolerance * 2.0f * trackH);
|
||||
DrawRectangle(px + 2, sweetY - sweetSpan / 2, pinW - 4, sweetSpan, Color{ 0, 240, 255, 60 });
|
||||
DrawLine(px, sweetY, px + pinW, sweetY, CyberColors::NeonCyan);
|
||||
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<int>((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<float>(dialCenterX), static_cast<float>(dialCenterY) },
|
||||
dialRadius,
|
||||
sc.getSuccessStart() - 90.0f,
|
||||
sc.getSuccessEnd() - 90.0f,
|
||||
32, RAYWHITE);
|
||||
DrawCircleSector({ static_cast<float>(dialCenterX), static_cast<float>(dialCenterY) },
|
||||
dialRadius,
|
||||
sc.getSuccessStart() - 90.0f,
|
||||
sc.getSuccessEnd() - 90.0f,
|
||||
32, Color{ 220, 220, 220, 120 });
|
||||
// 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<float>(dialCenterX), static_cast<float>(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<int>(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<int>(std::cos(needleRad) * (dialRadius + 8.0f));
|
||||
int needleEndY = dialCenterY + static_cast<int>(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<int>(succStartRatio * railW);
|
||||
int sW = static_cast<int>((succEndRatio - succStartRatio) * railW);
|
||||
|
||||
DrawLineEx({ static_cast<float>(dialCenterX), static_cast<float>(dialCenterY) },
|
||||
{ static_cast<float>(needleEndX), static_cast<float>(needleEndY) },
|
||||
4.0f, RED);
|
||||
DrawCircle(dialCenterX, dialCenterY, 14, RED);
|
||||
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<int>(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<int>(greatStartRatio * railW);
|
||||
int gW = std::max(8, static_cast<int>((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<int>(needleRatio * railW);
|
||||
|
||||
// Vertical reticle cursor (Strict 0-rounding)
|
||||
DrawLineEx({ static_cast<float>(curX), static_cast<float>(railY - 8) },
|
||||
{ static_cast<float>(curX), static_cast<float>(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<int>(std::cos(fluteAngle) * (cylinderR + 2.0f));
|
||||
int fluteY = centerY + static_cast<int>(std::sin(fluteAngle) * (cylinderR + 2.0f));
|
||||
DrawCircle(fluteX, fluteY, 12.0f, Color{ 8, 14, 24, 255 });
|
||||
DrawCircleLines(fluteX, fluteY, 12.0f, Color{ 40, 65, 95, 255 });
|
||||
}
|
||||
|
||||
// 2. Center extractor ratchet & hub
|
||||
DrawCircle(centerX, centerY, 34.0f, Color{ 14, 20, 32, 255 });
|
||||
DrawCircleLines(centerX, centerY, 34.0f, Color{ 70, 100, 140, 255 });
|
||||
DrawCircle(centerX, centerY, 14.0f, Color{ 220, 175, 45, 255 });
|
||||
DrawCircleLines(centerX, centerY, 14.0f, CyberColors::NeonYellow);
|
||||
DrawText(".357", centerX - 15, centerY - 6, 12, CyberColors::NeonYellow);
|
||||
// 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<int>(cylinderR) - 4;
|
||||
int strikerBaseY = centerY - static_cast<int>(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<float>(centerX), static_cast<float>(strikerTipY) },
|
||||
{ static_cast<float>(centerX - 12), static_cast<float>(strikerBaseY) },
|
||||
{ static_cast<float>(centerX + 12), static_cast<float>(strikerBaseY) },
|
||||
Color{ 255, 210, 50, 255 }
|
||||
);
|
||||
DrawRectangle(centerX - 18, strikerBaseY - 14, 36, 14, Color{ 210, 160, 30, 255 });
|
||||
DrawRectangleLines(centerX - 18, strikerBaseY - 14, 36, 14, CyberColors::NeonYellow);
|
||||
DrawCircle(centerX, strikerTipY, 2.5f + strikerPulse * 1.5f, CyberColors::NeonYellow);
|
||||
// 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<int>(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<int>(reticleR) - 3, chY, chX - static_cast<int>(reticleR) + 3, chY, CyberColors::NeonYellow);
|
||||
DrawLine(chX + static_cast<int>(reticleR) - 3, chY, chX + static_cast<int>(reticleR) + 3, chY, CyberColors::NeonYellow);
|
||||
DrawLine(chX, chY - static_cast<int>(reticleR) - 3, chX, chY - static_cast<int>(reticleR) + 3, CyberColors::NeonYellow);
|
||||
DrawLine(chX, chY + static_cast<int>(reticleR) - 3, chX, chY + static_cast<int>(reticleR) + 3, CyberColors::NeonYellow);
|
||||
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<ImplantType>& 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 };
|
||||
|
|
|
|||
|
|
@ -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<ImplantType>& choices, const Player& player, int selectedReplaceSlot = -1);
|
||||
static Rectangle getDraftCardButtonRect(int cardIndex, int screenW, int screenH);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue