Implementar Modo Logo (easter egg) y sistema automático de cursor

MODO LOGO (Easter Egg):
- Modo especial que muestra logo JAILGAMES como "marca de agua"
- Activación manual: tecla K (perpetuo, no sale automáticamente)
- Auto-salto desde DEMO/DEMO_LITE (15%/10% probabilidad, ≥500 pelotas)
- Configuración fija: PNG_SHAPE + tiny texture + MONOCHROME + 120% escala + 5000 pelotas
- Sistema de 5 acciones variadas con probabilidades ajustadas:
  * SHAPE→PHYSICS gravedad ON (50%) - caída dramática
  * SHAPE→PHYSICS gravedad OFF (50%) - ver rotaciones sin caer
  * PHYSICS→SHAPE (60%) - reconstruir logo y mostrar rotaciones
  * PHYSICS: forzar gravedad ON (20%) - caer mientras da vueltas
  * PHYSICS: forzar gravedad OFF (20%) - flotar mientras da vueltas
- Intervalos 4-8s (aumentado para completar ciclos de rotación PNG_SHAPE)
- Textos informativos suprimidos en Logo Mode
- Corrección cambio de textura: actualiza texture_ y setTexture() en pelotas
- PNG_SHAPE idle reducido a 0.5-2s para animación más dinámica

MEJORAS FÍSICAS GLOBALES:
- Impulso automático al quitar gravedad si >50% pelotas en superficie
- Usa isOnSurface() para detectar pelotas quietas (DEMO/DEMO_LITE/LOGO)
- Evita que quitar gravedad con pelotas paradas no haga nada visible

SISTEMA AUTOMÁTICO DE CURSOR:
- Importado mouse.h/mouse.cpp desde Coffee Crisis Arcade Edition
- Auto-oculta cursor tras 3s de inactividad (namespace Mouse)
- Reaparece inmediatamente al mover ratón
- Funciona en todos los modos (ventana, fullscreen F3, real fullscreen F4)
- Eliminadas llamadas manuales SDL_ShowCursor/HideCursor
- Soluciona bug: cursor visible al iniciar con argumento -f

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 23:31:04 +02:00
parent f0baa51415
commit be099c198c
7 changed files with 334 additions and 64 deletions

View File

