eliminades referencies antigues

This commit is contained in:
2026-04-07 10:06:42 +02:00
parent 158affe1f9
commit 39170a086e
13 changed files with 28 additions and 1302 deletions

930
CLAUDE.md
View File

@@ -23,90 +23,15 @@ Se han renombrado las referencias de `JailDoctor's Dilemma` → `Projecte 2026`
- El publisher `jailgames` (carpeta de config de sistema: `~/.config/jailgames/projecte_2026`). - El publisher `jailgames` (carpeta de config de sistema: `~/.config/jailgames/projecte_2026`).
- Los archivos `release/windows/jdd.rc` y `jdd.res` (solo bundlean el icono). - Los archivos `release/windows/jdd.rc` y `jdd.res` (solo bundlean el icono).
## Log de cambios realizados (sesiones de trabajo) ## Pendiente
### Eliminaciones - **PaletteColor enum** en `utils.hpp` sigue con los 16 índices antiguos (ZX Spectrum). Los índices 0-4 coinciden con CPC, pero 5+ mapean a colores diferentes (ej. `PaletteColor::BRIGHT_RED=5` ahora renderiza MAGENTA en CPC). `stringToColor()` y `SPECTRUM_REFERENCE` en `palette_manager.cpp` tampoco están actualizados. Falta decidir cómo migrar cuando se vea el resultado visual.
- **Clase `Stats` (persistencia CSV de muertes/visitas)** eliminada por completo: archivos `source/game/gameplay/stats.hpp/cpp`, entrada en CMakeLists, referencias en `Game` scene (`stats_`, `initStats()`, `addVisit/addDeath`), ficheros `stats.csv`/`stats_buffer.csv` en `config/assets.yaml`, patrón `*stats.txt` en `.gitignore`, campo `worst_nightmare` en `Options::Stats` + `Defaults::Stats::WORST_NIGHTMARE`, display de "worst nightmare" en `game_over.cpp`/`.hpp`, traducciones en locales, menciones en docs. - **Tile 6 (conveyor)** no soportado aún en el nuevo motor de Player.
- **Mantenido:** `Options::stats.items` y `Options::stats.rooms` — son contadores de runtime del marcador/game_over, no tienen persistencia CSV. Coincidencia de nombre con la clase Stats eliminada. - **`next()`/`previous()` en PaletteManager** desactivados de facto (solo 1 paleta). Posible futura reutilización para ciclar modos de ordenación.
### Música
- Eliminadas las 10 pistas originales (14 MB) de `data/music/`.
- Copiadas 2 pistas de `../pollo/data/music/`: `574070_KUVO_Farewell_to_school.ogg` y `574071_EA_DTV.ogg` (7 MB total).
- `config/assets.yaml` reducido a esas 2 entradas.
- Las 10 llamadas `playMusic()` existentes (title/game/loading_*/ending*/game_over) remapeadas alternadamente a una de las 2 pistas. Mapeo concreto en esta misma documentación si hace falta consultarlo (ver git log del cambio).
### Paleta
- Eliminadas las 23 paletas ZX Spectrum/otras de `data/palette/`.
- Traída `cpc.pal` de `../pollo/data/palettes/` (JASC-PAL, 28 entradas: CLEAR+27 colores CPC).
- `config/assets.yaml`: solo `cpc.pal`.
- `Defaults::Video::PALETTE_NAME = "cpc"`.
- Traída la clase `Color` de pollo a `source/utils/color.hpp` + `color.cpp` (enum `Color::Cpc` con 28 valores 0-27 + `Color::fromString()`).
- Struct RGB `Color` en `utils.hpp` **renombrada a `Rgb`** para evitar conflicto con la nueva clase. Propagado a `utils.cpp` (`colorAreEqual`), `screen.hpp`/`.cpp` (`clearRenderer`).
- Añadido `source/utils/color.cpp` a `CMakeLists.txt`.
- **Pendiente de revisar:** enum `PaletteColor` en `utils.hpp` sigue con los 16 índices antiguos (ZX Spectrum). Los índices 0-4 coinciden con CPC, pero 5+ mapean a colores diferentes (ej. `PaletteColor::BRIGHT_RED=5` ahora renderiza MAGENTA en CPC). `stringToColor()` también sin actualizar. `SPECTRUM_REFERENCE` en `palette_manager.cpp` sigue siendo la referencia ZX Spectrum de 16 colores. Falta decidir cómo migrar estos tres puntos cuando se vea el resultado visual.
- **`next()`/`previous()` en PaletteManager**: con una sola paleta hacen wrap al mismo índice (no crashean). El concepto de ciclar paletas queda desactivado de facto; posible futura reutilización para ciclar modos de ordenación.
### Otros
- Añadidos `desktop.ini` y `Thumbs.db` al `.gitignore`.
### Transiciones de pantalla (sesión abril 2026)
- **Cambio de pantalla por punto central:** `Player::handleBorders()` usa el centro del rectángulo del jugador para detectar cambio de pantalla (antes usaba bordes).
- **Conservación de momento:** `Player::switchBorders()` conserva velocidad y estado al cambiar de pantalla (antes forzaba ON_GROUND y reseteaba vy_).
- **Transición animada con scroll:** Al cambiar de habitación, ambas rooms se desplazan con easing `cubicInOut` durante 0.5s.
- Render offset global añadido a `Screen` (`setRenderOffset`) aplicado en los 6 métodos de `Surface::render()`.
- Enemigos de ambas habitaciones se actualizan durante la transición.
- El jugador puede moverse durante la transición.
- Estado en `Game`: `transitioning_`, `transition_timer_`, `transition_old_room_`, `transition_direction_`.
- Ficheros: `screen.hpp/cpp`, `surface.cpp`, `game.hpp/cpp`.
### Impulso de salto
- `JUMP_VELOCITY` incrementado 5%: de -170.0 a -178.5.
### Collision tilemap (sesión abril 2026)
- **Formato YAML nuevo:** `tilemap:` tiene dos sub-mapas: `draw:` (tilemap de dibujo, el original) y `collision:` (mapa de colisiones). `RoomLoader::parseTilemap()` lee ambos con fallback al formato antiguo.
- **`Room::Data::collision_tile_map`** — vector<int> con tipos: 0=vacío, 1=muro, 2=plataforma, 3=slope_l, 4=slope_r, 5=kill, 6=conveyor.
- **CollisionMap migrado:** `getTile()` lee directamente del `collision_tile_map` (antes deducía el tipo por rangos de índice del tileset de dibujo). Constructor ya no necesita `tile_set_width`.
- **Editor de colisiones:** Tecla 7 o `EDIT DRAW`/`EDIT COLLISION` alterna entre editar el tilemap de dibujo y el de colisiones. En modo collision se superpone el `collision.gif` (7 tiles) sobre el mapa de dibujo. Right-click abre el tile picker del tileset correspondiente. `RoomSaver` guarda ambos sub-mapas.
- **`collision.gif`** registrado en `assets.yaml` (7 tiles: vacío, muro, plataforma, slope_l, slope_r, kill, conveyor).
### Nuevo motor de colisiones tile-based (sesión abril 2026)
- **Clase `TileCollider`** (`source/game/gameplay/tile_collider.hpp/cpp`): queries directas contra el grid de tiles sin listas de superficies intermedias. API: `checkWallLeft/Right`, `checkCeiling`, `checkFloor` (con FloorHit struct), `hasGroundBelow`, `checkSlopeBelow` (con SlopeInfo struct), `getSlopeY`.
- Integrado en `CollisionMap` (miembro + getter) y expuesto via `Room::getTileCollider()`.
- **Player reescrito** con pipeline de 6 fases claras:
1. `handleInput()` — leer input
2. `updateVelocity()` + `applyGravity()` — calcular velocidades
3. `handleJumpAndDrop()` — salto + drop-through (plataformas y slopes con DOWN)
4. `moveHorizontal()` + `moveVertical()` — movimiento con colisión tile-based
5. `checkFalling()` — detectar caída
6. `syncSpriteAndCollider()` + `animate()` + `handleBorders()`
- **~20 métodos eliminados** del Player antiguo. Slopes gestionadas por tile (slope_tile_x/y/type) en vez de puntero a LineDiagonal.
- **Sistema antiguo de superficies preservado** en CollisionMap (no eliminado), simplemente ya no usado por Player.
- **Slopes — escalera diagonal de tiles:**
- Las slopes de 45° se pintan como escalera en el collision tilemap: cada tile una fila arriba/abajo y una columna al lado.
- `checkSlopeBelow`: escanea la fila de los pies Y la de arriba (la slope entry siempre está una fila arriba del suelo).
- `followSlope`: busca el siguiente tile de slope en la fila actual Y la de abajo (para descenso en escalera).
- `exitSlope`: comprueba suelo en `foot_y` y `foot_y+1` (boundary de fila al salir por abajo) y snapea al borde del tile.
- **Drop-through — sin flags, puramente posicional:**
- Al pulsar DOWN sobre slope/plataforma: `y_ += 1` y ON_AIR. No hay flags `dropping_through_`.
- `checkFloor` para PASSABLE: solo aterriza si `foot_y_current <= tile_top` (pies estaban por encima).
- `checkFloor` para slopes: solo aterriza si `foot_y_current <= slope_y` (pies por encima de la superficie).
- `isInsideAnySlope()`: si algún pie está por debajo de la superficie de cualquier slope que solape, bloquea TODOS los aterrizajes en slopes. Esto asegura que al hacer drop o saltar desde abajo, el jugador atraviesa toda la escalera de slopes sin quedarse pegado.
- **Kill tiles (tipo 5):** implementados en `TileCollider::touchesKillTile()`. Comprueba si el rectángulo del jugador solapa algún tile KILL. Se llama en `Player::update()` después del movimiento.
- **Motor antiguo eliminado:**
- `CollisionMap` reducido a wrapper mínimo: solo contiene `collision_tile_map_`, `conveyor_belt_direction_` y `TileCollider`.
- Eliminados de `CollisionMap`: enum `Tile` antiguo, `getTile()`, `getSlopeHeight()`, todas las listas de superficies (`bottom_floors_`, `top_floors_`, etc.), todos los `check*Surfaces()`, `initializeSurfaces()`, `collect*Tiles()`, `buildHorizontalLines()`, `set*Surfaces()`.
- Eliminados de `Room`: enum `Tile`, `getTile()`, `getSlopeHeight()`, `getTileSize()`, todos los `check*()` delegados, `getSlopeAtPoint()`.
- Eliminados de `utils.hpp/cpp`: structs `LineHorizontal`, `LineVertical`, `LineDiagonal`, `Line` y todos los `checkCollision` / `normalizeLine` que los usaban.
- `TilemapRenderer`: eliminado `renderDebugCollisionSurfaces()` (pintaba líneas de superficies). Modo debug (tecla 0) ahora renderiza el collision tilemap usando `collision.gif`.
- `TilemapRenderer::setAnimatedTiles()` ahora lee del `collision_tile_map` (valor 6 = conveyor) en vez de `CollisionMap::getTile()`.
- **Pendiente:** tile 6 (conveyor) no soportado aún en el nuevo motor de Player.
### Otros
- Añadidos `desktop.ini` y `Thumbs.db` al `.gitignore`.
--- ---
## Overview (legacy) ## Overview
**Projecte 2026** is a retro-style 2D puzzle platformer game built in C++20 using SDL3 for graphics and audio. The game features 60+ rooms, collectible items, enemies, and an achievement system. It targets multiple platforms (Windows, macOS, Linux) with a focus on retro aesthetics and precise collision detection. **Projecte 2026** is a retro-style 2D puzzle platformer game built in C++20 using SDL3 for graphics and audio. The game features 60+ rooms, collectible items, enemies, and an achievement system. It targets multiple platforms (Windows, macOS, Linux) with a focus on retro aesthetics and precise collision detection.
@@ -190,11 +115,13 @@ xvfb-run -a -s "-screen 0 1280x720x24" ./projecte_2026
- Debugging startup issues - Debugging startup issues
- Verifying configuration changes - Verifying configuration changes
### Static Analysis Tools (Linters) ---
## Static Analysis Tools (Linters)
This project uses two complementary static analysis tools for code quality: This project uses two complementary static analysis tools for code quality:
#### clang-tidy (Modernization & Best Practices) ### clang-tidy (Modernization & Best Practices)
**Purpose:** Detects style issues, modernization opportunities, and potential bugs. Best for refactoring and applying C++20 best practices. **Purpose:** Detects style issues, modernization opportunities, and potential bugs. Best for refactoring and applying C++20 best practices.
@@ -214,18 +141,11 @@ tools/linter/run_clang-tidy.sh --fix source/game/entities/player.cpp
tools/linter/run_clang-tidy.sh tools/linter/run_clang-tidy.sh
``` ```
**Common clang-tidy Checks:**
- `modernize-*`: C++ modernization (use auto, range-for, etc.)
- `readability-*`: Code readability improvements
- `performance-*`: Performance optimizations
- `bugprone-*`: Potential bug detection
**Known False Positives:** **Known False Positives:**
- `defaults.hpp`: May report errors in constant definitions - `defaults.hpp`: May report errors in constant definitions
- `readability-magic-numbers`: Acceptable for game constants (block sizes, speeds, etc.) - `readability-magic-numbers` / `cppcoreguidelines-avoid-magic-numbers`: Acceptable for game constants (block sizes, speeds, etc.)
- `cppcoreguidelines-avoid-magic-numbers`: Same as above, acceptable in game code
#### cppcheck (Bug Detection & Memory Safety) ### cppcheck (Bug Detection & Memory Safety)
**Purpose:** Detects bugs, memory leaks, undefined behavior, and style issues. Complementary to clang-tidy. **Purpose:** Detects bugs, memory leaks, undefined behavior, and style issues. Complementary to clang-tidy.
@@ -245,19 +165,12 @@ tools/linter/run_cppcheck.sh -w
**Output:** Results are saved in `tools/linter/cppcheck-result-*.txt` **Output:** Results are saved in `tools/linter/cppcheck-result-*.txt`
**Common cppcheck Checks:**
- Memory leaks and resource management
- Null pointer dereferences
- Buffer overflows and array bounds
- Uninitialized variables
- Dead code and unused functions
**Known False Positives:** **Known False Positives:**
- `unusedFunction`: May report false positives for callback functions or public API methods - `unusedFunction`: May report false positives for callback functions or public API methods
- `passedByValue`: Acceptable for small types like SDL_FRect in game code - `passedByValue`: Acceptable for small types like SDL_FRect in game code
- `constParameter`: Not always applicable for API consistency - `constParameter`: Not always applicable for API consistency
#### When to Use Each Tool ### When to Use Each Tool
| Scenario | Tool | Reason | | Scenario | Tool | Reason |
|----------|------|--------| |----------|------|--------|
@@ -267,16 +180,7 @@ tools/linter/run_cppcheck.sh -w
| **Hunting memory leaks** | cppcheck | Specialized detection | | **Hunting memory leaks** | cppcheck | Specialized detection |
| **Code modernization** | clang-tidy --fix | Automatic C++20 upgrades | | **Code modernization** | clang-tidy --fix | Automatic C++20 upgrades |
#### Best Practices ### Integration with Development Workflow
1. **Run linters after compilation succeeds** - Fix compile errors first
2. **Analyze specific files** - Faster feedback, avoids unrelated errors
3. **Review before applying --fix** - Understand changes before accepting
4. **Context matters** - Game code may legitimately have "magic numbers" for gameplay constants
5. **Use both tools** - They catch different issues and complement each other
6. **Check suppressions file** - `tools/linter/cppcheck_suppressions` excludes external/ and system headers
#### Integration with Development Workflow
When refactoring code (especially with `/refactor-class` command): When refactoring code (especially with `/refactor-class` command):
1. Make structural changes 1. Make structural changes
@@ -291,813 +195,7 @@ When refactoring code (especially with `/refactor-class` command):
--- ---
## 1. High-Level Architecture Overview ## Important Code Consistency Rules
The architecture follows a **layered, modular design** with clear separation of concerns:
```
┌─────────────────────────────────────┐
│ Application Layer │
│ (Director, Scene Manager) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Game Logic Layer │
│ (Game Scene, Entities, Gameplay) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Core Systems Layer │
│ (Rendering, Audio, Input, Resources)│
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ SDL3 & External Libraries │
│ (jail_audio, stb_image, stb_vorbis) │
└─────────────────────────────────────┘
```
### Key Design Principles
- **Singleton Pattern:** Core systems (Director, Screen, Audio, Input, Resource, Asset) use singleton pattern for global access
- **Scene-Based Architecture:** Game flow managed through discrete scenes (Logo, Title, Loading, Game, Ending, etc.)
- **Component-Based Entities:** Player, enemies, and items are discrete entities with render/update patterns
- **Resource Management:** All assets (textures, sounds, animations, tilemaps) cached through Resource singleton
- **Delta Time:** Frame-rate independent physics using DeltaTimer class
---
## 2. Directory Structure
```
source/
├── core/ # Core engine systems
│ ├── audio/ # Audio management
│ │ ├── audio.hpp/cpp # Audio singleton (music, sounds, volumes)
│ │ └── jail_audio.hpp # Custom jail_audio library wrapper
│ ├── input/ # Input handling
│ │ ├── input.hpp/cpp # Input manager (keyboard, gamepad)
│ │ ├── input_types.hpp # Input type definitions
│ │ ├── global_inputs.hpp # Global input state
│ │ └── mouse.hpp # Mouse input
│ ├── locale/ # Localization
│ │ └── locale.hpp # Locale/language support
│ ├── rendering/ # Graphics rendering
│ │ ├── screen.hpp/cpp # Screen/window singleton, SDL renderer
│ │ ├── surface.hpp/cpp # 8-bit indexed color surface abstraction
│ │ ├── sprite/ # Sprite rendering classes
│ │ │ ├── sprite.hpp # Static sprite rendering
│ │ │ ├── animated_sprite.hpp # Animated sprite with frame data
│ │ │ ├── moving_sprite.hpp # Moving sprite with velocity
│ │ │ └── dissolve_sprite.hpp # Dissolve transition sprite
│ │ ├── text.hpp/cpp # Text rendering system
│ │ ├── gif.hpp/cpp # GIF image loader
│ │ ├── pixel_reveal.hpp # Pixel reveal effect
│ │ ├── render_info.hpp # Render information data
│ │ ├── palette_manager.hpp # Palette management
│ │ ├── sdl3gpu/ # SDL3 GPU shader backend
│ │ │ ├── sdl3gpu_shader.hpp/cpp # CRT/post-processing shader effects
│ │ │ └── *_spv.h # Pre-compiled SPIR-V shader headers
│ │ └── shader_backend.hpp # Abstract shader interface
│ ├── resources/ # Asset & Resource management
│ │ ├── resource_list.hpp # Asset path registry (O(1) lookups)
│ │ ├── resource_cache.hpp # Resource caching singleton
│ │ ├── resource_loader.hpp # Asset loading logic
│ │ ├── resource_pack.hpp # Resource pack handling
│ │ ├── resource_helper.hpp # Resource utility functions
│ │ └── resource_types.hpp # Resource type definitions
│ └── system/ # System management
│ ├── director.hpp/cpp # Main application controller
│ ├── debug.hpp/cpp # Debug info overlay
│ └── global_events.hpp/cpp # Global event broadcasting
├── game/ # Game-specific logic
│ ├── entities/ # Game objects
│ │ ├── player.hpp/cpp # Player entity with physics
│ │ ├── enemy.hpp/cpp # Enemy entities
│ │ └── item.hpp/cpp # Collectible items
│ ├── gameplay/ # Core gameplay systems
│ │ ├── room.hpp/cpp # Room/level logic, collision
│ │ ├── room_loader.hpp/cpp # Room loading from YAML files
│ │ ├── room_tracker.hpp/cpp # Tracks visited rooms
│ │ ├── collision_map.hpp/cpp # Collision map data
│ │ ├── tilemap_renderer.hpp/cpp # Tilemap rendering
│ │ ├── enemy_manager.hpp/cpp # Enemy lifecycle management
│ │ ├── item_manager.hpp/cpp # Item lifecycle management
│ │ ├── scoreboard.hpp/cpp # Score display & data
│ │ ├── item_tracker.hpp/cpp # Tracks collected items
│ │ └── cheevos.hpp/cpp # Achievement system
│ ├── scenes/ # Game scenes (flow states)
│ │ ├── logo.hpp/cpp # JailGames logo screen
│ │ ├── loading_screen.hpp/cpp # Resource loading progress
│ │ ├── title.hpp/cpp # Title screen & menus
│ │ ├── game.hpp/cpp # Main gameplay loop
│ │ ├── game_over.hpp/cpp # Game over screen
│ │ ├── ending.hpp/cpp # Ending sequence 1
│ │ ├── ending2.hpp/cpp # Ending sequence 2
│ │ └── credits.hpp/cpp # Credits screen
│ ├── ui/ # User interface
│ │ ├── notifier.hpp/cpp # Achievement/notification display
│ │ ├── console.hpp/cpp # In-game debug console
│ │ └── console_commands.hpp/cpp # Console command definitions
│ ├── options.hpp/cpp # Game configuration/options
│ ├── game_control.hpp # Game control logic
│ ├── scene_manager.hpp # Scene flow state machine
│ └── defaults.hpp # Game defaults constants
├── external/ # Third-party libraries
│ ├── fkyaml_node.hpp # YAML parsing library
│ ├── stb_image.h # Image loading library
│ └── stb_vorbis.h # OGG Vorbis audio decoding
├── utils/ # Utility code
│ ├── delta_timer.hpp/cpp # Frame-rate independent timing
│ ├── easing_functions.hpp # Easing/interpolation functions
│ ├── defines.hpp # Game constants (resolutions, block sizes)
│ └── utils.hpp/cpp # Helper functions (colors, math)
└── main.cpp # Application entry point
config/ # Configuration files
└── assets.yaml # Asset registry (text-based configuration)
data/ # Game assets
├── font/ # Bitmap fonts + descriptors
├── palette/ # Color palettes (.pal files)
├── shaders/ # GLSL vertex/fragment shaders
├── room/ # Tilemaps & room definitions (01.tmx-60.tmx)
├── tilesets/ # Tileset graphics
├── enemies/ # Enemy sprites & animations
├── player/ # Player sprites & animations
├── items/ # Item sprite sheets
├── music/ # Background music (OGG)
├── sound/ # Sound effects (WAV)
├── logo/ # Logo images
├── loading/ # Loading screen graphics
├── title/ # Title screen graphics
├── ending/ # Ending sequence images
└── credits/ # Credits screen assets
```
---
## 3. Key Architectural Patterns
### 3.1 Singleton Pattern (Core Systems)
Most core systems use thread-safe singleton pattern:
```cpp
class Screen {
private:
static Screen* screen_; // Singleton instance
Screen(); // Private constructor
~Screen();
public:
static void init(); // Creates singleton
static void destroy(); // Destroys singleton
static Screen* get(); // Accesses singleton
};
```
**Singleton Systems:**
- `Screen` - Rendering, window management, palette/shader effects
- `Input` - Keyboard & gamepad input binding and checking
- `Audio` - Music and sound effect playback
- `Resource::Cache` - Asset loading, caching, streaming (with error handling)
- `Resource::List` - Asset path registry from assets.yaml (O(1) lookups)
- `Director` - Main application controller
- `Cheevos` - Achievement state management
- `Debug` - Debug information overlay
### 3.2 Scene-Based State Machine
The game uses a scene manager to control application flow:
```cpp
// namespace SceneManager
enum class Scene {
LOGO, LOADING_SCREEN, TITLE, CREDITS, GAME, DEMO,
GAME_OVER, ENDING, ENDING2, RESTART_CURRENT, QUIT
};
inline Scene current = Scene::LOGO; // Global scene state
inline Options options = ...; // Transition options
```
**Scene Hierarchy:**
- Each scene runs its own update/render loop
- `Director` switches between scenes based on `SceneManager::current`
- Scenes can request transitions via `SceneManager::current` assignment
### 3.3 Entity-Component Pattern (Simplified)
Entities (Player, Enemies, Items) have:
- **Update** - Logic, physics, animation
- **Render** - Draw to screen surface
- **Collision** - Hit detection (player collides with room, enemies, items)
### 3.4 Surface-Based Rendering Pipeline
The rendering system uses a **8-bit indexed color** pipeline:
```
SurfaceData (pixel buffer)
Surface (palette + rendering operations)
SDL_Texture (GPU texture)
SDL_Renderer (to SDL_Window)
Display
```
**Key Components:**
- `Surface` - 8-bit indexed pixel buffer with palette support
- `Sprite` - Renders a fixed region of a surface
- `AnimatedSprite` - Frame-based animation on top of sprite
- `MovingSprite` - Adds velocity/position to animated sprite
- `DissolveSprite` - Dissolve transition effect sprite
- Supports color replacement, palette swapping, and shader effects (CRT)
### 3.5 Tile-Based Collision System
Rooms use a sophisticated collision detection system:
```cpp
class Room {
std::vector<LineHorizontal> bottom_floors_; // Ground surfaces
std::vector<LineHorizontal> top_floors_; // Ceiling surfaces
std::vector<LineVertical> left_walls_; // Left walls
std::vector<LineVertical> right_walls_; // Right walls
std::vector<LineDiagonal> left_slopes_; // Ramps going left
std::vector<LineDiagonal> right_slopes_; // Ramps going right
std::vector<LineHorizontal> conveyor_belt_floors_; // Moving platforms
};
```
**Collision Detection:**
- Player has collision points and "feet" points for fine-grained detection
- Room provides `check*Surfaces()` methods for collision resolution
- Supports ramps, slopes, conveyor belts, and auto-surfaces
- Enemies have axis-aligned bounding boxes
### 3.6 Sprite Animation System
Animation is data-driven:
```cpp
struct AnimationData {
std::string name;
std::vector<SDL_FRect> frames; // Frame rectangles
int speed; // Milliseconds per frame
int loop; // Frame to return to (-1 = no loop)
bool completed;
int current_frame;
int counter;
};
// Loaded from .yaml animation definition files
// Rendered with AnimatedSprite
```
---
## 4. System Interactions & Data Flow
### 4.1 Application Lifecycle
```
main()
Director::Director() [Initialization]
├─ Resource::List::init() - Initialize asset registry singleton
├─ Director::setFileList() - Load assets.yaml via Resource::List (no verification)
├─ Options::loadFromFile() - Load game configuration
├─ Audio::init() - Initialize SDL audio
├─ Screen::init() - Create window, SDL renderer
├─ Input::init() - Bind keyboard/gamepad controls
├─ Resource::Cache::init() - Load ALL game resources (with verification)
│ └─ Throws exception if any required asset is missing
└─ Cheevos::init() - Load achievement state
Director::run() [Main loop]
├─ while (SceneManager::current != QUIT)
│ ├─ Logo::run()
│ ├─ LoadingScreen::run()
│ ├─ Title::run()
│ ├─ Game::run()
│ ├─ Ending::run()
│ └─ ...
└─ return 0
Director::~Director() [Cleanup]
├─ Options::saveToFile() - Save game settings
├─ Resource::Cache::destroy()
├─ Audio::destroy()
├─ Input::destroy()
├─ Screen::destroy()
└─ Resource::List::destroy()
```
### 4.2 Game Scene Flow (Core Gameplay Loop)
```cpp
Game::run() {
while (game is running) {
// Update
Input::checkInput() - Get player commands
Player::update() - Physics, animation, collision
Room::update() - Enemy AI, animated tiles
checkCollisions() - Player vs enemies, items, room
checkGameOver() - Win/lose conditions
// Render
Screen::start() - Prepare for drawing
Room::renderMap() - Draw tilemap
Room::renderEnemies() - Draw enemy sprites
Room::renderItems() - Draw item sprites
Player::render() - Draw player sprite
renderScoreboard() - Draw HUD
Screen::render() - Flush to GPU
}
}
```
### 4.3 Resource Loading & Caching
```
Director::setFileList()
└─ Resource::List::loadFromFile(config_path, PREFIX, system_folder_)
├─ Read config/assets.yaml - Parse text configuration file
├─ Parse YAML structure: assets grouped by category
├─ Replace variables (${PREFIX}, ${SYSTEM_FOLDER})
└─ Store in unordered_map (O(1) lookup) - Fast asset path retrieval
Game Scene initialization
└─ Resource::Cache::init() - Loads all resources
├─ loadSounds() - WAV files (with error handling)
├─ loadMusics() - OGG files (with error handling)
├─ loadSurfaces() - GIF/PNG images (with error handling)
├─ loadPalettes() - PAL palette files (with error handling)
├─ loadTextFiles() - Font definition files (with error handling)
├─ loadAnimations() - YAML animation definitions (with error handling)
└─ loadRooms() - Room YAML files (with error handling)
Note: Asset verification happens during actual loading.
If a required file is missing, Cache::load() throws detailed exception.
During gameplay
└─ Resource::Cache::get*(name) - Return cached resource
```
### 4.4 Input Flow
```
SDL_Event (from OS)
Input::checkInput(action)
├─ Check keyboard bindings - SDL_Scancode → InputAction
├─ Check gamepad bindings - SDL_GamepadButton → InputAction
└─ Return true if action active
Game logic uses checked inputs
└─ Player responds to actions
```
### 4.5 Audio Control Flow
```
Game code
├─ Audio::playMusic(name, loop)
├─ Audio::playSound(name, group)
└─ Audio::stopMusic()
Audio [wrapper/manager]
├─ Resource::getMusic(name)
├─ Resource::getSound(name)
└─ jail_audio C library
└─ SDL3 Audio device
```
---
## 5. Important Design Patterns & Conventions
### 5.1 Naming Conventions
**Classes:**
- `PascalCase` for classes: `Player`, `Room`, `Screen`, `Director`
- Suffix `Sprite` for sprite classes: `Sprite`, `AnimatedSprite`, `MovingSprite`
**Methods:**
- `get*()` for getters: `getWidth()`, `getRect()`
- `set*()` for setters: `setColor()`, `setPos()`
- `check*()` for boolean checks: `checkInput()`, `checkCollision()`
- `update()` for game logic updates
- `render()` for drawing
**Variables:**
- `snake_case` for member variables with `_` suffix: `x_`, `y_`, `sprite_`
- `UPPER_CASE` for constants: `BLOCK`, `MAX_VY`, `WIDTH`
- Private members: `private_member_`
**Structs for Data:**
- `XxxData` suffix: `PlayerData`, `RoomData`, `AnimationData`
- Used as initialization structures passed to constructors
### 5.2 Memory Management
- **Smart Pointers:** Uses `std::shared_ptr` for shared ownership (Surfaces, Sprites, Rooms)
- **Unique Pointers:** Uses `std::unique_ptr` for sole ownership (Director, local objects)
- **Raw Pointers:** Minimal use, mainly for singleton access or SDL objects
- **Static Allocation:** Singletons use static pointer pattern for RAII
### 5.3 Frame-Independent Physics
Uses `DeltaTimer` for delta time calculation:
```cpp
// In game loop
float delta = deltaTimer.tick(); // Get seconds since last frame
// Apply to physics
player.vy_ += gravity * delta;
player.y_ += player.vy_ * delta;
```
Provides **time scaling** for slow-motion effects.
### 5.4 Palette System
- 8-bit indexed color (256 colors per palette)
- Multiple palettes can be loaded and swapped via `PaletteManager`
- `Screen::setPaletteByName()` changes the active palette
- Supports palette sort modes (by luminosity, similarity to Spectrum palette, etc.)
- Supports color replacement per-render: `renderWithColorReplace()`
- CRT shader effects can modify colors in real-time
### 5.5 Configuration System
Game settings stored in configuration file:
```cpp
namespace Options {
inline Video video{}; // Screen resolution, fullscreen, etc.
inline Audio audio{}; // Music/sound volumes
inline Notification notifications{};
inline Cheat cheats{}; // Cheat codes
inline ControlScheme keys{}; // Control mapping
}
Options::loadFromFile(path); // Load from config.yaml
Options::saveToFile(path); // Save on exit
```
### 5.6 Achievement System
```cpp
struct Achievement {
int id;
std::string caption;
std::string description;
int icon;
bool completed;
bool obtainable;
};
Cheevos::unlock(id); // Unlock achievement
Cheevos::setUnobtainable(id); // Lock achievement
Cheevos::saveToFile(); // Persist state
```
Achievements trigger notifications on unlock.
---
## 6. Technology Stack
### Core Technologies
| Component | Technology | Version | Role |
|-----------|-----------|---------|------|
| **Graphics** | SDL3 | Latest | Window, rendering, input |
| **GPU Rendering** | SDL3 GPU | Latest | Shader effects (CRT, post-processing via SPIR-V) |
| **Audio** | SDL3 Audio | Latest | Audio device, mixing |
| **Audio Decoding** | jail_audio (custom) | 1.x | OGG/WAV playback |
| **Image Loading** | stb_image | v2.x | PNG/GIF image loading |
| **Audio Decoding** | stb_vorbis | v1.x | OGG Vorbis support |
| **Language** | C++ | C++20 | Standard library features |
| **Build System** | CMake | 3.10+ | Cross-platform building |
### C++ Features Used
- **Smart Pointers:** `std::shared_ptr`, `std::unique_ptr`
- **Standard Containers:** `std::vector`, `std::array`
- **Modern Features:** `std::move`, lambda functions, constexpr
- **Namespaces:** Extensive use for organization
- **Inline Variables:** C++17 `inline` for global game state
### Platform Support
- **Windows:** MinGW/MSVC with SDL3
- **macOS:** Apple Clang, arm64 architecture, OpenGL
- **Linux:** GCC with SDL3 and OpenGL
---
## 7. Key Classes & Their Responsibilities
### Core System Classes
| Class | Purpose | Pattern |
|-------|---------|---------|
| `Director` | Main application controller, scene manager | Singleton |
| `Screen` | Rendering, window, palette management | Singleton |
| `Input` | Keyboard & gamepad input | Singleton |
| `Audio` | Music and SFX playback | Singleton |
| `Resource::Cache` | Asset caching and loading (with detailed error messages) | Singleton |
| `Resource::List` | Asset path registry from config/assets.yaml, O(1) lookups, variable substitution | Singleton |
| `PaletteManager` | Palette loading, switching, and sorting | Managed by Screen |
| `Debug` | Debug overlay information | Singleton |
| `globalEvents` | Global SDL event handling (quit, device reset, mouse) | Namespace |
### Game Logic Classes
| Class | Purpose |
|-------|---------|
| `Game` | Main gameplay scene, orchestrates update/render |
| `Player` | Player entity with physics and animation |
| `Room` | Level data, collision detection |
| `RoomLoader` | Room loading from YAML files |
| `TilemapRenderer` | Tilemap rendering |
| `CollisionMap` | Collision map data |
| `EnemyManager` | Enemy lifecycle management |
| `ItemManager` | Item lifecycle management |
| `Enemy` | Enemy entity behavior and rendering |
| `Item` | Collectible items |
| `Scoreboard` | HUD display data |
| `Cheevos` | Achievement unlock and state |
| `ItemTracker` | Tracks collected items |
| `RoomTracker` | Tracks visited rooms |
### Rendering Classes
| Class | Purpose |
|-------|---------|
| `Surface` | 8-bit indexed color pixel buffer with palette |
| `Sprite` | Renders a sprite region |
| `AnimatedSprite` | Frame-based animation rendering |
| `MovingSprite` | Sprite with velocity/position |
| `DissolveSprite` | Dissolve transition sprite |
| `Text` | Text rendering system |
| `Sdl3gpuShader` | SDL3 GPU shader compilation and effects |
| `PaletteManager` | Palette loading and management |
### Utility Classes
| Class/Header | Purpose |
|-------|---------|
| `DeltaTimer` | Frame-rate independent timing |
| `easing_functions.hpp` | Easing/interpolation functions for animations |
---
## 8. Common Development Workflows
### Adding a New Input Action
1. Add to `InputAction` enum in `core/input/input.hpp`
2. Bind in `Director::initInput()`:
```cpp
Input::get()->bindKey(InputAction::NEW_ACTION, SDL_SCANCODE_X);
```
3. Check in game code:
```cpp
if (Input::get()->checkInput(InputAction::NEW_ACTION)) {
// Handle action
}
```
### Adding a New Scene
1. Create class inheriting appropriate base (usually standalone `run()` method)
2. Add to `SceneManager::Scene` enum
3. Implement in `Director::run()` switch statement
4. Set `SceneManager::current` to transition
### Adding Game Assets
1. Place file in `data/` directory
2. Add entry to `config/assets.yaml` under the appropriate category:
```yaml
assets:
category_name: # e.g., player, enemies, music, etc.
- type: BITMAP
path: ${PREFIX}/data/path/file.ext
```
Available types: `DATA`, `BITMAP`, `ANIMATION`, `MUSIC`, `SOUND`, `FONT`, `ROOM`, `PALETTE`
3. Optional flags can be added:
```yaml
- type: DATA
path: ${SYSTEM_FOLDER}/file.txt
required: false # Don't fail if missing
absolute: true # Path is absolute
```
4. Resource loads automatically during `Resource::init()`
5. Access via: `Resource::Cache::get()->getSurface("name")`
**Note:** No recompilation needed when adding/removing/modifying assets in `config/assets.yaml`
### Modifying Collision Detection
1. Update `Room::setBottomSurfaces()`, `setLeftSlopes()`, etc.
2. Modify Player collision point generation in `Player::updateColliderPoints()`
3. Adjust tile classification in `TileType` enum
4. Test with debug visualization (F12 key)
---
## 9. Debug Features
### Available at Runtime
- **F12** - Toggle debug info overlay (FPS, player position, collision points)
- **F1/F2** - Decrease/increase window zoom
- **F3** - Toggle fullscreen mode
- **F4** - Toggle shader effects
- **F5/F6** - Next/previous palette
- **F7** - Toggle integer scaling
- **M** - Toggle music
- **B** - Toggle border display
- **P** - Pause game
### Debug Mode Compilation
In debug builds (`#ifdef DEBUG`), renders:
- Player collision boxes
- Collision point visualization
- Debug information overlay
- Special debug event handling
---
## 10. Performance Considerations
### Optimization Points
1. **Rendering:** Uses indexed color (8-bit) to reduce memory bandwidth
2. **Surfaces:** Shared smart pointers reduce copying
3. **Collision:** Pre-computed tile surface lists avoid per-frame searches
4. **Animation:** Frame-based animation reduces computation vs. bone systems
5. **Audio:** Cached music and sound effects, no runtime decoding
6. **Delta Time:** Frame-rate independent logic for smooth gameplay
### Known Limitations
- Single-threaded architecture (SDL3 requires single-thread rendering)
- Surfaces stored entirely in CPU memory (not GPU-side textures)
- Palette system requires full surface redraw when changing colors
---
## 11. File Format Reference
### Asset Configuration File (config/assets.yaml)
YAML-based asset registry with grouped structure:
```yaml
# Projecte 2026 - Asset Configuration
# Variables: ${PREFIX}, ${SYSTEM_FOLDER}
assets:
# FONTS
fonts:
- type: BITMAP
path: ${PREFIX}/data/font/smb2.gif
- type: FONT
path: ${PREFIX}/data/font/smb2.fnt
# PLAYER
player:
- type: BITMAP
path: ${PREFIX}/data/player/player.gif
- type: ANIMATION
path: ${PREFIX}/data/player/player.yaml
# MUSIC
music:
- type: MUSIC
path: ${PREFIX}/data/music/title.ogg
# SYSTEM FILES (optional, absolute paths)
system:
- type: DATA
path: ${SYSTEM_FOLDER}/config.yaml
required: false
absolute: true
```
**Asset Structure:**
- Assets are organized into categories (fonts, palettes, shaders, player, enemies, etc.)
- Each asset entry contains:
- `type` - Asset type (required)
- `path` - File path with variable substitution (required)
- `required` - Whether file must exist (optional, default: `true`)
- `absolute` - Whether path is absolute (optional, default: `false`)
**Available Asset Types:**
- `DATA` - General data files (text, JSON, etc.)
- `BITMAP` - Images (GIF, PNG)
- `ANIMATION` - Animation definition files (.yaml)
- `MUSIC` - Music files (OGG)
- `SOUND` - Sound effects (WAV)
- `FONT` - Font definition files
- `ROOM` - Room data files (.yaml)
- `PALETTE` - Color palette files (.pal)
**Variables:**
- `${PREFIX}` - Replaced with `/../Resources` on macOS bundles, empty otherwise
- `${SYSTEM_FOLDER}` - Replaced with user's system config folder
### Animation Files (.yaml)
YAML-based animation definitions with frame data:
```yaml
animations:
- name: default
frameWidth: 16
frameHeight: 16
speed: 100
loop: 0
frames: [...]
```
### Room Data Files (.yaml)
YAML-based room definitions:
```yaml
room:
name_en: Starting Room
bgColor: 0x000000
connections: [...]
tilemap: [...]
```
### Tilemap Files (.tmx)
Tiled map format with tileset and collision data.
### Palette Files (.pal)
Binary 256-color palette format (256 × 4 bytes RGBA).
---
## 12. Quick Reference: Main Entry Points
### For Graphics Issues
- `Screen::render()` - Main rendering method
- `Screen::setPaletteByName()` - Palette switching
- `PaletteManager` - Palette loading and sorting
- `Surface` class - Pixel buffer operations
### For Input Issues
- `Input::checkInput()` - Input state checking
- `Director::initInput()` - Binding configuration
### For Audio Issues
- `Audio::playMusic()` - Music playback
- `Audio::playSound()` - SFX playback
- `jail_audio` library - Low-level audio operations
### For Game Logic
- `Game::run()` - Main game loop
- `Room` class - Collision and level logic
- `Player::update()` - Physics and movement
### For Asset Management
- `config/assets.yaml` - Asset configuration file (text-based, no recompilation needed)
- `Resource::List::loadFromFile()` - Loads asset registry from config file
- `Resource::List::get()` - Retrieves asset path (O(1) lookup with unordered_map)
- `Resource::Cache` - Asset loading and caching
- `Director::setFileList()` - Calls `Resource::List::loadFromFile()` with PREFIX and system_folder
---
## 13. Future Enhancement Points
### Identified Areas for Expansion
1. **More Scenes:** Additional game modes
3. **Custom Rooms:** Level editor integration
4. **Audio Streaming:** Background music without loading entire file
5. **Particle System:** Visual effects for pickups/collisions
6. **Controller Feedback:** Haptic feedback for game events
7. **Accessibility:** Font scaling, color-blind modes, key remapping UI
---
## Development Notes
- **Language:** All code comments and some variable names are in Spanish (maintaining original author style)
- **Compilation:** Use CMake - automatically handles platform differences
- **Performance Profiling:** Use debug overlay (F12) for basic metrics; consider external profilers for deeper analysis
- **Common Warnings:** The codebase has been cleaned of the `struct`/`class` forward declaration inconsistency warnings that previously appeared
### Important Code Consistency Rules
1. **Forward Declarations:** Always use `class` for forward declarations of classes defined with `class` (not `struct`). The `Surface` class is defined as `class Surface` in `surface.hpp:57`, so all forward declarations must use `class Surface;` 1. **Forward Declarations:** Always use `class` for forward declarations of classes defined with `class` (not `struct`). The `Surface` class is defined as `class Surface` in `surface.hpp:57`, so all forward declarations must use `class Surface;`

