From f97291110f7c451bffb7cb3438483ffaf7376bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Valor=20Mart=C3=ADnez?= Date: Wed, 21 Apr 2021 21:20:20 +0200 Subject: [PATCH] debuging memory leaks --- source/animatedsprite.cpp | 5 +- source/balloon.cpp | 1 + source/bullet.cpp | 6 +- source/const.h | 16 +- source/director.cpp | 453 ++++++ source/director.h | 101 ++ source/fade.cpp | 171 ++ source/fade.h | 55 + source/game.cpp | 3136 +++++++++++++++++++++++++++++++++++++ source/game.h | 470 ++++++ source/input.cpp | 163 ++ source/input.h | 58 + source/instructions.cpp | 230 +++ source/instructions.h | 54 + source/intro.cpp | 365 +++++ source/intro.h | 47 + source/item.cpp | 6 +- source/logo.cpp | 151 ++ source/logo.h | 44 + source/ltexture.cpp | 2 - source/menu.cpp | 10 +- source/menu.h | 1 + source/movingsprite.cpp | 1 - source/player.cpp | 9 +- source/smartsprite.cpp | 4 +- source/text.cpp | 13 +- source/text.h | 10 +- source/title.cpp | 655 ++++++++ source/title.h | 107 ++ 29 files changed, 6306 insertions(+), 38 deletions(-) create mode 100644 source/director.cpp create mode 100644 source/director.h create mode 100644 source/fade.cpp create mode 100644 source/fade.h create mode 100644 source/game.cpp create mode 100644 source/game.h create mode 100644 source/input.cpp create mode 100644 source/input.h create mode 100644 source/instructions.cpp create mode 100644 source/instructions.h create mode 100644 source/intro.cpp create mode 100644 source/intro.h create mode 100644 source/logo.cpp create mode 100644 source/logo.h create mode 100644 source/title.cpp create mode 100644 source/title.h diff --git a/source/animatedsprite.cpp b/source/animatedsprite.cpp index 6b96708..faf4ad4 100644 --- a/source/animatedsprite.cpp +++ b/source/animatedsprite.cpp @@ -1,17 +1,16 @@ #include "const.h" #include "animatedsprite.h" -#include // Constructor AnimatedSprite::AnimatedSprite() { - init(nullptr, nullptr); + //init(nullptr, nullptr); } // Destructor AnimatedSprite::~AnimatedSprite() { - init(nullptr, nullptr); + //init(nullptr, nullptr); } // Iniciador diff --git a/source/balloon.cpp b/source/balloon.cpp index ddabb7d..3313578 100644 --- a/source/balloon.cpp +++ b/source/balloon.cpp @@ -12,6 +12,7 @@ Balloon::Balloon() Balloon::~Balloon() { delete mSprite; + mSprite = nullptr; } // Inicializador diff --git a/source/bullet.cpp b/source/bullet.cpp index f2dbf6e..c7aeb08 100644 --- a/source/bullet.cpp +++ b/source/bullet.cpp @@ -5,14 +5,16 @@ Bullet::Bullet() { mSprite = new Sprite(); - init(0, 0, NO_KIND, nullptr, nullptr); + //init(0, 0, NO_KIND, nullptr, nullptr); + mKind = NO_KIND; } // Destructor Bullet::~Bullet() { - init(0, 0, NO_KIND, nullptr, nullptr); + //init(0, 0, NO_KIND, nullptr, nullptr); delete mSprite; + mSprite = nullptr; } // Iniciador diff --git a/source/const.h b/source/const.h index 06b608e..e4f81a0 100644 --- a/source/const.h +++ b/source/const.h @@ -141,20 +141,18 @@ const int SCREEN_THIRD_QUARTER_Y = (SCREEN_HEIGHT / 4) * 3; #define PROG_SECTION_INTRO 1 #define PROG_SECTION_TITLE 2 #define PROG_SECTION_GAME 3 -#define PROG_SECTION_INSTRUCTIONS 4 -#define PROG_SECTION_QUIT 5 +#define PROG_SECTION_QUIT 4 -// Secciones del juego +// Subsecciones #define GAME_SECTION_PLAY 0 #define GAME_SECTION_PAUSE 1 #define GAME_SECTION_GAMEOVER 2 +#define TITLE_SECTION_1 3 +#define TITLE_SECTION_2 4 +#define TITLE_SECTION_3 5 +#define TITLE_SECTION_INSTRUCTIONS 6 -// Secciones del titulo -#define TITLE_SECTION_1 0 -#define TITLE_SECTION_2 1 -#define TITLE_SECTION_3 2 - -// Secciones de las instrucciones +// Modo para las instrucciones #define INSTRUCTIONS_MODE_MANUAL 0 #define INSTRUCTIONS_MODE_AUTO 1 diff --git a/source/director.cpp b/source/director.cpp new file mode 100644 index 0000000..6e39e57 --- /dev/null +++ b/source/director.cpp @@ -0,0 +1,453 @@ +#include "const.h" +#include "utils.h" +#include "director.h" +#include +#ifdef __MIPSEL__ +#include +#include +#endif + +// Constructor +Director::Director(std::string path) +{ + // Crea los objetos + mInput = new Input(); + mOptions = new options_t; + + // Inicializa variables + setExecutablePath(path); + setFileList(); + checkFileList(); + + // Inicializa SDL + initSDL(); + + // Inicializa JailAudio + initJailAudio(); + +#ifdef __MIPSEL__ + DIR *dir = opendir("/media/data/local/home/.coffee_crisis"); + if (dir) + { + closedir(dir); + } + else if (ENOENT == errno) + { + int status = mkdir("/media/data/local/home/.coffee_crisis", 755); + } +#endif + + // Carga recursos + loadConfigFile(); + + // Inicializa el resto de variables + init(); +} + +Director::~Director() +{ + saveConfigFile(); + + delete mInput; + mInput = nullptr; + + delete mOptions; + mOptions = nullptr; + + SDL_DestroyRenderer(mRenderer); + SDL_DestroyWindow(mWindow); + mRenderer = nullptr; + mWindow = nullptr; + + SDL_Quit(); +} + +// Inicia las variables necesarias para arrancar el programa +void Director::init() +{ + mSection.name = PROG_SECTION_LOGO; + mSection.subsection = 0; + + // Teclado + mInput->bindKey(INPUT_UP, SDL_SCANCODE_UP); + mInput->bindKey(INPUT_DOWN, SDL_SCANCODE_DOWN); + mInput->bindKey(INPUT_LEFT, SDL_SCANCODE_LEFT); + mInput->bindKey(INPUT_RIGHT, SDL_SCANCODE_RIGHT); + mInput->bindKey(INPUT_ACCEPT, SDL_SCANCODE_RETURN); + mInput->bindKey(INPUT_CANCEL, SDL_SCANCODE_ESCAPE); +#ifdef __MIPSEL__ + mInput->bindKey(INPUT_BUTTON_1, SDL_SCANCODE_LSHIFT); + mInput->bindKey(INPUT_BUTTON_2, SDL_SCANCODE_SPACE); + mInput->bindKey(INPUT_BUTTON_3, SDL_SCANCODE_LCTRL); +#else + mInput->bindKey(INPUT_BUTTON_1, SDL_SCANCODE_Q); + mInput->bindKey(INPUT_BUTTON_2, SDL_SCANCODE_W); + mInput->bindKey(INPUT_BUTTON_3, SDL_SCANCODE_E); +#endif + mInput->bindKey(INPUT_BUTTON_4, SDL_SCANCODE_ESCAPE); // PAUSE + mInput->bindKey(INPUT_BUTTON_5, SDL_SCANCODE_ESCAPE); // ESCAPE +} + +// Inicializa JailAudio +void Director::initJailAudio() +{ + JA_Init(48000, AUDIO_S16, 2); +} + +// Arranca SDL y crea la ventana +bool Director::initSDL() +{ + // Indicador de inicialización + bool success = true; + + // Inicializa SDL + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_HAPTIC) < 0) + { + printf("SDL could not initialize!\nSDL Error: %s\n", SDL_GetError()); + success = false; + } + else + { + // Establece el filtro de la textura a nearest + if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0")) + { + printf("Warning: Nearest texture filtering not enabled!\n"); + } + + // Crea la ventana + mWindow = SDL_CreateWindow(WINDOW_CAPTION, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, VIEW_WIDTH, VIEW_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI); + if (mWindow == NULL) + { + printf("Window could not be created!\nSDL Error: %s\n", SDL_GetError()); + success = false; + } + else + { + // Crea un renderizador para la ventana con vsync + mRenderer = SDL_CreateRenderer(mWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (mRenderer == NULL) + { + printf("Renderer could not be created!\nSDL Error: %s\n", SDL_GetError()); + success = false; + } + else + { + // Inicializa el color de renderizado + SDL_SetRenderDrawColor(mRenderer, 0x00, 0x00, 0x00, 0xFF); + + // Establece el tamaño del buffer de renderizado + SDL_RenderSetLogicalSize(mRenderer, SCREEN_WIDTH, SCREEN_HEIGHT); + + // Establece el modo de mezcla + SDL_SetRenderDrawBlendMode(mRenderer, SDL_BLENDMODE_BLEND); + } + } + } + + printf("\n"); + return success; +} + +// Crea el indice de ficheros +void Director::setFileList() +{ + // Ficheros binarios +#ifdef __MIPSEL__ + mFileList[0] = "/media/data/local/home/.coffee_crisis/score.bin"; + mFileList[1] = "/media/data/local/home/.coffee_crisis/demo.bin"; + mFileList[2] = "/media/data/local/home/.coffee_crisis/config.bin"; +#else + mFileList[0] = mExecutablePath + "/" + "../data/score.bin"; + mFileList[1] = mExecutablePath + "/" + "../data/demo.bin"; + mFileList[2] = mExecutablePath + "/" + "../data/config.bin"; +#endif + + // Musicas + mFileList[3] = mExecutablePath + "/" + "../media/music/intro.ogg"; + mFileList[4] = mExecutablePath + "/" + "../media/music/playing.ogg"; + mFileList[5] = mExecutablePath + "/" + "../media/music/title.ogg"; + + // Sonidos + mFileList[6] = mExecutablePath + "/" + "../media/sound/balloon.wav"; + mFileList[7] = mExecutablePath + "/" + "../media/sound/bubble1.wav"; + mFileList[8] = mExecutablePath + "/" + "../media/sound/bubble2.wav"; + mFileList[9] = mExecutablePath + "/" + "../media/sound/bubble3.wav"; + mFileList[10] = mExecutablePath + "/" + "../media/sound/bubble4.wav"; + mFileList[11] = mExecutablePath + "/" + "../media/sound/bullet.wav"; + mFileList[12] = mExecutablePath + "/" + "../media/sound/coffeeout.wav"; + mFileList[13] = mExecutablePath + "/" + "../media/sound/hiscore.wav"; + mFileList[14] = mExecutablePath + "/" + "../media/sound/itemdrop.wav"; + mFileList[15] = mExecutablePath + "/" + "../media/sound/itempickup.wav"; + mFileList[16] = mExecutablePath + "/" + "../media/sound/menu_cancel.wav"; + mFileList[17] = mExecutablePath + "/" + "../media/sound/menu_move.wav"; + mFileList[18] = mExecutablePath + "/" + "../media/sound/menu_select.wav"; + mFileList[19] = mExecutablePath + "/" + "../media/sound/player_collision.wav"; + mFileList[20] = mExecutablePath + "/" + "../media/sound/stage_change.wav"; + mFileList[21] = mExecutablePath + "/" + "../media/sound/title.wav"; + mFileList[22] = mExecutablePath + "/" + "../media/sound/clock.wav"; + mFileList[23] = mExecutablePath + "/" + "../media/sound/powerball.wav"; + + // Texturas + mFileList[24] = mExecutablePath + "/" + "../media/gfx/balloon.png"; + mFileList[25] = mExecutablePath + "/" + "../media/gfx/bullet.png"; + mFileList[26] = mExecutablePath + "/" + "../media/gfx/font_black_x2.png"; + mFileList[27] = mExecutablePath + "/" + "../media/gfx/font_black.png"; + mFileList[28] = mExecutablePath + "/" + "../media/gfx/font_nokia.png"; + mFileList[29] = mExecutablePath + "/" + "../media/gfx/font_white_x2.png"; + mFileList[30] = mExecutablePath + "/" + "../media/gfx/font_white.png"; + mFileList[31] = mExecutablePath + "/" + "../media/gfx/game_bg.png"; + mFileList[32] = mExecutablePath + "/" + "../media/gfx/game_text.png"; + mFileList[33] = mExecutablePath + "/" + "../media/gfx/intro.png"; + mFileList[34] = mExecutablePath + "/" + "../media/gfx/items.png"; + mFileList[35] = mExecutablePath + "/" + "../media/gfx/logo.png"; + mFileList[36] = mExecutablePath + "/" + "../media/gfx/menu.png"; + mFileList[37] = mExecutablePath + "/" + "../media/gfx/player_body.png"; + mFileList[38] = mExecutablePath + "/" + "../media/gfx/player_death.png"; + mFileList[39] = mExecutablePath + "/" + "../media/gfx/player_legs.png"; + mFileList[40] = mExecutablePath + "/" + "../media/gfx/title.png"; +} + +// Comprueba que todos los ficheros existen +bool Director::checkFileList() +{ + bool success = true; + /*std::string p; + std::string filename; + SDL_RWops *file; + + // Comprueba los ficheros de musica + printf("\n>> MUSIC FILES\n"); + if (success) + for (int i = 0; i < TOTAL_MUSIC; i++) + { + p = mMusic[i].file.c_str(); + filename = p.substr(p.find_last_of("\\/") + 1); + file = SDL_RWFromFile(p.c_str(), "r+b"); + if (file != NULL) + { + printf("Checking file %-20s [OK]\n", filename.c_str()); + } + else + { + printf("Checking file %-20s [ERROR]\n", filename.c_str()); + success = false; + break; + } + SDL_RWclose(file); + } + + // Comprueba los ficheros de sonidos + printf("\n>> SOUND FILES\n"); + if (success) + for (int i = 0; i < TOTAL_SOUND; i++) + { + p = mSound[i].file.c_str(); + filename = p.substr(p.find_last_of("\\/") + 1); + file = SDL_RWFromFile(p.c_str(), "r+b"); + if (file != NULL) + { + printf("Checking file %-20s [OK]\n", filename.c_str()); + } + else + { + printf("Checking file %-20s [ERROR]\n", filename.c_str()); + success = false; + break; + } + SDL_RWclose(file); + } + + // Comprueba los ficheros con texturas + printf("\n>> TEXTURE FILES\n"); + if (success) + for (int i = 0; i < TOTAL_TEXTURE; i++) + { + p = mTexture[i].file.c_str(); + filename = p.substr(p.find_last_of("\\/") + 1); + file = SDL_RWFromFile(p.c_str(), "r+b"); + if (file != NULL) + { + printf("Checking file %-20s [OK]\n", filename.c_str()); + } + else + { + printf("Checking file %-20s [ERROR]\n", filename.c_str()); + success = false; + break; + } + SDL_RWclose(file); + } + + // Resultado + if (success) + printf("\n** All files OK.\n\n"); + else + printf("\n** A file is missing. Exiting.\n\n"); + +*/ + return success; +} + +// Carga el fichero de configuración +bool Director::loadConfigFile() +{ + // Indicador de éxito en la carga + bool success = true; + + const std::string p = mFileList[2]; + std::string filename = p.substr(p.find_last_of("\\/") + 1); + SDL_RWops *file = SDL_RWFromFile(p.c_str(), "r+b"); + + // El fichero no existe + if (file == NULL) + { + printf("Warning: Unable to open %s file\n", filename.c_str()); + + // Creamos el fichero para escritura + file = SDL_RWFromFile(p.c_str(), "w+b"); + if (file != NULL) + { + printf("New file (%s) created!\n", filename.c_str()); + + // Inicializamos los datos + mOptions->fullScreenMode = 0; + SDL_RWwrite(file, &mOptions->fullScreenMode, sizeof(mOptions->fullScreenMode), 1); + + mOptions->windowSize = 3; + SDL_RWwrite(file, &mOptions->windowSize, sizeof(mOptions->windowSize), 1); + + // Cerramos el fichero + SDL_RWclose(file); + } + else + { + printf("Error: Unable to create file %s\n", filename.c_str()); + success = false; + } + } + // El fichero existe + else + { + // Carga los datos + printf("Reading file %s\n", filename.c_str()); + SDL_RWread(file, &mOptions->fullScreenMode, sizeof(mOptions->fullScreenMode), 1); + SDL_RWread(file, &mOptions->windowSize, sizeof(mOptions->windowSize), 1); + + // Aplica las opciones + SDL_SetWindowFullscreen(mWindow, mOptions->fullScreenMode); + SDL_SetWindowSize(mWindow, SCREEN_WIDTH * mOptions->windowSize, SCREEN_HEIGHT * mOptions->windowSize); + + // Cierra el fichero + SDL_RWclose(file); + } + + return success; +} + +// Guarda el fichero de configuración +bool Director::saveConfigFile() +{ + bool success = true; + const std::string p = mFileList[2]; + std::string filename = p.substr(p.find_last_of("\\/") + 1); + SDL_RWops *file = SDL_RWFromFile(p.c_str(), "w+b"); + if (file != NULL) + { + // Guarda los datos + SDL_RWwrite(file, &mOptions->fullScreenMode, sizeof(Uint32), 1); + SDL_RWwrite(file, &mOptions->windowSize, sizeof(Uint8), 1); + + printf("Writing file %s\n", filename.c_str()); + + // Cierra el fichero + SDL_RWclose(file); + } + else + { + printf("Error: Unable to save %s file! %s\n", filename.c_str(), SDL_GetError()); + } + return success; +} + +// Establece el valor de la variable +void Director::setExecutablePath(std::string path) +{ + mExecutablePath = path.substr(0, path.find_last_of("\\/")); +} + +// Obtiene el valor de la variable +Uint8 Director::getSubsection() +{ + return mSection.subsection; +} + +// Obtiene el valor de la variable +Uint8 Director::getSection() +{ + return mSection.name; +} + +// Establece el valor de la variable +void Director::setSection(section_t section) +{ + mSection = section; +} + +void Director::runLogo() +{ + mLogo = new Logo(mRenderer, mFileList); + setSection(mLogo->run()); + delete mLogo; +} + +void Director::runIntro() +{ + mIntro = new Intro(mRenderer, mFileList); + setSection(mIntro->run()); + delete mIntro; +} + +void Director::runTitle() +{ + mTitle = new Title(mWindow, mRenderer, mInput, mFileList, mOptions); + setSection(mTitle->run(mSection.subsection)); + delete mTitle; +} + +void Director::runGame() +{ + mGame = new Game(mRenderer, mFileList, mInput, false); + setSection(mGame->run()); + delete mGame; +} + +void Director::run() +{ + for (int i = 0; i < 100; i++) + { + Game *test = new Game(mRenderer, mFileList, mInput, true); + delete test; + printf("%i\n", i); + } + mSection.name = PROG_SECTION_QUIT; + + // Bucle principal + while (!(getSection() == PROG_SECTION_QUIT)) + { + switch (getSection()) + { + case PROG_SECTION_LOGO: + runLogo(); + break; + case PROG_SECTION_INTRO: + runIntro(); + break; + case PROG_SECTION_TITLE: + runTitle(); + break; + case PROG_SECTION_GAME: + runGame(); + break; + } + } +} \ No newline at end of file diff --git a/source/director.h b/source/director.h new file mode 100644 index 0000000..f464fba --- /dev/null +++ b/source/director.h @@ -0,0 +1,101 @@ +#pragma once +#include "ifdefs.h" +#include "sprite.h" +#include "movingsprite.h" +#include "smartsprite.h" +#include "player.h" +#include "balloon.h" +#include "bullet.h" +#include "coffeedrop.h" +#include "item.h" +#include "text.h" +#include "text2.h" +#include "menu.h" +#include "const.h" +#include "jail_audio.h" +#include "utils.h" +#include "logo.h" +#include "intro.h" +#include "title.h" +#include "game.h" +#include "input.h" +#include "fade.h" +#include + +#ifndef DIRECTOR_H +#define DIRECTOR_H + +// Director +class Director +{ +private: + SDL_Window *mWindow; // La ventana donde dibujamos + SDL_Renderer *mRenderer; // El renderizador de la ventana + + Input *mInput; + + Logo *mLogo; // Objeto para la sección del logo + Intro *mIntro; // Objeto para la sección de la intro + Title *mTitle; // Objeto para la sección del titulo y el menu de opciones + Game *mGame; // Objeto para la sección del juego + + std::string mFileList[100]; // Vector con las rutas a los ficheros de recursos + struct options_t *mOptions; // Variable con todas las opciones del programa + + std::string mExecutablePath; // Path del ejecutable + section_t mSection; // Sección y subsección actual del programa; + + // Inicializa jail_audio + void initJailAudio(); + + // Arranca SDL y crea la ventana + bool initSDL(); + + // Crea el indice de ficheros + void setFileList(); + + // Comprueba que todos los ficheros existen + bool checkFileList(); + + // Carga el fichero de configuración + bool loadConfigFile(); + + // Guarda el fichero de configuración + bool saveConfigFile(); + + // Establece el valor de la variable + void setExecutablePath(std::string path); + + // Obtiene el valor de la variable + Uint8 getSubsection(); + + // Obtiene el valor de la variable + Uint8 getSection(); + + // Establece el valor de la variable + void setSection(section_t section); + + void runLogo(); + + void runIntro(); + + void runTitle(); + + void runGame(); + + +public: + // Constructor + Director(std::string path); + + // Destructor + ~Director(); + + // Inicia las variables necesarias para arrancar el programa + void init(); + + // Bucle principal + void run(); +}; + +#endif diff --git a/source/fade.cpp b/source/fade.cpp new file mode 100644 index 0000000..6818139 --- /dev/null +++ b/source/fade.cpp @@ -0,0 +1,171 @@ +#include "fade.h" +#include "const.h" + +// Constructor +Fade::Fade(SDL_Renderer *renderer) +{ + mRenderer = renderer; + + mBackbuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, SCREEN_WIDTH, SCREEN_HEIGHT); + if (mBackbuffer == NULL) + printf("Backbuffer could not be created!\nSDL Error: %s\n", SDL_GetError()); + + init(); +} + +// Destructor +Fade::~Fade() +{ + SDL_DestroyTexture(mBackbuffer); + mBackbuffer = nullptr; +} + +// Inicializa las variables +void Fade::init() +{ + mFadeType = FADE_CENTER; + mEnabled = false; + mFinished = false; + mCounter = 0; + mR = 0x27; + mG = 0x27; + mB = 0x36; +} + +// Pinta una transición en pantalla +void Fade::render() +{ + if (mEnabled && !mFinished) + { + switch (mFadeType) + { + case FADE_FULLSCREEN: + mRect1 = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; + + for (int i = 0; i < 256; i += 4) + { + // Dibujamos sobre el renderizador + SDL_SetRenderTarget(mRenderer, NULL); + + // Copia el backbuffer con la imagen que había al renderizador + SDL_RenderCopy(mRenderer, mBackbuffer, NULL, NULL); + + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, i); + SDL_RenderFillRect(mRenderer, &mRect1); + + // Vuelca el renderizador en pantalla + SDL_RenderPresent(mRenderer); + } + + // Deja todos los buffers del mismo color + SDL_SetRenderTarget(mRenderer, mBackbuffer); + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255); + SDL_RenderClear(mRenderer); + + SDL_SetRenderTarget(mRenderer, NULL); + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255); + SDL_RenderClear(mRenderer); + break; + + case FADE_CENTER: + mRect1 = {0, 0, SCREEN_WIDTH, 0}; + mRect2 = {0, 0, SCREEN_WIDTH, 0}; + + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 64); + + for (int i = 0; i < mCounter; i++) + { + mRect1.h = mRect2.h = i * 4; + mRect2.y = SCREEN_HEIGHT - (i * 4); + + SDL_RenderFillRect(mRenderer, &mRect1); + SDL_RenderFillRect(mRenderer, &mRect2); + } + + if ((mCounter * 4) > SCREEN_HEIGHT) + mFinished = true; + break; + + case FADE_RANDOM_SQUARE: + mRect1 = {0, 0, 32, 32}; + + for (Uint16 i = 0; i < 50; i++) + { + // Crea un color al azar + mR = 255 * (rand() % 2); + mG = 255 * (rand() % 2); + mB = 255 * (rand() % 2); + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 64); + + // Dibujamos sobre el backbuffer + SDL_SetRenderTarget(mRenderer, mBackbuffer); + + mRect1.x = rand() % (SCREEN_WIDTH - mRect1.w); + mRect1.y = rand() % (SCREEN_HEIGHT - mRect1.h); + SDL_RenderFillRect(mRenderer, &mRect1); + + // Volvemos a usar el renderizador de forma normal + SDL_SetRenderTarget(mRenderer, NULL); + + // Copiamos el backbuffer al renderizador + SDL_RenderCopy(mRenderer, mBackbuffer, NULL, NULL); + + // Volcamos el renderizador en pantalla + SDL_RenderPresent(mRenderer); + SDL_Delay(100); + } + break; + + default: + break; + } + } + + if (mFinished) + { + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255); + SDL_RenderClear(mRenderer); + } +} + +// Actualiza las variables internas +void Fade::update() +{ + if (mEnabled) + mCounter++; +} + +// Activa el fade +void Fade::activateFade() +{ + mEnabled = true; + mFinished = false; + mCounter = 0; +} + +// Comprueba si está activo +bool Fade::isEnabled() +{ + return mEnabled; +} + +// Comprueba si ha terminado la transicion +bool Fade::hasEnded() +{ + if (mFinished) + { + //mEnabled = false; + //mFinished = false; + return true; + } + else + { + return false; + } +} + +// Establece el tipo de fade +void Fade::setFadeType(Uint8 fadeType) +{ + mFadeType = fadeType; +} \ No newline at end of file diff --git a/source/fade.h b/source/fade.h new file mode 100644 index 0000000..4a6e63c --- /dev/null +++ b/source/fade.h @@ -0,0 +1,55 @@ +#pragma once +#include "ifdefs.h" +#include "ltexture.h" + +#ifndef FADE_H +#define FADE_H + +#define FADE_FULLSCREEN 0 +#define FADE_CENTER 1 +#define FADE_RANDOM_SQUARE 2 + +// Fade +class Fade +{ +private: + SDL_Renderer *mRenderer; // El renderizador de la ventana + SDL_Texture *mBackbuffer; // Textura para usar como backbuffer + Uint8 mFadeType; // Tipo de fade a realizar + Uint16 mCounter; // Contador interno + bool mEnabled; // Indica si el fade está activo + bool mFinished; // Indica si ha terminado la transición + Uint8 mR, mG, mB; // Colores para el fade + SDL_Rect mRect1; // Rectangulo usado para crear los efectos de transición + SDL_Rect mRect2; // Rectangulo usado para crear los efectos de transición + +public: + // Constructor + Fade(SDL_Renderer *renderer); + + // Destructor + ~Fade(); + + // Inicializa las variables + void init(); + + // Pinta una transición en pantalla + void render(); + + // Actualiza las variables internas + void update(); + + // Activa el fade + void activateFade(); + + // Comprueba si ha terminado la transicion + bool hasEnded(); + + // Comprueba si está activo + bool isEnabled(); + + // Establece el tipo de fade + void setFadeType(Uint8 fadeType); +}; + +#endif \ No newline at end of file diff --git a/source/game.cpp b/source/game.cpp new file mode 100644 index 0000000..a13ab3d --- /dev/null +++ b/source/game.cpp @@ -0,0 +1,3136 @@ +#include "game.h" +#ifdef __MIPSEL__ +#include +#include +#endif + +// Constructor +Game::Game(SDL_Renderer *renderer, std::string *filelist, Input *input, bool demo) +{ + // Referencia objetos + mRenderer = renderer; + mFileList = filelist; + mInput = input; + mDemo.enabled = demo; + + // Crea los objetos + mPlayer = new Player(); + + for (int i = 0; i < MAX_BALLOONS; i++) + mBalloon[i] = new Balloon(); + for (int i = 0; i < MAX_BULLETS; i++) + mBullet[i] = new Bullet(); + for (int i = 0; i < MAX_ITEMS; i++) + mItem[i] = new Item(); + for (int i = 0; i < MAX_SMART_SPRITES; i++) + mSmartSprite[i] = new SmartSprite(); + + mTextureBalloon = new LTexture(); + mTextureBullet = new LTexture(); + mTextureGameBG = new LTexture(); + mTextureGameText = new LTexture(); + mTextureItems = new LTexture(); + mTexturePlayerBody = new LTexture(); + mTexturePlayerDeath = new LTexture(); + mTexturePlayerLegs = new LTexture(); + mTextureText = new LTexture(); + mTextureText2 = new LTexture(); + + mText = new Text(mTextureText, mRenderer); + mTextX2 = new Text(mTextureText2, mRenderer); + + mMenuGameOver = new Menu(mRenderer, mText, mInput, mFileList); + mMenuPause = new Menu(mRenderer, mText, mInput, mFileList); + + mFade = new Fade(mRenderer); + mEventHandler = new SDL_Event(); + + mClouds1a = new MovingSprite(); + mClouds1b = new MovingSprite(); + mClouds2a = new MovingSprite(); + mClouds2b = new MovingSprite(); + m1000Bitmap = new SmartSprite(); + m2500Bitmap = new SmartSprite(); + m5000Bitmap = new SmartSprite(); + mSpriteBackground = new Sprite(); + mSpriteGetReady = new Sprite(); + mSpriteGradient = new Sprite(); + mSpriteGrass = new Sprite(); + mSpritePowerMeter = new Sprite(); + mSpriteScoreBoard = new Sprite(); + +#ifdef __MIPSEL__ + DIR *dir = opendir("/media/data/local/home/.coffee_crisis"); + if (dir) + { + closedir(dir); + } + else if (ENOENT == errno) + { + int status = mkdir("/media/data/local/home/.coffee_crisis", 755); + } +#endif +init(); +} + +Game::~Game() +{ + mRenderer = nullptr; + mFileList = nullptr; + mInput = nullptr; + + delete mPlayer; + mPlayer = nullptr; + + for (int i = 0; i < MAX_BALLOONS; i++) + { + delete mBalloon[i]; + mBalloon[i] = nullptr; + } + for (int i = 0; i < MAX_BULLETS; i++) + { + delete mBullet[i]; + mBullet[i] = nullptr; + } + for (int i = 0; i < MAX_ITEMS; i++) + { + delete mItem[i]; + mItem[i] = nullptr; + } + for (int i = 0; i < MAX_SMART_SPRITES; i++) + { + delete mSmartSprite[i]; + mSmartSprite[i] = nullptr; + } + + mTextureBalloon->unload(); + delete mTextureBalloon; + mTextureBalloon = nullptr; + + mTextureBullet->unload(); + delete mTextureBullet; + mTextureBullet = nullptr; + + mTextureGameBG->unload(); + delete mTextureGameBG; + mTextureGameBG = nullptr; + + mTextureGameText->unload(); + delete mTextureGameText; + mTextureGameText = nullptr; + + mTextureItems->unload(); + delete mTextureItems; + mTextureItems = nullptr; + + mTexturePlayerBody->unload(); + delete mTexturePlayerBody; + mTexturePlayerBody = nullptr; + + mTexturePlayerDeath->unload(); + delete mTexturePlayerDeath; + mTexturePlayerDeath = nullptr; + + mTexturePlayerLegs->unload(); + delete mTexturePlayerLegs; + mTexturePlayerLegs = nullptr; + + mTextureText->unload(); + delete mTextureText; + mTextureText = nullptr; + + mTextureText2->unload(); + delete mTextureText2; + mTextureText2 = nullptr; + + delete mText; + mText = nullptr; + + delete mTextX2; + mTextX2 = nullptr; + + delete mMenuGameOver; + mMenuGameOver = nullptr; + + delete mMenuPause; + mMenuPause = nullptr; + + delete mFade; + mFade = nullptr; + + delete mEventHandler; + mEventHandler = nullptr; + + delete mClouds1a; + mClouds1a = nullptr; + + delete mClouds1b; + mClouds1b = nullptr; + + delete mClouds2a; + mClouds2a = nullptr; + + delete mClouds2b; + mClouds2b = nullptr; + + delete m1000Bitmap; + m1000Bitmap = nullptr; + + delete m2500Bitmap; + m2500Bitmap = nullptr; + + delete m5000Bitmap; + m5000Bitmap = nullptr; + + delete mSpriteBackground; + mSpriteBackground = nullptr; + + delete mSpriteGetReady; + mSpriteGetReady = nullptr; + + delete mSpriteGradient; + mSpriteGradient = nullptr; + + delete mSpriteGrass; + mSpriteGrass = nullptr; + + delete mSpritePowerMeter; + mSpritePowerMeter = nullptr; + + delete mSpriteScoreBoard; + mSpriteScoreBoard = nullptr; + + JA_DeleteSound(mSoundBalloon); + JA_DeleteSound(mSoundBullet); + JA_DeleteSound(mSoundPlayerCollision); + JA_DeleteSound(mSoundHiScore); + JA_DeleteSound(mSoundItemDrop); + JA_DeleteSound(mSoundItemPickup); + JA_DeleteSound(mSoundCoffeeOut); + JA_DeleteSound(mSoundStageChange); + JA_DeleteSound(mSoundBubble1); + JA_DeleteSound(mSoundBubble2); + JA_DeleteSound(mSoundBubble3); + JA_DeleteSound(mSoundBubble4); + JA_DeleteSound(mSoundClock); + JA_DeleteSound(mSoundPowerBall); + + JA_DeleteMusic(mMusicPlaying); +} + +// Inicializa el vector con los valores del seno +void Game::initSin() +{ + // Vector con los valores del seno para 360 grados + for (int i = 0; i < 360; i++) + mSin[i] = sin((float)i * 3.14f / 180.0f); +} + +// Inicializa las variables necesarias para la sección 'Game' +void Game::init() +{ + // Carga los recursos + loadMedia(); + + // Carga ficheros + loadScoreFile(); + loadDemoFile(); + + mTicks = 0; + mTicksSpeed = 15; + + // Inicializa las variables + mSection.name = PROG_SECTION_GAME; + mSection.subsection = GAME_SECTION_PLAY; + mMenaceCurrent = 0; + mMenaceThreshold = 0; + mScore = 0; + mHiScoreAchieved = false; + mCurrentStage = 0; + mStageBitmapCounter = STAGE_COUNTER; + mDeathCounter = DEATH_COUNTER; + mExplosionTime = false; + mRemainingExplosions = REMAINING_EXPLOSIONS; + mRemainingExplosionsCounter = REMAINING_EXPLOSIONS_COUNTER; + mTimeStopped = false; + mTimeStoppedCounter = 0; + mCounter = 0; + mBalloonsPopped = 0; + mLastEnemyDeploy = 0; + mEnemyDeployCounter = 0; + mEnemySpeed = BALLOON_SPEED_1; + mEffect.flash = false; + mEffect.shake = false; + mEffect.shakeCounter = SHAKE_COUNTER; + mPowerBallEnabled = false; + mPostFade = 0; + + if (mDemo.enabled) + { + int num = rand() % 2; + if (num == 0) + { + mBalloonsPopped = 1000; + mCurrentStage = 3; + } + else + { + mBalloonsPopped = 1800; + mCurrentStage = 6; + } + } + + initSin(); + initPaths(); + initEnemyFormations(); + initEnemyPools(); + initGameStages(); + + // Modo debug + mDebug.enabled = false; + mDebug.enemySet = 0; + mDebug.gradR = mDebug.gradG = mDebug.gradB = 0; + + // Modo demo + mDemo.recording = false; + mDemo.counter = 0; + + // Inicializa los objetos de texto + mText->init(TEXT_FIXED, BLOCK); + mTextX2->init(TEXT_FIXED, BLOCK * 2); + + // Inicializa el objeto con el menu de pausa + mMenuPause->init("PAUSE", 0, 12 * BLOCK, MENU_BACKGROUND_SOLID); + mMenuPause->addItem("CONTINUE"); + mMenuPause->addItem("EXIT TO TITLE"); + mMenuPause->setDefaultActionWhenCancel(0); + mMenuPause->setBackgroundColor(0x29, 0x39, 0x41, 240); + mMenuPause->setSelectorColor(0xFF, 0x7A, 0x00, 255); + mMenuPause->setSelectorTextColor(0xFF, 0xFF, 0xFF); + mMenuPause->centerMenu(SCREEN_CENTER_X); + mMenuPause->centerMenuElements(); + + // Inicializa el objeto con el menu de la pantalla de game over + mMenuGameOver->init("GAME OVER", 0, PLAY_AREA_CENTER_Y + BLOCK * 4, MENU_BACKGROUND_TRANSPARENT); + mMenuGameOver->addItem("YES"); + mMenuGameOver->addItem("NO"); + mMenuGameOver->setDefaultActionWhenCancel(1); + mMenuGameOver->setBackgroundColor(0, 0, 0, 255); + mMenuGameOver->setSelectorColor(0x54, 0x6e, 0x7a, 255); + mMenuGameOver->setSelectorColor(0x54, 0x6e, 0x7a, 0); + mMenuGameOver->setSelectorTextColor(0xFF, 0xFF, 0xFF); + mMenuGameOver->setSelectorTextColor(0xFF, 0xF1, 0x76); + mMenuGameOver->setSelectorTextColor(0xFF, 0x7A, 0x00); + mMenuGameOver->centerMenu(SCREEN_CENTER_X); + mMenuGameOver->centerMenuElements(); + + // Sprites + mClouds1a->init(0, 0, 256, 52, -0.4f, 0.0f, 0.0f, 0.0f, mTextureGameBG, mRenderer); + mClouds1a->setSpriteClip(256, 0, 256, 52); + + mClouds1b->init(256, 0, 256, 52, -0.4f, 0.0f, 0.0f, 0.0f, mTextureGameBG, mRenderer); + mClouds1b->setSpriteClip(256, 0, 256, 52); + + mClouds2a->init(0, 52, 256, 32, -0.2f, 0.0f, 0.0f, 0.0f, mTextureGameBG, mRenderer); + mClouds2a->setSpriteClip(256, 52, 256, 32); + + mClouds2b->init(256, 52, 256, 32, -0.2f, 0.0f, 0.0f, 0.0f, mTextureGameBG, mRenderer); + mClouds2b->setSpriteClip(256, 52, 256, 32); + + mSpriteGrass->init(0, 85, SCREEN_WIDTH, 6, mTextureGameBG, mRenderer); + mSpriteGrass->setPosY(154); + + mSpriteScoreBoard->init(0, 160, SCREEN_WIDTH, 32, mTextureGameBG, mRenderer); + mSpriteScoreBoard->setSpriteClip(0, 160, 256, 32); + + mSpritePowerMeter->init(PLAY_AREA_CENTER_THIRD_QUARTER_X - 20, HISCORE_NUMBER_Y + 4, 40, 8, mTextureGameBG, mRenderer); + mSpritePowerMeter->setSpriteClip(256, 192 - 8, 40, 8); + + // Objeto jugador + mPlayer->init(PLAY_AREA_CENTER_X - 11, PLAY_AREA_BOTTOM - 24, mTexturePlayerLegs, mTexturePlayerBody, mRenderer); + + // Establece a cero todos los valores del vector de objetos globo + resetBalloons(); + + // Con los globos creados, calcula el nivel de amenaza + setMenace(); + + // Establece a cero todos los valores del vector de objetos bala + resetBullets(); + + // Establece a cero todos los valores del vector de objetos item + resetItems(); + + // Establece a cero todos los valores del vector de objetos SmafrtSprite + resetSmartSprites(); + + // Inicializa el bitmap de GetLeady! + mSpriteGetReady->init(0, PLAY_AREA_CENTER_Y - 10, 109, 20, mTextureGameText, mRenderer); + mSpriteGetReady->setSpriteClip(0, 0, 109, 20); + + // Inicializa el bitmap de 1000 puntos + m1000Bitmap->init(mTextureGameText, mRenderer); + m1000Bitmap->setPosX(0); + m1000Bitmap->setPosY(0); + m1000Bitmap->setWidth(26); + m1000Bitmap->setHeight(9); + m1000Bitmap->setVelX(0.0f); + m1000Bitmap->setVelY(-0.5f); + m1000Bitmap->setAccelX(0.0f); + m1000Bitmap->setAccelY(-0.1f); + m1000Bitmap->setSpriteClip(0, 20, 26, 9); + m1000Bitmap->setEnabled(false); + m1000Bitmap->setEnabledTimer(0); + m1000Bitmap->setDestX(0); + m1000Bitmap->setDestY(0); + + // Inicializa el bitmap de 2500 puntos + m2500Bitmap->init(mTextureGameText, mRenderer); + m2500Bitmap->setPosX(0); + m2500Bitmap->setPosY(0); + m2500Bitmap->setWidth(28); + m2500Bitmap->setHeight(9); + m2500Bitmap->setVelX(0.0f); + m2500Bitmap->setVelY(-0.5f); + m2500Bitmap->setAccelX(0.0f); + m2500Bitmap->setAccelY(-0.1f); + m2500Bitmap->setSpriteClip(26, 20, 28, 9); + m2500Bitmap->setEnabled(false); + m2500Bitmap->setEnabledTimer(0); + m2500Bitmap->setDestX(0); + m2500Bitmap->setDestY(0); + + // Inicializa el bitmap de 5000 puntos + m5000Bitmap->init(mTextureGameText, mRenderer); + m5000Bitmap->setPosX(0); + m5000Bitmap->setPosY(0); + m5000Bitmap->setWidth(28); + m5000Bitmap->setHeight(9); + m5000Bitmap->setVelX(0.0f); + m5000Bitmap->setVelY(-0.5f); + m5000Bitmap->setAccelX(0.0f); + m5000Bitmap->setAccelY(-0.1f); + m5000Bitmap->setSpriteClip(54, 20, 28, 9); + m5000Bitmap->setEnabled(false); + m5000Bitmap->setEnabledTimer(0); + m5000Bitmap->setDestX(0); + m5000Bitmap->setDestY(0); + + // Los fondos + mSpriteBackground->init(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, mTextureGameBG, mRenderer); + mSpriteGradient->init(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, mTextureGameBG, mRenderer); + mGradientRect[0] = {0, 192, SCREEN_WIDTH, SCREEN_HEIGHT}; + mGradientRect[1] = {256, 192, SCREEN_WIDTH, SCREEN_HEIGHT}; + mGradientRect[2] = {0, 384, SCREEN_WIDTH, SCREEN_HEIGHT}; + mGradientRect[3] = {256, 384, SCREEN_WIDTH, SCREEN_HEIGHT}; +} + +// Carga los recursos necesarios para la sección 'Game' +bool Game::loadMedia() +{ + bool success = true; + + // Texturas + success &= loadTextureFromFile(mTextureText, mFileList[30], mRenderer); + success &= loadTextureFromFile(mTextureText2, mFileList[29], mRenderer); + success &= loadTextureFromFile(mTexturePlayerLegs, mFileList[39], mRenderer); + success &= loadTextureFromFile(mTexturePlayerBody, mFileList[37], mRenderer); + success &= loadTextureFromFile(mTexturePlayerDeath, mFileList[38], mRenderer); + success &= loadTextureFromFile(mTextureBalloon, mFileList[24], mRenderer); + success &= loadTextureFromFile(mTextureBullet, mFileList[25], mRenderer); + success &= loadTextureFromFile(mTextureGameBG, mFileList[31], mRenderer); + success &= loadTextureFromFile(mTextureItems, mFileList[34], mRenderer); + success &= loadTextureFromFile(mTextureGameText, mFileList[32], mRenderer); + + // Sonidos + mSoundBalloon = JA_LoadSound(mFileList[6].c_str()); + mSoundBubble1 = JA_LoadSound(mFileList[7].c_str()); + mSoundBubble2 = JA_LoadSound(mFileList[8].c_str()); + mSoundBubble3 = JA_LoadSound(mFileList[9].c_str()); + mSoundBubble4 = JA_LoadSound(mFileList[10].c_str()); + mSoundBullet = JA_LoadSound(mFileList[11].c_str()); + mSoundClock = JA_LoadSound(mFileList[22].c_str()); + mSoundCoffeeOut = JA_LoadSound(mFileList[12].c_str()); + mSoundHiScore = JA_LoadSound(mFileList[13].c_str()); + mSoundItemDrop = JA_LoadSound(mFileList[14].c_str()); + mSoundItemPickup = JA_LoadSound(mFileList[15].c_str()); + mSoundPlayerCollision = JA_LoadSound(mFileList[19].c_str()); + mSoundPowerBall = JA_LoadSound(mFileList[23].c_str()); + mSoundStageChange = JA_LoadSound(mFileList[20].c_str()); + + // Musicas + mMusicPlaying = JA_LoadMusic(mFileList[4].c_str()); + + return success; +} + +// Carga el fichero de puntos +bool Game::loadScoreFile() +{ + // Indicador de éxito en la carga + bool success = true; + const std::string p = mFileList[0]; + const std::string filename = p.substr(p.find_last_of("\\/") + 1); + SDL_RWops *file = SDL_RWFromFile(p.c_str(), "r+b"); + + // El fichero no existe + if (file == NULL) + { + printf("Warning: Unable to open %s file\n", filename.c_str()); + + // Creamos el fichero para escritura + file = SDL_RWFromFile(p.c_str(), "w+b"); + if (file != NULL) + { + printf("New file (%s) created!\n", filename.c_str()); + + // Inicializamos los datos + for (int i = 0; i < TOTAL_SCORE_DATA; ++i) + { + mScoreDataFile[i] = 0; + SDL_RWwrite(file, &mScoreDataFile[i], sizeof(Uint32), 1); + } + + // Cerramos el fichero + SDL_RWclose(file); + } + else + { + printf("Error: Unable to create file %s\n", filename.c_str()); + success = false; + } + } + // El fichero existe + else + { + // Cargamos los datos + printf("Reading file %s\n", filename.c_str()); + for (int i = 0; i < TOTAL_SCORE_DATA; ++i) + SDL_RWread(file, &mScoreDataFile[i], sizeof(Uint32), 1); + + // Cierra el fichero + SDL_RWclose(file); + } + + // Establece el valor de la máxima puntuación a partir del vector con los datos + if (mScoreDataFile[0] == 0) + mHiScore = 10000; + // Comprueba el checksum para ver si se ha modificado el fichero + else if (mScoreDataFile[0] % 43 == mScoreDataFile[1]) + mHiScore = mScoreDataFile[0]; + else + mHiScore = 10000; + + return success; +} + +// Carga el fichero de datos para la demo +bool Game::loadDemoFile() +{ + // Indicador de éxito en la carga + bool success = true; + const std::string p = mFileList[1]; + const std::string filename = p.substr(p.find_last_of("\\/") + 1); + SDL_RWops *file = SDL_RWFromFile(p.c_str(), "r+b"); + + // El fichero no existe + if (file == NULL) + { + printf("Warning: Unable to open %s file\n", filename.c_str()); + + // Creamos el fichero para escritura + file = SDL_RWFromFile(p.c_str(), "w+b"); + if (file != NULL) + { + printf("New file (%s) created!\n", filename.c_str()); + + // Inicializamos los datos + for (int i = 0; i < TOTAL_DEMO_DATA; ++i) + { + mDemo.keys.left = 0; + mDemo.keys.right = 0; + mDemo.keys.noInput = 0; + mDemo.keys.fire = 0; + mDemo.keys.fireLeft = 0; + mDemo.keys.fireRight = 0; + mDemo.dataFile[i] = mDemo.keys; + SDL_RWwrite(file, &mDemo.dataFile[i], sizeof(demoKeys_t), 1); + } + + // Cerramos el fichero + SDL_RWclose(file); + } + else + { + printf("Error: Unable to create file %s\n", filename.c_str()); + success = false; + } + } + // El fichero existe + else + { + // Cargamos los datos + printf("Reading file %s\n", filename.c_str()); + for (int i = 0; i < TOTAL_DEMO_DATA; ++i) + SDL_RWread(file, &mDemo.dataFile[i], sizeof(demoKeys_t), 1); + + // Cierra el fichero + SDL_RWclose(file); + } + + return success; +} + +// Guarda el fichero de puntos +bool Game::saveScoreFile() +{ + bool success = true; + const std::string p = mFileList[0]; + const std::string filename = p.substr(p.find_last_of("\\/") + 1); + SDL_RWops *file = SDL_RWFromFile(p.c_str(), "w+b"); + if (file != NULL) + { + // Guardamos los datos + for (int i = 0; i < TOTAL_SCORE_DATA; ++i) + { + SDL_RWwrite(file, &mScoreDataFile[i], sizeof(Uint32), 1); + } + + printf("Writing file %s\n", filename.c_str()); + + // Cerramos el fichero + SDL_RWclose(file); + } + else + { + printf("Error: Unable to save %s file! %s\n", filename.c_str(), SDL_GetError()); + } + return success; +} + +// Guarda el fichero de datos para la demo +bool Game::saveDemoFile() +{ + bool success = true; + const std::string p = mFileList[1]; + const std::string filename = p.substr(p.find_last_of("\\/") + 1); + if (mDemo.recording) + { + SDL_RWops *file = SDL_RWFromFile(p.c_str(), "w+b"); + if (file != NULL) + { + // Guardamos los datos + for (int i = 0; i < TOTAL_DEMO_DATA; ++i) + { + SDL_RWwrite(file, &mDemo.dataFile[i], sizeof(demoKeys_t), 1); + } + + printf("Writing file %s\n", filename.c_str()); + + // Cerramos el fichero + SDL_RWclose(file); + } + else + { + printf("Error: Unable to save %s file! %s\n", filename.c_str(), SDL_GetError()); + } + } + return success; +} + +// Inicializa las formaciones enemigas +void Game::initEnemyFormations() +{ + const int y4 = (PLAY_AREA_TOP - BLOCK); + const int x4_0 = PLAY_AREA_LEFT; + const int x4_100 = (PLAY_AREA_RIGHT)-BALLOON_WIDTH_4; + + const int y3 = (PLAY_AREA_TOP - BLOCK); + const int x3_0 = PLAY_AREA_LEFT; + const int x3_100 = (PLAY_AREA_RIGHT)-BALLOON_WIDTH_3; + + const int y2 = (PLAY_AREA_TOP - BLOCK); + const int x2_0 = PLAY_AREA_LEFT; + const int x2_100 = (PLAY_AREA_RIGHT)-BALLOON_WIDTH_2; + + const int y1 = (PLAY_AREA_TOP - BLOCK); + const int x1_0 = PLAY_AREA_LEFT; + const int x1_50 = PLAY_AREA_CENTER_X - (BALLOON_WIDTH_1 / 2); + const int x1_100 = (PLAY_AREA_RIGHT)-BALLOON_WIDTH_1; + + const Uint16 creationTime = 300; + int incX = 0; + Uint8 incTime = 0; + Uint8 j = 0; + + // #00 - Dos enemigos BALLOON4 uno a cada extremo + j = 0; + mEnemyFormation[j].numberOfEnemies = 2; + incX = x4_100; + incTime = 0; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x4_0 + (i * incX); + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE * (((i % 2) * 2) - 1); + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime + (incTime * i); + } + + // #01 - Dos enemigos BALLOON4 uno a cada cuarto. Ambos van hacia el centro + j = 1; + mEnemyFormation[j].numberOfEnemies = 2; + incX = PLAY_AREA_CENTER_X; + incTime = 0; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = PLAY_AREA_CENTER_FIRST_QUARTER_X - (BALLOON_WIDTH_4 / 2) + (i * incX); + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE * (((i % 2) * 2) - 1); + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime + (incTime * i); + } + + // #02 - Cuatro enemigos BALLOON2 uno detras del otro. A la izquierda y hacia el centro + j = 2; + mEnemyFormation[j].numberOfEnemies = 4; + incX = BALLOON_WIDTH_2 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x2_0 + (i * incX); + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_2; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #03 - Cuatro enemigos BALLOON2 uno detras del otro. A la derecha y hacia el centro + j = 3; + mEnemyFormation[j].numberOfEnemies = 4; + incX = BALLOON_WIDTH_2 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x2_100 - (i * incX); + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_2; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #04 - Tres enemigos BALLOON3. 0, 25, 50. Hacia la derecha + j = 4; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_WIDTH_3 * 2; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #05 - Tres enemigos BALLOON3. 50, 75, 100. Hacia la izquierda + j = 5; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_WIDTH_3 * 2; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_100 - (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #06 - Tres enemigos BALLOON3. 0, 0, 0. Hacia la derecha + j = 6; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_WIDTH_3 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #07 - Tres enemigos BALLOON3. 100, 100, 100. Hacia la izquierda + j = 7; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_WIDTH_3 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_100 - (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #08 - Seis enemigos BALLOON1. 0, 0, 0, 0, 0, 0. Hacia la derecha + j = 8; + mEnemyFormation[j].numberOfEnemies = 6; + incX = BALLOON_WIDTH_1 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x1_0 + (i * incX); + mEnemyFormation[j].init[i].y = 13; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_1; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #09 - Seis enemigos BALLOON1. 100, 100, 100, 100, 100, 100. Hacia la izquierda + j = 9; + mEnemyFormation[j].numberOfEnemies = 6; + incX = BALLOON_WIDTH_1 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x1_100 - (i * incX); + mEnemyFormation[j].init[i].y = 13; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_1; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #10 - Tres enemigos BALLOON4 seguidos desde la izquierda + j = 10; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_WIDTH_4 + 1; + incTime = 15; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x4_0 + (i * incX); + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #11 - Tres enemigos BALLOON4 seguidos desde la derecha + j = 11; + mEnemyFormation[j].numberOfEnemies = 3; + incX = BALLOON_WIDTH_4 + 1; + incTime = 15; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x4_100 - (i * incX); + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #12 - Seis enemigos BALLOON2 uno detras del otro. A la izquierda y hacia el centro + j = 12; + mEnemyFormation[j].numberOfEnemies = 6; + incX = BALLOON_WIDTH_2 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x2_0 + (i * incX); + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_2; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #13 - Seis enemigos BALLOON2 uno detras del otro. A la derecha y hacia el centro + j = 13; + mEnemyFormation[j].numberOfEnemies = 6; + incX = BALLOON_WIDTH_2 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x2_100 - (i * incX); + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_2; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #14 - Cinco enemigos BALLOON3. Hacia la derecha. Separados + j = 14; + mEnemyFormation[j].numberOfEnemies = 5; + incX = BALLOON_WIDTH_3 * 2; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #15 - Cinco enemigos BALLOON3. Hacia la izquierda. Separados + j = 15; + mEnemyFormation[j].numberOfEnemies = 5; + incX = BALLOON_WIDTH_3 * 2; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_100 - (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #16 - Cinco enemigos BALLOON3. Hacia la derecha. Juntos + j = 16; + mEnemyFormation[j].numberOfEnemies = 5; + incX = BALLOON_WIDTH_3 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #17 - Cinco enemigos BALLOON3. Hacia la izquierda. Juntos + j = 17; + mEnemyFormation[j].numberOfEnemies = 5; + incX = BALLOON_WIDTH_3 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x3_100 - (i * incX); + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_3; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #18 - Doce enemigos BALLOON1. Hacia la derecha. Juntos + j = 18; + mEnemyFormation[j].numberOfEnemies = 12; + incX = BALLOON_WIDTH_1 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x1_0 + (i * incX); + mEnemyFormation[j].init[i].y = y1; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].kind = BALLOON_1; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #19 - Doce enemigos BALLOON1. Hacia la izquierda. Juntos + j = 19; + mEnemyFormation[j].numberOfEnemies = 12; + incX = BALLOON_WIDTH_1 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + mEnemyFormation[j].init[i].x = x1_100 - (i * incX); + mEnemyFormation[j].init[i].y = y1; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].kind = BALLOON_1; + mEnemyFormation[j].init[i].creationCounter = creationTime - (incTime * i); + } + + // #20 - Dos enemigos BALLOON4 seguidos desde la izquierda/derecha. Simetricos + j = 20; + mEnemyFormation[j].numberOfEnemies = 4; + incX = BALLOON_WIDTH_4 + 1; + incTime = 0; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x4_0 + (i * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + } + else + { + mEnemyFormation[j].init[i].x = x4_100 - ((i - half) * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + } + mEnemyFormation[j].init[i].y = y4; + mEnemyFormation[j].init[i].kind = BALLOON_4; + mEnemyFormation[j].init[i].creationCounter = creationTime + (incTime * i); + } + + // #21 - Diez enemigos BALLOON2 uno detras del otro. Izquierda/derecha. Simetricos + j = 21; + mEnemyFormation[j].numberOfEnemies = 10; + incX = BALLOON_WIDTH_2 + 1; + incTime = 3; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x2_0 + (i * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x2_100 - ((i - half) * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y2; + mEnemyFormation[j].init[i].kind = BALLOON_2; + } + + // #22 - Diez enemigos BALLOON3. Hacia la derecha/izquierda. Separados. Simetricos + j = 22; + mEnemyFormation[j].numberOfEnemies = 10; + incX = BALLOON_WIDTH_3 * 2; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x3_100 - ((i - half) * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].kind = BALLOON_3; + } + + // #23 - Diez enemigos BALLOON3. Hacia la derecha. Juntos. Simetricos + j = 23; + mEnemyFormation[j].numberOfEnemies = 10; + incX = BALLOON_WIDTH_3 + 1; + incTime = 10; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x3_0 + (i * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x3_100 - ((i - half) * incX); + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y3; + mEnemyFormation[j].init[i].kind = BALLOON_3; + } + + // #24 - Treinta enemigos BALLOON1. Del centro hacia los extremos. Juntos. Simetricos + j = 24; + mEnemyFormation[j].numberOfEnemies = 30; + incX = 0; + incTime = 5; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x1_50; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) + (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x1_50; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) + (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y1; + mEnemyFormation[j].init[i].kind = BALLOON_1; + } + + // #25 - Treinta enemigos BALLOON1. Del centro hacia adentro. Juntos. Simetricos + j = 25; + mEnemyFormation[j].numberOfEnemies = 30; + incX = BALLOON_WIDTH_1 + 1; + incTime = 5; + for (int i = 0; i < mEnemyFormation[j].numberOfEnemies; i++) + { + Uint8 half = mEnemyFormation[j].numberOfEnemies / 2; + if (i < half) + { + mEnemyFormation[j].init[i].x = x1_50 + 20; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_NEGATIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * i); + } + else + { + mEnemyFormation[j].init[i].x = x1_50 - 20; + mEnemyFormation[j].init[i].velX = BALLOON_VELX_POSITIVE; + mEnemyFormation[j].init[i].creationCounter = (creationTime) - (incTime * (i - half)); + } + mEnemyFormation[j].init[i].y = y1; + mEnemyFormation[j].init[i].kind = BALLOON_1; + } + + // Crea las mismas formaciones pero con hexagonos a partir de la posición 50 del vector + for (int k = 0; k < 50; k++) + { + mEnemyFormation[k + 50].numberOfEnemies = mEnemyFormation[k].numberOfEnemies; + for (int i = 0; i < mEnemyFormation[k + 50].numberOfEnemies; i++) + { + mEnemyFormation[k + 50].init[i].x = mEnemyFormation[k].init[i].x; + mEnemyFormation[k + 50].init[i].y = mEnemyFormation[k].init[i].y; + mEnemyFormation[k + 50].init[i].velX = mEnemyFormation[k].init[i].velX; + mEnemyFormation[k + 50].init[i].creationCounter = mEnemyFormation[k].init[i].creationCounter; + mEnemyFormation[k + 50].init[i].kind = mEnemyFormation[k].init[i].kind + 4; + } + } + + // TEST + mEnemyFormation[99].numberOfEnemies = 4; + + mEnemyFormation[99].init[0].x = 10; + mEnemyFormation[99].init[0].y = y1; + mEnemyFormation[99].init[0].velX = 0; + mEnemyFormation[99].init[0].kind = BALLOON_1; + mEnemyFormation[99].init[0].creationCounter = 200; + + mEnemyFormation[99].init[1].x = 50; + mEnemyFormation[99].init[1].y = y1; + mEnemyFormation[99].init[1].velX = 0; + mEnemyFormation[99].init[1].kind = BALLOON_2; + mEnemyFormation[99].init[1].creationCounter = 200; + + mEnemyFormation[99].init[2].x = 90; + mEnemyFormation[99].init[2].y = y1; + mEnemyFormation[99].init[2].velX = 0; + mEnemyFormation[99].init[2].kind = BALLOON_3; + mEnemyFormation[99].init[2].creationCounter = 200; + + mEnemyFormation[99].init[3].x = 140; + mEnemyFormation[99].init[3].y = y1; + mEnemyFormation[99].init[3].velX = 0; + mEnemyFormation[99].init[3].kind = BALLOON_4; + mEnemyFormation[99].init[3].creationCounter = 200; +} + +// Inicializa los conjuntos de formaciones +void Game::initEnemyPools() +{ + // EnemyPool #0 + mEnemyPool[0].set[0] = &mEnemyFormation[0]; + mEnemyPool[0].set[1] = &mEnemyFormation[1]; + mEnemyPool[0].set[2] = &mEnemyFormation[2]; + mEnemyPool[0].set[3] = &mEnemyFormation[3]; + mEnemyPool[0].set[4] = &mEnemyFormation[4]; + mEnemyPool[0].set[5] = &mEnemyFormation[5]; + mEnemyPool[0].set[6] = &mEnemyFormation[6]; + mEnemyPool[0].set[7] = &mEnemyFormation[7]; + mEnemyPool[0].set[8] = &mEnemyFormation[8]; + mEnemyPool[0].set[9] = &mEnemyFormation[9]; + + // EnemyPool #1 + mEnemyPool[1].set[0] = &mEnemyFormation[10]; + mEnemyPool[1].set[1] = &mEnemyFormation[11]; + mEnemyPool[1].set[2] = &mEnemyFormation[12]; + mEnemyPool[1].set[3] = &mEnemyFormation[13]; + mEnemyPool[1].set[4] = &mEnemyFormation[14]; + mEnemyPool[1].set[5] = &mEnemyFormation[15]; + mEnemyPool[1].set[6] = &mEnemyFormation[16]; + mEnemyPool[1].set[7] = &mEnemyFormation[17]; + mEnemyPool[1].set[8] = &mEnemyFormation[18]; + mEnemyPool[1].set[9] = &mEnemyFormation[19]; + + // EnemyPool #2 + mEnemyPool[2].set[0] = &mEnemyFormation[0]; + mEnemyPool[2].set[1] = &mEnemyFormation[1]; + mEnemyPool[2].set[2] = &mEnemyFormation[2]; + mEnemyPool[2].set[3] = &mEnemyFormation[3]; + mEnemyPool[2].set[4] = &mEnemyFormation[4]; + mEnemyPool[2].set[5] = &mEnemyFormation[55]; + mEnemyPool[2].set[6] = &mEnemyFormation[56]; + mEnemyPool[2].set[7] = &mEnemyFormation[57]; + mEnemyPool[2].set[8] = &mEnemyFormation[58]; + mEnemyPool[2].set[9] = &mEnemyFormation[59]; + + // EnemyPool #3 + mEnemyPool[3].set[0] = &mEnemyFormation[50]; + mEnemyPool[3].set[1] = &mEnemyFormation[51]; + mEnemyPool[3].set[2] = &mEnemyFormation[52]; + mEnemyPool[3].set[3] = &mEnemyFormation[53]; + mEnemyPool[3].set[4] = &mEnemyFormation[54]; + mEnemyPool[3].set[5] = &mEnemyFormation[5]; + mEnemyPool[3].set[6] = &mEnemyFormation[6]; + mEnemyPool[3].set[7] = &mEnemyFormation[7]; + mEnemyPool[3].set[8] = &mEnemyFormation[8]; + mEnemyPool[3].set[9] = &mEnemyFormation[9]; + + // EnemyPool #4 + mEnemyPool[4].set[0] = &mEnemyFormation[60]; + mEnemyPool[4].set[1] = &mEnemyFormation[61]; + mEnemyPool[4].set[2] = &mEnemyFormation[62]; + mEnemyPool[4].set[3] = &mEnemyFormation[63]; + mEnemyPool[4].set[4] = &mEnemyFormation[64]; + mEnemyPool[4].set[5] = &mEnemyFormation[65]; + mEnemyPool[4].set[6] = &mEnemyFormation[66]; + mEnemyPool[4].set[7] = &mEnemyFormation[67]; + mEnemyPool[4].set[8] = &mEnemyFormation[68]; + mEnemyPool[4].set[9] = &mEnemyFormation[69]; + + // EnemyPool #5 + mEnemyPool[5].set[0] = &mEnemyFormation[10]; + mEnemyPool[5].set[1] = &mEnemyFormation[61]; + mEnemyPool[5].set[2] = &mEnemyFormation[12]; + mEnemyPool[5].set[3] = &mEnemyFormation[63]; + mEnemyPool[5].set[4] = &mEnemyFormation[14]; + mEnemyPool[5].set[5] = &mEnemyFormation[65]; + mEnemyPool[5].set[6] = &mEnemyFormation[16]; + mEnemyPool[5].set[7] = &mEnemyFormation[67]; + mEnemyPool[5].set[8] = &mEnemyFormation[18]; + mEnemyPool[5].set[9] = &mEnemyFormation[69]; + + // EnemyPool #6 + mEnemyPool[6].set[0] = &mEnemyFormation[60]; + mEnemyPool[6].set[1] = &mEnemyFormation[11]; + mEnemyPool[6].set[2] = &mEnemyFormation[62]; + mEnemyPool[6].set[3] = &mEnemyFormation[13]; + mEnemyPool[6].set[4] = &mEnemyFormation[64]; + mEnemyPool[6].set[5] = &mEnemyFormation[15]; + mEnemyPool[6].set[6] = &mEnemyFormation[66]; + mEnemyPool[6].set[7] = &mEnemyFormation[17]; + mEnemyPool[6].set[8] = &mEnemyFormation[68]; + mEnemyPool[6].set[9] = &mEnemyFormation[19]; + + // EnemyPool #7 + mEnemyPool[7].set[0] = &mEnemyFormation[20]; + mEnemyPool[7].set[1] = &mEnemyFormation[21]; + mEnemyPool[7].set[2] = &mEnemyFormation[22]; + mEnemyPool[7].set[3] = &mEnemyFormation[23]; + mEnemyPool[7].set[4] = &mEnemyFormation[24]; + mEnemyPool[7].set[5] = &mEnemyFormation[65]; + mEnemyPool[7].set[6] = &mEnemyFormation[66]; + mEnemyPool[7].set[7] = &mEnemyFormation[67]; + mEnemyPool[7].set[8] = &mEnemyFormation[68]; + mEnemyPool[7].set[9] = &mEnemyFormation[69]; + + // EnemyPool #8 + mEnemyPool[8].set[0] = &mEnemyFormation[70]; + mEnemyPool[8].set[1] = &mEnemyFormation[71]; + mEnemyPool[8].set[2] = &mEnemyFormation[72]; + mEnemyPool[8].set[3] = &mEnemyFormation[73]; + mEnemyPool[8].set[4] = &mEnemyFormation[74]; + mEnemyPool[8].set[5] = &mEnemyFormation[15]; + mEnemyPool[8].set[6] = &mEnemyFormation[16]; + mEnemyPool[8].set[7] = &mEnemyFormation[17]; + mEnemyPool[8].set[8] = &mEnemyFormation[18]; + mEnemyPool[8].set[9] = &mEnemyFormation[19]; + + // EnemyPool #9 + mEnemyPool[9].set[0] = &mEnemyFormation[20]; + mEnemyPool[9].set[1] = &mEnemyFormation[21]; + mEnemyPool[9].set[2] = &mEnemyFormation[22]; + mEnemyPool[9].set[3] = &mEnemyFormation[23]; + mEnemyPool[9].set[4] = &mEnemyFormation[24]; + mEnemyPool[9].set[5] = &mEnemyFormation[70]; + mEnemyPool[9].set[6] = &mEnemyFormation[71]; + mEnemyPool[9].set[7] = &mEnemyFormation[72]; + mEnemyPool[9].set[8] = &mEnemyFormation[73]; + mEnemyPool[9].set[9] = &mEnemyFormation[74]; +} + +// Inicializa las fases del juego +void Game::initGameStages() +{ + // STAGE 1 + mStage[0].number = 1; + mStage[0].currentPower = 0; + mStage[0].powerToComplete = 200; + mStage[0].minMenace = 7 + (4 * 1); + mStage[0].maxMenace = 7 + (4 * 3); + mStage[0].enemyPool = &mEnemyPool[0]; + + // STAGE 2 + mStage[1].number = 2; + mStage[1].currentPower = 0; + mStage[1].powerToComplete = 300; + mStage[1].minMenace = 7 + (4 * 2); + mStage[1].maxMenace = 7 + (4 * 4); + mStage[1].enemyPool = &mEnemyPool[1]; + + // STAGE 3 + mStage[2].number = 3; + mStage[2].currentPower = 0; + mStage[2].powerToComplete = 600; + mStage[2].minMenace = 7 + (4 * 3); + mStage[2].maxMenace = 7 + (4 * 6); + mStage[2].enemyPool = &mEnemyPool[2]; + + // STAGE 4 + mStage[3].number = 4; + mStage[3].currentPower = 0; + mStage[3].powerToComplete = 600; + mStage[3].minMenace = 7 + (4 * 3); + mStage[3].maxMenace = 7 + (4 * 7); + mStage[3].enemyPool = &mEnemyPool[3]; + + // STAGE 5 + mStage[4].number = 5; + mStage[4].currentPower = 0; + mStage[4].powerToComplete = 600; + mStage[4].minMenace = 7 + (4 * 4); + mStage[4].maxMenace = 7 + (4 * 8); + mStage[4].enemyPool = &mEnemyPool[4]; + + // STAGE 6 + mStage[5].number = 6; + mStage[5].currentPower = 0; + mStage[5].powerToComplete = 600; + mStage[5].minMenace = 7 + (4 * 5); + mStage[5].maxMenace = 7 + (4 * 10); + mStage[5].enemyPool = &mEnemyPool[5]; + + // STAGE 7 + mStage[6].number = 7; + mStage[6].currentPower = 0; + mStage[6].powerToComplete = 650; + mStage[6].minMenace = 7 + (4 * 6); + mStage[6].maxMenace = 7 + (4 * 11); + mStage[6].enemyPool = &mEnemyPool[6]; + + // STAGE 8 + mStage[7].number = 8; + mStage[7].currentPower = 0; + mStage[7].powerToComplete = 750; + mStage[7].minMenace = 7 + (4 * 7); + mStage[7].maxMenace = 7 + (4 * 12); + mStage[7].enemyPool = &mEnemyPool[7]; + + // STAGE 9 + mStage[8].number = 9; + mStage[8].currentPower = 0; + mStage[8].powerToComplete = 850; + mStage[8].minMenace = 7 + (4 * 8); + mStage[8].maxMenace = 7 + (4 * 13); + mStage[8].enemyPool = &mEnemyPool[8]; + + // STAGE 10 + mStage[9].number = 10; + mStage[9].currentPower = 0; + mStage[9].powerToComplete = 950; + mStage[9].minMenace = 7 + (4 * 9); + mStage[9].maxMenace = 7 + (4 * 15); + mStage[9].enemyPool = &mEnemyPool[9]; +} + +// Crea una formación de enemigos +void Game::deployEnemyFormation() +{ + // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última + if (mEnemyDeployCounter == 0) + { + + // En este punto se decide entre crear una powerball o una formación enemiga + if ((rand() % 100 < 15) && (canPowerBallBeCreated())) + { + // Crea una powerball + createPowerBall(); + + mEnemyDeployCounter = 50; + } + else + { + + // Elige una formación enemiga la azar + Uint8 set = (rand() % 10); + + // Evita repetir la ultima formación enemiga desplegada + if (set == mLastEnemyDeploy) + { + set++; + set %= 10; + } + mLastEnemyDeploy = set; + + if (mDebug.enabled) + set = mDebug.enemySet; + + Uint8 numEnemies = mStage[mCurrentStage].enemyPool->set[set]->numberOfEnemies; + for (int i = 0; i < numEnemies; i++) + createNewBalloon(mStage[mCurrentStage].enemyPool->set[set]->init[i].x, + mStage[mCurrentStage].enemyPool->set[set]->init[i].y, + mStage[mCurrentStage].enemyPool->set[set]->init[i].kind, + mStage[mCurrentStage].enemyPool->set[set]->init[i].velX, + mEnemySpeed, + mStage[mCurrentStage].enemyPool->set[set]->init[i].creationCounter, + mTextureBalloon); + + mEnemyDeployCounter = 255; + } + } +} + +// Aumenta el poder de la fase +void Game::increaseStageCurrentPower(Uint8 power) +{ + mStage[mCurrentStage].currentPower += power; +} + +// Establece el valor de la variable +void Game::setScore(Uint32 score) +{ + mScore = score; +} + +// Establece el valor de la variable +void Game::setHiScore(Uint32 score) +{ + mHiScore = score; +} + +// Actualiza el valor de mHiScore en caso necesario +void Game::updateHiScore() +{ + // La puntuación es mayor que la máxima puntuación + if (mScore > mHiScore) + { + // Actualiza la máxima puntuación + mHiScore = mScore; + + // Almacena la máxima puntuación en el fichero junto con un checksum + mScoreDataFile[0] = mHiScore; + mScoreDataFile[1] = mHiScore % 43; + + // Si superamos la máxima puntuación + if (mHiScoreAchieved == false) + { + mHiScoreAchieved = true; + JA_PlaySound(mSoundHiScore); + } + } +} + +// Transforma un valor numérico en una cadena de 6 cifras +std::string Game::updateScoreText(Uint32 num) +{ + //return (std::to_string(num)); + + if ((num >= 0) && (num <= 9)) + { + return ("000000" + std::to_string(num)); + } + + if ((num >= 10) && (num <= 99)) + { + return ("00000" + std::to_string(num)); + } + + if ((num >= 100) && (num <= 999)) + { + return ("0000" + std::to_string(num)); + } + + if ((num >= 1000) && (num <= 9999)) + { + return ("000" + std::to_string(num)); + } + + if ((num >= 010000) && (num <= 99999)) + { + return ("00" + std::to_string(num)); + } + + if ((num >= 100000) && (num <= 999999)) + { + return ("0" + std::to_string(num)); + } + + if ((num >= 1000000) && (num <= 9999999)) + { + return (std::to_string(num)); + } + + return (std::to_string(num)); +} + +// Pinta el marcador en pantalla usando un objeto texto +void Game::renderScoreBoard() +{ + color_t color = {0, 0, 0}; + + // Si el jugador esta muerto, no pintes el fondo del marcador, así que pase por encima cuando sale despedido + if (mPlayer->isAlive()) + mSpriteScoreBoard->render(); + + //if (!mDemo.enabled) + { + // SCORE + mText->writeCentered(PLAY_AREA_CENTER_FIRST_QUARTER_X, SCORE_WORD_Y - 6, "SCORE", 0); + mText->writeCentered(PLAY_AREA_CENTER_FIRST_QUARTER_X, SCORE_NUMBER_Y - 6, updateScoreText(mScore), 0); + + // HI-SCORE + mText->writeCentered(PLAY_AREA_CENTER_THIRD_QUARTER_X, HISCORE_WORD_Y - 6, "HI-SCORE", 0); + mText->writeCentered(PLAY_AREA_CENTER_THIRD_QUARTER_X, HISCORE_NUMBER_Y - 6, updateScoreText(mHiScore), 0); + + // MULT + color.g = 255; + mText->writeColored(MULTIPLIER_WORD_X, MULTIPLIER_WORD_Y - 6, "MULT", color); + + color.g = 192; + mTextX2->writeShadowed(PLAY_AREA_CENTER_X - 16, SCORE_WORD_Y + 5, std::to_string(mPlayer->getScoreMultiplier()).substr(0, 1), color, 1); + mText->writeShadowed(PLAY_AREA_CENTER_X - 2, SCORE_WORD_Y + 12, ".", color); + mText->writeShadowed(PLAY_AREA_CENTER_X + 4, SCORE_WORD_Y + 12, std::to_string(mPlayer->getScoreMultiplier()).substr(2, 1), color); + + // STAGE + mText->writeCentered(PLAY_AREA_CENTER_FIRST_QUARTER_X, SCORE_NUMBER_Y + 4, "STAGE " + std::to_string(mStage[mCurrentStage].number), 0); + + // POWER + mSpritePowerMeter->setSpriteClip(256, 184, 40, 8); + mSpritePowerMeter->render(); + const float percent = (mStage[mCurrentStage].currentPower * 40.0f) / mStage[mCurrentStage].powerToComplete; + mSpritePowerMeter->setSpriteClip(296, 184, (int)percent, 8); + mSpritePowerMeter->render(); + } + /*else + { + mText->writeCentered(SCREEN_FIRST_QUARTER_X, SCORE_WORD_Y - 3, "A game by", -1); + mText->writeCentered(SCREEN_FIRST_QUARTER_X, SCORE_NUMBER_Y, "JailDesigner", -1); + mText->writeCentered(SCREEN_THIRD_QUARTER_X, HISCORE_WORD_Y - 3, "Intro music by", -1); + mText->writeCentered(SCREEN_THIRD_QUARTER_X, HISCORE_NUMBER_Y, "JailDoctor", -1); + }*/ +} + +// Actualiza las variables del jugador +void Game::updatePlayer() +{ + mPlayer->update(); + + // Comprueba la colisión entre el jugador y los globos + if (checkPlayerBalloonCollision()) + { + if (mPlayer->isAlive()) + { + if (mDemo.enabled) + mSection = {PROG_SECTION_TITLE, TITLE_SECTION_INSTRUCTIONS}; + else + killPlayer(); + } + } +} + +// Actualiza las variables de la fase +void Game::updateStage() +{ + if (mStage[mCurrentStage].currentPower >= mStage[mCurrentStage].powerToComplete) + { + // Cambio de fase + mCurrentStage++; + JA_PlaySound(mSoundStageChange); + mStageBitmapCounter = 0; + mEnemySpeed = BALLOON_SPEED_1; + setBalloonSpeed(mEnemySpeed); + mEffect.flash = true; + mEffect.shake = true; + } + + if (mStageBitmapCounter < STAGE_COUNTER) + mStageBitmapCounter++; +} + +// Actualiza el estado de muerte +void Game::updateDeath() +{ + if (!mPlayer->isAlive()) + { + if (mDeathCounter > 0) + { + mDeathCounter--; + if ((mDeathCounter == 250) || (mDeathCounter == 200) || (mDeathCounter == 180) || (mDeathCounter == 120) || (mDeathCounter == 60)) + { + Uint8 sound = rand() % 4; + switch (sound) + { + case 0: + JA_PlaySound(mSoundBubble1, 0); + break; + case 1: + JA_PlaySound(mSoundBubble2, 0); + break; + case 2: + JA_PlaySound(mSoundBubble3, 0); + break; + case 3: + JA_PlaySound(mSoundBubble4, 0); + break; + + default: + break; + } + } + + // Animación + if ((mDeathCounter / 5) % 2 == 0) + mSmartSprite[mDeathIndex]->setSpriteClip(0, 0, 24, 24); + else + mSmartSprite[mDeathIndex]->setSpriteClip(24, 0, 24, 24); + + // Rebote en los laterales + if (mSmartSprite[mDeathIndex]->getVelX() > 0) + { + if (mSmartSprite[mDeathIndex]->getPosX() > (SCREEN_WIDTH - mSmartSprite[mDeathIndex]->getWidth())) + { + mSmartSprite[mDeathIndex]->setPosX(SCREEN_WIDTH - mSmartSprite[mDeathIndex]->getWidth()); + mSmartSprite[mDeathIndex]->setVelX(mSmartSprite[mDeathIndex]->getVelX() * (-1)); + mSmartSprite[mDeathIndex]->setDestX(mSmartSprite[mDeathIndex]->getDestX() * (-1)); + } + } + else + { + if (mSmartSprite[mDeathIndex]->getPosX() < 0) + { + mSmartSprite[mDeathIndex]->setPosX(0); + mSmartSprite[mDeathIndex]->setVelX(mSmartSprite[mDeathIndex]->getVelX() * (-1)); + mSmartSprite[mDeathIndex]->setDestX(mSmartSprite[mDeathIndex]->getDestX() * (-1)); + } + } + } + else + { + mSection.subsection = GAME_SECTION_GAMEOVER; + } + } +} + +// Renderiza el fade final cuando se acaba la partida +void Game::renderDeathFade() +{ + if (!mPlayer->isAlive() && (mDeathCounter < 150)) + { + // 192 / 6 = 32, 6 cuadrados de 32 pixeles + SDL_Rect rect[12]; + Uint8 h = (150 - mDeathCounter) / 3; + SDL_SetRenderDrawColor(mRenderer, 0x27, 0x27, 0x36, 255); + for (int i = 0; i < 12; i++) + { + rect[i].x = 0; + rect[i].y = i * 16; + rect[i].w = SCREEN_WIDTH; + if (i == 0) + rect[i].h = h; + else + rect[i].h = std::max(rect[i - 1].h - 3, 0); + SDL_RenderFillRect(mRenderer, &rect[i]); + } + } +} + +// Actualiza los globos +void Game::updateBalloons() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + mBalloon[i]->update(); +} + +// Pinta en pantalla todos los globos activos +void Game::renderBalloons() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + { + mBalloon[i]->render(); + if ((mDebug.enabled) && (mBalloon[i]->isPopping() == false)) + mText->writeCentered(mBalloon[i]->getPosX() + (mBalloon[i]->getWidth() / 2), mBalloon[i]->getPosY() - 8, std::to_string(i), 0); + } +} + +// Devuelve el primer indice no activo del vector de globos +Uint8 Game::getBalloonFreeIndex() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + if (!mBalloon[i]->isEnabled()) + return i; + + return 0; +} + +// Crea un globo nuevo en el vector de globos +Uint8 Game::createNewBalloon(float x, int y, Uint8 kind, float velx, float speed, Uint16 creationtimer, LTexture *texture) +{ + const Uint8 index = getBalloonFreeIndex(); + mBalloon[index]->init(x, y, kind, velx, speed, creationtimer, texture, mRenderer); + return index; +} + +// Crea una PowerBall +void Game::createPowerBall() +{ + const int posY = (PLAY_AREA_TOP); + const int left = PLAY_AREA_LEFT; + const int center = PLAY_AREA_CENTER_X - (BALLOON_WIDTH_4 / 2); + const int right = (PLAY_AREA_RIGHT)-BALLOON_WIDTH_4; + const int x[3] = {left, center, right}; + const int posX = x[rand() % 3]; + + mBalloon[getBalloonFreeIndex()]->init(posX, posY, POWER_BALL, BALLOON_VELX_POSITIVE * (((rand() % 2) * 2) - 1), mEnemySpeed, 100, mTextureBalloon, mRenderer); + mPowerBallEnabled = true; +} + +// Establece a cero todos los valores del vector de objetos globo +void Game::resetBalloons() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + mBalloon[i]->disable(); +} + +// Establece la velocidad de los globos +void Game::setBalloonSpeed(float speed) +{ + for (int i = 0; i < MAX_BALLOONS; i++) + if (mBalloon[i]->isEnabled()) + mBalloon[i]->setSpeed(speed); +} + +// Incrementa la velocidad de los globos +void Game::incBalloonSpeed() +{ + if (mEnemySpeed == BALLOON_SPEED_1) + mEnemySpeed = BALLOON_SPEED_2; + else if (mEnemySpeed == BALLOON_SPEED_2) + mEnemySpeed = BALLOON_SPEED_3; + else if (mEnemySpeed == BALLOON_SPEED_3) + mEnemySpeed = BALLOON_SPEED_4; + else if (mEnemySpeed == BALLOON_SPEED_4) + mEnemySpeed = BALLOON_SPEED_5; + + setBalloonSpeed(mEnemySpeed); +} + +// Decrementa la velocidad de los globos +void Game::decBalloonSpeed() +{ + if (mEnemySpeed == BALLOON_SPEED_5) + mEnemySpeed = BALLOON_SPEED_4; + else if (mEnemySpeed == BALLOON_SPEED_4) + mEnemySpeed = BALLOON_SPEED_3; + else if (mEnemySpeed == BALLOON_SPEED_3) + mEnemySpeed = BALLOON_SPEED_2; + else if (mEnemySpeed == BALLOON_SPEED_2) + mEnemySpeed = BALLOON_SPEED_1; + + setBalloonSpeed(mEnemySpeed); +} + +// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase +void Game::updateBalloonSpeed() +{ + const float percent = (float)mStage[mCurrentStage].currentPower / (float)mStage[mCurrentStage].powerToComplete; + if (mEnemySpeed == BALLOON_SPEED_1) + { + if (percent > 0.2f) + incBalloonSpeed(); + } + else if (mEnemySpeed == BALLOON_SPEED_2) + { + if (percent > 0.4f) + incBalloonSpeed(); + } + else if (mEnemySpeed == BALLOON_SPEED_3) + { + if (percent > 0.6f) + incBalloonSpeed(); + } + else if (mEnemySpeed == BALLOON_SPEED_4) + { + if (percent > 0.8f) + incBalloonSpeed(); + } +} + +// Explosiona un globo. Lo destruye y crea otros dos si es el caso +void Game::popBalloon(Uint8 index) +{ + // Otorga los puntos correspondientes al globo + mPlayer->addScore(Uint32(mBalloon[index]->getScore() * mPlayer->getScoreMultiplier())); + setScore(mPlayer->getScore()); + updateHiScore(); + + // Aumenta el poder de la fase + increaseStageCurrentPower(1); + mBalloonsPopped++; + + const Uint8 kind = mBalloon[index]->getKind(); + Uint8 freeIndex = 0; + switch (kind) + { + // Si es del tipo más pequeño, simplemente elimina el globo + case BALLOON_1: + mBalloon[index]->pop(); + break; + + case HEXAGON_1: + mBalloon[index]->pop(); + break; + + // Si es del tipo PowerBall, destruye todos los globos + case POWER_BALL: + destroyAllBalloons(); + mPowerBallEnabled = false; + break; + + // En cualquier otro caso, crea dos globos de un tipo inferior + default: + freeIndex = getBalloonFreeIndex(); + mBalloon[freeIndex]->init(0, mBalloon[index]->getPosY(), mBalloon[index]->getKind() - 1, BALLOON_VELX_NEGATIVE, mEnemySpeed, 0, mTextureBalloon, mRenderer); + mBalloon[freeIndex]->allignTo(mBalloon[index]->getPosX() + (mBalloon[index]->getWidth() / 2)); + if (mBalloon[freeIndex]->getClass() == BALLOON_CLASS) + mBalloon[freeIndex]->setVelY(-2.50f); + else if (mBalloon[freeIndex]->getClass() == HEXAGON_CLASS) + mBalloon[freeIndex]->setVelY(BALLOON_VELX_NEGATIVE); + + freeIndex = getBalloonFreeIndex(); + mBalloon[freeIndex]->init(0, mBalloon[index]->getPosY(), mBalloon[index]->getKind() - 1, BALLOON_VELX_POSITIVE, mEnemySpeed, 0, mTextureBalloon, mRenderer); + mBalloon[freeIndex]->allignTo(mBalloon[index]->getPosX() + (mBalloon[index]->getWidth() / 2)); + if (mBalloon[freeIndex]->getClass() == BALLOON_CLASS) + mBalloon[freeIndex]->setVelY(-2.50f); + else if (mBalloon[freeIndex]->getClass() == HEXAGON_CLASS) + mBalloon[freeIndex]->setVelY(BALLOON_VELX_NEGATIVE); + + // Elimina el globo + mBalloon[index]->pop(); + break; + } + + // Recalcula el nivel de amenaza + setMenace(); +} + +// Explosiona un globo. Lo destruye +void Game::destroyBalloon(Uint8 index) +{ + int score = 0; + Uint8 power = 0; + + // Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos + switch (mBalloon[index]->getSize()) + { + case BALLOON_SIZE_4: + score = BALLOON_SCORE_4 + (2 * BALLOON_SCORE_3) + (4 * BALLOON_SCORE_2) + (8 * BALLOON_SCORE_1); + power = 15; + break; + + case BALLOON_SIZE_3: + score = BALLOON_SCORE_3 + (2 * BALLOON_SCORE_2) + (4 * BALLOON_SCORE_1); + power = 7; + break; + + case BALLOON_SIZE_2: + score = BALLOON_SCORE_2 + (2 * BALLOON_SCORE_1); + power = 3; + break; + + case BALLOON_SIZE_1: + score = BALLOON_SCORE_1; + power = 1; + break; + + default: + score = 0; + power = 0; + break; + } + + // Otorga los puntos correspondientes al globo + mPlayer->addScore(Uint32(score * mPlayer->getScoreMultiplier())); + setScore(mPlayer->getScore()); + updateHiScore(); + + // Aumenta el poder de la fase + increaseStageCurrentPower(power); + mBalloonsPopped += power; + + // Destruye el globo + mBalloon[index]->pop(); + + // Recalcula el nivel de amenaza + setMenace(); +} + +// Explosiona todos los globos +void Game::popAllBalloons() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + if ((mBalloon[i]->isEnabled()) && (!mBalloon[i]->isPopping()) && (!mBalloon[i]->isBeingCreated())) + popBalloon(i); + + JA_PlaySound(mSoundBalloon); +} + +// Destruye todos los globos +void Game::destroyAllBalloons() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + if ((mBalloon[i]->isEnabled()) && (!mBalloon[i]->isPopping()) && (!mBalloon[i]->isBeingCreated())) + destroyBalloon(i); + + JA_PlaySound(mSoundPowerBall); + mEffect.flash = true; + mEffect.shake = true; +} + +// Detiene todos los globos +void Game::stopAllBalloons(Uint16 time) +{ + for (int i = 0; i < MAX_BALLOONS; i++) + if (mBalloon[i]->isEnabled()) + { + mBalloon[i]->setStop(true); + mBalloon[i]->setStoppedTimer(time); + } +} + +// Pone en marcha todos los globos +void Game::startAllBalloons() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + if ((mBalloon[i]->isEnabled()) && (!mBalloon[i]->isBeingCreated())) + { + mBalloon[i]->setStop(false); + mBalloon[i]->setStoppedTimer(0); + } +} + +// Obtiene el numero de globos activos +Uint8 Game::countBalloons() +{ + Uint8 num = 0; + + for (int i = 0; i < MAX_BALLOONS; i++) + if (mBalloon[i]->isEnabled()) + if (!mBalloon[i]->isPopping()) + num++; + + return num; +} + +// Comprueba la colisión entre el jugador y los globos activos +bool Game::checkPlayerBalloonCollision() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + if ((mBalloon[i]->isEnabled()) && !(mBalloon[i]->isStopped()) && !(mBalloon[i]->isInvulnerable())) + if (checkCollision(mPlayer->getCollider(), mBalloon[i]->getCollider())) + return true; + + return false; +} + +// Comprueba la colisión entre el jugador y los items +void Game::checkPlayerItemCollision() +{ + if (mPlayer->isAlive()) + for (int i = 0; i < MAX_ITEMS; i++) + { + if (mItem[i]->isEnabled()) + { + if (checkCollision(mPlayer->getCollider(), mItem[i]->getCollider())) + { + switch (mItem[i]->getClass()) + { + case ITEM_POINTS_1_DISK: + mPlayer->addScore(1000); + setScore(mPlayer->getScore()); + updateHiScore(); + createItemScoreSprite(mItem[i]->getPosX() + (mItem[i]->getWidth() / 2) - (m1000Bitmap->getWidth() / 2), mPlayer->getPosY(), m1000Bitmap); + JA_PlaySound(mSoundItemPickup); + break; + case ITEM_POINTS_2_GAVINA: + mPlayer->addScore(2500); + setScore(mPlayer->getScore()); + updateHiScore(); + createItemScoreSprite(mItem[i]->getPosX() + (mItem[i]->getWidth() / 2) - (m2500Bitmap->getWidth() / 2), mPlayer->getPosY(), m2500Bitmap); + JA_PlaySound(mSoundItemPickup); + break; + case ITEM_POINTS_3_PACMAR: + mPlayer->addScore(5000); + setScore(mPlayer->getScore()); + updateHiScore(); + createItemScoreSprite(mItem[i]->getPosX() + (mItem[i]->getWidth() / 2) - (m5000Bitmap->getWidth() / 2), mPlayer->getPosY(), m5000Bitmap); + JA_PlaySound(mSoundItemPickup); + break; + case ITEM_CLOCK: + enableTimeStopItem(); + JA_PlaySound(mSoundItemPickup); + break; + case ITEM_COFFEE: + mPlayer->giveExtraHit(); + JA_PlaySound(mSoundItemPickup); + break; + + default: + break; + } + mItem[i]->erase(); + } + } + } +} + +// Comprueba y procesa la colisión entre las balas y los globos +void Game::checkBulletBalloonCollision() +{ + for (int i = 0; i < MAX_BALLOONS; i++) + for (int j = 0; j < MAX_BULLETS; j++) + if (mBalloon[i]->isEnabled() && (!mBalloon[i]->isInvulnerable()) && mBullet[j]->isActive()) + if (checkCollision(mBalloon[i]->getCollider(), mBullet[j]->getCollider())) + { + mPlayer->incScoreMultiplier(); + popBalloon(i); + if (!mDemo.enabled) + JA_PlaySound(mSoundBalloon); + mBullet[j]->erase(); + setMenace(); + const Uint8 droppeditem = dropItem(); + if ((droppeditem != NO_KIND) && !(mDemo.enabled) && !(mDemo.recording)) + { + createItem(mBalloon[i]->getPosX(), mBalloon[i]->getPosY(), droppeditem); + JA_PlaySound(mSoundItemDrop); + } + break; + } +} + +// Mueve las balas activas +void Game::moveBullets() +{ + for (int i = 0; i < MAX_BULLETS; i++) + if (mBullet[i]->isActive()) + if (mBullet[i]->move() == MSG_BULLET_OUT) + mPlayer->decScoreMultiplier(); +} + +// Pinta las balas activas +void Game::renderBullets() +{ + for (int i = 0; i < MAX_BULLETS; i++) + if (mBullet[i]->isActive()) + mBullet[i]->render(); +} + +// Devuelve el primer indice no activo del vector de balas +Uint8 Game::getBulletFreeIndex() +{ + for (int i = 0; i < MAX_BULLETS; i++) + if (mBullet[i]->isActive() == false) + return i; + + return 0; +} + +// Establece a cero todos los valores del vector de objetos bala +void Game::resetBullets() +{ + for (int i = 0; i < MAX_BULLETS; i++) + mBullet[i]->erase(); +} + +// Crea un objeto bala +void Game::createBullet(int x, int y, Uint8 kind) +{ + mBullet[getBulletFreeIndex()]->init(x, y, kind, mTextureBullet, mRenderer); +} + +// Actualiza los items +void Game::updateItems() +{ + for (int i = 0; i < MAX_ITEMS; i++) + mItem[i]->update(); +} + +// Pinta los items activos +void Game::renderItems() +{ + for (int i = 0; i < MAX_ITEMS; i++) + mItem[i]->render(); +} + +// Devuelve el primer indice no activo del vector de items +Uint8 Game::getItemFreeIndex() +{ + for (int i = 0; i < MAX_ITEMS; i++) + if (mItem[i]->getClass() == NO_KIND) + return i; + + return 0; +} + +// Establece a cero todos los valores del vector de objetos item +void Game::resetItems() +{ + for (int i = 0; i < MAX_ITEMS; i++) + mItem[i]->erase(); +} + +// Devuelve un item en función del azar +Uint8 Game::dropItem() +{ + //return ITEM_COFFEE; + + const Uint8 luckyNumber = rand() % 99; + const Uint8 item = rand() % 5; + + switch (item) + { + case 0: + if (luckyNumber < 10) + return ITEM_POINTS_1_DISK; + break; + case 1: + if (luckyNumber < 6) + return ITEM_POINTS_2_GAVINA; + break; + case 2: + if (luckyNumber < 3) + return ITEM_POINTS_3_PACMAR; + break; + case 3: + if (luckyNumber < 5) + return ITEM_CLOCK; + break; + case 4: + if (luckyNumber < 5) + return ITEM_COFFEE; + break; + default: + break; + } + + return NO_KIND; +} + +// Crea un objeto item +void Game::createItem(int x, int y, Uint8 kind) +{ + mItem[getItemFreeIndex()]->init(kind, x, y, mTextureItems, mRenderer); +} + +// Crea un objeto SmartSprite para mostrar la puntuación al coger un objeto +void Game::createItemScoreSprite(int x, int y, SmartSprite *sprite) +{ + const Uint8 index = getSmartSpriteFreeIndex(); + + *mSmartSprite[index] = *sprite; + mSmartSprite[index]->setPosX(x); + mSmartSprite[index]->setPosY(y); + mSmartSprite[index]->setDestX(x); + mSmartSprite[index]->setDestY(y - 15); + mSmartSprite[index]->setEnabled(true); + mSmartSprite[index]->setEnabledTimer(100); +} + +// Dibuja el efecto de flash +void Game::renderFlashEffect() +{ + if (mEffect.flash) + { + // Pantallazo blanco + SDL_SetRenderDrawColor(mRenderer, 0xFF, 0xFF, 0xFF, 0xFF); + SDL_RenderClear(mRenderer); + + mEffect.flash = false; + } +} + +// Actualiza el efecto de agitar la pantalla +void Game::updateShakeEffect() +{ + if (mEffect.shake) + { + if (mEffect.shakeCounter > 0) + mEffect.shakeCounter--; + else + mEffect.shake = false; + } +} + +// Crea un SmartSprite para arrojar el item café al recibir un impacto +void Game::throwCoffee(int x, int y) +{ + const Uint8 index = getSmartSpriteFreeIndex(); + + mSmartSprite[index]->init(mTextureItems, mRenderer); + mSmartSprite[index]->setPosX(x - 8); + mSmartSprite[index]->setPosY(y - 8); + mSmartSprite[index]->setWidth(16); + mSmartSprite[index]->setHeight(16); + mSmartSprite[index]->setVelX(-1.0f + ((rand() % 5) * 0.5f)); + mSmartSprite[index]->setVelY(-4.0f); + mSmartSprite[index]->setAccelX(0.0f); + mSmartSprite[index]->setAccelY(0.2f); + mSmartSprite[index]->setDestX(x + (mSmartSprite[index]->getVelX() * 50)); + mSmartSprite[index]->setDestY(SCREEN_HEIGHT + 1); + mSmartSprite[index]->setEnabled(true); + mSmartSprite[index]->setEnabledTimer(1); + mSmartSprite[index]->setSpriteClip(80, 16, 16, 16); + mSmartSprite[index]->setRotate(true); + mSmartSprite[index]->setRotateSpeed(10); + mSmartSprite[index]->setRotateAmount(90.0); +} + +// Crea un SmartSprite para arrojar el item café al recibir un impacto +void Game::throwPlayer(int x, int y) +{ + const int sentit = ((rand() % 2) ? 1 : -1); + + mDeathIndex = getSmartSpriteFreeIndex(); + mSmartSprite[mDeathIndex]->init(mTexturePlayerDeath, mRenderer); + mSmartSprite[mDeathIndex]->setPosX(x); + mSmartSprite[mDeathIndex]->setPosY(y); + mSmartSprite[mDeathIndex]->setWidth(24); + mSmartSprite[mDeathIndex]->setHeight(24); + mSmartSprite[mDeathIndex]->setVelX(2.0f * sentit); + mSmartSprite[mDeathIndex]->setVelY(-5.0f); + mSmartSprite[mDeathIndex]->setAccelX(0.0f); + mSmartSprite[mDeathIndex]->setAccelY(0.2f); + mSmartSprite[mDeathIndex]->setDestX(SCREEN_WIDTH * sentit); + mSmartSprite[mDeathIndex]->setDestY(SCREEN_HEIGHT + 1); + mSmartSprite[mDeathIndex]->setEnabled(true); + mSmartSprite[mDeathIndex]->setEnabledTimer(1); + mSmartSprite[mDeathIndex]->setSpriteClip(0, 0, 24, 24); +} + +// Actualiza los SmartSprites +void Game::updateSmartSprites() +{ + for (int i = 0; i < MAX_SMART_SPRITES; i++) + mSmartSprite[i]->update(); +} + +// Pinta los SmartSprites activos +void Game::renderSmartSprites() +{ + for (int i = 0; i < MAX_SMART_SPRITES; i++) + mSmartSprite[i]->render(); +} + +// Devuelve el primer indice no activo del vector de SmartSprites +Uint8 Game::getSmartSpriteFreeIndex() +{ + for (int i = 0; i < MAX_SMART_SPRITES; i++) + if (!mSmartSprite[i]->isEnabled()) + return i; + + return 0; +} + +// Establece a cero todos los valores del vector de objetos SmafrtSprite +void Game::resetSmartSprites() +{ + for (int i = 0; i < MAX_SMART_SPRITES; i++) + mSmartSprite[i]->erase(); +} + +// Acciones a realizar cuando el jugador muere +void Game::killPlayer() +{ + if (!mPlayer->isInvulnerable()) + { + if (mPlayer->hasExtraHit()) + { + mPlayer->removeExtraHit(); + throwCoffee(mPlayer->getPosX() + (mPlayer->getWidth() / 2), mPlayer->getPosY() + (mPlayer->getHeight() / 2)); + JA_PlaySound(mSoundCoffeeOut); + } + else + { + stopAllBalloons(10); + JA_StopMusic(); + JA_PlaySound(mSoundPlayerCollision); + shakeScreen(); + SDL_Delay(500); + JA_PlaySound(mSoundCoffeeOut); + throwPlayer(mPlayer->getPosX(), mPlayer->getPosY()); + mPlayer->setAlive(false); + } + } +} + +// Calcula y establece el valor de amenaza en funcion de los globos activos +void Game::setMenace() +{ + mMenaceCurrent = 0; + for (int i = 0; i < MAX_BALLOONS; i++) + if (mBalloon[i]->isEnabled()) + mMenaceCurrent += mBalloon[i]->getMenace(); +} + +// Obtiene el valor de la variable +Uint8 Game::getMenace() +{ + return mMenaceCurrent; +} + +// Establece el valor de la variable +void Game::setTimeStopped(bool value) +{ + mTimeStopped = value; +} + +// Obtiene el valor de la variable +bool Game::isTimeStopped() +{ + return mTimeStopped; +} + +// Establece el valor de la variable +void Game::setTimeStoppedCounter(Uint16 value) +{ + mTimeStoppedCounter = value; +} + +// Incrementa el valor de la variable +void Game::incTimeStoppedCounter(Uint16 value) +{ + mTimeStoppedCounter += value; +} + +// Actualiza y comprueba el valor de la variable +void Game::updateTimeStoppedCounter() +{ + if (isTimeStopped()) + { + if (mTimeStoppedCounter > 0) + { + mTimeStoppedCounter--; + stopAllBalloons(TIME_STOPPED_COUNTER); + } + else + { + disableTimeStopItem(); + } + } +} + +// Establece el valor de la variable +void Game::setExplosionTime(bool value) +{ + mExplosionTime = value; +} + +// Obtiene el valor de la variable +bool Game::isExplosionTime() +{ + return mExplosionTime; +} + +// Establece el valor de la variable +void Game::setRemainingExplosions(Uint8 value) +{ + mRemainingExplosions = value; +} + +// Actualiza y comprueba el valor de la variable +void Game::updateRemainingExplosionsCounter() +{ + if (isExplosionTime()) + { + if (mRemainingExplosionsCounter > 0) + { + mRemainingExplosionsCounter--; + } + else if (mRemainingExplosions > 0) + { + popAllBalloons(); + mRemainingExplosions--; + mRemainingExplosionsCounter = REMAINING_EXPLOSIONS_COUNTER; + } + else + { + mExplosionTime = false; + mRemainingExplosions = REMAINING_EXPLOSIONS; + mRemainingExplosionsCounter = REMAINING_EXPLOSIONS_COUNTER; + } + } +} + +// Actualiza la variable mEnemyDeployCounter +void Game::updateEnemyDeployCounter() +{ + if (mEnemyDeployCounter > 0) + mEnemyDeployCounter--; +} + +// Actualiza el campo de juego +void Game::updatePlayField() +{ + // Comprueba el teclado/mando + checkGameInput(); + + // Actualiza las variables del jugador + updatePlayer(); + + // Actualiza el fondo + updateBackground(); + + // Mueve los globos + updateBalloons(); + + // Mueve las balas + moveBullets(); + + // Actualiza los items + updateItems(); + + // Actualiza el valor de mCurrentStage + updateStage(); + + // Actualiza el estado de muerte + updateDeath(); + + // Actualiza los SmartSprites + updateSmartSprites(); + + // Actualiza los contadores de estado y efectos + updateTimeStoppedCounter(); + updateRemainingExplosionsCounter(); + updateEnemyDeployCounter(); + updateShakeEffect(); + + // Comprueba las colisiones entre globos y balas + checkBulletBalloonCollision(); + + // Comprueba las colisiones entre elk jugador y los items + checkPlayerItemCollision(); + + // Comprueba el nivel de amenaza + updateMenace(); + + // Actualiza la velocidad de los enemigos + updateBalloonSpeed(); +} + +// Actualiza el fondo +void Game::updateBackground() +{ + mClouds1a->move(); + mClouds1b->move(); + mClouds2a->move(); + mClouds2b->move(); + + if (mClouds1a->getPosX() < -mClouds1a->getWidth()) + mClouds1a->setPosX(mClouds1a->getWidth()); + + if (mClouds1b->getPosX() < -mClouds1b->getWidth()) + mClouds1b->setPosX(mClouds1b->getWidth()); + + if (mClouds2a->getPosX() < -mClouds2a->getWidth()) + mClouds2a->setPosX(mClouds2a->getWidth()); + + if (mClouds2b->getPosX() < -mClouds2b->getWidth()) + mClouds2b->setPosX(mClouds2b->getWidth()); + + mSpriteGrass->setSpriteClip(256, 85 + (6 * (mCounter / 20 % 2)), SCREEN_WIDTH, 6); + + if (mEffect.shake) + mSpriteBackground->setPosX(((mEffect.shakeCounter % 2) * 2) - 1); + else + mSpriteBackground->setPosX(0); +} + +// Dibuja el fondo +void Game::renderBackground() +{ + const float gradientNumber = std::min(((float)mBalloonsPopped / 900.0f), 3.0f); + const float percent = gradientNumber - (int)gradientNumber; + const int alpha = std::max((255 - (int)(255 * percent)), 0); + + // Dibuja el gradiente 2 + mSpriteGradient->setSpriteClip(mGradientRect[((int)gradientNumber + 1) % 4]); + mSpriteGradient->render(); + + // Dibuja el gradiente 1 con una opacidad cada vez menor + mSpriteGradient->setSpriteClip(mGradientRect[(int)gradientNumber]); + mTextureGameBG->setAlpha(alpha); + mSpriteGradient->render(); + mTextureGameBG->setAlpha(255); + + mClouds1a->render(); + mClouds1b->render(); + mClouds2a->render(); + mClouds2b->render(); + + mSpriteBackground->render(); + + mSpriteGrass->render(); +} + +// Dibuja el campo de juego +void Game::renderPlayField() +{ + renderBackground(); + renderBalloons(); + renderBullets(); + renderItems(); + renderSmartSprites(); + mPlayer->render(); + renderMessages(); + renderDeathFade(); + renderScoreBoard(); + renderFlashEffect(); +} + +// Gestiona el nivel de amenaza +void Game::updateMenace() +{ + const float percent = mStage[mCurrentStage].currentPower / mStage[mCurrentStage].powerToComplete; + const Uint8 difference = mStage[mCurrentStage].maxMenace - mStage[mCurrentStage].minMenace; + + // Aumenta el nivel de amenaza en función de la puntuación + mMenaceThreshold = mStage[mCurrentStage].minMenace + (difference * percent); + + // Si el nivel de amenza es inferior al umbral + if (mMenaceCurrent < mMenaceThreshold) + { + // Crea una formación de enemigos + deployEnemyFormation(); + + // Recalcula el nivel de amenaza con el nuevo globo + setMenace(); + } +} + +// Gestiona la entrada durante el juego +void Game::checkGameInput() +{ + mDemo.keys.left = 0; + mDemo.keys.right = 0; + mDemo.keys.noInput = 0; + mDemo.keys.fire = 0; + mDemo.keys.fireLeft = 0; + mDemo.keys.fireRight = 0; + + // Modo Demo activo + if (mDemo.enabled) + { + if (mDemo.dataFile[mDemo.counter].left == 1) + mPlayer->setInput(INPUT_LEFT); + + if (mDemo.dataFile[mDemo.counter].right == 1) + mPlayer->setInput(INPUT_RIGHT); + + if (mDemo.dataFile[mDemo.counter].noInput == 1) + mPlayer->setInput(NO_INPUT); + + if (mDemo.dataFile[mDemo.counter].fire == 1) + if (mPlayer->canFire()) + { + mPlayer->setInput(INPUT_FIRE_UP); + createBullet(mPlayer->getPosX() + (mPlayer->getWidth() / 2) - 4, mPlayer->getPosY() + (mPlayer->getHeight() / 2), BULLET_UP); + mPlayer->setFireCooldown(10); + } + + if (mDemo.dataFile[mDemo.counter].fireLeft == 1) + if (mPlayer->canFire()) + { + mPlayer->setInput(INPUT_FIRE_LEFT); + createBullet(mPlayer->getPosX() + (mPlayer->getWidth() / 2) - 4, mPlayer->getPosY() + (mPlayer->getHeight() / 2), BULLET_UP); + mPlayer->setFireCooldown(10); + } + + if (mDemo.dataFile[mDemo.counter].fireRight == 1) + if (mPlayer->canFire()) + { + mPlayer->setInput(INPUT_FIRE_RIGHT); + createBullet(mPlayer->getPosX() + (mPlayer->getWidth() / 2) - 4, mPlayer->getPosY() + (mPlayer->getHeight() / 2), BULLET_UP); + mPlayer->setFireCooldown(10); + } + + // Comprueba el input de pausa + if (mInput->checkInput(INPUT_PAUSE, REPEAT_FALSE)) + mSection.name = PROG_SECTION_TITLE; + + // Incrementa el contador de la demo + if (mDemo.counter < TOTAL_DEMO_DATA) + mDemo.counter++; + else + mSection = {PROG_SECTION_TITLE, TITLE_SECTION_INSTRUCTIONS}; + } + // Modo Demo no activo + else if (mPlayer->isAlive()) + { + // Input a la izquierda + if (mInput->checkInput(INPUT_LEFT, REPEAT_TRUE)) + { + mPlayer->setInput(INPUT_LEFT); + mDemo.keys.left = 1; + } + else + { + // Input a la derecha + if (mInput->checkInput(INPUT_RIGHT, REPEAT_TRUE)) + { + mPlayer->setInput(INPUT_RIGHT); + mDemo.keys.right = 1; + } + else + { + // Ninguno de los dos inputs anteriores + mPlayer->setInput(NO_INPUT); + mDemo.keys.noInput = 1; + } + } + // Comprueba el input de disparar al centro + if (mInput->checkInput(INPUT_BUTTON_2, REPEAT_TRUE)) + { + if (mPlayer->canFire()) + { + mPlayer->setInput(INPUT_FIRE_UP); + createBullet(mPlayer->getPosX() + (mPlayer->getWidth() / 2) - 4, mPlayer->getPosY() + (mPlayer->getHeight() / 2), BULLET_UP); + mPlayer->setFireCooldown(10); + + // Reproduce el sonido de disparo + JA_PlaySound(mSoundBullet); + + mDemo.keys.fire = 1; + } + } + + // Comprueba el input de desiparar a la izquierda + if (mInput->checkInput(INPUT_BUTTON_1, REPEAT_TRUE)) + { + if (mPlayer->canFire()) + { + mPlayer->setInput(INPUT_FIRE_LEFT); + createBullet(mPlayer->getPosX() + (mPlayer->getWidth() / 2) - 4, mPlayer->getPosY() + (mPlayer->getHeight() / 2), BULLET_LEFT); + mPlayer->setFireCooldown(10); + + // Reproduce el sonido de disparo + JA_PlaySound(mSoundBullet); + + mDemo.keys.fireLeft = 1; + } + } + + // Comprueba el input de disparar a la derecha + if (mInput->checkInput(INPUT_BUTTON_3, REPEAT_TRUE)) + { + if (mPlayer->canFire()) + { + mPlayer->setInput(INPUT_FIRE_RIGHT); + createBullet(mPlayer->getPosX() + (mPlayer->getWidth() / 2) - 4, mPlayer->getPosY() + (mPlayer->getHeight() / 2), BULLET_RIGHT); + mPlayer->setFireCooldown(10); + + // Reproduce el sonido de disparo + JA_PlaySound(mSoundBullet); + + mDemo.keys.fireRight = 1; + } + } + + // Comprueba el input de pausa + if (mInput->checkInput(INPUT_CANCEL, REPEAT_FALSE)) + { + mSection.subsection = GAME_SECTION_PAUSE; + + if (JA_GetMusicState() == JA_MUSIC_PLAYING) + JA_PauseMusic(); + } + + if (mDemo.counter < TOTAL_DEMO_DATA) + { + if (mDemo.recording) + mDemo.dataFile[mDemo.counter] = mDemo.keys; + mDemo.counter++; + } + else if (mDemo.recording) + { + mSection.name = PROG_SECTION_QUIT; + } + } +} + +// Pinta diferentes mensajes en la pantalla +void Game::renderMessages() +{ + // GetReady + if ((mCounter < STAGE_COUNTER) && (!mDemo.enabled)) + { + mSpriteGetReady->setPosX((int)mGetReadyBitmapPath[mCounter]); + mSpriteGetReady->render(); + } + + // Time Stopped + if (mTimeStopped) + { + if ((mTimeStoppedCounter > 100) || (mTimeStoppedCounter % 10 > 4)) + //mText->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, "Time Stopped: " + std::to_string(mTimeStoppedCounter / 10)); + mText->writeDX(TXT_CENTER | TXT_SHADOW, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, "Time Stopped: " + std::to_string(mTimeStoppedCounter / 10), 0, noColor, 1, shdwTxtColor); + + if (mTimeStoppedCounter > 100) + { + if (mTimeStoppedCounter % 30 == 0) + JA_PlaySound(mSoundClock, false); + } + else + { + if (mTimeStoppedCounter % 15 == 0) + JA_PlaySound(mSoundClock, false); + } + } + + // D E M O + if (mDemo.enabled) + if (mDemo.counter % 30 > 14) + mTextX2->writeDX(TXT_CENTER | TXT_SHADOW, PLAY_AREA_CENTER_X, PLAY_AREA_FIRST_QUARTER_Y, "D E M O", 0, noColor, 2, shdwTxtColor); + + // STAGE NUMBER + if (mStageBitmapCounter < STAGE_COUNTER) + mTextX2->writeDX(TXT_CENTER | TXT_SHADOW, PLAY_AREA_CENTER_X, mStageBitmapPath[mStageBitmapCounter], "STAGE " + std::to_string(mStage[mCurrentStage].number), 0, noColor, 2, shdwTxtColor); +} + +// Habilita el efecto del item de detener el tiempo +void Game::enableTimeStopItem() +{ + stopAllBalloons(TIME_STOPPED_COUNTER); + setTimeStopped(true); + incTimeStoppedCounter(TIME_STOPPED_COUNTER); + if (JA_GetMusicState() == JA_MUSIC_PLAYING) + JA_PauseMusic(); +} + +// Deshabilita el efecto del item de detener el tiempo +void Game::disableTimeStopItem() +{ + mTimeStopped = false; + setTimeStoppedCounter(0); + startAllBalloons(); + if (JA_GetMusicState() == JA_MUSIC_PAUSED) + JA_ResumeMusic(); +} + +// Agita la pantalla +void Game::shakeScreen() +{ + const int v[] = {-1, 1, -1, 1, -1, 1, -1, 0}; + for (int n = 0; n < 8; n++) + { + // Limpia la pantalla + SDL_SetRenderDrawColor(mRenderer, 0x00, 0x00, 0x00, 0xFF); + SDL_RenderClear(mRenderer); + + // Dibuja los objetos + mSpriteBackground->setPosX(0); + mSpriteBackground->setWidth(1); + mSpriteBackground->setSpriteClip(0, 0, 1, 192); + renderBackground(); + + mSpriteBackground->setPosX(255); + mSpriteBackground->setSpriteClip(255, 0, 1, 192); + mSpriteBackground->render(); + + mSpriteBackground->setPosX(v[n]); + mSpriteBackground->setWidth(256); + mSpriteBackground->setSpriteClip(0, 0, 256, 192); + mSpriteBackground->render(); + + mSpriteGrass->render(); + renderBalloons(); + renderBullets(); + renderItems(); + mPlayer->render(); + renderScoreBoard(); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + SDL_Delay(50); + } +} + +// Bucle para el juego +section_t Game::run() +{ + init(); + + while (mSection.name == PROG_SECTION_GAME) + { + // Sección juego en pausa + if (mSection.subsection == GAME_SECTION_PAUSE) + runPausedGame(); + + // Sección Game Over + if (mSection.subsection == GAME_SECTION_GAMEOVER) + runGameOverScreen(); + + // Sección juego jugando + if (mSection.subsection == GAME_SECTION_PLAY) + { + // Si la música no está sonando + if ((JA_GetMusicState() == JA_MUSIC_INVALID) || (JA_GetMusicState() == JA_MUSIC_STOPPED)) + { + // Reproduce la música + if (mPlayer->isAlive()) + JA_PlayMusic(mMusicPlaying); + } + + // Comprueba que la diferencia de ticks sea mayor a la velocidad del juego + if (SDL_GetTicks() - mTicks > mTicksSpeed) + { + // Actualiza el contador de ticks + mTicks = SDL_GetTicks(); + + // Actualiza el contador de juego + mCounter++; + + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + else if ((mEventHandler->type == SDL_KEYDOWN) && (mEventHandler->key.repeat == 0)) + { + if (mDebug.enabled) + { + switch (mEventHandler->key.keysym.scancode) + { + case SDL_SCANCODE_F: + mDebug.enemySet--; + if (mDebug.enemySet == 255) + mDebug.enemySet = 0; + break; + + case SDL_SCANCODE_R: + mDebug.enemySet = std::min((int)++mDebug.enemySet, 9); + break; + + case SDL_SCANCODE_T: + mTicksSpeed *= 2; + break; + + case SDL_SCANCODE_G: + createNewBalloon(100, 0, BALLOON_1, BALLOON_VELX_POSITIVE, 1.0F, 0, mTextureBalloon); + break; + + case SDL_SCANCODE_P: + popAllBalloons(); + break; + + case SDL_SCANCODE_Z: + incBalloonSpeed(); + break; + + case SDL_SCANCODE_X: + decBalloonSpeed(); + break; + + case SDL_SCANCODE_C: + mDebug.gradB += 10; + break; + + case SDL_SCANCODE_I: + mPlayer->setInvulnerable(true); + mPlayer->setInvulnerableCounter(65000); + break; + + case SDL_SCANCODE_M: + break; + + case SDL_SCANCODE_N: + break; + + default: + break; + } + } + } + } + + // Actualiza la lógica del juego + updatePlayField(); + } + + // Limpia la pantalla + SDL_SetRenderDrawColor(mRenderer, 0x00, 0x00, 0x00, 0xFF); + SDL_RenderClear(mRenderer); + + // Dibuja los objetos + renderPlayField(); + + // Pinta la informacion de debug + renderDebugInfo(); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + } + } + + return mSection; +} + +// Bucle para el menu de pausa del juego +void Game::runPausedGame() +{ + // Reinicia el menu + mMenuPause->reset(); + + while ((mSection.subsection == GAME_SECTION_PAUSE) && (mSection.name == PROG_SECTION_GAME)) + { + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + } + + // Calcula la lógica de los objetos + if (SDL_GetTicks() - mTicks > mTicksSpeed) + { + // Actualiza el contador de ticks + mTicks = SDL_GetTicks(); + + // Actualiza la lógica del menu + mMenuPause->update(); + mFade->update(); + if (mFade->hasEnded()) + { + mSection.name = PROG_SECTION_TITLE; + mSection.subsection = TITLE_SECTION_1; + JA_StopMusic(); + break; + } + } + + // Dibuja los objetos + renderBackground(); + renderBalloons(); + renderBullets(); + mPlayer->render(); + renderScoreBoard(); + mMenuPause->render(); + mFade->render(); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + + // Comprueba las entradas para el menu + mMenuPause->checkInput(); + + // Comprueba si se ha seleccionado algún item del menú + switch (mMenuPause->getItemSelected()) + { + case 0: + //mMenuPause->reset(); + mSection.name = PROG_SECTION_GAME; + mSection.subsection = GAME_SECTION_PLAY; + if (JA_GetMusicState() == JA_MUSIC_PAUSED) + JA_ResumeMusic(); + break; + + case 1: + //mMenuPause->reset(); + mFade->setFadeType(FADE_CENTER); + mFade->activateFade(); + break; + + default: + break; + } + } +} + +// Bucle para la pantalla de game over +void Game::runGameOverScreen() +{ + // Reinicia el menu + mMenuGameOver->reset(); + + while ((mSection.subsection == GAME_SECTION_GAMEOVER) && (mSection.name == PROG_SECTION_GAME)) + { + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + } + + // Calcula la lógica de los objetos + if (SDL_GetTicks() - mTicks > mTicksSpeed) + { + // Actualiza el contador de ticks + mTicks = SDL_GetTicks(); + + // Actualiza la lógica del menu + mMenuGameOver->update(); + mFade->update(); + if (mFade->hasEnded()) + { + switch (mPostFade) + { + case 0: // YES + mSection.name = PROG_SECTION_GAME; + mSection.subsection = GAME_SECTION_PLAY; + //JA_StopMusic(); + init(); + break; + + case 1: // NO + mSection.name = PROG_SECTION_TITLE; + mSection.subsection = TITLE_SECTION_1; + //JA_StopMusic(); + break; + + default: + break; + } + + break; + } + } + + // Limpia la pantalla + SDL_SetRenderDrawColor(mRenderer, 0x27, 0x27, 0x36, 0xFF); + SDL_RenderClear(mRenderer); + + // Dibuja los objetos + mTextX2->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 4), "GAME OVER", 0); + mText->writeCentered(PLAY_AREA_CENTER_X, PLAY_AREA_CENTER_Y - (BLOCK * 1), "YOUR SCORE: " + std::to_string(mPlayer->getScore()), 0); + mText->write(PLAY_AREA_CENTER_X - (mText->lenght("RETRY?", 0) / 2), PLAY_AREA_CENTER_Y + BLOCK * 2, "RETRY?", 0); + mMenuGameOver->render(); + mFade->render(); + + // Muestra la pantalla + SDL_RenderPresent(mRenderer); + + // Comprueba las entradas para el menu + mMenuGameOver->checkInput(); + + // Comprueba si se ha seleccionado algún item del menú + switch (mMenuGameOver->getItemSelected()) + { + case 0: // YES + mPostFade = 0; + mFade->activateFade(); + break; + + case 1: // NO + mPostFade = 1; + mFade->activateFade(); + break; + + default: + break; + } + } +} + +// Dibuja la informacion de debug en pantalla +void Game::renderDebugInfo() +{ + if (mDebug.enabled) + { + const color_t color = {0xFF, 0x20, 0x20}; + + SDL_RenderSetLogicalSize(mRenderer, SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2); + + mText->writeShadowed(2, 2 + 0 * BLOCK, "menace(umb): " + std::to_string(mMenaceCurrent) + "(" + std::to_string(mMenaceThreshold) + ")", color); + mText->writeShadowed(2, 2 + 1 * BLOCK, "currentPower: " + std::to_string(mStage[mCurrentStage].currentPower), color); + mText->writeShadowed(2, 2 + 2 * BLOCK, "mCurrentStage: " + std::to_string(mCurrentStage), color); + mText->writeShadowed(2, 2 + 3 * BLOCK, "mCounter: " + std::to_string(mCounter), color); + mText->writeShadowed(2, 2 + 4 * BLOCK, "(R)enemyset: " + std::to_string(mDebug.enemySet), color); + mText->writeShadowed(2, 2 + 5 * BLOCK, "RGB: " + std::to_string(mDebug.gradR) + "," + std::to_string(mDebug.gradG) + "," + std::to_string(mDebug.gradB), color); + mText->writeShadowed(2, 2 + 6 * BLOCK, "(I)invuln : " + std::to_string(mPlayer->getInvulnerableCounter()), color); + mText->writeShadowed(2, 2 + 7 * BLOCK, "balloons: " + std::to_string(countBalloons()), color); + mText->writeShadowed(2, 2 + 8 * BLOCK, "balloonsPop: " + std::to_string(mBalloonsPopped), color); + mText->writeShadowed(2, 2 + 9 * BLOCK, "(Z-X)ballSped:" + std::to_string(mEnemySpeed), color); + } +} + +// Indica si se puede crear una powerball +bool Game::canPowerBallBeCreated() +{ + if ((!mPowerBallEnabled) && (calculateScreenPower() > 0)) + return true; + else + return false; +} + +// Calcula el poder actual de los globos en pantalla +int Game::calculateScreenPower() +{ + int power = 0; + + for (int i = 0; i < MAX_BALLOONS; i++) + if (mBalloon[i]->isEnabled()) + power += mBalloon[i]->getPower(); + + return power; +} + +// Inicializa las variables que contienen puntos de ruta para mover objetos +void Game::initPaths() +{ + // Letrero de STAGE # + const int firstPart = STAGE_COUNTER / 4; // 50 + const int secondPart = firstPart * 3; // 150 + const int centerPoint = PLAY_AREA_CENTER_Y - (BLOCK * 2); + const int distance = (PLAY_AREA_BOTTOM) - (PLAY_AREA_CENTER_Y - 16); + + for (int i = 0; i < STAGE_COUNTER; i++) + { + if (i < firstPart) + mStageBitmapPath[i] = (mSin[(int)((i * 1.8f) + 90)] * (distance) + centerPoint); + else if (i < secondPart) + mStageBitmapPath[i] = (int)centerPoint; + else + mStageBitmapPath[i] = (mSin[(int)(((i - 149) * 1.8f) + 90)] * (centerPoint + 17) - 17); + } + + const float start1 = PLAY_AREA_LEFT - 130; + const float finish1 = PLAY_AREA_CENTER_X - 55; + + const float start2 = finish1; + const float finish2 = PLAY_AREA_RIGHT + 20; + + const float distance1 = finish1 - start1; + const float distance2 = finish2 - start2; + + // Letrero de GetReady + for (int i = 0; i < STAGE_COUNTER; i++) + { + if (i < firstPart) + { + mGetReadyBitmapPath[i] = mSin[(int)(i * 1.8f)]; + mGetReadyBitmapPath[i] *= distance1; + mGetReadyBitmapPath[i] -= 130; + } + else if (i < secondPart) + mGetReadyBitmapPath[i] = (int)finish1; + else + { + mGetReadyBitmapPath[i] = mSin[(int)((i - 150) * 1.8f)]; + mGetReadyBitmapPath[i] *= distance2; + mGetReadyBitmapPath[i] += finish1; + } + } +} diff --git a/source/game.h b/source/game.h new file mode 100644 index 0000000..ff32870 --- /dev/null +++ b/source/game.h @@ -0,0 +1,470 @@ +#pragma once +#include "ifdefs.h" +#include "const.h" +#include "utils.h" + +#include "sprite.h" +#include "movingsprite.h" +#include "smartsprite.h" + +#include "player.h" +#include "balloon.h" +#include "bullet.h" +#include "item.h" + +#include "text.h" +#include "text2.h" +#include "menu.h" +#include "input.h" +#include "fade.h" +#include "jail_audio.h" + +#ifndef GAME_H +#define GAME_H + +// Game +class Game +{ +private: + struct enemyInits_t + { + int x; // Posición en el eje X donde crear al enemigo + int y; // Posición en el eje Y donde crear al enemigo + float velX; // Velocidad inicial en el eje X + Uint8 kind; // Tipo de enemigo + Uint16 creationCounter; // Temporizador para la creación del enemigo + }; + + struct enemyFormation_t // Contiene la información de una formación enemiga + { + Uint8 numberOfEnemies; // Cantidad de enemigos que forman la formación + enemyInits_t init[50]; // Vector con todas las inicializaciones de los enemigos de la formación + }; + enemyFormation_t mEnemyFormation[100]; // Vector con todas las formaciones enemigas + + struct enemyPool_t + { + enemyFormation_t *set[10]; // Conjunto de formaciones enemigas + }; + enemyPool_t mEnemyPool[10]; // Variable con los diferentes conjuntos de formaciones enemigas + + struct stage_t // Contiene todas las variables relacionadas con una fase + { + enemyPool_t *enemyPool; // El conjunto de formaciones enemigas de la fase + Uint16 currentPower; // Cantidad actual de poder + Uint16 powerToComplete; // Cantidad de poder que se necesita para completar la fase + Uint8 maxMenace; // Umbral máximo de amenaza de la fase + Uint8 minMenace; // Umbral mínimo de amenaza de la fase + Uint8 number; // Numero de fase + }; + + struct effect_t + { + bool flash; // Indica si se ha de pintar la pantalla de blanco + bool shake; // Indica si se ha de agitar la pantalla + Uint8 shakeCounter; // Contador para medir el tiempo que dura el efecto + }; + + SDL_Renderer *mRenderer; // El renderizador de la ventana + std::string *mFileList; // Lista de ficheros con los recursos + Input *mInput; // Manejador de entrada + + Player *mPlayer; // El jugador + + Balloon *mBalloon[MAX_BALLOONS]; // Vector con los objetos globo + Bullet *mBullet[MAX_BULLETS]; // Vector con los objetos bala + Item *mItem[MAX_ITEMS]; // Vector con los objetos item + SmartSprite *mSmartSprite[MAX_SMART_SPRITES]; // Vector para almacenar y gestionar SmartSprites + + LTexture *mTextureBalloon; // Textura para los enemigos + LTexture *mTextureBullet; // Textura para las balas + LTexture *mTextureGameBG; // Textura para el fondo del juego + LTexture *mTextureGameText; // Textura para los sprites con textos + LTexture *mTextureItems; // Textura para los items + LTexture *mTexturePlayerBody; // Textura para el cuerpo del jugador + LTexture *mTexturePlayerDeath; // Textura para la animación de muerte del jugador + LTexture *mTexturePlayerLegs; // Textura para las piernas del jugador + LTexture *mTextureText; // Textura para el texto + LTexture *mTextureText2; // Textura para el texto + + Text *mText; // Variable con todos los objetos de texto + Text *mTextX2; // Variable con todos los objetos de texto + + Menu *mMenuGameOver; // Menú de la pantalla de game over + Menu *mMenuPause; // Menú de la pantalla de pausa + + Fade *mFade; // Objeto para renderizar fades + SDL_Event *mEventHandler; // Manejador de eventos + + MovingSprite *mClouds1a; // Sprite para las nubes superiores + MovingSprite *mClouds1b; // Sprite para las nubes superiores + MovingSprite *mClouds2a; // Sprite para las nubes inferiores + MovingSprite *mClouds2b; // Sprite para las nubes inferiores + SmartSprite *m1000Bitmap; // Sprite con el texto 1.000 + SmartSprite *m2500Bitmap; // Sprite con el texto 2.500 + SmartSprite *m5000Bitmap; // Sprite con el texto 5.000 + Sprite *mSpriteBackground; // Sprite con los graficos frontales del fondo + Sprite *mSpriteGetReady; // Sprite para el texto de GetReady del principio de la partida + Sprite *mSpriteGradient; // Sprite con los graficos del degradado de color de fondo + Sprite *mSpriteGrass; // Sprite para la hierba + Sprite *mSpritePowerMeter; // Sprite para el medidor de poder de la fase + Sprite *mSpriteScoreBoard; // Sprite para el fondo del marcador + + JA_Sound mSoundBalloon; // Sonido para la explosión del globo + JA_Sound mSoundBullet; // Sonido para los disparos + JA_Sound mSoundPlayerCollision; // Sonido para la colisión del jugador con un enemigo + JA_Sound mSoundHiScore; // Sonido para cuando se alcanza la máxima puntuación + JA_Sound mSoundItemDrop; // Sonido para cuando se genera un item + JA_Sound mSoundItemPickup; // Sonido para cuando se recoge un item + JA_Sound mSoundCoffeeOut; // Sonido para cuando el jugador pierde el café al recibir un impacto + JA_Sound mSoundStageChange; // Sonido para cuando se cambia de fase + JA_Sound mSoundBubble1; // Sonido para cuando el jugador muere + JA_Sound mSoundBubble2; // Sonido para cuando el jugador muere + JA_Sound mSoundBubble3; // Sonido para cuando el jugador muere + JA_Sound mSoundBubble4; // Sonido para cuando el jugador muere + JA_Sound mSoundClock; // Sonido para cuando se detiene el tiempo con el item reloj + JA_Sound mSoundPowerBall; // Sonido para cuando se explota una Power Ball + + JA_Music mMusicPlaying; // Musica de fondo + + Uint32 mTicks; // Contador de ticks para ajustar la velocidad del programa + Uint8 mTicksSpeed; // Velocidad a la que se repiten los bucles del programa + + Uint32 mScore; // Puntuación actual + Uint32 mHiScore; // Puntuación máxima + bool mHiScoreAchieved; // Indica si se ha superado la puntuación máxima + section_t mSection; // Seccion actual dentro del juego + stage_t mStage[10]; // Variable con los datos de cada pantalla + Uint8 mCurrentStage; // Indica la fase actual + Uint8 mStageBitmapCounter; // Contador para el tiempo visible del texto de Stage + float mStageBitmapPath[STAGE_COUNTER]; // Vector con los puntos Y por donde se desplaza el texto + float mGetReadyBitmapPath[STAGE_COUNTER]; // Vector con los puntos X por donde se desplaza el texto + Uint16 mDeathCounter; // Contador para la animación de muerte del jugador + Uint8 mDeathIndex; // Indice del vector de smartsprites que contiene el sprite del jugador + Uint8 mMenaceCurrent; // Nivel de amenaza actual + Uint8 mMenaceThreshold; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el numero de globos + bool mTimeStopped; // Indica si el tiempo está detenido + Uint16 mTimeStoppedCounter; // Temporizador para llevar la cuenta del tiempo detenido + Uint8 mRemainingExplosions; // Cantidad de explosiones restantes + Uint16 mRemainingExplosionsCounter; // Temporizador para la cantidad de explosiones restantes + bool mExplosionTime; // Indica si las explosiones estan en marcha + Uint32 mCounter; // Contador para el juego + Uint32 mScoreDataFile[TOTAL_SCORE_DATA]; // Datos del fichero de puntos + SDL_Rect mGradientRect[4]; // Vector con las coordenadas de los 4 gradientes + Uint16 mBalloonsPopped; // Lleva la cuenta de los globos explotados + Uint8 mLastEnemyDeploy; // Guarda cual ha sido la última formación desplegada para no repetir; + Uint8 mEnemyDeployCounter; // Cuando se lanza una formación, se le da un valor y no sale otra hasta que llegue a cero + float mEnemySpeed; // Velocidad a la que se mueven los enemigos + effect_t mEffect; // Variable para gestionar los efectos visuales + bool mPowerBallEnabled; // Indica si hay una powerball ya activa + Uint8 mPostFade; // Qué hacer al acabar el fade + Uint8 mNextProgSection; + float mSin[360]; // Vector con los valores del seno para 360 grados + + struct demo_t + { + bool enabled; // Indica si está activo el modo demo + bool recording; // Indica si está activado el modo para grabar la demo + Uint16 counter; // Contador para el modo demo + demoKeys_t keys; // Variable con las pulsaciones de teclas del modo demo + demoKeys_t dataFile[TOTAL_DEMO_DATA]; // Datos del fichero con los movimientos para la demo + }; + demo_t mDemo; // Variable con todas las variables relacionadas con el modo demo + + struct debug_t + { + bool enabled; // Indica si se va a mostrar la información de debug + Uint8 enemySet; // Escoge el set enemigo a generar + Uint8 gradR, gradG, gradB; // Colores RGB para modificar el color del gradiente de fondo + float hudW, hudH; // Multiplica el tamaño del hud de debug; + }; + debug_t mDebug; + +public: + // Constructor + Game(SDL_Renderer *renderer, std::string *filelist, Input *input, bool demo); + + // Destructor + ~Game(); + + // Inicializa el vector con los valores del seno + void initSin(); + + // Inicializa las variables necesarias para la sección 'Game' + void init(); + + // Carga los recursos necesarios para la sección 'Game' + bool loadMedia(); + + // Carga el fichero de puntos + bool loadScoreFile(); + + // Carga el fichero de datos para la demo + bool loadDemoFile(); + + // Guarda el fichero de puntos + bool saveScoreFile(); + + // Guarda el fichero de datos para la demo + bool saveDemoFile(); + + // Inicializa las formaciones enemigas + void initEnemyFormations(); + + // Inicializa los conjuntos de formaciones + void initEnemyPools(); + + // Inicializa las fases del juego + void initGameStages(); + + // Crea una formación de enemigos + void deployEnemyFormation(); + + // Aumenta el poder de la fase + void increaseStageCurrentPower(Uint8 power); + + // Establece el valor de la variable + void setScore(Uint32 score); + + // Establece el valor de la variable + void setHiScore(Uint32 score); + + // Actualiza el valor de HiScore en caso necesario + void updateHiScore(); + + // Transforma un valor numérico en una cadena de 6 cifras + std::string updateScoreText(Uint32 num); + + // Pinta el marcador en pantalla usando un objeto texto + void renderScoreBoard(); + + // Actualiza las variables del jugador + void updatePlayer(); + + // Actualiza las variables de la fase + void updateStage(); + + // Actualiza el estado de muerte + void updateDeath(); + + // Renderiza el fade final cuando se acaba la partida + void renderDeathFade(); + + // Actualiza los globos + void updateBalloons(); + + // Pinta en pantalla todos los globos activos + void renderBalloons(); + + // Devuelve el primer indice no activo del vector de globos + Uint8 getBalloonFreeIndex(); + + // Crea un globo nuevo en el vector de globos + Uint8 createNewBalloon(float x, int y, Uint8 kind, float velx, float speed, Uint16 stoppedcounter, LTexture *texture); + + // Crea una PowerBall + void createPowerBall(); + + // Establece a cero todos los valores del vector de objetos globo + void resetBalloons(); + + // Establece la velocidad de los globos + void setBalloonSpeed(float speed); + + // Incrementa la velocidad de los globos + void incBalloonSpeed(); + + // Decrementa la velocidad de los globos + void decBalloonSpeed(); + + // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase + void updateBalloonSpeed(); + + // Explosiona un globo. Lo destruye y crea otros dos si es el caso + void popBalloon(Uint8 index); + + // Explosiona un globo. Lo destruye + void destroyBalloon(Uint8 index); + + // Explosiona todos los globos + void popAllBalloons(); + + // Destruye todos los globos + void destroyAllBalloons(); + + // Detiene todos los globos + void stopAllBalloons(Uint16 time); + + // Pone en marcha todos los globos + void startAllBalloons(); + + // Obtiene el numero de globos activos + Uint8 countBalloons(); + + // Comprueba la colisión entre el jugador y los globos activos + bool checkPlayerBalloonCollision(); + + // Comprueba la colisión entre el jugador y los items + void checkPlayerItemCollision(); + + // Comprueba la colisión entre las balas y los globos + void checkBulletBalloonCollision(); + + // Mueve las balas activas + void moveBullets(); + + // Pinta las balas activas + void renderBullets(); + + // Devuelve el primer indice no activo del vector de balas + Uint8 getBulletFreeIndex(); + + // Establece a cero todos los valores del vector de objetos bala + void resetBullets(); + + // Crea un objeto bala + void createBullet(int x, int y, Uint8 kind); + + // Actualiza los items + void updateItems(); + + // Pinta los items activos + void renderItems(); + + // Devuelve el primer indice no activo del vector de items + Uint8 getItemFreeIndex(); + + // Establece a cero todos los valores del vector de objetos item + void resetItems(); + + // Devuelve un item en función del azar + Uint8 dropItem(); + + // Crea un objeto item + void createItem(int x, int y, Uint8 kind); + + // Crea un objeto SmartSprite + void createItemScoreSprite(int x, int y, SmartSprite *sprite); + + // Dibuja el efecto de flash + void renderFlashEffect(); + + // Actualiza el efecto de agitar la pantalla + void updateShakeEffect(); + + // Crea un SmartSprite para arrojar el item café al recibir un impacto + void throwCoffee(int x, int y); + + // Crea un SmartSprite para arrojar al jugador al morir + void throwPlayer(int x, int y); + + // Actualiza los SmartSprites + void updateSmartSprites(); + + // Pinta los SmartSprites activos + void renderSmartSprites(); + + // Devuelve el primer indice no activo del vector de SmartSprites + Uint8 getSmartSpriteFreeIndex(); + + // Establece a cero todos los valores del vector de objetos SmafrtSprite + void resetSmartSprites(); + + // Acciones a realizar cuando el jugador muere + void killPlayer(); + + // Obtiene el valor de la variable + Uint8 getSubsection(); + + // Calcula y establece el valor de amenaza en funcion de los globos activos + void setMenace(); + + // Obtiene el valor de la variable + Uint8 getMenace(); + + // Establece el valor de la variable + void setTimeStopped(bool value); + + // Obtiene el valor de la variable + bool isTimeStopped(); + + // Establece el valor de la variable + void setTimeStoppedCounter(Uint16 value); + + // Incrementa el valor de la variable + void incTimeStoppedCounter(Uint16 value); + + // Actualiza la variable EnemyDeployCounter + void updateEnemyDeployCounter(); + + // Actualiza y comprueba el valor de la variable + void updateTimeStoppedCounter(); + + // Establece el valor de la variable + void setExplosionTime(bool value); + + // Obtiene el valor de la variable + bool isExplosionTime(); + + // Establece el valor de la variable + void setRemainingExplosions(Uint8 value); + + // Actualiza y comprueba el valor de la variable + void updateRemainingExplosionsCounter(); + + // Gestiona el nivel de amenaza + void updateMenace(); + + // Actualiza el campo de juego + void updatePlayField(); + + // Actualiza el fondo + void updateBackground(); + + // Dibuja el fondo + void renderBackground(); + + // Dibuja el campo de juego + void renderPlayField(); + + // Gestiona las entradas desde el mando de juego + bool checkGameController(Uint8 state); + + // Gestiona la entrada durante el juego + void checkGameInput(); + + // Pinta diferentes mensajes en la pantalla + void renderMessages(); + + // Habilita el efecto del item de detener el tiempo + void enableTimeStopItem(); + + // Deshabilita el efecto del item de detener el tiempo + void disableTimeStopItem(); + + // Agita la pantalla + void shakeScreen(); + + // Bucle para el juego + section_t run(); + + // Bucle para el menu de pausa del juego + void runPausedGame(); + + // Bucle para la pantalla de game over + void runGameOverScreen(); + + // Dibuja la informacion de debug en pantalla + void renderDebugInfo(); + + // Indica si se puede crear una powerball + bool canPowerBallBeCreated(); + + // Calcula el poder actual de los globos en pantalla + int calculateScreenPower(); + + // Inicializa las variables que contienen puntos de ruta para mover objetos + void initPaths(); +}; + +#endif diff --git a/source/input.cpp b/source/input.cpp new file mode 100644 index 0000000..a292a3e --- /dev/null +++ b/source/input.cpp @@ -0,0 +1,163 @@ +#include "input.h" + +// Constructor +Input::Input() +{ + for (int i = 0; i < 15; i++) + { + mInput[i].scancode = 0; + mInput[i].active = false; + } +} + +// Destructor +Input::~Input() +{ +} + +// Asigna uno de los posibles inputs a una tecla del teclado +void Input::bindKey(Uint8 input, SDL_Scancode code) +{ + mInput[input].scancode = code; +} + +// Comprueba si un input esta activo +bool Input::checkInput(Uint8 input, bool repeat) +{ + const Uint8 *mKeystates = SDL_GetKeyboardState(NULL); + + if (repeat) + { + if (mKeystates[mInput[input].scancode] != 0) + return true; + else + return false; + } + else + { + if (!mInput[input].active) + { + if (mKeystates[mInput[input].scancode] != 0) + { + mInput[input].active = true; + return true; + } + else + { + return false; + } + } + else + { + if (mKeystates[mInput[input].scancode] == 0) + { + mInput[input].active = false; + return false; + } + else + { + return false; + } + } + } +} + +// Gestiona las entradas desde el mando de juego +bool Input::checkGameController(Uint8 state) +{ + bool success = false; + + // No hay mando. Siempre devuelve falso salvo NO_INPUT que siempre es cierto + /*if (!mGameControllerFound) + { + if (state == NO_INPUT) + return true; + else + return false; + } + + + switch (state) + { + case INPUT_NULL: + success = !checkGameController(INPUT_UP) && !checkGameController(INPUT_DOWN) && !checkGameController(INPUT_LEFT) && !checkGameController(INPUT_RIGHT) && + !checkGameController(INPUT_ACCEPT) && !checkGameController(INPUT_CANCEL) && !checkGameController(INPUT_PAUSE) && + !checkGameController(INPUT_FIRE_UP) && !checkGameController(INPUT_FIRE_LEFT) && !checkGameController(INPUT_FIRE_RIGHT); + break; + case INPUT_UP: + success = (SDL_JoystickGetAxis(mGameController, 1) < -JOYSTICK_DEAD_ZONE) || (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_DPAD_UP)); + break; + case INPUT_DOWN: + success = (SDL_JoystickGetAxis(mGameController, 1) > JOYSTICK_DEAD_ZONE) || (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_DPAD_DOWN)); + break; + case INPUT_LEFT: + success = (SDL_JoystickGetAxis(mGameController, 0) < -JOYSTICK_DEAD_ZONE) || (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_DPAD_LEFT)); + break; + case INPUT_RIGHT: + success = (SDL_JoystickGetAxis(mGameController, 0) > JOYSTICK_DEAD_ZONE) || (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_DPAD_RIGHT)); + break; + case INPUT_ACCEPT: + success = (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_B)); + break; + case INPUT_CANCEL: + success = (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_A)); + break; + case INPUT_PAUSE: + success = (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_START)); + break; + case INPUT_FIRE_UP: + success = (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_Y)); + break; + case INPUT_FIRE_LEFT: + success = (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_X)); + break; + case INPUT_FIRE_RIGHT: + success = (SDL_JoystickGetButton(mGameController, SDL_CONTROLLER_BUTTON_B)); + break; + + default: + break; + }*/ + return success; +} + +// Comprueba si hay algun mando conectado + /*if (SDL_NumJoysticks() < 1) + { + printf("Warning: No joysticks connected!\n"); + mGameControllerFound = false; + } + else + { + // Carga el mando + mGameController = SDL_JoystickOpen(0); + mGameControllerFound = true; + + if (mGameController == NULL) + { + printf("Warning: Unable to open game controller!\nSDL Error: %s\n", SDL_GetError()); + mGameControllerFound = false; + } + else + { + printf("%i joysticks were found.\n", SDL_NumJoysticks()); + std::cout << SDL_JoystickNumButtons(mGameController) << " buttons\n"; + + // Obtiene el dispositivo de control háptico + mControllerHaptic = SDL_HapticOpenFromJoystick(mGameController); + if (mControllerHaptic == NULL) + { + printf("Warning: Controller does not support haptics!\nSDL Error: %s\n", SDL_GetError()); + } + else + { + printf("Haptics detected\n"); + + // Inicializa la vibración + if (SDL_HapticRumbleInit(mControllerHaptic) < 0) + { + printf("Warning: Unable to initialize rumble!\nSDL Error: %s\n", SDL_GetError()); + } + } + } + }*/ diff --git a/source/input.h b/source/input.h new file mode 100644 index 0000000..523de6b --- /dev/null +++ b/source/input.h @@ -0,0 +1,58 @@ +#pragma once +#include "ifdefs.h" + +#ifndef INPUT_H +#define INPUT_H + +#define INPUT_NULL 0 +#define INPUT_UP 1 +#define INPUT_DOWN 2 +#define INPUT_LEFT 3 +#define INPUT_RIGHT 4 +#define INPUT_ACCEPT 5 +#define INPUT_CANCEL 6 +#define INPUT_BUTTON_1 7 +#define INPUT_BUTTON_2 8 +#define INPUT_BUTTON_3 9 +#define INPUT_BUTTON_4 10 +#define INPUT_BUTTON_5 11 +#define INPUT_BUTTON_6 12 +#define INPUT_BUTTON_7 13 +#define INPUT_BUTTON_8 14 + +#define REPEAT_TRUE true +#define REPEAT_FALSE false + +// Clase Input +class Input +{ +private: + struct input_t + { + Uint8 scancode; // Scancode asociado + bool active; // Indica si está activo + }; + input_t mInput[15]; // Vector con las teclas asociadas a los inputs predefinidos + + //SDL_Joystick *mGameController; // Manejador para el mando 1 + //SDL_Haptic *mControllerHaptic; // Manejador para el mando con vibración + //bool mGameControllerFound; // Variable para saber si hay un mando conectado + +public: + // Constructor + Input(); + + // Destructor + ~Input(); + + // Asigna uno de los posibles inputs a una tecla del teclado + void bindKey(Uint8 input, SDL_Scancode code); + + // Comprueba si un input esta activo + bool checkInput(Uint8 input, bool repeat); + + // Gestiona las entradas desde el mando de juego + bool checkGameController(Uint8 state); +}; + +#endif diff --git a/source/instructions.cpp b/source/instructions.cpp new file mode 100644 index 0000000..098a911 --- /dev/null +++ b/source/instructions.cpp @@ -0,0 +1,230 @@ +#include "instructions.h" +#ifdef __MIPSEL__ +#include +#include +#endif + +const Uint8 SELF = 0; + +// Constructor +Instructions::Instructions(SDL_Renderer *renderer, std::string *fileList) +{ + // Copia la dirección del renderizador + mRenderer = renderer; + + // Copia la dirección del la lista de ficheros + mFileList = fileList; + + // Reserva memoria para los punteros + mEventHandler = new SDL_Event(); + mItemTexture = new LTexture(); + mTextTexture = new LTexture(); + mSprite = new Sprite(); + mText = new Text(mTextTexture, mRenderer); + + // Crea un backbuffer para el renderizador + mBackbuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, SCREEN_WIDTH, SCREEN_HEIGHT); + if (mBackbuffer == NULL) + printf("Backbuffer could not be created!\nSDL Error: %s\n", SDL_GetError()); +} + +// Destructor +Instructions::~Instructions() +{ + mItemTexture->unload(); + delete mItemTexture; + mItemTexture = nullptr; + + mTextTexture->unload(); + delete mTextTexture; + mTextTexture = nullptr; + + delete mSprite; + mSprite = nullptr; + + delete mEventHandler; + mEventHandler = nullptr; + + delete mText; + mText = nullptr; + + SDL_DestroyTexture(mBackbuffer); + mBackbuffer = nullptr; +} + +// Carga los recursos necesarios para la sección 'Instructions' +bool Instructions::loadMedia() +{ + bool success = true; + + success &= loadTextureFromFile(mItemTexture, mFileList[34], mRenderer); + success &= loadTextureFromFile(mTextTexture, mFileList[30], mRenderer); + + return success; +} + +// Inicializa las variables necesarias para la sección 'Instructions' +void Instructions::init() +{ + // Carga los recursos + loadMedia(); + + // Inicializa variables + mSection.name = SELF; + mSprite->init(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, mItemTexture, mRenderer); + mTicks = 0; + mTicksSpeed = 15; + mText->init(TEXT_FIXED, BLOCK); + mManualQuit = false; + mCounter = 0; +} + +// Bucle para la pantalla de instrucciones +void Instructions::run(Uint8 mode) +{ + init(); + + while (mSection.name == SELF) + { + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + + if ((mEventHandler->type == SDL_KEYDOWN) || (mEventHandler->type == SDL_JOYBUTTONDOWN)) + { + if (mode == INSTRUCTIONS_MODE_AUTO) + { + JA_StopMusic(); + mSection.name = PROG_SECTION_TITLE; + mSection.subsection = TITLE_SECTION_1; + } + else + mManualQuit = true; + } + } + + // Actualiza las variables + if (SDL_GetTicks() - mTicks > mTicksSpeed) + { + // Actualiza el contador de ticks + mTicks = SDL_GetTicks(); + + // Modo automático + if (mode == INSTRUCTIONS_MODE_AUTO) + { + mCounter++; + + if (mCounter == INSTRUCTIONS_COUNTER) + { + mSection.name = PROG_SECTION_TITLE; + mSection.subsection = TITLE_SECTION_1; + } + } + // Modo manual + else + { + ++mCounter %= 60000; + + if (mManualQuit) + { + mSection.name = PROG_SECTION_TITLE; + mSection.subsection = TITLE_SECTION_3; + } + } + } + + // Pinta en pantalla + SDL_Rect window = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; + SDL_Rect srcRect = {0, 0, 16, 16}; + + const color_t orangeColor = {0xFF, 0x7A, 0x00}; + + const SDL_Rect destRect1 = {60, 88 + (16 * 0), 16, 16}; // Disquito + const SDL_Rect destRect2 = {60, 88 + (16 * 1), 16, 16}; // Gavineixon + const SDL_Rect destRect3 = {60, 88 + (16 * 2), 16, 16}; // Pacmar + const SDL_Rect destRect4 = {60, 88 + (16 * 3), 16, 16}; // Time Stopper + const SDL_Rect destRect5 = {60, 88 + (16 * 4), 16, 16}; // Coffee + + // Pinta en el backbuffer el texto y los sprites + SDL_SetRenderTarget(mRenderer, mBackbuffer); + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, 255); + SDL_RenderClear(mRenderer); + + // Escribe el texto + mText->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, SCREEN_CENTER_X, 8, "OBJECTIVE", 0, orangeColor, 1, shdwTxtColor); + mText->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, SCREEN_CENTER_X, 24, "YOU HAVE TO POP AS MANY", 0, noColor, 1, shdwTxtColor); + mText->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, SCREEN_CENTER_X, 34, "BALLOONS AS YOU CAN", 0, noColor, 1, shdwTxtColor); + mText->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, SCREEN_CENTER_X, 48, "DIFFICULTY WILL BE INCREASED", 0, noColor, 1, shdwTxtColor); + mText->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, SCREEN_CENTER_X, 58, "AS YOU SCORE POINTS", 0, noColor, 1, shdwTxtColor); + mText->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, SCREEN_CENTER_X, 75, "ITEMS", 0, orangeColor, 1, shdwTxtColor); + + mText->writeShadowed(84, 92, "1.000 POINTS", shdwTxtColor); + mText->writeShadowed(84, 108, "2.500 POINTS", shdwTxtColor); + mText->writeShadowed(84, 124, "5.000 POINTS", shdwTxtColor); + mText->writeShadowed(84, 140, "TIME STOPPER", shdwTxtColor); + mText->writeShadowed(84, 156, "EXTRA HIT", shdwTxtColor); + + if ((mode == INSTRUCTIONS_MODE_MANUAL) && (mCounter % 50 > 14)) + mText->writeDX(TXT_CENTER | TXT_COLOR | TXT_SHADOW, SCREEN_CENTER_X, SCREEN_HEIGHT - 12, "PRESS ANY KEY TO RETURN", 0, orangeColor, 1, shdwTxtColor); + + // Disquito + mSprite->init(destRect1, mItemTexture, mRenderer); + srcRect.x = 0; + srcRect.y = 16 * (((mCounter + 12) / 36) % 2); + mSprite->setSpriteClip(srcRect); + mSprite->render(); + + // Gavineixon + mSprite->init(destRect2, mItemTexture, mRenderer); + srcRect.x += srcRect.w; + srcRect.y = 16 * (((mCounter + 9) / 36) % 2); + mSprite->setSpriteClip(srcRect); + mSprite->render(); + + // Pacmar + mSprite->init(destRect3, mItemTexture, mRenderer); + srcRect.x += srcRect.w; + srcRect.y = 16 * (((mCounter + 6) / 36) % 2); + mSprite->setSpriteClip(srcRect); + mSprite->render(); + + // Time Stopper + mSprite->init(destRect4, mItemTexture, mRenderer); + srcRect.x += srcRect.w; + srcRect.y = 16 * (((mCounter + 3) / 36) % 2); + mSprite->setSpriteClip(srcRect); + mSprite->render(); + + // Coffee + mSprite->init(destRect5, mItemTexture, mRenderer); + srcRect.x += (srcRect.w * 2); // Se salta el icono del TNT + srcRect.y = 16 * (((mCounter + 0) / 36) % 2); + mSprite->setSpriteClip(srcRect); + mSprite->render(); + + // Cambia el destino de renderizado + SDL_SetRenderTarget(mRenderer, nullptr); + + // Limpia el renderizador + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, 255); + SDL_RenderClear(mRenderer); + + // Establece la ventana del backbuffer + if (mode == INSTRUCTIONS_MODE_AUTO) + window.y = std::max(8, SCREEN_HEIGHT - mCounter + 100); + else + window.y = 0; + + // Copia el backbuffer al renderizador + SDL_RenderCopy(mRenderer, mBackbuffer, NULL, &window); + + // Dibuja el renderizador en pantalla + SDL_RenderPresent(mRenderer); + } +} diff --git a/source/instructions.h b/source/instructions.h new file mode 100644 index 0000000..d2636bb --- /dev/null +++ b/source/instructions.h @@ -0,0 +1,54 @@ +#pragma once +#include "ifdefs.h" +#include "utils.h" +#include "const.h" + +#include "sprite.h" +#include "text.h" +#include "jail_audio.h" + +#ifndef INSTRUCTIONS_H +#define INSTRUCTIONS_H + +// Instructions +class Instructions +{ +private: + LTexture *mItemTexture; // Textura con los graficos + LTexture *mTextTexture; // Textura con los graficos + SDL_Event *mEventHandler; // Manejador de eventos + SDL_Renderer *mRenderer; // El renderizador de la ventana + SDL_Texture *mBackbuffer; // Textura para usar como backbuffer + Sprite *mSprite; // Sprite con la textura de las instrucciones + std::string *mFileList; // Lista de ficheros + Text *mText; // Objeto para escribir texto + Uint16 mCounter; // Contador + section_t mSection; // Estado del bucle principal para saber si continua o se sale + Uint32 mTicks; // Contador de ticks para ajustar la velocidad del programa + Uint8 mTicksSpeed; // Velocidad a la que se repiten los bucles del programa + bool mManualQuit; // Indica si se quiere salir del modo manual + + // Carga los recursos + bool loadMedia(); + + // Actualiza las variables + void update(); + + // Pinta en pantalla + void render(); + + // Inicializa las variables + void init(); + +public: + // Constructor + Instructions(SDL_Renderer *renderer, std::string *fileList); + + // Destructor + ~Instructions(); + + // Bucle principal + void run(Uint8 mode); +}; + +#endif diff --git a/source/intro.cpp b/source/intro.cpp new file mode 100644 index 0000000..d70645a --- /dev/null +++ b/source/intro.cpp @@ -0,0 +1,365 @@ +#include "intro.h" +#ifdef __MIPSEL__ +#include +#include +#endif + +// Constructor +Intro::Intro(SDL_Renderer *renderer, std::string *fileList) +{ + // Copia la dirección del renderizador + mRenderer = renderer; + + // Copia la dirección del la lista de ficheros + mFileList = fileList; + + // Reserva memoria para los punteros + mEventHandler = new SDL_Event(); + mBitmapTexture = new LTexture(); + mTextTexture = new LTexture(); + + for (int i = 0; i < INTRO_TOTAL_BITMAPS; i++) + mBitmap[i] = new SmartSprite(); + + for (int i = 0; i < INTRO_TOTAL_TEXTS; i++) + mText[i] = new Text2(mTextTexture, mRenderer); +} + +// Destructor +Intro::~Intro() +{ + delete mEventHandler; + mEventHandler = nullptr; + + mBitmapTexture->unload(); + delete mBitmapTexture; + mBitmapTexture = nullptr; + + mTextTexture->unload(); + delete mTextTexture; + mTextTexture = nullptr; + + for (int i = 0; i < INTRO_TOTAL_BITMAPS; i++) + { + delete mBitmap[i]; + mBitmap[i] = nullptr; + } + for (int i = 0; i < INTRO_TOTAL_TEXTS; i++) + { + delete mText[i]; + mText[i] = nullptr; + } + + JA_DeleteMusic(mMusic); +} + +// Inicializa las variables +void Intro::init() +{ + // Carga los recursos + loadMedia(); + + mSection = {PROG_SECTION_INTRO, 0}; + mTicks = 0; + mTicksSpeed = 15; + + // Inicializa el vector de eventos de la intro + for (int i = 0; i < INTRO_TOTAL_EVENTS; i++) + mEvents[i] = EVENT_WAITING; + + // Inicializa los bitmaps de la intro + for (int i = 0; i < INTRO_TOTAL_BITMAPS; i++) + { + mBitmap[i]->init(mBitmapTexture, mRenderer); + mBitmap[i]->setId(i); + mBitmap[i]->setIntroEvents(&mEvents[0]); + mBitmap[i]->setWidth(128); + mBitmap[i]->setHeight(96); + mBitmap[i]->setEnabled(false); + mBitmap[i]->setEnabledTimer(20); + mBitmap[i]->setDestX(SCREEN_CENTER_X - 64); + mBitmap[i]->setDestY(SCREEN_FIRST_QUARTER_Y - 24); + } + + mBitmap[0]->setPosX(-128); + mBitmap[0]->setPosY(SCREEN_FIRST_QUARTER_Y - 24); + mBitmap[0]->setVelX(0.0f); + mBitmap[0]->setVelY(0.0f); + mBitmap[0]->setAccelX(0.6f); + mBitmap[0]->setAccelY(0.0f); + mBitmap[0]->setSpriteClip(0, 0, 128, 96); + + mBitmap[1]->setPosX(SCREEN_WIDTH); + mBitmap[1]->setPosY(SCREEN_FIRST_QUARTER_Y - 24); + mBitmap[1]->setVelX(-1.0f); + mBitmap[1]->setVelY(0.0f); + mBitmap[1]->setAccelX(-0.3f); + mBitmap[1]->setAccelY(0.0f); + mBitmap[1]->setSpriteClip(128, 0, 128, 96); + + mBitmap[2]->setPosX(SCREEN_CENTER_X - 64); + mBitmap[2]->setPosY(-96); + mBitmap[2]->setVelX(0.0f); + mBitmap[2]->setVelY(3.0f); + mBitmap[2]->setAccelX(0.1f); + mBitmap[2]->setAccelY(0.3f); + mBitmap[2]->setSpriteClip(0, 96, 128, 96); + + mBitmap[2]->setEnabledTimer(250); + + mBitmap[3]->setPosX(SCREEN_CENTER_X - 64); + mBitmap[3]->setPosY(SCREEN_HEIGHT); + mBitmap[3]->setVelX(0.0f); + mBitmap[3]->setVelY(-0.7f); + mBitmap[3]->setAccelX(0.0f); + mBitmap[3]->setAccelY(0.0f); + mBitmap[3]->setSpriteClip(128, 96, 128, 96); + + mBitmap[4]->setPosX(SCREEN_CENTER_X - 64); + mBitmap[4]->setPosY(-96); + mBitmap[4]->setVelX(0.0f); + mBitmap[4]->setVelY(3.0f); + mBitmap[4]->setAccelX(0.1f); + mBitmap[4]->setAccelY(0.3f); + mBitmap[4]->setSpriteClip(0, 192, 128, 96); + + mBitmap[5]->setPosX(SCREEN_WIDTH); + mBitmap[5]->setPosY(SCREEN_FIRST_QUARTER_Y - 24); + mBitmap[5]->setVelX(-0.7f); + mBitmap[5]->setVelY(0.0f); + mBitmap[5]->setAccelX(0.0f); + mBitmap[5]->setAccelY(0.0f); + mBitmap[5]->setSpriteClip(128, 192, 128, 96); + + // Inicializa los textos de la intro + for (int i = 0; i < INTRO_TOTAL_TEXTS; i++) + { + mText[i]->init(TEXT_VARIABLE, 10); + mText[i]->setId(6 + i); + mText[i]->setIntroEvents(&mEvents[0]); + mText[i]->setPosX(BLOCK * 0); + mText[i]->setPosY(SCREEN_HEIGHT - (BLOCK * 6)); + mText[i]->setKerning(-1); + mText[i]->setEnabled(false); + mText[i]->setEnabledTimer(180); + } + + mText[0]->setCaption("Un dia qualsevol de l'any 2000"); + mText[0]->setWrittingSpeed(10); + + mText[1]->setCaption("Tot esta tranquil a la UPV"); + mText[1]->setWrittingSpeed(10); + + mText[2]->setCaption("Fins que un desaprensiu..."); + mText[2]->setWrittingSpeed(15); + + mText[3]->setCaption("HEY! ME ANE A FERME UN CORTAET..."); + mText[3]->setWrittingSpeed(10); + + mText[4]->setCaption("UAAAAAAAAAAAAA!!!"); + mText[4]->setWrittingSpeed(1); + + mText[5]->setCaption("Espera un moment..."); + mText[5]->setWrittingSpeed(20); + + mText[6]->setCaption("Si resulta que no tinc solt!"); + mText[6]->setWrittingSpeed(2); + + mText[7]->setCaption("MERDA DE MAQUINA!"); + mText[7]->setWrittingSpeed(3); + + mText[8]->setCaption("Blop... blop... blop..."); + mText[8]->setWrittingSpeed(20); + + for (int i = 0; i < INTRO_TOTAL_TEXTS; i++) + { + mText[i]->center(SCREEN_CENTER_X); + } +} + +// Carga los recursos +bool Intro::loadMedia() +{ + bool success = true; + + // Texturas + success &= loadTextureFromFile(mBitmapTexture, mFileList[33], mRenderer); + success &= loadTextureFromFile(mTextTexture, mFileList[28], mRenderer); + + // Musicas + mMusic = JA_LoadMusic(mFileList[3].c_str()); + + return success; +} + +// Bucle principal +section_t Intro::run() +{ + init(); + + // Si la música no está sonando + if ((JA_GetMusicState() == JA_MUSIC_INVALID) || (JA_GetMusicState() == JA_MUSIC_STOPPED)) + { + // Reproduce la música + JA_PlayMusic(mMusic, 0); + } + + while (mSection.name == PROG_SECTION_INTRO) + { + if (SDL_GetTicks() - mTicks > mTicksSpeed) + { + // Actualiza el contador de ticks + mTicks = SDL_GetTicks(); + + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + + if ((mEventHandler->type == SDL_KEYDOWN) || (mEventHandler->type == SDL_JOYBUTTONDOWN)) + { + JA_StopMusic(); + mSection = {PROG_SECTION_TITLE, TITLE_SECTION_1}; + } + } + + // Actualiza los objetos + for (int i = 0; i < INTRO_TOTAL_BITMAPS; i++) + mBitmap[i]->update(); + for (int i = 0; i < INTRO_TOTAL_TEXTS; i++) + mText[i]->update(); + + // Guión de eventos + // Primera imagen - UPV + if (mEvents[BITMAP0] == EVENT_WAITING) + { + mBitmap[0]->setEnabled(true); + mEvents[BITMAP0] = EVENT_RUNNING; + } + + // Primer texto de la primera imagen + if ((mEvents[BITMAP0] == EVENT_COMPLETED) && (mEvents[TEXT0] == EVENT_WAITING)) + { + mText[0]->setEnabled(true); + mEvents[TEXT0] = EVENT_RUNNING; + } + + // Segundo texto de la primera imagen + if ((mEvents[TEXT0] == EVENT_COMPLETED) && (mEvents[TEXT1] == EVENT_WAITING)) + { + mText[0]->setEnabled(false); + mText[1]->setEnabled(true); + mEvents[TEXT1] = EVENT_RUNNING; + } + + // Tercer texto de la primera imagen + if ((mEvents[TEXT1] == EVENT_COMPLETED) && (mEvents[TEXT2] == EVENT_WAITING)) + { + mText[1]->setEnabled(false); + mText[2]->setEnabled(true); + mEvents[TEXT2] = EVENT_RUNNING; + } + + // Segunda imagen - Máquina + if ((mEvents[TEXT2] == EVENT_COMPLETED) && (mEvents[BITMAP1] == EVENT_WAITING)) + { + mBitmap[0]->setEnabled(false); + mText[2]->setEnabled(false); + mBitmap[1]->setEnabled(true); + mEvents[BITMAP1] = EVENT_RUNNING; + } + + // Primer texto de la segunda imagen + if ((mEvents[BITMAP1] == EVENT_COMPLETED) && (mEvents[TEXT3] == EVENT_WAITING)) + { + mText[3]->setEnabled(true); + mEvents[TEXT3] = EVENT_RUNNING; + } + + // Tercera imagen junto con primer texto - GRITO + if ((mEvents[TEXT3] == EVENT_COMPLETED) && (mEvents[BITMAP2] == EVENT_WAITING) && (mEvents[TEXT4] == EVENT_WAITING)) + { + mBitmap[1]->setEnabled(false); + mText[3]->setEnabled(false); + mBitmap[2]->setEnabled(true); + mText[4]->setEnabled(true); + mEvents[BITMAP2] = EVENT_RUNNING; + mEvents[TEXT4] = EVENT_RUNNING; + } + + // Cuarta imagen junto con primer texto - Reflexión + if ((mEvents[TEXT4] == EVENT_COMPLETED) && (mEvents[BITMAP3] == EVENT_WAITING) && (mEvents[TEXT5] == EVENT_WAITING)) + { + mBitmap[2]->setEnabled(false); + mText[4]->setEnabled(false); + mBitmap[3]->setEnabled(true); + mText[5]->setEnabled(true); + mEvents[BITMAP3] = EVENT_RUNNING; + mEvents[TEXT5] = EVENT_RUNNING; + } + + // Segundo texto de la cuarta imagen + if ((mEvents[TEXT5] == EVENT_COMPLETED) && (mEvents[TEXT6] == EVENT_WAITING)) + { + mText[5]->setEnabled(false); + mText[6]->setEnabled(true); + mEvents[TEXT6] = EVENT_RUNNING; + } + + // Quinta imagen - Patada + if ((mEvents[TEXT6] == EVENT_COMPLETED) && (mEvents[BITMAP4] == EVENT_WAITING)) + { + mBitmap[3]->setEnabled(false); + mText[6]->setEnabled(false); + mBitmap[4]->setEnabled(true); + mEvents[BITMAP4] = EVENT_RUNNING; + } + + // Primer texto de la quinta imagen + if ((mEvents[BITMAP4] == EVENT_COMPLETED) && (mEvents[TEXT7] == EVENT_WAITING)) + { + mText[7]->setEnabled(true); + mEvents[TEXT7] = EVENT_RUNNING; + } + + // Sexta imagen junto con texto - Globos de café + if ((mEvents[TEXT7] == EVENT_COMPLETED) && (mEvents[BITMAP5] == EVENT_WAITING) && (mEvents[TEXT8] == EVENT_WAITING)) + { + mBitmap[4]->setEnabled(false); + mText[7]->setEnabled(false); + mBitmap[5]->setEnabled(true); + mText[8]->setEnabled(true); + mEvents[BITMAP5] = EVENT_RUNNING; + mEvents[TEXT8] = EVENT_RUNNING; + } + + // Acaba el último texto + if (mEvents[TEXT8] == EVENT_COMPLETED) + { + mText[8]->setEnabled(false); + JA_StopMusic(); + mSection = {PROG_SECTION_TITLE, TITLE_SECTION_1}; + } + } + + // Limpia la pantalla + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, 0xFF); + SDL_RenderClear(mRenderer); + + // Dibuja los objetos + for (int i = 0; i < INTRO_TOTAL_BITMAPS; i++) + mBitmap[i]->render(); + for (int i = 0; i < INTRO_TOTAL_TEXTS; i++) + mText[i]->render(); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + } + + return mSection; +} diff --git a/source/intro.h b/source/intro.h new file mode 100644 index 0000000..4f062b0 --- /dev/null +++ b/source/intro.h @@ -0,0 +1,47 @@ +#pragma once +#include "ifdefs.h" +#include "const.h" +#include "utils.h" + +#include "smartsprite.h" +#include "text2.h" +#include "jail_audio.h" + +#ifndef INTRO_H +#define INTRO_H + +// Intro +class Intro +{ +private: + LTexture *mBitmapTexture; // Textura con los graficos + LTexture *mTextTexture; // Textura con los caracteres de texto + SDL_Event *mEventHandler; // Manejador de eventos + SDL_Renderer *mRenderer; // El renderizador de la ventana + std::string *mFileList; // Lista de ficheros + section_t mSection; // Estado del bucle principal para saber si continua o se sale + Uint32 mTicks; // Contador de ticks para ajustar la velocidad del programa + Uint8 mTicksSpeed; // Velocidad a la que se repiten los bucles del programa + SmartSprite *mBitmap[INTRO_TOTAL_BITMAPS]; // Vector con los sprites inteligentes para los dibujos de la intro + Text2 *mText[INTRO_TOTAL_TEXTS]; // Textos de la intro + Uint8 mEvents[INTRO_TOTAL_EVENTS]; // Vector para coordinar los eventos de la intro + JA_Music mMusic; // Musica para la intro + +public: + // Constructor + Intro(SDL_Renderer *renderer, std::string *fileList); + + // Destructor + ~Intro(); + + // Inicializa las variables + void init(); + + // Carga los recursos + bool loadMedia(); + + // Bucle principal + section_t run(); +}; + +#endif diff --git a/source/item.cpp b/source/item.cpp index b5f5e2f..fe701b2 100644 --- a/source/item.cpp +++ b/source/item.cpp @@ -5,14 +5,16 @@ Item::Item() { mSprite = new AnimatedSprite(); - init(NO_KIND, 0, 0, nullptr, nullptr); + //init(NO_KIND, 0, 0, nullptr, nullptr); + mClass = NO_KIND; } // Destructor Item::~Item() { - init(NO_KIND, 0, 0, nullptr, nullptr); + //init(NO_KIND, 0, 0, nullptr, nullptr); delete mSprite; + mSprite = nullptr; } // Iniciador diff --git a/source/logo.cpp b/source/logo.cpp new file mode 100644 index 0000000..7c2e5e0 --- /dev/null +++ b/source/logo.cpp @@ -0,0 +1,151 @@ +#include "logo.h" +#ifdef __MIPSEL__ +#include +#include +#endif + +// Constructor +Logo::Logo(SDL_Renderer *renderer, std::string *fileList) +{ + // Copia la dirección del renderizador + mRenderer = renderer; + + // Copia la dirección del la lista de ficheros + mFileList = fileList; + + // Reserva memoria para los punteros + mEventHandler = new SDL_Event(); + mTexture = new LTexture(); + mSprite = new Sprite(); + + // Crea un backbuffer para el renderizador + mBackbuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, SCREEN_WIDTH, SCREEN_HEIGHT); + if (mBackbuffer == NULL) + printf("Backbuffer could not be created!\nSDL Error: %s\n", SDL_GetError()); +} + +// Destructor +Logo::~Logo() +{ + mTexture->unload(); + delete mTexture; + mTexture = nullptr; + + delete mSprite; + mSprite = nullptr; + + delete mEventHandler; + mEventHandler = nullptr; + + SDL_DestroyTexture(mBackbuffer); + mBackbuffer = nullptr; +} + +// Inicializa las variables necesarias para la sección 'Logo' +void Logo::init() +{ + // Carga los recursos + loadMedia(); + + // Inicializa variables + mCounter = 0; + mSection.name = PROG_SECTION_LOGO; + mSection.subsection = 0; + mSprite->init(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, mTexture, mRenderer); + mTicks = 0; + mTicksSpeed = 15; +} + +// Carga los recursos necesarios para la sección 'Logo' +bool Logo::loadMedia() +{ + bool success = true; + + success &= loadTextureFromFile(mTexture, mFileList[35], mRenderer); + + return success; +} + +// Bucle para el logo del juego +section_t Logo::run() +{ + init(); + const SDL_Rect rect = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; + + while (mSection.name == PROG_SECTION_LOGO) + { + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + + // Cualquier tecla pulsada + if ((mEventHandler->type == SDL_KEYDOWN) || (mEventHandler->type == SDL_JOYBUTTONDOWN)) + { + mSection.name = PROG_SECTION_TITLE; + mSection.subsection = TITLE_SECTION_1; + } + } + + // Cambia el destino donde se pinta todo + //SDL_SetRenderTarget(mRenderer, mBackbuffer); + + // Limpia el destino + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, 255); + SDL_RenderClear(mRenderer); + + // Dibuja los objetos + mSprite->render(); + + // Dibuja el fade + if (mCounter >= 200) + { + Uint16 alpha = mCounter - 200; + if (alpha < 256) + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, alpha); + else + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, 255); // alpha - 0 trans, 255 opaco + SDL_RenderFillRect(mRenderer, &rect); + } + + // Vuelve a usar el renderizador como destino + //SDL_SetRenderTarget(mRenderer, NULL); + + // Copia el backbufer al renderizador + //SDL_RenderCopy(mRenderer, mBackbuffer, NULL, NULL); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + + // Comprueba si ha terminado el logo + if (SDL_GetTicks() - mTicks > mTicksSpeed) + { + // Actualiza el contador de ticks + mTicks = SDL_GetTicks(); + + if (mCounter == 0) + { + if (JA_GetMusicState() == JA_MUSIC_PLAYING) + JA_StopMusic(); + } + + if (mCounter == 500) // minimo 200 + 255 + { + mCounter = 0; + mSection.name = PROG_SECTION_INTRO; + mSection.subsection = 0; + } + else + { + mCounter++; + } + } + } + + return mSection; +} diff --git a/source/logo.h b/source/logo.h new file mode 100644 index 0000000..0d81f74 --- /dev/null +++ b/source/logo.h @@ -0,0 +1,44 @@ +#pragma once +#include "ifdefs.h" +#include "const.h" +#include "utils.h" + +#include "sprite.h" +#include "jail_audio.h" + +#ifndef LOGO_H +#define LOGO_H + +// Logo +class Logo +{ +private: + LTexture *mTexture; // Textura con los graficos + SDL_Event *mEventHandler; // Manejador de eventos + SDL_Renderer *mRenderer; // El renderizador de la ventana + SDL_Texture *mBackbuffer; // Textura para usar como backbuffer + Sprite *mSprite; // Sprite con la textura del logo + std::string *mFileList; // Lista de ficheros + Uint16 mCounter; // Contador + section_t mSection; // Estado del bucle principal para saber si continua o se sale + Uint32 mTicks; // Contador de ticks para ajustar la velocidad del programa + Uint8 mTicksSpeed; // Velocidad a la que se repiten los bucles del programa + +public: + // Constructor + Logo(SDL_Renderer *renderer, std::string *fileList); + + // Destructor + ~Logo(); + + // Inicializa las variables + void init(); + + // Carga los recursos + bool loadMedia(); + + // Bucle principal + section_t run(); +}; + +#endif diff --git a/source/ltexture.cpp b/source/ltexture.cpp index cc2d146..0e6fd17 100644 --- a/source/ltexture.cpp +++ b/source/ltexture.cpp @@ -1,5 +1,3 @@ -#include -#include #include "const.h" #include "ltexture.h" #define STB_IMAGE_IMPLEMENTATION diff --git a/source/menu.cpp b/source/menu.cpp index e05d9cb..92932cf 100644 --- a/source/menu.cpp +++ b/source/menu.cpp @@ -1,6 +1,4 @@ -#include "ifdefs.h" #include "const.h" -#include "text.h" #include "menu.h" // Constructor @@ -17,6 +15,7 @@ Menu::~Menu() mRenderer = nullptr; mText = nullptr; mInput = nullptr; + mFileList = nullptr; } // Inicializador @@ -272,6 +271,13 @@ void Menu::render() SDL_SetRenderDrawColor(mRenderer, mSelector.r, mSelector.g, mSelector.b, mSelector.a); SDL_RenderFillRect(mRenderer, &mSelector.rect); + // Renderiza el borde del fondo + if (mBackgroundType == MENU_BACKGROUND_SOLID) + { + SDL_SetRenderDrawColor(mRenderer, mRectBG.r, mRectBG.g, mRectBG.b, 255); + SDL_RenderDrawRect(mRenderer, &mRectBG.rect); + } + // Renderiza el sprite del selector //mSelectorSprite.render(); diff --git a/source/menu.h b/source/menu.h index a859cd1..965a307 100644 --- a/source/menu.h +++ b/source/menu.h @@ -1,4 +1,5 @@ #pragma once +#include "ifdefs.h" #include "sprite.h" #include "text.h" #include "input.h" diff --git a/source/movingsprite.cpp b/source/movingsprite.cpp index 70e4509..253ebbd 100644 --- a/source/movingsprite.cpp +++ b/source/movingsprite.cpp @@ -1,6 +1,5 @@ #include "const.h" #include "movingsprite.h" -#include // Constructor MovingSprite::MovingSprite() diff --git a/source/player.cpp b/source/player.cpp index 96f9682..db115fb 100644 --- a/source/player.cpp +++ b/source/player.cpp @@ -7,18 +7,19 @@ Player::Player() mSpriteLegs = new AnimatedSprite(); mSpriteBody = new AnimatedSprite(); mSpriteHead = new AnimatedSprite(); - init(0, 0, nullptr, nullptr, nullptr); } // Destructor Player::~Player() { - init(0, 0, nullptr, nullptr, nullptr); - mSpriteLegs = nullptr; - mSpriteBody = nullptr; delete mSpriteLegs; + mSpriteLegs = nullptr; + delete mSpriteBody; + mSpriteBody = nullptr; + delete mSpriteHead; + mSpriteHead = nullptr; } // Iniciador diff --git a/source/smartsprite.cpp b/source/smartsprite.cpp index a5ea765..c88f02f 100644 --- a/source/smartsprite.cpp +++ b/source/smartsprite.cpp @@ -4,13 +4,13 @@ // Constructor SmartSprite::SmartSprite() { - init(nullptr, nullptr); + //init(nullptr, nullptr); } // Destructor SmartSprite::~SmartSprite() { - init(nullptr, nullptr); + //init(nullptr, nullptr); } // Inicializador diff --git a/source/text.cpp b/source/text.cpp index f0a7deb..ae9547b 100644 --- a/source/text.cpp +++ b/source/text.cpp @@ -13,6 +13,7 @@ Text::Text(LTexture *texture, SDL_Renderer *renderer) Text::~Text() { delete mSprite; + mSprite = nullptr; } // Inicializador @@ -131,11 +132,11 @@ void Text::init(Uint8 type, Uint8 size) } // Escribe texto en pantalla -void Text::write(int x, int y, std::string text, int kerning, Uint8 lenght) +void Text::write(int x, int y, std::string text, int kerning, int lenght) { Uint16 shift = 0; - if (lenght == 0) + if (lenght == -1) lenght = text.length(); for (int i = 0; i < lenght; ++i) @@ -149,7 +150,7 @@ void Text::write(int x, int y, std::string text, int kerning, Uint8 lenght) } // Escribe el texto con colores -void Text::writeColored(int x, int y, std::string text, color_t color, int kerning, Uint8 lenght) +void Text::writeColored(int x, int y, std::string text, color_t color, int kerning, int lenght) { mSprite->getTexture()->setColor(color.r, color.g, color.b); write(x, y, text, kerning, lenght); @@ -157,7 +158,7 @@ void Text::writeColored(int x, int y, std::string text, color_t color, int kerni } // Escribe el texto con sombra -void Text::writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance, int kerning, Uint8 lenght) +void Text::writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance, int kerning, int lenght) { mSprite->getTexture()->setColor(color.r, color.g, color.b); write(x + shadowDistance, y + shadowDistance, text, kerning, lenght); @@ -166,14 +167,14 @@ void Text::writeShadowed(int x, int y, std::string text, color_t color, Uint8 sh } // Escribe el texto centrado en un punto x -void Text::writeCentered(int x, int y, std::string text, int kerning, Uint8 lenght) +void Text::writeCentered(int x, int y, std::string text, int kerning, int lenght) { x -= (Text::lenght(text, kerning) / 2); write(x, y, text, kerning, lenght); } // Escribe texto con extras -void Text::writeDX(Uint8 flags, int x, int y, std::string text, int kerning, color_t textColor, Uint8 shadowDistance, color_t shadowColor, Uint8 lenght) +void Text::writeDX(Uint8 flags, int x, int y, std::string text, int kerning, color_t textColor, Uint8 shadowDistance, color_t shadowColor, int lenght) { const bool centered = ((flags & TXT_CENTER) == TXT_CENTER); const bool shadowed = ((flags & TXT_SHADOW) == TXT_SHADOW); diff --git a/source/text.h b/source/text.h index 4c4cb8a..6ec2b68 100644 --- a/source/text.h +++ b/source/text.h @@ -37,19 +37,19 @@ public: void init(Uint8 type, Uint8 size); // Escribe el texto en pantalla - void write(int x, int y, std::string text, int kerning = 0, Uint8 lenght = 0); + void write(int x, int y, std::string text, int kerning = 0, int lenght = -1); // Escribe el texto con colores - void writeColored(int x, int y, std::string text, color_t color, int kerning = 0, Uint8 lenght = 0); + void writeColored(int x, int y, std::string text, color_t color, int kerning = 0, int lenght = -1); // Escribe el texto con sombra - void writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance = 1, int kerning = 0, Uint8 lenght = 0); + void writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance = 1, int kerning = 0, int lenght = -1); // Escribe el texto centrado en un punto x y con kerning - void writeCentered(int x, int y, std::string text, int kerning = 0, Uint8 lenght = 0); + void writeCentered(int x, int y, std::string text, int kerning = 0, int lenght = -1); // Escribe texto con extras - void writeDX(Uint8 flags, int x, int y, std::string text, int kerning = 0, color_t textColor = {255, 255, 255}, Uint8 shadowDistance = 1, color_t shadowColor = {0, 0, 0}, Uint8 lenght = 0); + void writeDX(Uint8 flags, int x, int y, std::string text, int kerning = 0, color_t textColor = {255, 255, 255}, Uint8 shadowDistance = 1, color_t shadowColor = {0, 0, 0}, int lenght = -1); // Obtiene la longitud en pixels de una cadena Uint16 lenght(std::string text, int kerning); diff --git a/source/title.cpp b/source/title.cpp new file mode 100644 index 0000000..c726099 --- /dev/null +++ b/source/title.cpp @@ -0,0 +1,655 @@ +#include "title.h" +#ifdef __MIPSEL__ +#include +#include +#endif + +// Constructor +Title::Title(SDL_Window *window, SDL_Renderer *renderer, Input *input, std::string *fileList, options_t *options) +{ + // Copia las direcciones de los punteros + mWindow = window; + mRenderer = renderer; + mInput = input; + mFileList = fileList; + mOptions = options; + + // Reserva memoria para los punteros propios + mEventHandler = new SDL_Event(); + mFade = new Fade(renderer); + mTitleTexture = new LTexture(); + mItemsTexture = new LTexture(); + mTextTexture = new LTexture(); + mCoffeeBitmap = new SmartSprite(); + mCrisisBitmap = new SmartSprite(); + mDustBitmapL = new AnimatedSprite(); + mDustBitmapR = new AnimatedSprite(); + mTile = new Sprite(); + mText = new Text(mTextTexture, mRenderer); + mMenu.title = new Menu(mRenderer, mText, mInput, mFileList); + mMenu.options = new Menu(mRenderer, mText, mInput, mFileList); + + mBackground = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2); + if (mBackground == NULL) + printf("TitleSurface could not be created!\nSDL Error: %s\n", SDL_GetError()); +} + +// Destructor +Title::~Title() +{ + delete mEventHandler; + mEventHandler = nullptr; + + delete mText; + mText = nullptr; + + delete mFade; + mFade = nullptr; + + mTitleTexture->unload(); + delete mTitleTexture; + mTitleTexture = nullptr; + + mItemsTexture->unload(); + delete mItemsTexture; + mItemsTexture = nullptr; + + mTextTexture->unload(); + delete mTextTexture; + mTextTexture = nullptr; + + delete mCoffeeBitmap; + mCoffeeBitmap = nullptr; + + delete mCrisisBitmap; + mCrisisBitmap = nullptr; + + delete mDustBitmapL; + mDustBitmapL = nullptr; + + delete mDustBitmapR; + mDustBitmapR = nullptr; + + delete mTile; + mTile = nullptr; + + delete mMenu.title; + mMenu.title = nullptr; + + delete mMenu.options; + mMenu.options = nullptr; + + mMenu.active = nullptr; + + JA_DeleteSound(mSound); + JA_DeleteMusic(mMusic); + + SDL_DestroyTexture(mBackground); + mBackground = nullptr; + + mWindow = nullptr; + mRenderer = nullptr; + mInput = nullptr; + mFileList = nullptr; + mOptions = nullptr; +} + +// Inicializa las variables necesarias para la sección 'Title' +void Title::init(Uint8 subsection) +{ + // Carga los recursos + loadMedia(); + + // Inicializa variables + mSection.name = PROG_SECTION_TITLE; + mSection.subsection = subsection; + mCounter = TITLE_COUNTER; + mBackgroundCounter = 0; + mBackgroundMode = rand() % 2; + mMenuVisible = false; + mMenu.active = mMenu.title; + mNextSection.name = PROG_SECTION_GAME; + mPostFade = 0; + mTicks = 0; + mTicksSpeed = 15; + mText->init(TEXT_FIXED, BLOCK); + mFade->init(); + + // Inicializa el bitmap de Coffee + mCoffeeBitmap->init(mTitleTexture, mRenderer); + mCoffeeBitmap->setId(0); + mCoffeeBitmap->setIntroEvents(&mEvents[0]); + mCoffeeBitmap->setPosX(45); + mCoffeeBitmap->setPosY(11 - 200); + mCoffeeBitmap->setWidth(167); + mCoffeeBitmap->setHeight(46); + mCoffeeBitmap->setVelX(0.0f); + mCoffeeBitmap->setVelY(2.5f); + mCoffeeBitmap->setAccelX(0.0f); + mCoffeeBitmap->setAccelY(0.1f); + mCoffeeBitmap->setSpriteClip(0, 0, 167, 46); + mCoffeeBitmap->setEnabled(true); + mCoffeeBitmap->setEnabledTimer(0); + mCoffeeBitmap->setDestX(45); + mCoffeeBitmap->setDestY(11); + + // Inicializa el bitmap de Crisis + mCrisisBitmap->init(mTitleTexture, mRenderer); + mCrisisBitmap->setId(1); + mCrisisBitmap->setIntroEvents(&mEvents[0]); + mCrisisBitmap->setPosX(60); + mCrisisBitmap->setPosY(57 + 200); + mCrisisBitmap->setWidth(137); + mCrisisBitmap->setHeight(46); + mCrisisBitmap->setVelX(0.0f); + mCrisisBitmap->setVelY(-2.5f); + mCrisisBitmap->setAccelX(0.0f); + mCrisisBitmap->setAccelY(-0.1f); + mCrisisBitmap->setSpriteClip(0, 46, 137, 46); + mCrisisBitmap->setEnabled(true); + mCrisisBitmap->setEnabledTimer(0); + mCrisisBitmap->setDestX(60); + mCrisisBitmap->setDestY(57); + + // Inicializa el bitmap de DustRight + mDustBitmapR->init(mTitleTexture, mRenderer); + mDustBitmapR->setPosX(218); + mDustBitmapR->setPosY(47); + mDustBitmapR->setWidth(16); + mDustBitmapR->setHeight(14); + mDustBitmapR->setCurrentFrame(0); + mDustBitmapR->setAnimationCounter(0); + mDustBitmapR->setAnimationNumFrames(0, 7); + mDustBitmapR->setAnimationSpeed(0, 8); + mDustBitmapR->setAnimationLoop(0, false); + mDustBitmapR->setAnimationFrames(0, 0, 160 + (mDustBitmapR->getWidth() * 0), 80, mDustBitmapR->getWidth(), mDustBitmapR->getHeight()); + mDustBitmapR->setAnimationFrames(0, 1, 160 + (mDustBitmapR->getWidth() * 1), 80, mDustBitmapR->getWidth(), mDustBitmapR->getHeight()); + mDustBitmapR->setAnimationFrames(0, 2, 160 + (mDustBitmapR->getWidth() * 2), 80, mDustBitmapR->getWidth(), mDustBitmapR->getHeight()); + mDustBitmapR->setAnimationFrames(0, 3, 160 + (mDustBitmapR->getWidth() * 3), 80, mDustBitmapR->getWidth(), mDustBitmapR->getHeight()); + mDustBitmapR->setAnimationFrames(0, 4, 160 + (mDustBitmapR->getWidth() * 4), 80, mDustBitmapR->getWidth(), mDustBitmapR->getHeight()); + mDustBitmapR->setAnimationFrames(0, 5, 160 + (mDustBitmapR->getWidth() * 5), 80, mDustBitmapR->getWidth(), mDustBitmapR->getHeight()); + mDustBitmapR->setAnimationFrames(0, 6, 160 + (mDustBitmapR->getWidth() * 6), 80, mDustBitmapR->getWidth(), mDustBitmapR->getHeight()); + + // Inicializa el bitmap de DustLeft + mDustBitmapL->init(mTitleTexture, mRenderer); + mDustBitmapL->setPosX(33); + mDustBitmapL->setPosY(47); + mDustBitmapL->setWidth(16); + mDustBitmapL->setHeight(14); + mDustBitmapL->setCurrentFrame(0); + mDustBitmapL->setAnimationCounter(0); + mDustBitmapL->setAnimationNumFrames(0, 7); + mDustBitmapL->setAnimationSpeed(0, 8); + mDustBitmapL->setAnimationLoop(0, false); + mDustBitmapL->setAnimationFrames(0, 0, 160 + (mDustBitmapL->getWidth() * 0), 66, mDustBitmapL->getWidth(), mDustBitmapL->getHeight()); + mDustBitmapL->setAnimationFrames(0, 1, 160 + (mDustBitmapL->getWidth() * 1), 66, mDustBitmapL->getWidth(), mDustBitmapL->getHeight()); + mDustBitmapL->setAnimationFrames(0, 2, 160 + (mDustBitmapL->getWidth() * 2), 66, mDustBitmapL->getWidth(), mDustBitmapL->getHeight()); + mDustBitmapL->setAnimationFrames(0, 3, 160 + (mDustBitmapL->getWidth() * 3), 66, mDustBitmapL->getWidth(), mDustBitmapL->getHeight()); + mDustBitmapL->setAnimationFrames(0, 4, 160 + (mDustBitmapL->getWidth() * 4), 66, mDustBitmapL->getWidth(), mDustBitmapL->getHeight()); + mDustBitmapL->setAnimationFrames(0, 5, 160 + (mDustBitmapL->getWidth() * 5), 66, mDustBitmapL->getWidth(), mDustBitmapL->getHeight()); + mDustBitmapL->setAnimationFrames(0, 6, 160 + (mDustBitmapL->getWidth() * 6), 66, mDustBitmapL->getWidth(), mDustBitmapL->getHeight()); + + // Inicializa el vector de eventos de la pantalla de titulo + for (int i = 0; i < TITLE_TOTAL_EVENTS; i++) + mEvents[i] = EVENT_WAITING; + + // Crea el mosaico de fondo del titulo + SDL_SetRenderTarget(mRenderer, mBackground); + SDL_SetRenderDrawColor(mRenderer, 0x43, 0x43, 0x4F, 0xFF); + SDL_RenderClear(mRenderer); + + mTile->init(0, 0, 64, 64, mTitleTexture, mRenderer); + mTile->setSpriteClip(192, 0, 64, 64); + for (int i = 0; i < 8; i++) + for (int j = 0; j < 6; j++) + { + mTile->setPosX(i * 64); + mTile->setPosY(j * 64); + mTile->render(); + } + + SDL_SetRenderTarget(mRenderer, nullptr); + + mBackgroundWindow.x = 0; + mBackgroundWindow.y = 0; + mBackgroundWindow.w = SCREEN_WIDTH; + mBackgroundWindow.h = SCREEN_HEIGHT; + + // Inicializa los valores del vector con los valores del seno + for (int i = 0; i < 360; i++) + { + mSin[i] = sin((float)i * 3.14f / 180.0f); + } + + // Inicializa los objetos de menu + mMenu.title->init("TITLE", 0, 15 * BLOCK, MENU_BACKGROUND_SOLID); + mMenu.title->addItem("PLAY"); + mMenu.title->addItem("OPTIONS"); + mMenu.title->addItem("HOW TO PLAY", 0, 5); + mMenu.title->addItem("QUIT"); + mMenu.title->setDefaultActionWhenCancel(3); + mMenu.title->setBackgroundColor(0x30, 0x30, 0x40, 192); + mMenu.title->setSelectorColor(0xe5, 0x1c, 0x23, 255); + mMenu.title->setSelectorTextColor(0xFF, 0xF1, 0x76); + mMenu.title->centerMenu(SCREEN_CENTER_X); + mMenu.title->centerMenuElements(); + + mMenu.options->init("OPTIONS", 0, 15 * BLOCK, MENU_BACKGROUND_SOLID); + mMenu.options->addItem("FULLSCREEN"); + mMenu.options->addItem("WINDOWS SIZE", 0, 5); + mMenu.options->addItem("[OK]"); + mMenu.options->addItem("[CANCEL]"); + mMenu.options->setDefaultActionWhenCancel(3); + mMenu.options->setBackgroundColor(0x30, 0x30, 0x40, 192); + mMenu.options->setSelectorColor(0xe5, 0x1c, 0x23, 255); + mMenu.options->setSelectorTextColor(0xFF, 0xF1, 0x76); + mMenu.options->centerMenu(SCREEN_CENTER_X); + mMenu.options->centerMenuElements(); + + // Actualiza los elementos del menu de opciones con los valores correspondientes + updateOptionsMenuLabels(); +} + +// Carga los recursos necesarios para la sección 'Title' +bool Title::loadMedia() +{ + // Indicador de éxito en la carga + bool success = true; + + // Texturas + success &= loadTextureFromFile(mTitleTexture, mFileList[40], mRenderer); + success &= loadTextureFromFile(mItemsTexture, mFileList[34], mRenderer); + success &= loadTextureFromFile(mTextTexture, mFileList[30], mRenderer); + + // Sonidos + mSound = JA_LoadSound(mFileList[21].c_str()); + + // Musicas + mMusic = JA_LoadMusic(mFileList[5].c_str()); + + return success; +} + +// Cambia el valor de la variable de modo de pantalla completa +void Title::switchFullScreenModeVar() +{ + switch (mOptions->fullScreenMode) + { + case 0: + mOptions->fullScreenMode = SDL_WINDOW_FULLSCREEN; + break; + case SDL_WINDOW_FULLSCREEN: + mOptions->fullScreenMode = SDL_WINDOW_FULLSCREEN_DESKTOP; + break; + case SDL_WINDOW_FULLSCREEN_DESKTOP: + mOptions->fullScreenMode = 0; + break; + + default: + mOptions->fullScreenMode = 0; + break; + } +} + +// Actualiza los elementos del menu de opciones +void Title::updateOptionsMenuLabels() +{ + switch (mOptions->fullScreenMode) + { + case 0: + mMenu.options->setItemCaption(0, "WINDOWED"); + break; + case SDL_WINDOW_FULLSCREEN: + mMenu.options->setItemCaption(0, "FULLSCREEN"); + break; + case SDL_WINDOW_FULLSCREEN_DESKTOP: + mMenu.options->setItemCaption(0, "FAKE FULLSCREEN"); + break; + + default: + mMenu.options->setItemCaption(0, "WINDOWED"); + break; + } + mMenu.options->setItemCaption(1, "WINDOWS SIZE " + std::to_string(mOptions->windowSize)); + mMenu.options->centerMenu(SCREEN_CENTER_X); + mMenu.options->centerMenuElements(); +} + +// Aplica las opciones de menu seleccionadas +void Title::applyOptions() +{ + SDL_SetWindowFullscreen(mWindow, mOptions->fullScreenMode); + SDL_SetWindowSize(mWindow, SCREEN_WIDTH * mOptions->windowSize, SCREEN_HEIGHT * mOptions->windowSize); +} + +// Bucle para el titulo del juego +section_t Title::run(Uint8 subsection) +{ + init(subsection); + + while (mSection.name == PROG_SECTION_TITLE) + { + // Sección 1 - Titulo desplazandose + if (mSection.subsection == TITLE_SECTION_1) + { + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + } + + // Calcula la lógica de los objetos + if (SDL_GetTicks() - mTicks > mTicksSpeed) + { + // Actualiza el contador de ticks + mTicks = SDL_GetTicks(); + + // Actualiza los objetos + mCoffeeBitmap->update(); + mCrisisBitmap->update(); + } + + // Limpia la pantalla + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, 255); + SDL_RenderClear(mRenderer); + + // Dibuja los objetos + mCoffeeBitmap->render(); + mCrisisBitmap->render(); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + + // Si los objetos han llegado a su destino, cambiamos de Sección + if ((mEvents[0] == EVENT_COMPLETED) && (mEvents[0] == EVENT_COMPLETED)) + { + mSection.subsection = TITLE_SECTION_2; + + // Pantallazo blanco + SDL_SetRenderDrawColor(mRenderer, 0xFF, 0xFF, 0xFF, 0xFF); + SDL_RenderClear(mRenderer); + SDL_RenderPresent(mRenderer); + } + } + + // Sección 2 - Titulo vibrando + if (mSection.subsection == TITLE_SECTION_2) + { + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + } + + // Reproduce el efecto sonoro + JA_PlaySound(mSound); + + // Agita la pantalla + int v[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0}; + int a = mCoffeeBitmap->getPosX(); + int b = mCrisisBitmap->getPosX(); + for (int n = 0; n < 11 * 3; n++) + { + // Limpia la pantalla + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, 255); + SDL_RenderClear(mRenderer); + + // Dibuja los objetos + mCoffeeBitmap->setPosX(a + v[n / 3]); + mCrisisBitmap->setPosX(b + v[n / 3]); + mCoffeeBitmap->render(); + mCrisisBitmap->render(); + mDustBitmapR->animate(0); + mDustBitmapL->animate(0); + mDustBitmapR->render(); + mDustBitmapL->render(); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + } + + mSection.subsection = TITLE_SECTION_3; + } + + // Sección 3 - La pantalla de titulo con el menú y la música + if (mSection.subsection == TITLE_SECTION_3) + { + if (mCounter > 0) + { + // Comprueba los eventos que hay en la cola + while (SDL_PollEvent(mEventHandler) != 0) + { + // Evento de salida de la aplicación + if (mEventHandler->type == SDL_QUIT) + { + mSection.name = PROG_SECTION_QUIT; + break; + } + + if ((mEventHandler->type == SDL_KEYUP) || (mEventHandler->type == SDL_JOYBUTTONUP)) + { + mMenuVisible = true; + mCounter = TITLE_COUNTER; + } + } + + // Si la música no está sonando + if ((JA_GetMusicState() == JA_MUSIC_INVALID) || (JA_GetMusicState() == JA_MUSIC_STOPPED)) + { + // Reproduce la música + JA_PlayMusic(mMusic); + } + + // Calcula la lógica de los objetos + if (SDL_GetTicks() - mTicks > mTicksSpeed) + { + // Actualiza el contador de ticks + mTicks = SDL_GetTicks(); + + // Actualiza la lógica del titulo + mMenu.active->update(); + mFade->update(); + if (mFade->hasEnded()) + { + switch (mPostFade) + { + case 0: // PLAY + mSection.name = PROG_SECTION_GAME; + JA_StopMusic(); + mDemo = false; + break; + + case 1: // QUIT + mSection.name = PROG_SECTION_QUIT; + JA_StopMusic(); + break; + + case 2: // TIME OUT + mCounter = TITLE_COUNTER; + //mSection = mNextSection; + //mSection.name = PROG_SECTION_TITLE; + //mSection.subsection = TITLE_SECTION_INSTRUCTIONS; + mMenu.active->reset(); + //toogleTitleNextGS(); + runDemoGame(); + runInstructions(INSTRUCTIONS_MODE_AUTO); + init(); + mDemo = true; + break; + + default: + break; + } + } + + // Comprueba las entradas para el menu + if (mMenuVisible == true) + mMenu.active->checkInput(); + + // Comprueba si se ha seleccionado algún item del menú + if (mMenu.active->getName() == "TITLE") + { + switch (mMenu.active->getItemSelected()) + { + case 0: // PLAY + mPostFade = 0; + mFade->activateFade(); + break; + case 1: // OPTIONS + mMenu.active = mMenu.options; + mOptions->fullScreenModePrevious = mOptions->fullScreenMode; + mOptions->windowSizePrevious = mOptions->windowSize; + break; + case 2: // HOW TO PLAY + runInstructions(INSTRUCTIONS_MODE_MANUAL); + break; + case 3: // QUIT + mPostFade = 1; + mFade->activateFade(); + break; + + default: + break; + } + } + + // Comprueba si se ha seleccionado algún item de opciones + if (mMenu.active->getName() == "OPTIONS") + { + switch (mMenu.active->getItemSelected()) + { + case 0: // Fullscreen mode + switchFullScreenModeVar(); + updateOptionsMenuLabels(); + mMenu.active->deselectItem(); + break; + case 1: // Windows size + mOptions->windowSize++; + if (mOptions->windowSize == 5) + mOptions->windowSize = 1; + updateOptionsMenuLabels(); + mMenu.active->deselectItem(); + break; + case 2: // OK + applyOptions(); + mMenu.active->reset(); + mMenu.active = mMenu.title; + break; + case 3: // CANCEL + mOptions->fullScreenMode = mOptions->fullScreenModePrevious; + mOptions->windowSize = mOptions->windowSizePrevious; + updateOptionsMenuLabels(); + mMenu.active->reset(); + mMenu.active = mMenu.title; + break; + + default: + break; + } + } + + if (mMenu.active->getName() == "TITLE") + { + mCounter--; + } + } + + // Limpia la pantalla + SDL_SetRenderDrawColor(mRenderer, bgColor.r, bgColor.g, bgColor.b, 255); + SDL_RenderClear(mRenderer); + + // Pinta el tileado de fondo + switch (mBackgroundMode) + { + case 0: // El tileado de fondo se desplaza en diagonal + mBackgroundWindow.x++; + mBackgroundWindow.x %= 64; + mBackgroundWindow.y++; + mBackgroundWindow.y %= 64; + break; + + case 1: // El tileado de fondo se desplaza en circulo + ++mBackgroundCounter %= 360; + mBackgroundWindow.x = 128 + (int(mSin[(mBackgroundCounter + 270) % 360] * 128)); + mBackgroundWindow.y = 96 + (int(mSin[(360 - mBackgroundCounter) % 360] * 96)); + break; + + default: + break; + } + + SDL_RenderCopy(mRenderer, mBackground, &mBackgroundWindow, NULL); + + // Dibuja los objetos + mCoffeeBitmap->render(); + mCrisisBitmap->render(); + if (mMenuVisible == true) + mMenu.active->render(); + mDustBitmapR->animate(0); + mDustBitmapL->animate(0); + mDustBitmapR->render(); + mDustBitmapL->render(); + + // PRESS ANY KEY! + if ((mCounter % 50 > 14) && (mMenuVisible == false)) + mText->writeDX(TXT_CENTER | TXT_SHADOW, SCREEN_CENTER_X, PLAY_AREA_THIRD_QUARTER_Y + BLOCK, "PRESS ANY KEY!", 0, noColor, 1, shdwTxtColor); + + // Texto con el copyright y versión + mText->writeDX(TXT_CENTER | TXT_SHADOW, SCREEN_CENTER_X, SCREEN_HEIGHT - (BLOCK * 2), TEXT_COPYRIGHT, 0, noColor, 1, shdwTxtColor); + + // Fade + mFade->render(); + + // Actualiza la pantalla + SDL_RenderPresent(mRenderer); + } + else if (mCounter == 0) + { + //mPostFade = 2; + //mFade->activateFade(); + runDemoGame(); + runInstructions(INSTRUCTIONS_MODE_AUTO); + init(); + } + } + + // Sección Instrucciones + if (mSection.subsection == TITLE_SECTION_INSTRUCTIONS) + { + runInstructions(INSTRUCTIONS_MODE_AUTO); + init(); + } + } + + return mSection; +} + +// Ejecuta la parte donde se muestran las instrucciones +void Title::runInstructions(Uint8 mode) +{ + printf("carrega instructions\n"); + mInstructions = new Instructions(mRenderer, mFileList); + mInstructions->run(mode); + delete mInstructions; + printf("borra instructions\n"); +} + +// Ejecuta el juego en modo demo +void Title::runDemoGame() +{ + printf("carrega joc\n"); + mDemoGame = new Game(mRenderer, mFileList, mInput, true); + mDemoGame->run(); + delete mDemoGame; + printf("borra joc\n"); +} \ No newline at end of file diff --git a/source/title.h b/source/title.h new file mode 100644 index 0000000..7af2578 --- /dev/null +++ b/source/title.h @@ -0,0 +1,107 @@ +#pragma once +#include "ifdefs.h" +#include "const.h" +#include "utils.h" + +#include "sprite.h" +#include "movingsprite.h" +#include "smartsprite.h" + +#include "item.h" +#include "text.h" +#include "menu.h" +#include "fade.h" +#include "input.h" +#include "instructions.h" +#include "game.h" +#include "jail_audio.h" + +#ifndef TITLE_H +#define TITLE_H + +// Title +class Title +{ +private: + SDL_Window *mWindow; // Ventana de la aplicación + AnimatedSprite *mDustBitmapL; // Sprite con la el polvo que aparece al colisionar el texto de la pantalla de titulo + AnimatedSprite *mDustBitmapR; // Sprite con la el polvo que aparece al colisionar el texto de la pantalla de titulo + bool mMenuVisible; // Indicador para saber si se muestra el menu del titulo o la frase intermitente + float mSin[360]; // Vector con los valores del seno precalculados + JA_Music mMusic; // Musica para el titulo + JA_Sound mSound; // Sonido con el impacto del título + LTexture *mItemsTexture; // Textura con los gráficos de los items para las instrucciones + LTexture *mTitleTexture; // Textura con los graficos para el titulo + LTexture *mTextTexture; // Textura con los gráficos para el texto + SDL_Event *mEventHandler; // Manejador de eventos + SDL_Rect mBackgroundWindow; // Ventana visible para la textura de fondo del titulo + SDL_Renderer *mRenderer; // El renderizador de la ventana + SDL_Texture *mBackbuffer; // Textura para usar como backbuffer + SDL_Texture *mBackground; // Textura dibujar el fondo del titulo + SmartSprite *mCoffeeBitmap; // Sprite con la palabra COFFEE para la pantalla de titulo + SmartSprite *mCrisisBitmap; // Sprite con la palabra CRISIS para la pantalla de titulo + Sprite *mTile; // Sprite para dibujar el fondo de pantalla del título + std::string *mFileList; // Lista de ficheros + Uint16 mBackgroundCounter; // Temporizador para el fondo de tiles de la pantalla de titulo + Uint16 mCounter; // Temporizador para la pantalla de titulo + Uint32 mTicks; // Contador de ticks para ajustar la velocidad del programa + Uint8 mBackgroundMode; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo + Uint8 mEvents[TITLE_TOTAL_EVENTS]; // Vector para coordinar los eventos de la pantalla de titulo + bool mDemo; // Indica si el modo demo estará activo + section_t mSection; // Indicador para el bucle del titulo + section_t mNextSection; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo + Uint8 mTicksSpeed; // Velocidad a la que se repiten los bucles del programa + Text *mText; // Objeto de texto para poder escribir textos en pantalla + Fade *mFade; // Objeto para realizar fundidos en pantalla + Uint8 mPostFade; // Opción a realizar cuando termina el fundido + Input *mInput; // Objeto para leer las entradas de teclado o mando + Instructions *mInstructions; // Objeto para la sección de las instrucciones + Game *mDemoGame; // Objeto para lanzar la demo del juego + + struct menu_t + { + Menu *title; // Menu de la pantalla de título + Menu *options; // Menú de la pantalla de opciones + Menu *active; // Menu activo (de momento para la pantalla del titulo) + bool keyPressed; // Variable para evitar la repetición de teclas en los menus + }; + menu_t mMenu; // Variable con todos los objetos menus y sus variables + + struct options_t *mOptions; // Variable con todas las variables de las opciones del programa + + // Carga los recursos necesarios para la sección 'Title' + bool loadMedia(); + + // Cambia el valor de la variable de modo de pantalla completa + void switchFullScreenModeVar(); + + // Actualiza los elementos del menu de opciones + void updateOptionsMenuLabels(); + + // Aplica las opciones de menu seleccionadas + void applyOptions(); + + // Ejecuta la parte donde se muestran las instrucciones + void runInstructions(Uint8 mode); + + // Ejecuta el juego en modo demo + void runDemoGame(); + +public: + // Constructor + Title(SDL_Window *window, SDL_Renderer *renderer, Input *input, std::string *fileList, options_t *options); + + // Destructor + ~Title(); + + // Inicializa las variables necesarias para la sección 'Title' + void init(Uint8 subsection = TITLE_SECTION_1); + + // Resetea las variables necesarias para la sección 'Title' + void reset(Uint8 subsection = TITLE_SECTION_1); + + // Bucle para el titulo del juego + section_t run(Uint8 subsection = TITLE_SECTION_1); +}; + +#endif