@@ -23,6 +23,7 @@
#include "ball.h" // for Ball
#include "external/dbgtxt.h" // for dbg_init, dbg_print
#include "external/mouse.h" // for Mouse namespace
#include "external/texture.h" // for Texture
#include "shapes/atom_shape.h" // for AtomShape
#include "shapes/cube_shape.h" // for CubeShape
@@ -242,6 +243,9 @@ void Engine::calculateDeltaTime() {
}
void Engine::update() {
// Actualizar visibilidad del cursor (auto-ocultar tras inactividad)
Mouse::updateCursorVisibility();
// Calcular FPS
fps_frame_count_++;
Uint64 current_time = SDL_GetTicks();
@@ -295,6 +299,9 @@ void Engine::update() {
void Engine::handleEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
// Procesar eventos de ratón (auto-ocultar cursor)
Mouse::handleEvent(event);
// Salir del bucle si se detecta una petición de cierre
if (event.type == SDL_EVENT_QUIT) {
should_exit_ = true;
@@ -623,6 +630,20 @@ void Engine::handleEvents() {
text_init_time_ = SDL_GetTicks();
}
break;
// Toggle Modo LOGO (easter egg - marca de agua)
case SDLK_K:
toggleLogoMode();
// Mostrar texto informativo
if (logo_mode_enabled_) {
text_ = "LOGO MODE ON";
} else {
text_ = "LOGO MODE OFF";
}
text_pos_ = (current_screen_width_ - static_cast<int>(text_.length() * 8)) / 2;
show_text_ = true;
text_init_time_ = SDL_GetTicks();
break;
}
}
}
@@ -759,6 +780,21 @@ void Engine::render() {
mode_text = "MODE SHAPE";
}
dbg_print(8, 72, mode_text.c_str(), 0, 255, 128); // Verde claro para modo
// Debug: Mostrar modo DEMO/LOGO activo (siempre visible cuando debug está ON)
if (logo_mode_enabled_) {
int logo_text_width = 9 * 8; // "LOGO MODE" = 9 caracteres × 8 píxeles
int logo_x = (current_screen_width_ - logo_text_width) / 2;
dbg_print(logo_x, 80, "LOGO MODE", 255, 128, 0); // Naranja para Logo Mode
} else if (demo_mode_enabled_) {
int demo_text_width = 9 * 8; // "DEMO MODE" = 9 caracteres × 8 píxeles
int demo_x = (current_screen_width_ - demo_text_width) / 2;
dbg_print(demo_x, 80, "DEMO MODE", 255, 165, 0); // Naranja para DEMO
} else if (demo_lite_enabled_) {
int lite_text_width = 14 * 8; // "DEMO LITE MODE" = 14 caracteres × 8 píxeles
int lite_x = (current_screen_width_ - lite_text_width) / 2;
dbg_print(lite_x, 80, "DEMO LITE MODE", 255, 200, 0); // Amarillo-naranja para DEMO LITE
}
}
SDL_RenderPresent(renderer_);
@@ -860,6 +896,21 @@ void Engine::forceBallsGravityOn() {
}
void Engine::forceBallsGravityOff() {
// Contar cuántas pelotas están en superficie (suelo/techo/pared)
int balls_on_surface = 0;
for (const auto& ball : balls_) {
if (ball->isOnSurface()) {
balls_on_surface++;
}
}
// Si la mayoría (>50%) están en superficie, aplicar impulso para que se vea el efecto
float surface_ratio = static_cast<float>(balls_on_surface) / static_cast<float>(balls_.size());
if (surface_ratio > 0.5f) {
pushBallsAwayFromGravity(); // Dar impulso contrario a gravedad
}
// Desactivar gravedad
for (auto& ball : balls_) {
ball->forceGravityOff();
}
@@ -889,13 +940,6 @@ void Engine::toggleFullscreen() {
fullscreen_enabled_ = !fullscreen_enabled_;
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
// Ocultar/mostrar cursor según modo fullscreen
if (fullscreen_enabled_) {
SDL_HideCursor();
} else {
SDL_ShowCursor();
}
}
void Engine::toggleRealFullscreen() {
@@ -930,9 +974,6 @@ void Engine::toggleRealFullscreen() {
}
SDL_free(displays);
}
// Ocultar cursor en real fullscreen
SDL_HideCursor();
} else {
// Volver a resolución base (configurada por CLI o default)
current_screen_width_ = base_screen_width_;
@@ -947,9 +988,6 @@ void Engine::toggleRealFullscreen() {
// Reinicar la escena con resolución original
initBalls(scenario_);
// Mostrar cursor al salir de real fullscreen
SDL_ShowCursor();
}
}
@@ -1366,8 +1404,8 @@ Color Engine::getInterpolatedColor(size_t ball_index) const {
// Sistema de Modo DEMO (auto-play)
void Engine::updateDemoMode() {
// Verificar si algún modo está activo
bool is_demo_active = demo_mode_enabled_ || demo_lite_enabled_;
// Verificar si algún modo está activo (DEMO, DEMO LITE o LOGO)
bool is_demo_active = demo_mode_enabled_ || demo_lite_enabled_ || logo_mode_enabled_;
if (!is_demo_active) return;
// Actualizar timer
@@ -1375,22 +1413,92 @@ void Engine::updateDemoMode() {
// Si es hora de ejecutar acción
if (demo_timer_ >= demo_next_action_time_) {
// Ejecutar acción según modo activo
bool is_lite = demo_lite_enabled_;
performDemoAction(is_lite);
// MODO LOGO: Sistema de acciones variadas con gravedad dinámica
if (logo_mode_enabled_) {
// Elegir acción aleatoria ponderada
int action = rand() % 100;
// Resetear timer y calcular próximo intervalo aleatorio
demo_timer_ = 0.0f;
if (current_mode_ == SimulationMode::SHAPE) {
// Logo quieto (formado) → 2 opciones posibles
if (action < 50) {
// 50%: SHAPE → PHYSICS con gravedad ON (caída dramática)
toggleShapeMode(true);
} else {
// 50%: SHAPE → PHYSICS con gravedad OFF (dar vueltas sin caer)
toggleShapeMode(false);
}
} else {
// Logo animado (PHYSICS) → 3 opciones posibles
if (action < 60) {
// 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
toggleShapeMode(false);
} else if (action < 80) {
// 20%: Forzar gravedad ON (empezar a caer mientras da vueltas)
forceBallsGravityOn();
} else {
// 20%: Forzar gravedad OFF (flotar mientras da vueltas)
forceBallsGravityOff();
}
}
// Usar intervalos diferentes según modo
float interval_min = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN;
float interval_max = is_lite ? DEMO_LITE_ACTION_INTERVAL_MAX : DEMO_ACTION_INTERVAL_MAX;
float interval_range = interval_max - interval_min;
demo_next_action_time_ = interval_min + (rand() % 1000) / 1000.0f * interval_range;
// Resetear timer con intervalos de Logo Mode
demo_timer_ = 0.0f;
float interval_range = LOGO_ACTION_INTERVAL_MAX - LOGO_ACTION_INTERVAL_MIN;
demo_next_action_time_ = LOGO_ACTION_INTERVAL_MIN + (rand() % 1000) / 1000.0f * interval_range;
// Solo salir automáticamente si NO es modo manual (tecla K)
// Probabilidad de salir: 25% en cada acción → promedio 4 acciones antes de salir
if (!logo_mode_is_manual_ && rand() % 100 < 25) {
exitLogoMode(true); // Volver a DEMO/DEMO_LITE
}
}
// MODO DEMO/DEMO_LITE: Acciones normales
else {
bool is_lite = demo_lite_enabled_;
performDemoAction(is_lite);
// Resetear timer y calcular próximo intervalo aleatorio
demo_timer_ = 0.0f;
// Usar intervalos diferentes según modo
float interval_min = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN;
float interval_max = is_lite ? DEMO_LITE_ACTION_INTERVAL_MAX : DEMO_ACTION_INTERVAL_MAX;
float interval_range = interval_max - interval_min;
demo_next_action_time_ = interval_min + (rand() % 1000) / 1000.0f * interval_range;
}
}
}
void Engine::performDemoAction(bool is_lite) {
// ============================================
// SALTO AUTOMÁTICO A LOGO MODE (Easter Egg)
// ============================================
if (is_lite) {
// DEMO LITE: Verificar condiciones para salto a Logo Mode
if (static_cast<int>(balls_.size()) >= LOGO_MODE_MIN_BALLS &&
current_theme_ == ColorTheme::MONOCHROME) {
// 10% probabilidad de saltar a Logo Mode
if (rand() % 100 < LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE) {
enterLogoMode(true); // Entrar desde DEMO
return;
}
}
} else {
// DEMO COMPLETO: Verificar condiciones para salto a Logo Mode
if (static_cast<int>(balls_.size()) >= LOGO_MODE_MIN_BALLS) {
// 15% probabilidad de saltar a Logo Mode
if (rand() % 100 < LOGO_JUMP_PROBABILITY_FROM_DEMO) {
enterLogoMode(true); // Entrar desde DEMO
return;
}
}
}
// ============================================
// ACCIONES NORMALES DE DEMO/DEMO_LITE
// ============================================
int TOTAL_WEIGHT;
int random_value;
int accumulated_weight = 0;
@@ -1415,11 +1523,11 @@ void Engine::performDemoAction(bool is_lite) {
return;
}
// Activar figura 3D (25%)
// Activar figura 3D (25%) - PNG_SHAPE excluido (reservado para Logo Mode)
accumulated_weight += DEMO_LITE_WEIGHT_SHAPE;
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM, ShapeType::PNG_SHAPE};
int shape_index = rand() % 9;
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
int shape_index = rand() % 8;
activateShape(shapes[shape_index]);
return;
}
@@ -1458,11 +1566,11 @@ void Engine::performDemoAction(bool is_lite) {
return;
}
// Activar figura 3D (20%)
// Activar figura 3D (20%) - PNG_SHAPE excluido (reservado para Logo Mode)
accumulated_weight += DEMO_WEIGHT_SHAPE;
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM, ShapeType::PNG_SHAPE};
int shape_index = rand() % 9;
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
int shape_index = rand() % 8;
activateShape(shapes[shape_index]);
return;
}
@@ -1629,6 +1737,125 @@ void Engine::toggleGravityOnOff() {
}
}
// ============================================================================
// SISTEMA DE MODO LOGO (Easter Egg - "Marca de Agua")
// ============================================================================
// Entrar al Modo Logo (manual con tecla K o automático desde DEMO)
void Engine::enterLogoMode(bool from_demo) {
// Verificar mínimo de pelotas
if (static_cast<int>(balls_.size()) < LOGO_MODE_MIN_BALLS) {
// Ajustar a 5000 pelotas automáticamente
scenario_ = 5; // Escenario 5000 pelotas (índice 5 en BALL_COUNT_SCENARIOS)
initBalls(scenario_);
}
// Guardar estado previo (para restaurar al salir)
logo_previous_theme_ = current_theme_;
logo_previous_texture_index_ = current_texture_index_;
logo_previous_shape_scale_ = shape_scale_factor_;
// Buscar índice de textura "tiny"
size_t tiny_index = current_texture_index_; // Por defecto mantener actual
for (size_t i = 0; i < texture_names_.size(); i++) {
if (texture_names_[i] == "tiny") {
tiny_index = i;
break;
}
}
// Aplicar configuración fija del Modo Logo
if (tiny_index != current_texture_index_) {
current_texture_index_ = tiny_index;
int old_size = current_ball_size_;
current_ball_size_ = textures_[current_texture_index_]->getWidth();
updateBallSizes(old_size, current_ball_size_);
// Actualizar textura global y en cada pelota
texture_ = textures_[current_texture_index_];
for (auto& ball : balls_) {
ball->setTexture(texture_);
}
}
// Cambiar a tema MONOCHROME
startThemeTransition(ColorTheme::MONOCHROME);
// Establecer escala a 120%
shape_scale_factor_ = LOGO_MODE_SHAPE_SCALE;
clampShapeScale();
// Activar PNG_SHAPE (el logo)
activateShape(ShapeType::PNG_SHAPE);
// Activar modo logo
logo_mode_enabled_ = true;
// Marcar si es activación manual (tecla K) o automática (desde DEMO)
logo_mode_is_manual_ = !from_demo;
// Configurar timer para alternancia SHAPE/PHYSICS
demo_timer_ = 0.0f;
demo_next_action_time_ = LOGO_ACTION_INTERVAL_MIN +
(rand() % 1000) / 1000.0f * (LOGO_ACTION_INTERVAL_MAX - LOGO_ACTION_INTERVAL_MIN);
std::cout << "[LOGO MODE] Activado" << (from_demo ? " (desde DEMO)" : " (manual)") << "\n";
}
// Salir del Modo Logo (volver a estado anterior o salir de DEMO)
void Engine::exitLogoMode(bool return_to_demo) {
if (!logo_mode_enabled_) return;
// Restaurar estado previo
startThemeTransition(logo_previous_theme_);
if (logo_previous_texture_index_ != current_texture_index_ &&
logo_previous_texture_index_ < textures_.size()) {
current_texture_index_ = logo_previous_texture_index_;
int old_size = current_ball_size_;
current_ball_size_ = textures_[current_texture_index_]->getWidth();
updateBallSizes(old_size, current_ball_size_);
// Actualizar textura global y en cada pelota
texture_ = textures_[current_texture_index_];
for (auto& ball : balls_) {
ball->setTexture(texture_);
}
}
shape_scale_factor_ = logo_previous_shape_scale_;
clampShapeScale();
generateShape();
// Desactivar modo logo
logo_mode_enabled_ = false;
logo_mode_is_manual_ = false; // Resetear flag manual
if (!return_to_demo) {
// Salida manual (tecla K): desactivar todos los modos DEMO
demo_mode_enabled_ = false;
demo_lite_enabled_ = false;
} else {
// Volver a DEMO: reconfigurar timer
demo_timer_ = 0.0f;
demo_next_action_time_ = (demo_lite_enabled_ ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN) +
(rand() % 1000) / 1000.0f *
((demo_lite_enabled_ ? DEMO_LITE_ACTION_INTERVAL_MAX : DEMO_ACTION_INTERVAL_MAX) -
(demo_lite_enabled_ ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN));
}
std::cout << "[LOGO MODE] Desactivado" << (return_to_demo ? " (volviendo a DEMO)" : " (salida manual)") << "\n";
}
// Toggle manual del Modo Logo (tecla K)
void Engine::toggleLogoMode() {
if (logo_mode_enabled_) {
exitLogoMode(false); // Salir y desactivar DEMO
} else {
enterLogoMode(false); // Entrar manualmente
}
}
// Sistema de cambio de sprites dinámico
void Engine::updateBallSizes(int old_size, int new_size) {
float delta_size = static_cast<float>(new_size - old_size);
@@ -1728,8 +1955,8 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
forceBallsGravityOn();
}
// Mostrar texto informativo (solo si NO estamos en modo demo)
if (!demo_mode_enabled_ && !demo_lite_enabled_) {
// Mostrar texto informativo (solo si NO estamos en modo demo o logo)
if (!demo_mode_enabled_ && !demo_lite_enabled_ && !logo_mode_enabled_) {
text_ = "MODO FISICA";
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;
@@ -1793,8 +2020,8 @@ void Engine::activateShape(ShapeType type) {
ball->enableRotoBallAttraction(true);
}
// Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo)
if (active_shape_ && !demo_mode_enabled_ && !demo_lite_enabled_) {
// Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo o logo)
if (active_shape_ && !demo_mode_enabled_ && !demo_lite_enabled_ && !logo_mode_enabled_) {
text_ = std::string("MODO ") + active_shape_->getName();
int text_width = static_cast<int>(text_.length() * 8);
text_pos_ = (current_screen_width_ - text_width) / 2;