View File

@@ -7,7 +7,6 @@ title:
play: "1. JUGAR" play: "1. JUGAR"
keyboard: "2. REDEFINIR TECLES" keyboard: "2. REDEFINIR TECLES"
joystick: "3. REDEFINIR MANDO" joystick: "3. REDEFINIR MANDO"
projects: "4. PROJECTES"
keys: keys:
prompt0: "PREM UNA TECLA PER A ESQUERRA" prompt0: "PREM UNA TECLA PER A ESQUERRA"
prompt1: "PREM UNA TECLA PER A DRETA" prompt1: "PREM UNA TECLA PER A DRETA"
@@ -24,7 +23,6 @@ title:
prompt2: "PREM UN BOTÓ PER A SALTAR" prompt2: "PREM UN BOTÓ PER A SALTAR"
defined: "BOTONS DEFINITS" defined: "BOTONS DEFINITS"
already_used: "BOTÓ JA USAT! PROVA'N UN ALTRE" already_used: "BOTÓ JA USAT! PROVA'N UN ALTRE"
projects: "PROJECTES"
game_over: game_over:
title: "G A M E O V E R" title: "G A M E O V E R"
@@ -74,30 +72,6 @@ credits:
achievements: achievements:
header: "ASSOLIMENT DESBLOQUEJAT!" header: "ASSOLIMENT DESBLOQUEJAT!"
c1: "COSES BRILLANTS"
d1: "Aconseguiu el 25% dels objectes"
c2: "A MITJAN CAMÍ"
d2: "Aconseguiu el 50% dels objectes"
c3: "QUASI HI SOM"
d3: "Aconseguiu el 75% dels objectes"
c4: "EL COL·LECCIONISTA"
d4: "Aconseguiu el 100% dels objectes"
c5: "PASSEJANT PER ACÍ"
d5: "Visiteu 20 sales"
c6: "M'HE PERDUT"
d6: "Visiteu 40 sales"
c7: "M'AGRADA EXPLORAR"
d7: "Visiteu totes les sales"
c8: "JA ESTÀ?"
d8: "Completeu el joc"
c9: "UN FORAT EM VA ENGOLIR"
d9: "Completeu el joc sense entrar a la presó"
c10: "ELS MEUS PROJECTES"
d10: "Completeu el joc amb tots els objectes"
c11: "M'AGRADEN ELS MEUS AMICS DE COLORS"
d11: "Completeu el joc sense morir"
c12: "PROJECTES A CORRE-CUITA"
d12: "Completeu el joc en menys de 30 minuts"
ui: ui:
press_again_menu: "PREM DE NOU PER TORNAR AL MENÚ" press_again_menu: "PREM DE NOU PER TORNAR AL MENÚ"

View File

@@ -7,7 +7,6 @@ title:
play: "1. PLAY" play: "1. PLAY"
keyboard: "2. REDEFINE KEYBOARD" keyboard: "2. REDEFINE KEYBOARD"
joystick: "3. REDEFINE JOYSTICK" joystick: "3. REDEFINE JOYSTICK"
projects: "4. PROJECTS"
keys: keys:
prompt0: "PRESS KEY FOR LEFT" prompt0: "PRESS KEY FOR LEFT"
prompt1: "PRESS KEY FOR RIGHT" prompt1: "PRESS KEY FOR RIGHT"
@@ -24,7 +23,6 @@ title:
prompt2: "PRESS BUTTON FOR JUMP" prompt2: "PRESS BUTTON FOR JUMP"
defined: "BUTTONS DEFINED" defined: "BUTTONS DEFINED"
already_used: "BUTTON ALREADY USED! TRY ANOTHER" already_used: "BUTTON ALREADY USED! TRY ANOTHER"
projects: "PROJECTS"
game_over: game_over:
title: "G A M E O V E R" title: "G A M E O V E R"
@@ -74,30 +72,6 @@ credits:
achievements: achievements:
header: "ACHIEVEMENT UNLOCKED!" header: "ACHIEVEMENT UNLOCKED!"
c1: "SHINY THINGS"
d1: "Get 25% of the items"
c2: "HALF THE WORK"
d2: "Get 50% of the items"
c3: "GETTING THERE"
d3: "Get 75% of the items"
c4: "THE COLLECTOR"
d4: "Get 100% of the items"
c5: "WANDERING AROUND"
d5: "Visit 20 rooms"
c6: "I GOT LOST"
d6: "Visit 40 rooms"
c7: "I LIKE TO EXPLORE"
d7: "Visit all rooms"
c8: "FINISH THE GAME"
d8: "Complete the game"
c9: "I WAS SUCKED BY A HOLE"
d9: "Complete the game without entering the jail"
c10: "MY LITTLE PROJECTS"
d10: "Complete the game with all items"
c11: "I LIKE MY MULTICOLOURED FRIENDS"
d11: "Complete the game without dying"
c12: "SHIT PROJECTS DONE FAST"
d12: "Complete the game in under 30 minutes"
ui: ui:
press_again_menu: "PRESS AGAIN TO RETURN TO MENU" press_again_menu: "PRESS AGAIN TO RETURN TO MENU"

View File

@@ -60,7 +60,6 @@ namespace Defaults::Sound {
namespace Defaults::Cheat { namespace Defaults::Cheat {
constexpr bool INFINITE_LIVES = false; // Vidas infinitas desactivadas por defecto constexpr bool INFINITE_LIVES = false; // Vidas infinitas desactivadas por defecto
constexpr bool INVINCIBLE = false; // Invencibilidad desactivada por defecto constexpr bool INVINCIBLE = false; // Invencibilidad desactivada por defecto
constexpr bool JAIL_IS_OPEN = false; // Jail abierta desactivada por defecto
} // namespace Defaults::Cheat } // namespace Defaults::Cheat
namespace Defaults::Stats { namespace Defaults::Stats {
@@ -88,13 +87,8 @@ namespace Defaults::Localization {
constexpr const char* LANGUAGE = "ca"; // Idioma por defecto (en = inglés, ca = catalán) constexpr const char* LANGUAGE = "ca"; // Idioma por defecto (en = inglés, ca = catalán)
} // namespace Defaults::Localization } // namespace Defaults::Localization
namespace Defaults::Game::Items {
constexpr const float PERCENT_TO_OPEN_THE_JAIL = 0.9F; // Porcentaje de items necesarios para abrir la jail
} // namespace Defaults::Game::Items
namespace Defaults::Game::Room { namespace Defaults::Game::Room {
constexpr const char* INITIAL = "03.yaml"; // Habitación de inicio constexpr const char* INITIAL = "03.yaml"; // Habitación de inicio
constexpr const char* END_ROOM = "01"; // Habitación final (jail)
} // namespace Defaults::Game::Room } // namespace Defaults::Game::Room
namespace Defaults::Game::Player { namespace Defaults::Game::Player {

View File

@@ -45,19 +45,6 @@ Cheevos::~Cheevos() {
// Inicializa los logros // Inicializa los logros
void Cheevos::init() { // NOLINT(readability-convert-member-functions-to-static) void Cheevos::init() { // NOLINT(readability-convert-member-functions-to-static)
cheevos_list_.clear(); cheevos_list_.clear();
auto* loc = Locale::get();
cheevos_list_.emplace_back(Achievement{.id = 1, .caption = loc->get("achievements.c1"), .description = loc->get("achievements.d1"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 2, .caption = loc->get("achievements.c2"), .description = loc->get("achievements.d2"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 3, .caption = loc->get("achievements.c3"), .description = loc->get("achievements.d3"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 4, .caption = loc->get("achievements.c4"), .description = loc->get("achievements.d4"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 5, .caption = loc->get("achievements.c5"), .description = loc->get("achievements.d5"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 6, .caption = loc->get("achievements.c6"), .description = loc->get("achievements.d6"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 7, .caption = loc->get("achievements.c7"), .description = loc->get("achievements.d7"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 8, .caption = loc->get("achievements.c8"), .description = loc->get("achievements.d8"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 9, .caption = loc->get("achievements.c9"), .description = loc->get("achievements.d9"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 10, .caption = loc->get("achievements.c10"), .description = loc->get("achievements.d10"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 11, .caption = loc->get("achievements.c11"), .description = loc->get("achievements.d11"), .icon = 2});
cheevos_list_.emplace_back(Achievement{.id = 12, .caption = loc->get("achievements.c12"), .description = loc->get("achievements.d12"), .icon = 2});
} }
// Busca un logro por id y devuelve el indice // Busca un logro por id y devuelve el indice

View File

@@ -31,7 +31,6 @@ void Scoreboard::render() {
// Actualiza las variables del objeto // Actualiza las variables del objeto
void Scoreboard::update(float delta_time) { void Scoreboard::update(float delta_time) {
updateItemsColor(delta_time);
fillTexture(); fillTexture();
if (!is_paused_) { if (!is_paused_) {
@@ -64,17 +63,6 @@ void Scoreboard::setPaused(bool value) {
} }
} }
// Actualiza el color de la cantidad de items recogidos
void Scoreboard::updateItemsColor(float delta_time) {
if (!data_->jail_is_open) { return; }
items_color_timer_ += delta_time;
if (items_color_timer_ >= ITEMS_COLOR_BLINK_DURATION * 2.0F) {
items_color_timer_ = 0.0F;
}
items_color_ = (items_color_timer_ < ITEMS_COLOR_BLINK_DURATION) ? VALUE_COLOR : 6;
}
// Devuelve la cantidad de minutos de juego transcurridos // Devuelve la cantidad de minutos de juego transcurridos
auto Scoreboard::getMinutes() -> int { auto Scoreboard::getMinutes() -> int {
return getTime().minutes; return getTime().minutes;
@@ -114,7 +102,7 @@ void Scoreboard::fillTexture() {
x += text->length(SEP); x += text->length(SEP);
text->writeColored(x, LINE1_Y, ITEMS_LABEL, LABEL_COLOR); text->writeColored(x, LINE1_Y, ITEMS_LABEL, LABEL_COLOR);
x += text->length(ITEMS_LABEL); x += text->length(ITEMS_LABEL);
text->writeColoredMono(x, LINE1_Y, ITEMS_STR, items_color_, MONO_W); text->writeColoredMono(x, LINE1_Y, ITEMS_STR, VALUE_COLOR, MONO_W);
x += text->lengthMono(ITEMS_STR, MONO_W); x += text->lengthMono(ITEMS_STR, MONO_W);
text->writeColored(x, LINE1_Y, SEP, LABEL_COLOR); text->writeColored(x, LINE1_Y, SEP, LABEL_COLOR);
x += text->length(SEP); x += text->length(SEP);

View File

@@ -18,7 +18,6 @@ class Scoreboard {
bool music{true}; // Indica si ha de sonar la música durante el juego bool music{true}; // Indica si ha de sonar la música durante el juego
Uint8 color{0}; // Color para escribir el texto del marcador Uint8 color{0}; // Color para escribir el texto del marcador
Uint32 ini_clock{0}; // Tiempo inicial para calcular el tiempo transcurrido Uint32 ini_clock{0}; // Tiempo inicial para calcular el tiempo transcurrido
bool jail_is_open{false}; // Indica si se puede entrar a la Jail
}; };
// Métodos públicos // Métodos públicos
@@ -39,8 +38,6 @@ class Scoreboard {
}; };
// Constantes de tiempo // Constantes de tiempo
static constexpr float ITEMS_COLOR_BLINK_DURATION = 0.333F;
// Posición de los elementos (2 líneas centradas verticalmente en surface de 24px) // Posición de los elementos (2 líneas centradas verticalmente en surface de 24px)
static constexpr int LINE1_Y = 5; static constexpr int LINE1_Y = 5;
static constexpr int LINE2_Y = 13; static constexpr int LINE2_Y = 13;
@@ -51,7 +48,6 @@ class Scoreboard {
// Métodos privados // Métodos privados
auto getTime() -> ClockData; auto getTime() -> ClockData;
void updateItemsColor(float delta_time);
void fillTexture(); void fillTexture();
// Objetos y punteros // Objetos y punteros
@@ -63,7 +59,5 @@ class Scoreboard {
Uint32 paused_time_{0}; Uint32 paused_time_{0};
Uint32 paused_time_elapsed_{0}; Uint32 paused_time_elapsed_{0};
ClockData clock_{}; ClockData clock_{};
Uint8 items_color_{VALUE_COLOR};
SDL_FRect surface_dest_{}; SDL_FRect surface_dest_{};
float items_color_timer_{0.0F};
}; };

View File

@@ -39,11 +39,10 @@ namespace Options {
State infinite_lives{Defaults::Cheat::INFINITE_LIVES ? State::ENABLED : State::DISABLED}; // Indica si el jugador dispone de vidas infinitas State infinite_lives{Defaults::Cheat::INFINITE_LIVES ? State::ENABLED : State::DISABLED}; // Indica si el jugador dispone de vidas infinitas
State invincible{Defaults::Cheat::INVINCIBLE ? State::ENABLED : State::DISABLED}; // Indica si el jugador puede morir State invincible{Defaults::Cheat::INVINCIBLE ? State::ENABLED : State::DISABLED}; // Indica si el jugador puede morir
State jail_is_open{Defaults::Cheat::JAIL_IS_OPEN ? State::ENABLED : State::DISABLED}; // Indica si la Jail está abierta
// Método para comprobar si alguno de los tres primeros trucos está activo // Método para comprobar si algún truco está activo
[[nodiscard]] auto enabled() const -> bool { [[nodiscard]] auto enabled() const -> bool {
return infinite_lives == State::ENABLED || invincible == State::ENABLED || jail_is_open == State::ENABLED; return infinite_lives == State::ENABLED || invincible == State::ENABLED;
} }
}; };

View File

@@ -19,7 +19,6 @@
#include "core/system/global_events.hpp" // Para check #include "core/system/global_events.hpp" // Para check
#include "game/defaults.hpp" // Para Defaults::Game #include "game/defaults.hpp" // Para Defaults::Game
#include "game/game_control.hpp" // Para GameControl #include "game/game_control.hpp" // Para GameControl
#include "game/gameplay/cheevos.hpp" // Para Cheevos
#include "game/gameplay/item_tracker.hpp" // Para ItemTracker #include "game/gameplay/item_tracker.hpp" // Para ItemTracker
#include "game/gameplay/room.hpp" // Para Room, RoomData #include "game/gameplay/room.hpp" // Para Room, RoomData
#include "game/gameplay/room_tracker.hpp" // Para RoomTracker #include "game/gameplay/room_tracker.hpp" // Para RoomTracker
@@ -39,7 +38,7 @@
// Constructor // Constructor
Game::Game(Mode mode) Game::Game(Mode mode)
: scoreboard_data_(std::make_shared<Scoreboard::Data>(0, 9, 0, true, 0, SDL_GetTicks(), Options::cheats.jail_is_open == Options::Cheat::State::ENABLED)), : scoreboard_data_(std::make_shared<Scoreboard::Data>(0, 9, 0, true, 0, SDL_GetTicks())),
scoreboard_(std::make_shared<Scoreboard>(scoreboard_data_)), scoreboard_(std::make_shared<Scoreboard>(scoreboard_data_)),
room_tracker_(std::make_shared<RoomTracker>()), room_tracker_(std::make_shared<RoomTracker>()),
mode_(mode), mode_(mode),
@@ -69,13 +68,9 @@ Game::Game(Mode mode)
demoInit(); demoInit();
room_ = std::make_shared<Room>(current_room_, scoreboard_data_); room_ = std::make_shared<Room>(current_room_, scoreboard_data_);
initPlayer(spawn_data_, room_); initPlayer(spawn_data_, room_);
total_items_ = getTotalItems();
game_backbuffer_surface_ = std::make_shared<Surface>(Options::game.width, Options::game.height); game_backbuffer_surface_ = std::make_shared<Surface>(Options::game.width, Options::game.height);
changeRoom(current_room_); changeRoom(current_room_);
Cheevos::get()->enable(!Options::cheats.enabled()); // Deshabilita los logros si hay trucos activados
Cheevos::get()->clearUnobtainableState();
#ifdef _DEBUG #ifdef _DEBUG
Console::get()->setScope("debug"); Console::get()->setScope("debug");
@@ -335,8 +330,7 @@ void Game::updatePlaying(float delta_time) {
checkPlayerAndItems(); checkPlayerAndItems();
checkPlayerAndEnemies(); checkPlayerAndEnemies();
checkIfPlayerIsAlive(); checkIfPlayerIsAlive();
checkEndGame();
checkSomeCheevos();
// Avanzar transición // Avanzar transición
if (transitioning_) { if (transitioning_) {
@@ -884,9 +878,6 @@ void Game::killPlayer() {
--scoreboard_data_->lives; --scoreboard_data_->lives;
} }
// Invalida el logro de pasarse el juego sin morir
Cheevos::get()->setUnobtainable(11);
// Sonido // Sonido
Audio::get()->playSound("death.wav", Audio::Group::GAME); Audio::get()->playSound("death.wav", Audio::Group::GAME);
@@ -906,41 +897,6 @@ void Game::setScoreBoardColor() { // NOLINT(readability-convert-member-function
scoreboard_data_->color = IS_BLACK || IS_BRIGHT_BLACK ? 14 : BORDER_COLOR; scoreboard_data_->color = IS_BLACK || IS_BRIGHT_BLACK ? 14 : BORDER_COLOR;
} }
// Comprueba si ha finalizado el juego
auto Game::checkEndGame() -> bool {
const bool IS_ON_THE_ROOM = room_->getNumber() == Defaults::Game::Room::END_ROOM; // Estar en la habitación que toca
const bool HAVE_THE_ITEMS =
scoreboard_data_->items >= int(total_items_ * Defaults::Game::Items::PERCENT_TO_OPEN_THE_JAIL) ||
Options::cheats.jail_is_open == Options::Cheat::State::ENABLED; // Con mas del 90% de los items recogidos
const bool IS_ON_THE_DOOR = player_->getRect().x <= 128; // Y en la ubicación que toca (En la puerta)
scoreboard_data_->jail_is_open = HAVE_THE_ITEMS;
if (HAVE_THE_ITEMS && IS_ON_THE_ROOM && IS_ON_THE_DOOR) {
// Comprueba los logros de completar el juego
checkEndGameCheevos();
// Iniciar transición de fade en vez de cambio inmediato de escena
transitionToState(State::FADE_TO_ENDING);
Audio::get()->fadeOutMusic(1000); // Fade out de música en 1 segundo
return true;
}
return false;
}
// Obtiene la cantidad total de items que hay en el mapeado del juego
auto Game::getTotalItems() -> int {
int items = 0;
auto rooms = Resource::Cache::get()->getRooms();
for (const auto& room : rooms) {
items += room.room->items.size();
}
return items;
}
// Pone el juego en pausa // Pone el juego en pausa
void Game::togglePause() { void Game::togglePause() {
paused_ = !paused_; paused_ = !paused_;
@@ -950,63 +906,6 @@ void Game::togglePause() {
scoreboard_->setPaused(paused_); scoreboard_->setPaused(paused_);
} }
// Comprueba algunos logros
void Game::checkSomeCheevos() { // NOLINT(readability-convert-member-functions-to-static)
auto* cheevos = Cheevos::get();
// Logros sobre la cantidad de items
if (scoreboard_data_->items == total_items_) {
cheevos->unlock(4);
cheevos->unlock(3);
cheevos->unlock(2);
cheevos->unlock(1);
} else if (scoreboard_data_->items >= total_items_ * 0.75F) {
cheevos->unlock(3);
cheevos->unlock(2);
cheevos->unlock(1);
} else if (scoreboard_data_->items >= total_items_ * 0.5F) {
cheevos->unlock(2);
cheevos->unlock(1);
} else if (scoreboard_data_->items >= total_items_ * 0.25F) {
cheevos->unlock(1);
}
// Logros sobre las habitaciones visitadas
if (scoreboard_data_->rooms >= 60) {
cheevos->unlock(7);
cheevos->unlock(6);
cheevos->unlock(5);
} else if (scoreboard_data_->rooms >= 40) {
cheevos->unlock(6);
cheevos->unlock(5);
} else if (scoreboard_data_->rooms >= 20) {
cheevos->unlock(5);
}
}
// Comprueba los logros de completar el juego
void Game::checkEndGameCheevos() { // NOLINT(readability-convert-member-functions-to-static)
auto* cheevos = Cheevos::get();
// "Complete the game"
cheevos->unlock(8);
// "Complete the game without entering the jail"
cheevos->unlock(9);
// "Complete the game with all items"
if (scoreboard_data_->items == total_items_) {
cheevos->unlock(10);
}
// "Complete the game without dying"
cheevos->unlock(11);
// "Complete the game in under 30 minutes"
if (scoreboard_->getMinutes() < 30) {
cheevos->unlock(12);
}
}
// Inicializa al jugador // Inicializa al jugador
void Game::initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room) { // NOLINT(readability-convert-member-functions-to-static) void Game::initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room) { // NOLINT(readability-convert-member-functions-to-static)

View File

@@ -75,11 +75,7 @@ class Game {
void checkIfPlayerIsAlive(); // Comprueba si el jugador esta vivo void checkIfPlayerIsAlive(); // Comprueba si el jugador esta vivo
void killPlayer(); // Mata al jugador void killPlayer(); // Mata al jugador
void setScoreBoardColor(); // Pone el color del marcador en función del color del borde de la habitación void setScoreBoardColor(); // Pone el color del marcador en función del color del borde de la habitación
auto checkEndGame() -> bool; // Comprueba si ha finalizado el juego
static auto getTotalItems() -> int; // Obtiene la cantidad total de items que hay en el mapeado del juego
void togglePause(); // Pone el juego en pausa void togglePause(); // Pone el juego en pausa
void checkSomeCheevos(); // Comprueba algunos logros
void checkEndGameCheevos(); // Comprueba los logros de completar el juego
void initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room); // Inicializa al jugador void initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room); // Inicializa al jugador
void endTransition(); // Finaliza la transición entre pantallas void endTransition(); // Finaliza la transición entre pantallas
void keepMusicPlaying(); // Hace sonar la música void keepMusicPlaying(); // Hace sonar la música
@@ -106,7 +102,6 @@ class Game {
DeltaTimer delta_timer_; // Timer para calcular delta time DeltaTimer delta_timer_; // Timer para calcular delta time
std::string current_room_; // Fichero de la habitación actual std::string current_room_; // Fichero de la habitación actual
Player::SpawnData spawn_data_; // Lugar de la habitación donde aparece el jugador Player::SpawnData spawn_data_; // Lugar de la habitación donde aparece el jugador
int total_items_; // Cantidad total de items que hay en el mapeado del juego
bool paused_{false}; // Indica si el juego se encuentra en pausa bool paused_{false}; // Indica si el juego se encuentra en pausa
float state_time_{0.0F}; // Tiempo acumulado en el estado actual float state_time_{0.0F}; // Tiempo acumulado en el estado actual
float fade_accumulator_{0.0F}; // Acumulador de tiempo para el fade float fade_accumulator_{0.0F}; // Acumulador de tiempo para el fade

View File

@@ -16,7 +16,6 @@
#include "core/resources/resource_list.hpp" // Para Asset #include "core/resources/resource_list.hpp" // Para Asset
#include "core/system/event_buffer.hpp" // Para EventBuffer #include "core/system/event_buffer.hpp" // Para EventBuffer
#include "core/system/global_events.hpp" // Para check #include "core/system/global_events.hpp" // Para check
#include "game/gameplay/cheevos.hpp" // Para Cheevos, Achievement
#include "game/options.hpp" // Para Options, options, SectionState, Section #include "game/options.hpp" // Para Options, options, SectionState, Section
#include "game/scene_manager.hpp" // Para SceneManager #include "game/scene_manager.hpp" // Para SceneManager
#include "game/ui/console.hpp" // Para Console #include "game/ui/console.hpp" // Para Console
@@ -42,7 +41,6 @@ Title::Title()
SceneManager::options = SceneManager::Options::NONE; SceneManager::options = SceneManager::Options::NONE;
// Acciones iniciales // Acciones iniciales
createCheevosTexture(); // Crea y rellena la textura para mostrar los logros
Screen::get()->setBorderColor(0); // Cambia el color del borde Screen::get()->setBorderColor(0); // Cambia el color del borde
Audio::get()->playMusic("574071_EA_DTV.ogg"); // Inicia la musica Audio::get()->playMusic("574071_EA_DTV.ogg"); // Inicia la musica
} }
@@ -111,11 +109,6 @@ void Title::handleMainMenuKeyPress(SDL_Keycode key) {
// Si no hay gamepad, simplemente no hacer nada // Si no hay gamepad, simplemente no hacer nada
break; break;
case SDLK_4:
// PROJECTS
transitionToState(State::CHEEVOS_MENU);
break;
default: default:
break; break;
} }
@@ -139,19 +132,6 @@ void Title::handleInput(float delta_time) {
return; return;
} }
switch (state_) {
case State::CHEEVOS_MENU:
if (Input::get()->checkAction(InputAction::ACCEPT, Input::DO_NOT_ALLOW_REPEAT) ||
Input::get()->checkAction(InputAction::CANCEL, Input::DO_NOT_ALLOW_REPEAT)) {
resetCheevosScroll();
transitionToState(State::MAIN_MENU);
}
break;
default:
break;
}
GlobalInputs::handle(); GlobalInputs::handle();
} }
@@ -175,10 +155,6 @@ void Title::updateState(float delta_time) {
updateMainMenu(delta_time); updateMainMenu(delta_time);
break; break;
case State::CHEEVOS_MENU:
updateCheevosMenu(delta_time);
break;
case State::FADE_MENU: case State::FADE_MENU:
updateFadeMenu(delta_time); updateFadeMenu(delta_time);
break; break;
@@ -231,49 +207,6 @@ void Title::updateMainMenu(float delta_time) {
} }
} }
// Actualiza el estado CHEEVOS_MENU
void Title::updateCheevosMenu(float delta_time) {
// Determina la velocidad objetivo basada en el input
float target_velocity = 0.0F;
if (Input::get()->checkAction(InputAction::RIGHT, Input::ALLOW_REPEAT)) {
target_velocity = CHEEVOS_SCROLL_MAX_SPEED; // Scroll hacia abajo
} else if (Input::get()->checkAction(InputAction::LEFT, Input::ALLOW_REPEAT)) {
target_velocity = -CHEEVOS_SCROLL_MAX_SPEED; // Scroll hacia arriba
}
// Interpola suavemente la velocidad actual hacia la velocidad objetivo
if (target_velocity != 0.0F) {
// Acelerando hacia la velocidad objetivo
const float ACCELERATION_STEP = CHEEVOS_SCROLL_ACCELERATION * delta_time;
if (cheevos_scroll_velocity_ < target_velocity) {
cheevos_scroll_velocity_ = std::min(cheevos_scroll_velocity_ + ACCELERATION_STEP, target_velocity);
} else if (cheevos_scroll_velocity_ > target_velocity) {
cheevos_scroll_velocity_ = std::max(cheevos_scroll_velocity_ - ACCELERATION_STEP, target_velocity);
}
} else {
// Desacelerando hacia 0
const float DECELERATION_STEP = CHEEVOS_SCROLL_DECELERATION * delta_time;
if (cheevos_scroll_velocity_ > 0.0F) {
cheevos_scroll_velocity_ = std::max(cheevos_scroll_velocity_ - DECELERATION_STEP, 0.0F);
} else if (cheevos_scroll_velocity_ < 0.0F) {
cheevos_scroll_velocity_ = std::min(cheevos_scroll_velocity_ + DECELERATION_STEP, 0.0F);
}
}
// Aplica la velocidad actual al scroll position
if (cheevos_scroll_velocity_ != 0.0F) {
cheevos_surface_view_.y += cheevos_scroll_velocity_ * delta_time;
// Ajusta los límites
const float BOTTOM = cheevos_surface_->getHeight() - cheevos_surface_view_.h;
cheevos_surface_view_.y = std::clamp(cheevos_surface_view_.y, 0.0F, BOTTOM);
cheevos_sprite_->setClip(cheevos_surface_view_);
}
// No incrementar state_time_ (no timeout en este estado)
}
// Actualiza el estado FADE_MENU // Actualiza el estado FADE_MENU
void Title::updateFadeMenu(float delta_time) { void Title::updateFadeMenu(float delta_time) {
fade_accumulator_ += delta_time; fade_accumulator_ += delta_time;
@@ -310,68 +243,6 @@ void Title::render() {
Screen::get()->render(); Screen::get()->render();
} }
// Crea y rellena la textura para mostrar los logros
void Title::createCheevosTexture() { // NOLINT(readability-convert-member-functions-to-static)
// Define la zona central del menu (entre el logo y la marquesina)
constexpr int MENU_ZONE_Y = 73; // Top of menu zone
constexpr int MENU_ZONE_HEIGHT = 102; // Height of menu zone
// Crea la textura con el listado de logros
const auto CHEEVOS_LIST = Cheevos::get()->list();
const auto TEXT = Resource::Cache::get()->getText("subatomic");
constexpr int CHEEVOS_TEXTURE_WIDTH = 200;
constexpr int CHEEVOS_TEXTURE_VIEW_HEIGHT = MENU_ZONE_HEIGHT;
constexpr int CHEEVOS_PADDING = 10;
const int CHEEVO_HEIGHT = CHEEVOS_PADDING + (TEXT->getCharacterSize() * 2) + 1;
const int CHEEVOS_TEXTURE_HEIGHT = (CHEEVO_HEIGHT * CHEEVOS_LIST.size()) + 2 + TEXT->getCharacterSize() + 8;
cheevos_surface_ = std::make_shared<Surface>(CHEEVOS_TEXTURE_WIDTH, CHEEVOS_TEXTURE_HEIGHT);
// Prepara para dibujar sobre la textura
auto previuos_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(cheevos_surface_);
// Rellena la textura con color sólido
const auto CHEEVOS_BG_COLOR = 0;
cheevos_surface_->clear(CHEEVOS_BG_COLOR);
// Escribe la lista de logros en la textura
const std::string CHEEVOS_OWNER = Locale::get()->get("title.projects"); // NOLINT(readability-static-accessed-through-instance)
const std::string CHEEVOS_LIST_CAPTION = CHEEVOS_OWNER + " (" + std::to_string(Cheevos::get()->getTotalUnlockedAchievements()) + " / " + std::to_string(Cheevos::get()->size()) + ")";
int pos = 2;
TEXT->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, cheevos_surface_->getWidth() / 2, pos, CHEEVOS_LIST_CAPTION, 1, 9);
pos += TEXT->getCharacterSize();
const Uint8 CHEEVO_LOCKED_COLOR = 14;
const Uint8 CHEEVO_UNLOCKED_COLOR = 9;
constexpr int LINE_X1 = (CHEEVOS_TEXTURE_WIDTH / 7) * 3;
constexpr int LINE_X2 = LINE_X1 + ((CHEEVOS_TEXTURE_WIDTH / 7) * 1);
for (const auto& cheevo : CHEEVOS_LIST) {
const Uint8 CHEEVO_COLOR = cheevo.completed ? CHEEVO_UNLOCKED_COLOR : CHEEVO_LOCKED_COLOR;
pos += CHEEVOS_PADDING;
constexpr int HALF = CHEEVOS_PADDING / 2;
cheevos_surface_->drawLine(LINE_X1, pos - HALF - 1, LINE_X2, pos - HALF - 1, CHEEVO_COLOR);
TEXT->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, CHEEVOS_TEXTURE_WIDTH / 2, pos, cheevo.caption, 1, CHEEVO_COLOR);
pos += TEXT->getCharacterSize() + 1;
TEXT->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, CHEEVOS_TEXTURE_WIDTH / 2, pos, cheevo.description, 1, CHEEVO_COLOR);
pos += TEXT->getCharacterSize();
}
// Restablece el RenderSurface
Screen::get()->setRendererSurface(previuos_renderer);
// Crea el sprite para el listado de logros (usa la zona del menu)
cheevos_sprite_ = std::make_unique<Sprite>(cheevos_surface_, (GameCanvas::WIDTH - cheevos_surface_->getWidth()) / 2, MENU_ZONE_Y, cheevos_surface_->getWidth(), cheevos_surface_->getHeight());
cheevos_surface_view_ = {.x = 0, .y = 0, .w = cheevos_surface_->getWidth(), .h = CHEEVOS_TEXTURE_VIEW_HEIGHT};
cheevos_sprite_->setClip(cheevos_surface_view_);
}
// Resetea el scroll de la lista de logros
void Title::resetCheevosScroll() {
cheevos_surface_view_.y = 0;
cheevos_scroll_velocity_ = 0.0F;
cheevos_sprite_->setClip(cheevos_surface_view_);
}
// Dibuja el logo con el titulo del juego // Dibuja el logo con el titulo del juego
void Title::renderGameLogo() { void Title::renderGameLogo() {
game_logo_sprite_->render(); game_logo_sprite_->render();
@@ -389,30 +260,24 @@ void Title::renderMainMenu() {
return; return;
} }
// Zona central del menu (debe coincidir con la textura de cheevos) // Zona central del menu
constexpr int MENU_ZONE_Y = 73; constexpr int MENU_ZONE_Y = 73;
constexpr int MENU_ZONE_HEIGHT = 102; constexpr int MENU_ZONE_HEIGHT = 102;
// Menú principal normal con 4 opciones centradas verticalmente en la zona // Menú principal normal con 3 opciones centradas verticalmente en la zona
const Uint8 COLOR = 8; const Uint8 COLOR = 8;
const int TEXT_SIZE = menu_text_->getCharacterSize(); const int TEXT_SIZE = menu_text_->getCharacterSize();
const int MENU_CENTER_Y = MENU_ZONE_Y + (MENU_ZONE_HEIGHT / 2); const int MENU_CENTER_Y = MENU_ZONE_Y + (MENU_ZONE_HEIGHT / 2);
const int SPACING = 2 * TEXT_SIZE; // Espaciado entre opciones const int SPACING = 2 * TEXT_SIZE; // Espaciado entre opciones
// Calcula posiciones centradas verticalmente (4 items con espaciado) // Calcula posiciones centradas verticalmente (3 items con espaciado)
const int TOTAL_HEIGHT = 3 * SPACING; // 3 espacios entre 4 items const int TOTAL_HEIGHT = 2 * SPACING; // 2 espacios entre 3 items
const int START_Y = MENU_CENTER_Y - (TOTAL_HEIGHT / 2); const int START_Y = MENU_CENTER_Y - (TOTAL_HEIGHT / 2);
auto* loc = Locale::get(); auto* loc = Locale::get();
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.menu.play"), 1, COLOR); menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y, loc->get("title.menu.play"), 1, COLOR);
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + SPACING, loc->get("title.menu.keyboard"), 1, COLOR); menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + SPACING, loc->get("title.menu.keyboard"), 1, COLOR);
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + (2 * SPACING), loc->get("title.menu.joystick"), 1, COLOR); menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + (2 * SPACING), loc->get("title.menu.joystick"), 1, COLOR);
menu_text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, START_Y + (3 * SPACING), loc->get("title.menu.projects"), 1, COLOR);
}
// Dibuja el menu de logros
void Title::renderCheevosMenu() {
cheevos_sprite_->render();
} }
// Dibuja los elementos en la surface // Dibuja los elementos en la surface
@@ -432,12 +297,6 @@ void Title::fillTitleSurface() {
break; break;
case State::CHEEVOS_MENU:
renderGameLogo();
renderCheevosMenu();
break;
default: default:
break; break;
} }
@@ -534,7 +393,7 @@ void Title::applyKeyboardRemap() { // NOLINT(readability-convert-member-functio
// Dibuja la pantalla de redefinir teclado // Dibuja la pantalla de redefinir teclado
void Title::renderKeyboardRemap() const { void Title::renderKeyboardRemap() const {
// Zona central del menu (debe coincidir con la textura de cheevos) // Zona central del menu
constexpr int MENU_ZONE_Y = 73; constexpr int MENU_ZONE_Y = 73;
constexpr int MENU_ZONE_HEIGHT = 102; constexpr int MENU_ZONE_HEIGHT = 102;
@@ -582,7 +441,7 @@ void Title::renderKeyboardRemap() const {
// Dibuja la pantalla de redefinir joystick // Dibuja la pantalla de redefinir joystick
void Title::renderJoystickRemap() const { void Title::renderJoystickRemap() const {
// Zona central del menu (debe coincidir con la textura de cheevos) // Zona central del menu
constexpr int MENU_ZONE_Y = 73; constexpr int MENU_ZONE_Y = 73;
constexpr int MENU_ZONE_HEIGHT = 102; constexpr int MENU_ZONE_HEIGHT = 102;

View File

@@ -26,7 +26,6 @@ class Title {
// --- Estructuras y enumeraciones --- // --- Estructuras y enumeraciones ---
enum class State { enum class State {
MAIN_MENU, MAIN_MENU,
CHEEVOS_MENU,
FADE_MENU, FADE_MENU,
POST_FADE_MENU, POST_FADE_MENU,
}; };
@@ -35,10 +34,6 @@ class Title {
static constexpr float FADE_STEP_INTERVAL = 0.05F; // Intervalo entre pasos de fade (antes cada 4 frames) static constexpr float FADE_STEP_INTERVAL = 0.05F; // Intervalo entre pasos de fade (antes cada 4 frames)
static constexpr float POST_FADE_DELAY = 1.0F; // Delay después del fade (pantalla en negro) static constexpr float POST_FADE_DELAY = 1.0F; // Delay después del fade (pantalla en negro)
static constexpr float KEYBOARD_REMAP_DISPLAY_DELAY = 2.0F; // Tiempo mostrando teclas definidas antes de guardar static constexpr float KEYBOARD_REMAP_DISPLAY_DELAY = 2.0F; // Tiempo mostrando teclas definidas antes de guardar
static constexpr float CHEEVOS_SCROLL_MAX_SPEED = 180.0F; // Velocidad máxima de scroll de logros (pixels/segundo)
static constexpr float CHEEVOS_SCROLL_ACCELERATION = 600.0F; // Aceleración del scroll (pixels/segundo²)
static constexpr float CHEEVOS_SCROLL_DECELERATION = 800.0F; // Desaceleración del scroll (pixels/segundo²)
// --- Métodos --- // --- Métodos ---
void handleEvents(); // Comprueba el manejador de eventos void handleEvents(); // Comprueba el manejador de eventos
void handleMainMenuKeyPress(SDL_Keycode key); // Maneja las teclas del menu principal void handleMainMenuKeyPress(SDL_Keycode key); // Maneja las teclas del menu principal
@@ -46,12 +41,10 @@ class Title {
void updateState(float delta_time); // Actualiza el estado actual void updateState(float delta_time); // Actualiza el estado actual
void transitionToState(State new_state); // Transiciona a un nuevo estado void transitionToState(State new_state); // Transiciona a un nuevo estado
void updateMainMenu(float delta_time); // Actualiza MAIN_MENU void updateMainMenu(float delta_time); // Actualiza MAIN_MENU
void updateCheevosMenu(float delta_time); // Actualiza CHEEVOS_MENU
void updateFadeMenu(float delta_time); // Actualiza FADE_MENU void updateFadeMenu(float delta_time); // Actualiza FADE_MENU
void updatePostFadeMenu(float delta_time); // Actualiza POST_FADE_MENU void updatePostFadeMenu(float delta_time); // Actualiza POST_FADE_MENU
void renderGameLogo(); // Dibuja el logo con el titulo del juego void renderGameLogo(); // Dibuja el logo con el titulo del juego
void renderMainMenu(); // Dibuja el menu principal void renderMainMenu(); // Dibuja el menu principal
void renderCheevosMenu(); // Dibuja el menu de logros
void renderKeyboardRemap() const; // Dibuja la pantalla de redefinir teclado void renderKeyboardRemap() const; // Dibuja la pantalla de redefinir teclado
void renderJoystickRemap() const; // Dibuja la pantalla de redefinir joystick void renderJoystickRemap() const; // Dibuja la pantalla de redefinir joystick
void handleKeyboardRemap(const SDL_Event& event); // Maneja la captura de teclas void handleKeyboardRemap(const SDL_Event& event); // Maneja la captura de teclas
@@ -63,24 +56,16 @@ class Title {
void applyJoystickRemap(); // Aplica y guarda los botones del gamepad redefinidos void applyJoystickRemap(); // Aplica y guarda los botones del gamepad redefinidos
static auto getActionName(int step) -> std::string; // Retorna el nombre de la accion (LEFT/RIGHT/JUMP) static auto getActionName(int step) -> std::string; // Retorna el nombre de la accion (LEFT/RIGHT/JUMP)
static auto getButtonName(int button) -> std::string; // Retorna el nombre amigable del boton del gamepad static auto getButtonName(int button) -> std::string; // Retorna el nombre amigable del boton del gamepad
void createCheevosTexture(); // Crea y rellena la surface para mostrar los logros
void resetCheevosScroll(); // Resetea el scroll de la lista de logros
void fillTitleSurface(); // Dibuja los elementos en la surface void fillTitleSurface(); // Dibuja los elementos en la surface
// --- Variables miembro --- // --- Variables miembro ---
// Objetos y punteros // Objetos y punteros
std::shared_ptr<Surface> game_logo_surface_; // Textura con los graficos std::shared_ptr<Surface> game_logo_surface_; // Textura con los graficos
std::unique_ptr<Sprite> game_logo_sprite_; // SSprite para manejar la surface std::unique_ptr<Sprite> game_logo_sprite_; // SSprite para manejar la surface
std::shared_ptr<Surface> cheevos_surface_; // Textura con la lista de logros
std::unique_ptr<Sprite> cheevos_sprite_; // SSprite para manejar la surface con la lista de logros
std::shared_ptr<Surface> title_surface_; // Surface donde se dibuja toda la clase std::shared_ptr<Surface> title_surface_; // Surface donde se dibuja toda la clase
std::unique_ptr<DeltaTimer> delta_timer_; // Timer para delta time std::unique_ptr<DeltaTimer> delta_timer_; // Timer para delta time
std::shared_ptr<Text> menu_text_; // Texto para los menus std::shared_ptr<Text> menu_text_; // Texto para los menus
// Variables de estado del menú de logros
SDL_FRect cheevos_surface_view_; // Zona visible de la surface con el listado de logros
float cheevos_scroll_velocity_{0.0F}; // Velocidad actual del scroll de logros (pixels/segundo)
// Variables de estado general // Variables de estado general
State state_; // Estado en el que se encuentra el bucle principal State state_; // Estado en el que se encuentra el bucle principal
float state_time_{0.0F}; // Tiempo acumulado en el estado actual float state_time_{0.0F}; // Tiempo acumulado en el estado actual

View File

@@ -794,11 +794,7 @@ static auto cmdShow(const std::vector<std::string>& args) -> std::string {
Notifier::get()->show({"NOTIFICATION"}); Notifier::get()->show({"NOTIFICATION"});
return "Notification shown"; return "Notification shown";
} }
if (!args.empty() && args[0] == "CHEEVO") { if (args.empty() || args[0] != "INFO") { return "usage: show [info|notification]"; }
Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c1")}, Notifier::Style::CHEEVO, -1, false); // NOLINT(readability-static-accessed-through-instance)
return "Cheevo notification shown";
}
if (args.empty() || args[0] != "INFO") { return "usage: show [info|notification|cheevo]"; }
#else #else
if (args.empty() || args[0] != "INFO") { return "usage: show [info]"; } if (args.empty() || args[0] != "INFO") { return "usage: show [info]"; }
#endif #endif
@@ -818,7 +814,7 @@ static auto cmdHide(const std::vector<std::string>& args) -> std::string {
// CHEAT [subcomando] // CHEAT [subcomando]
static auto cmdCheat(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity) static auto cmdCheat(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
if (args.empty()) { return "usage: cheat [infinite lives|invincibility|open the jail|close the jail]"; } if (args.empty()) { return "usage: cheat [infinite lives|invincibility]"; }
// CHEAT INFINITE LIVES [ON|OFF] // CHEAT INFINITE LIVES [ON|OFF]
if (args[0] == "INFINITE") { if (args[0] == "INFINITE") {
@@ -858,23 +854,7 @@ static auto cmdCheat(const std::vector<std::string>& args) -> std::string { //
return std::string("Invincibility ") + (cheat == State::ENABLED ? "ON" : "OFF"); return std::string("Invincibility ") + (cheat == State::ENABLED ? "ON" : "OFF");
} }
// CHEAT OPEN THE JAIL return "usage: cheat [infinite lives|invincibility]";
if (args[0] == "OPEN") {
if (args.size() < 3 || args[1] != "THE" || args[2] != "JAIL") { return "usage: cheat open the jail"; }
if (Options::cheats.jail_is_open == Options::Cheat::State::ENABLED) { return "Jail already open"; }
Options::cheats.jail_is_open = Options::Cheat::State::ENABLED;
return "Jail opened";
}
// CHEAT CLOSE THE JAIL
if (args[0] == "CLOSE") {
if (args.size() < 3 || args[1] != "THE" || args[2] != "JAIL") { return "usage: cheat close the jail"; }
if (Options::cheats.jail_is_open == Options::Cheat::State::DISABLED) { return "Jail already closed"; }
Options::cheats.jail_is_open = Options::Cheat::State::DISABLED;
return "Jail closed";
}
return "usage: cheat [infinite lives|invincibility|open the jail|close the jail]";
} }
// PLAYER SKIN / PLAYER COLOR // PLAYER SKIN / PLAYER COLOR