#include "game.h" #include // for SDL_BLENDMODE_BLEND #include // for SDLK_1, SDLK_2, SDLK_3, SDLK_h #include // for SDL_PIXELFORMAT_RGBA8888 #include // for SDL_RWFromFile, SDL_RWclose, SDL_R... #include // for SDL_GetTicks #include // for SDL_WINDOWEVENT_FOCUS_GAINED, SDL_... #include // for rand #include // for min #include // for basic_ifstream #include // for char_traits, basic_istream, ifstream #include "asset.h" // for Asset #include "background.h" // for Background #include "balloon.h" // for Balloon, BALLOON_SPEED_1, BALLOON_... #include "bullet.h" // for Bullet, BulletType::LEFT, BulletType::RIGHT #include "enemy_formations.h" // for stage_t, EnemyFormations, enemyIni... #include "explosions.h" // for Explosions #include "fade.h" // for Fade, FADE_RANDOM_SQUARE, FADE_VEN... #include "global_inputs.h" // for globalInputs::check #include "input.h" // for inputs_e, Input, INPUT_DO_NOT_ALLO... #include "item.h" // for Item, ITEM_COFFEE_MACHINE, ITEM_CLOCK #include "jail_audio.h" // for JA_PlaySound, JA_DeleteSound, JA_L... #include "lang.h" // for getText #include "manage_hiscore_table.h" // for ManageHiScoreTable #include "options.h" // for options #include "param.h" // for param #include "player.h" // for Player, playerStatus::PLAYING, PLA... #include "scoreboard.h" // for Scoreboard, scoreboard_modes_e #include "screen.h" // for Screen #include "smart_sprite.h" // for SmartSprite #include "text.h" // for Text, TXT_CENTER #include "texture.h" // for Texture struct JA_Music_t; struct JA_Sound_t; #define GAME_OVER_COUNTER 350 // Constructor Game::Game(int playerID, int currentStage, bool demo, JA_Music_t *music) { // Copia los punteros this->music = music; asset = Asset::get(); input = Input::get(); screen = Screen::get(); renderer = screen->getRenderer(); // Pasa variables this->demo.enabled = demo; this->currentStage = currentStage; lastStageReached = currentStage; difficulty = options.game.difficulty; // Crea los objetos Scoreboard::init(renderer); scoreboard = Scoreboard::get(); eventHandler = std::make_unique(); fade = std::make_unique(renderer); background = std::make_unique(renderer); explosions = std::make_unique(); enemyFormations = std::make_unique(); // Carga los recursos loadMedia(); // Inicializa los vectores con los datos para la demo if (demo) { // Aleatoriza la asignación del fichero const auto index1 = rand() % 2; const auto index2 = (index1 + 1) % 2; loadDemoFile(asset->get("demo1.bin"), &this->demo.dataFile[index1]); loadDemoFile(asset->get("demo2.bin"), &this->demo.dataFile[index2]); } background->setPos(param.game.playArea.rect); n1000Sprite = std::make_unique(gameTextTexture.get()); n2500Sprite = std::make_unique(gameTextTexture.get()); n5000Sprite = std::make_unique(gameTextTexture.get()); explosions->addTexture(1, explosionsTextures[0], explosionsAnimations[0]); explosions->addTexture(2, explosionsTextures[1], explosionsAnimations[1]); explosions->addTexture(3, explosionsTextures[2], explosionsAnimations[2]); explosions->addTexture(4, explosionsTextures[3], explosionsAnimations[3]); canvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.playArea.rect.w, param.game.playArea.rect.h); SDL_SetTextureBlendMode(canvas, SDL_BLENDMODE_BLEND); // Inicializa las variables necesarias para la sección 'Game' init(playerID); } Game::~Game() { // Guarda las puntuaciones en un fichero auto manager = std::make_unique(&options.game.hiScoreTable); manager->saveToFile(asset->get("score.bin")); #ifdef RECORDING saveDemoFile(asset->get("demo1.bin")); #endif // Elimina todos los objetos contenidos en vectores deleteAllVectorObjects(); // Libera los recursos unloadMedia(); Scoreboard::destroy(); SDL_DestroyTexture(canvas); } // Inicializa las variables necesarias para la sección 'Game' void Game::init(int playerID) { ticks = 0; ticksSpeed = 15; // Elimina qualquier jugador que hubiese antes de crear los nuevos for (auto player : players) { if (player) { delete player; } }; players.clear(); // Crea los dos jugadores Player *player1 = new Player(1, (param.game.playArea.firstQuarterX * ((0 * 2) + 1)) - 11, param.game.playArea.rect.h - 30, ¶m.game.playArea.rect, playerTextures[0], playerAnimations); player1->setScoreBoardPanel(SCOREBOARD_LEFT_PANEL); player1->setName(lang::getText(53)); const int controller1 = getController(player1->getId()); player1->setController(controller1); players.push_back(player1); Player *player2 = new Player(2, (param.game.playArea.firstQuarterX * ((1 * 2) + 1)) - 11, param.game.playArea.rect.h - 30, ¶m.game.playArea.rect, playerTextures[1], playerAnimations); player2->setScoreBoardPanel(SCOREBOARD_RIGHT_PANEL); player2->setName(lang::getText(54)); const int controller2 = getController(player2->getId()); player2->setController(controller2); players.push_back(player2); // Obtiene el "id" del jugador que va a jugar Player *player = getPlayer(playerID); // Cambia el estado del jugador seleccionado player->setStatusPlaying(playerStatus::PLAYING); // Como es el principio del juego, empieza sin inmunidad player->setInvulnerable(false); // Variables relacionadas con la dificultad switch (difficulty) { case gameDifficulty::EASY: { defaultEnemySpeed = BALLOON_SPEED_1; difficultyScoreMultiplier = 0.5f; difficultyColor = difficultyEasyColor; scoreboard->setColor(difficultyColor); break; } case gameDifficulty::NORMAL: { defaultEnemySpeed = BALLOON_SPEED_1; difficultyScoreMultiplier = 1.0f; difficultyColor = difficultyNormalColor; scoreboard->setColor(scoreboardColor); break; } case gameDifficulty::HARD: { defaultEnemySpeed = BALLOON_SPEED_5; difficultyScoreMultiplier = 1.5f; difficultyColor = difficultyHardColor; scoreboard->setColor(difficultyColor); break; } default: break; } // Variables para el marcador scoreboard->setPos({param.scoreboard.x, param.scoreboard.y, param.scoreboard.w, param.scoreboard.h}); for (auto player : players) { scoreboard->setName(player->getScoreBoardPanel(), player->getName()); if (player->isWaiting()) { scoreboard->setMode(player->getScoreBoardPanel(), scoreboardMode::WAITING); } } scoreboard->setMode(SCOREBOARD_CENTER_PANEL, scoreboardMode::STAGE_INFO); // Resto de variables hiScore.score = options.game.hiScoreTable[0].score; hiScore.name = options.game.hiScoreTable[0].name; paused = false; gameCompleted = false; gameCompletedCounter = 0; section::name = section::NAME_GAME; section::options = section::OPTIONS_GAME_PLAY_1P; currentPower = 0; menaceCurrent = 0; menaceThreshold = 0; hiScoreAchieved = false; stageBitmapCounter = STAGE_COUNTER; gameOverCounter = GAME_OVER_COUNTER; timeStopped = false; timeStoppedCounter = 0; counter = 0; lastEnemyDeploy = 0; enemyDeployCounter = 0; enemySpeed = defaultEnemySpeed; helper.needCoffee = false; helper.needCoffeeMachine = false; helper.needPowerBall = false; helper.counter = HELP_COUNTER; helper.itemPoints1Odds = ITEM_POINTS_1_DISK_ODDS; helper.itemPoints2Odds = ITEM_POINTS_2_GAVINA_ODDS; helper.itemPoints3Odds = ITEM_POINTS_3_PACMAR_ODDS; helper.itemClockOdds = ITEM_CLOCK_ODDS; helper.itemCoffeeOdds = ITEM_COFFEE_ODDS; helper.itemCoffeeMachineOdds = ITEM_COFFEE_MACHINE_ODDS; powerBallEnabled = false; powerBallCounter = 0; coffeeMachineEnabled = false; balloonsPopped = 0; #ifdef DEBUG autoPopBalloons = false; #endif // Inicializa las variables para el modo DEMO if (demo.enabled) { // Selecciona una pantalla al azar const int demos = 3; const int demo = rand() % demos; const int stages[demos] = {0, 3, 5}; currentStage = stages[demo]; // Actualiza el numero de globos explotados según la fase de la demo for (int i = 0; i < currentStage; ++i) { balloonsPopped += enemyFormations->getStage(i).powerToComplete; } // Activa o no al otro jugador if (rand() % 2 == 0) { const int otherPlayer = playerID == 1 ? 2 : 1; Player *player = getPlayer(otherPlayer); player->setStatusPlaying(playerStatus::PLAYING); } for (auto player : players) { // Añade 0, 1 o 2 cafes al jugador for (int i = 0; i < rand() % 3; ++i) { player->giveExtraHit(); } // Empieza sin inmunidad player->setInvulnerable(false); } // Deshabilita los sonidos JA_EnableSound(false); // Configura los marcadores scoreboard->setMode(SCOREBOARD_LEFT_PANEL, scoreboardMode::DEMO); scoreboard->setMode(SCOREBOARD_RIGHT_PANEL, scoreboardMode::DEMO); } initPaths(); totalPowerToCompleteGame = 0; for (int i = 0; i < 10; ++i) { totalPowerToCompleteGame += enemyFormations->getStage(i).powerToComplete; } // Modo grabar demo #ifdef RECORDING demo.recording = true; #else demo.recording = false; #endif demo.counter = 0; // Inicializa el objeto para el fundido fade->setColor(fadeColor.r, fadeColor.g, fadeColor.b); fade->setPost(param.fade.postDuration); fade->setType(FADE_VENETIAN); // Con los globos creados, calcula el nivel de amenaza evaluateAndSetMenace(); // Inicializa el bitmap de 1000 puntos constexpr int height = 15; constexpr int sprite1Width = 35; constexpr int sprite2Width = 38; constexpr int sprite3Width = 39; n1000Sprite->setPosX(0); n1000Sprite->setPosY(0); n1000Sprite->setWidth(sprite1Width); n1000Sprite->setHeight(height); n1000Sprite->setVelX(0.0f); n1000Sprite->setVelY(-0.5f); n1000Sprite->setAccelX(0.0f); n1000Sprite->setAccelY(-0.1f); n1000Sprite->setSpriteClip(0, 0, sprite1Width, height); n1000Sprite->setEnabled(false); n1000Sprite->setEnabledCounter(0); n1000Sprite->setDestX(0); n1000Sprite->setDestY(0); // Inicializa el bitmap de 2500 puntos n2500Sprite->setPosX(0); n2500Sprite->setPosY(0); n2500Sprite->setWidth(sprite2Width); n2500Sprite->setHeight(height); n2500Sprite->setVelX(0.0f); n2500Sprite->setVelY(-0.5f); n2500Sprite->setAccelX(0.0f); n2500Sprite->setAccelY(-0.1f); n2500Sprite->setSpriteClip(sprite1Width, 0, sprite2Width, height); n2500Sprite->setEnabled(false); n2500Sprite->setEnabledCounter(0); n2500Sprite->setDestX(0); n2500Sprite->setDestY(0); // Inicializa el bitmap de 5000 puntos n5000Sprite->setPosX(0); n5000Sprite->setPosY(0); n5000Sprite->setWidth(sprite3Width); n5000Sprite->setHeight(height); n5000Sprite->setVelX(0.0f); n5000Sprite->setVelY(-0.5f); n5000Sprite->setAccelX(0.0f); n5000Sprite->setAccelY(-0.1f); n5000Sprite->setSpriteClip(sprite1Width + sprite2Width, 0, sprite3Width, height); n5000Sprite->setEnabled(false); n5000Sprite->setEnabledCounter(0); n5000Sprite->setDestX(0); n5000Sprite->setDestY(0); } // Carga los recursos necesarios para la sección 'Game' void Game::loadMedia() { #ifdef VERBOSE std::cout << std::endl << "** LOADING RESOURCES FOR GAME SECTION" << std::endl; #endif playerAnimations.clear(); balloonAnimations.clear(); itemAnimations.clear(); player1Textures.clear(); player2Textures.clear(); itemTextures.clear(); balloonTextures.clear(); explosionsTextures.clear(); // Texturas bulletTexture = std::make_unique(renderer, asset->get("bullet.png")); gameTextTexture = std::make_unique(renderer, asset->get("game_text.png")); // Texturas - Globos Texture *balloon1Texture = new Texture(renderer, asset->get("balloon1.png")); balloonTextures.push_back(balloon1Texture); Texture *balloon2Texture = new Texture(renderer, asset->get("balloon2.png")); balloonTextures.push_back(balloon2Texture); Texture *balloon3Texture = new Texture(renderer, asset->get("balloon3.png")); balloonTextures.push_back(balloon3Texture); Texture *balloon4Texture = new Texture(renderer, asset->get("balloon4.png")); balloonTextures.push_back(balloon4Texture); Texture *balloon5Texture = new Texture(renderer, asset->get("powerball.png")); balloonTextures.push_back(balloon5Texture); // Texturas - Explosiones Texture *explosion1Texture = new Texture(renderer, asset->get("explosion1.png")); explosionsTextures.push_back(explosion1Texture); Texture *explosion2Texture = new Texture(renderer, asset->get("explosion2.png")); explosionsTextures.push_back(explosion2Texture); Texture *explosion3Texture = new Texture(renderer, asset->get("explosion3.png")); explosionsTextures.push_back(explosion3Texture); Texture *explosion4Texture = new Texture(renderer, asset->get("explosion4.png")); explosionsTextures.push_back(explosion4Texture); // Texturas - Items Texture *item1 = new Texture(renderer, asset->get("item_points1_disk.png")); itemTextures.push_back(item1); Texture *item2 = new Texture(renderer, asset->get("item_points2_gavina.png")); itemTextures.push_back(item2); Texture *item3 = new Texture(renderer, asset->get("item_points3_pacmar.png")); itemTextures.push_back(item3); Texture *item4 = new Texture(renderer, asset->get("item_clock.png")); itemTextures.push_back(item4); Texture *item5 = new Texture(renderer, asset->get("item_coffee.png")); itemTextures.push_back(item5); Texture *item6 = new Texture(renderer, asset->get("item_coffee_machine.png")); itemTextures.push_back(item6); // Texturas - Player1 Texture *player1 = new Texture(renderer, asset->get("player1.gif")); player1->addPalette(asset->get("player1_pal1.gif")); player1->addPalette(asset->get("player1_pal2.gif")); player1->addPalette(asset->get("player1_pal3.gif")); player1Textures.push_back(player1); Texture *player1Power = new Texture(renderer, asset->get("player_power.gif")); player1Power->addPalette(asset->get("player_power_pal.gif")); player1Textures.push_back(player1Power); playerTextures.push_back(player1Textures); // Texturas - Player2 Texture *player2 = new Texture(renderer, asset->get("player2.gif")); player2->addPalette(asset->get("player2_pal1.gif")); player2->addPalette(asset->get("player2_pal2.gif")); player2->addPalette(asset->get("player2_pal3.gif")); player2Textures.push_back(player2); Texture *player2Power = new Texture(renderer, asset->get("player_power.gif")); player2Power->addPalette(asset->get("player_power_pal.gif")); player2Power->setPalette(1); player2Textures.push_back(player2Power); playerTextures.push_back(player2Textures); // Animaciones -- Jugador { std::vector *playerAnimation = new std::vector; loadAnimations(asset->get("player.ani"), playerAnimation); playerAnimations.push_back(playerAnimation); std::vector *playerPowerAnimation = new std::vector; loadAnimations(asset->get("player_power.ani"), playerPowerAnimation); playerAnimations.push_back(playerPowerAnimation); } // Animaciones -- Globos { std::vector *balloon1Animation = new std::vector; loadAnimations(asset->get("balloon1.ani"), balloon1Animation); balloonAnimations.push_back(balloon1Animation); std::vector *balloon2Animation = new std::vector; loadAnimations(asset->get("balloon2.ani"), balloon2Animation); balloonAnimations.push_back(balloon2Animation); std::vector *balloon3Animation = new std::vector; loadAnimations(asset->get("balloon3.ani"), balloon3Animation); balloonAnimations.push_back(balloon3Animation); std::vector *balloon4Animation = new std::vector; loadAnimations(asset->get("balloon4.ani"), balloon4Animation); balloonAnimations.push_back(balloon4Animation); std::vector *balloon5Animation = new std::vector; loadAnimations(asset->get("powerball.ani"), balloon5Animation); balloonAnimations.push_back(balloon5Animation); } // Animaciones -- Explosiones std::vector *explosions1Animation = new std::vector; loadAnimations(asset->get("explosion1.ani"), explosions1Animation); explosionsAnimations.push_back(explosions1Animation); std::vector *explosions2Animation = new std::vector; loadAnimations(asset->get("explosion2.ani"), explosions2Animation); explosionsAnimations.push_back(explosions2Animation); std::vector *explosions3Animation = new std::vector; loadAnimations(asset->get("explosion3.ani"), explosions3Animation); explosionsAnimations.push_back(explosions3Animation); std::vector *explosions4Animation = new std::vector; loadAnimations(asset->get("explosion4.ani"), explosions4Animation); explosionsAnimations.push_back(explosions4Animation); // Animaciones -- Items std::vector *item1Animation = new std::vector; loadAnimations(asset->get("item_points1_disk.ani"), item1Animation); itemAnimations.push_back(item1Animation); std::vector *item2Animation = new std::vector; loadAnimations(asset->get("item_points2_gavina.ani"), item2Animation); itemAnimations.push_back(item2Animation); std::vector *item3Animation = new std::vector; loadAnimations(asset->get("item_points3_pacmar.ani"), item3Animation); itemAnimations.push_back(item3Animation); std::vector *item4Animation = new std::vector; loadAnimations(asset->get("item_clock.ani"), item4Animation); itemAnimations.push_back(item4Animation); std::vector *item5Animation = new std::vector; loadAnimations(asset->get("item_coffee.ani"), item5Animation); itemAnimations.push_back(item5Animation); std::vector *item6Animation = new std::vector; loadAnimations(asset->get("item_coffee_machine.ani"), item6Animation); itemAnimations.push_back(item6Animation); // Texto text = new Text(asset->get("smb2.gif"), asset->get("smb2.txt"), renderer); textBig = new Text(asset->get("smb2_big.png"), asset->get("smb2_big.txt"), renderer); textNokia2 = new Text(asset->get("nokia2.png"), asset->get("nokia2.txt"), renderer); textNokiaBig2 = new Text(asset->get("nokia_big2.png"), asset->get("nokia_big2.txt"), renderer); // Sonidos balloonSound = JA_LoadSound(asset->get("balloon.wav").c_str()); bubble1Sound = JA_LoadSound(asset->get("bubble1.wav").c_str()); bubble2Sound = JA_LoadSound(asset->get("bubble2.wav").c_str()); bubble3Sound = JA_LoadSound(asset->get("bubble3.wav").c_str()); bubble4Sound = JA_LoadSound(asset->get("bubble4.wav").c_str()); bulletSound = JA_LoadSound(asset->get("bullet.wav").c_str()); clockSound = JA_LoadSound(asset->get("clock.wav").c_str()); coffeeOutSound = JA_LoadSound(asset->get("coffeeout.wav").c_str()); hiScoreSound = JA_LoadSound(asset->get("hiscore.wav").c_str()); itemDropSound = JA_LoadSound(asset->get("itemdrop.wav").c_str()); itemPickUpSound = JA_LoadSound(asset->get("itempickup.wav").c_str()); playerCollisionSound = JA_LoadSound(asset->get("player_collision.wav").c_str()); powerBallSound = JA_LoadSound(asset->get("powerball.wav").c_str()); stageChangeSound = JA_LoadSound(asset->get("stage_change.wav").c_str()); coffeeMachineSound = JA_LoadSound(asset->get("title.wav").c_str()); #ifdef VERBOSE std::cout << "** RESOURCES FOR GAME SECTION LOADED" << std::endl << std::endl; #endif } // Libera los recursos previamente cargados void Game::unloadMedia() { // Texturas for (auto texture : player1Textures) { if (texture) { delete texture; } } player1Textures.clear(); for (auto texture : player2Textures) { if (texture) { delete texture; } } player2Textures.clear(); for (auto texture : itemTextures) { if (texture) { delete texture; } } itemTextures.clear(); for (auto texture : balloonTextures) { if (texture) { delete texture; } } balloonTextures.clear(); for (auto texture : explosionsTextures) { if (texture) { delete texture; } } explosionsTextures.clear(); // Animaciones for (auto animation : playerAnimations) { if (animation) { delete animation; } } playerAnimations.clear(); for (auto animation : balloonAnimations) { if (animation) { delete animation; } } balloonAnimations.clear(); for (auto animation : explosionsAnimations) { if (animation) { delete animation; } } explosionsAnimations.clear(); for (auto animation : itemAnimations) { if (animation) { delete animation; } } itemAnimations.clear(); // Text delete text; delete textBig; delete textNokia2; delete textNokiaBig2; // Sonidos JA_DeleteSound(balloonSound); JA_DeleteSound(bulletSound); JA_DeleteSound(playerCollisionSound); JA_DeleteSound(hiScoreSound); JA_DeleteSound(itemDropSound); JA_DeleteSound(itemPickUpSound); JA_DeleteSound(coffeeOutSound); JA_DeleteSound(stageChangeSound); JA_DeleteSound(bubble1Sound); JA_DeleteSound(bubble2Sound); JA_DeleteSound(bubble3Sound); JA_DeleteSound(bubble4Sound); JA_DeleteSound(clockSound); JA_DeleteSound(powerBallSound); JA_DeleteSound(coffeeMachineSound); } // Carga el fichero de datos para la demo bool Game::loadDemoFile(std::string filePath, demoKeys_t (*dataFile)[TOTAL_DEMO_DATA]) { // Indicador de éxito en la carga bool success = true; const std::string fileName = filePath.substr(filePath.find_last_of("\\/") + 1); SDL_RWops *file = SDL_RWFromFile(filePath.c_str(), "r+b"); if (file == nullptr) { // El fichero no existe #ifdef VERBOSE std::cout << "Warning: Unable to open " << fileName.c_str() << " file" << std::endl; #endif // Creamos el fichero para escritura file = SDL_RWFromFile(filePath.c_str(), "w+b"); // Si ha creado el fichero if (file != nullptr) { #ifdef VERBOSE std::cout << "New file (" << fileName.c_str() << ") created!" << std::endl; #endif // Inicializas los datos y los guarda en el fichero for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { demoKeys_t tmp; tmp.left = 0; tmp.right = 0; tmp.noInput = 0; tmp.fire = 0; tmp.fireLeft = 0; tmp.fireRight = 0; (*dataFile)[i] = tmp; SDL_RWwrite(file, &tmp, sizeof(demoKeys_t), 1); } // Cerramos el fichero SDL_RWclose(file); } else { // Si no puede crear el fichero #ifdef VERBOSE std::cout << "Error: Unable to create file " << fileName.c_str() << std::endl; #endif success = false; } } // El fichero existe else { // Mensaje de proceder a la carga de los datos #ifdef VERBOSE std::cout << "Reading file: " << fileName.c_str() << std::endl; #endif // Lee todos los datos del fichero y los deja en el destino for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { demoKeys_t tmp; SDL_RWread(file, &tmp, sizeof(demoKeys_t), 1); (*dataFile)[i] = tmp; } // Cierra el fichero SDL_RWclose(file); } return success; } #ifdef RECORDING // Guarda el fichero de datos para la demo bool Game::saveDemoFile(std::string filePath) { bool success = true; const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1); SDL_RWops *file = SDL_RWFromFile(filePath.c_str(), "w+b"); if (file != nullptr) { // Guardamos los datos for (int i = 0; i < TOTAL_DEMO_DATA; ++i) { SDL_RWwrite(file, &demo.dataFile[0][i], sizeof(demoKeys_t), 1); } #ifdef VERBOSE std::cout << "Writing file " << filename.c_str() << std::endl; #endif // VERBOSE // Cerramos el fichero SDL_RWclose(file); } else { #ifdef VERBOSE std::cout << "Error: Unable to save " << filename.c_str() << " file! " << SDL_GetError() << std::endl; #endif // VERBOSE } return success; } #endif // RECORDING // Crea una formación de enemigos void Game::deployEnemyFormation() { // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última if (enemyDeployCounter == 0) { // En este punto se decide entre crear una powerball o una formación enemiga if ((rand() % 100 < 15) && (canPowerBallBeCreated())) { // Crea una powerball createPowerBall(); // Da un poco de margen para que se creen mas enemigos enemyDeployCounter = 300; } else { // Decrementa el contador de despliegues enemigos de la PowerBall powerBallCounter > 0 ? powerBallCounter-- : powerBallCounter = 0; // Elige una formación enemiga la azar int set = rand() % 10; // Evita repetir la ultima formación enemiga desplegada if (set == lastEnemyDeploy) { ++set %= 10; } lastEnemyDeploy = set; const stage_t stage = enemyFormations->getStage(currentStage); const int numEnemies = stage.enemyPool->set[set]->numberOfEnemies; for (int i = 0; i < numEnemies; ++i) { createBalloon(stage.enemyPool->set[set]->init[i].x, stage.enemyPool->set[set]->init[i].y, stage.enemyPool->set[set]->init[i].kind, stage.enemyPool->set[set]->init[i].velX, enemySpeed, stage.enemyPool->set[set]->init[i].creationCounter); } enemyDeployCounter = 300; } } } // Aumenta el poder de la fase void Game::increaseStageCurrentPower(int power) { currentPower += power; } // Actualiza el valor de hiScore en caso necesario void Game::updateHiScore() { // Si la puntuación actual es mayor que la máxima puntuación for (auto player : players) { if (player->getScore() > hiScore.score) { // Actualiza la máxima puntuación hiScore.score = player->getScore(); // Si se supera la máxima puntuación emite sonido if (hiScoreAchieved == false) { hiScoreAchieved = true; JA_PlaySound(hiScoreSound); } } } } // Actualiza las variables del jugador void Game::updatePlayers() { for (auto player : players) { player->update(); if (player->isPlaying()) { // Comprueba la colisión entre el jugador y los globos if (checkPlayerBalloonCollision(player)) { killPlayer(player); if (demo.enabled && allPlayersAreNotPlaying()) { fade->setType(FADE_RANDOM_SQUARE); fade->activate(); } } // Comprueba las colisiones entre el jugador y los items checkPlayerItemCollision(player); } } } // Dibuja a los jugadores void Game::renderPlayers() { for (auto player : players) { if (!player->isWaiting()) { player->render(); #ifdef DEBUG // SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); // const circle_t c = player->getCollider(); // DrawCircle(renderer, c.x, c.y, c.r); #endif } } } // Comprueba si hay cambio de fase y actualiza las variables void Game::updateStage() { if (currentPower >= enemyFormations->getStage(currentStage).powerToComplete) { // Cambio de fase currentStage++; currentPower = 0; lastStageReached = currentStage; if (currentStage == 10) { // Ha llegado al final el juego gameCompleted = true; // Marca el juego como completado currentStage = 9; // Deja el valor dentro de los limites destroyAllBalloons(); // Destruye a todos los enemigos currentPower = 0; // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos lo globos menaceCurrent = 255; // Sube el nivel de amenaza para que no cree mas globos for (auto player : players) { // Añade un millon de puntos a los jugadores que queden vivos if (player->isPlaying()) { player->addScore(1000000); } } updateHiScore(); JA_StopMusic(); } JA_PlaySound(stageChangeSound); stageBitmapCounter = 0; enemySpeed = defaultEnemySpeed; setBalloonSpeed(enemySpeed); screen->flash(flashColor, 5); screen->shake(); } // Incrementa el contador del bitmap que aparece mostrando el cambio de fase if (stageBitmapCounter < STAGE_COUNTER) { stageBitmapCounter++; } // Si el juego se ha completado, el bitmap se detiene en el centro de la pantalla if (gameCompleted) { if (stageBitmapCounter > 100) { stageBitmapCounter = 100; } } } // Actualiza el estado de fin de la partida void Game::updateGameOver() { // Comprueba si todos los jugadores estan esperando if (allPlayersAreWaiting()) { // Entonces los pone en estado de Game Over for (auto player : players) { player->setStatusPlaying(playerStatus::GAME_OVER); } } // Si todos estan en estado de Game Over if (allPlayersAreGameOver()) { if (gameOverCounter > 0) { gameOverCounter--; if ((gameOverCounter == 250) || (gameOverCounter == 200) || (gameOverCounter == 180) || (gameOverCounter == 120) || (gameOverCounter == 60)) { // Hace sonar aleatoriamente uno de los 4 sonidos de burbujas const int index = rand() % 4; JA_Sound_t *sound[4] = {bubble1Sound, bubble2Sound, bubble3Sound, bubble4Sound}; JA_PlaySound(sound[index], 0); } if (gameOverCounter == 150) { fade->activate(); } } if (fade->hasEnded()) { section::name = section::NAME_HI_SCORE_TABLE; } } } // Actualiza los globos void Game::updateBalloons() { for (auto balloon : balloons) { balloon->update(); } } // Pinta en pantalla todos los globos activos void Game::renderBalloons() { for (auto balloon : balloons) { balloon->render(); } } // Crea un globo nuevo en el vector de globos int Game::createBalloon(float x, int y, int kind, float velx, float speed, int creationtimer) { const int index = (kind - 1) % 4; Balloon *b = new Balloon(x, y, kind, velx, speed, creationtimer, balloonTextures[index], balloonAnimations[index]); balloons.push_back(b); return (int)(balloons.size() - 1); } // Crea una PowerBall void Game::createPowerBall() { const int values = 6; const int posY = -BLOCK; const int left = param.game.playArea.rect.x; const int center = param.game.playArea.centerX - (BALLOON_WIDTH_4 / 2); const int right = param.game.playArea.rect.w - BALLOON_WIDTH_4; const float vpos = BALLOON_VELX_POSITIVE; const float vneg = BALLOON_VELX_NEGATIVE; const int luck = rand() % values; const int x[values] = {left, left, center, center, right, right}; const float vx[values] = {vpos, vpos, vpos, vneg, vneg, vneg}; Balloon *b = new Balloon(x[luck], posY, POWER_BALL, vx[luck], enemySpeed, 300, balloonTextures[4], balloonAnimations[4]); balloons.push_back(b); powerBallEnabled = true; powerBallCounter = POWERBALL_COUNTER; } // Establece la velocidad de los globos void Game::setBalloonSpeed(float speed) { for (auto balloon : balloons) { if (balloon->isEnabled()) { balloon->setSpeed(speed); } } } // Incrementa la velocidad de los globos void Game::incBalloonSpeed() { // La velocidad solo se incrementa en el modo normal if (difficulty == gameDifficulty::NORMAL) { if (enemySpeed == BALLOON_SPEED_1) { enemySpeed = BALLOON_SPEED_2; } else if (enemySpeed == BALLOON_SPEED_2) { enemySpeed = BALLOON_SPEED_3; } else if (enemySpeed == BALLOON_SPEED_3) { enemySpeed = BALLOON_SPEED_4; } else if (enemySpeed == BALLOON_SPEED_4) { enemySpeed = BALLOON_SPEED_5; } setBalloonSpeed(enemySpeed); } } // Decrementa la velocidad de los globos void Game::decBalloonSpeed() { // La velocidad solo se decrementa en el modo normal if (difficulty == gameDifficulty::NORMAL) { if (enemySpeed == BALLOON_SPEED_5) { enemySpeed = BALLOON_SPEED_4; } else if (enemySpeed == BALLOON_SPEED_4) { enemySpeed = BALLOON_SPEED_3; } else if (enemySpeed == BALLOON_SPEED_3) { enemySpeed = BALLOON_SPEED_2; } else if (enemySpeed == BALLOON_SPEED_2) { enemySpeed = BALLOON_SPEED_1; } setBalloonSpeed(enemySpeed); } } // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase void Game::updateBalloonSpeed() { const float percent = (float)currentPower / (float)enemyFormations->getStage(currentStage).powerToComplete; if (enemySpeed == BALLOON_SPEED_1) { if (percent > 0.2f) { incBalloonSpeed(); } } else if (enemySpeed == BALLOON_SPEED_2) { if (percent > 0.4f) { incBalloonSpeed(); } } else if (enemySpeed == BALLOON_SPEED_3) { if (percent > 0.6f) { incBalloonSpeed(); } } else if (enemySpeed == BALLOON_SPEED_4) { if (percent > 0.8f) { incBalloonSpeed(); } } } // Explosiona un globo. Lo destruye y crea otros dos si es el caso void Game::popBalloon(Balloon *balloon) { // Aumenta el poder de la fase increaseStageCurrentPower(1); balloonsPopped++; const int kind = balloon->getKind(); if (kind == POWER_BALL) { destroyAllBalloons(); powerBallEnabled = false; enemyDeployCounter = 20; } else { const int size = balloon->getSize(); if (size == BALLOON_SIZE_1) { // Si es del tipo más pequeño, simplemente elimina el globo explosions->add(balloon->getPosX(), balloon->getPosY(), size); balloon->pop(); } else { // En cualquier otro caso, crea dos globos de un tipo inferior const int index = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, BALLOON_VELX_NEGATIVE, enemySpeed, 0); balloons[index]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2)); if (balloons[index]->getClass() == BALLOON_CLASS) { balloons[index]->setVelY(-2.50f); } else { balloons[index]->setVelY(BALLOON_VELX_NEGATIVE); } const int index2 = createBalloon(0, balloon->getPosY(), balloon->getKind() - 1, BALLOON_VELX_POSITIVE, enemySpeed, 0); balloons[index2]->allignTo(balloon->getPosX() + (balloon->getWidth() / 2)); if (balloons[index2]->getClass() == BALLOON_CLASS) { balloons[index2]->setVelY(-2.50f); } else { balloons[index2]->setVelY(BALLOON_VELX_NEGATIVE); } // Elimina el globo explosions->add(balloon->getPosX(), balloon->getPosY(), size); balloon->pop(); } } // Recalcula el nivel de amenaza evaluateAndSetMenace(); } // Explosiona un globo. Lo destruye = no crea otros globos void Game::destroyBalloon(Balloon *balloon) { int score = 0; // Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos const int size = balloon->getSize(); switch (size) { case BALLOON_SIZE_4: score = BALLOON_SCORE_4 + (2 * BALLOON_SCORE_3) + (4 * BALLOON_SCORE_2) + (8 * BALLOON_SCORE_1); break; case BALLOON_SIZE_3: score = BALLOON_SCORE_3 + (2 * BALLOON_SCORE_2) + (4 * BALLOON_SCORE_1); break; case BALLOON_SIZE_2: score = BALLOON_SCORE_2 + (2 * BALLOON_SCORE_1); break; case BALLOON_SIZE_1: score = BALLOON_SCORE_1; break; default: score = 0; break; } // Otorga los puntos correspondientes al globo for (auto player : players) { player->addScore(Uint32(score * player->getScoreMultiplier() * difficultyScoreMultiplier)); } updateHiScore(); // Aumenta el poder de la fase const int power = balloon->getPower(); increaseStageCurrentPower(power); balloonsPopped += power; // Destruye el globo explosions->add(balloon->getPosX(), balloon->getPosY(), size); balloon->pop(); // Recalcula el nivel de amenaza evaluateAndSetMenace(); } // Explosiona todos los globos void Game::popAllBalloons() { for (auto balloon : balloons) { if (balloon->canBePopped()) { popBalloon(balloon); } } JA_PlaySound(balloonSound); } // Destruye todos los globos void Game::destroyAllBalloons() { for (auto balloon : balloons) { if (balloon->canBeDestroyed()) { destroyBalloon(balloon); } } enemyDeployCounter = 255; JA_PlaySound(powerBallSound); screen->flash(flashColor, 5); screen->shake(); } // Detiene todos los globos void Game::stopAllBalloons(int time) { for (auto balloon : balloons) { if (balloon->isEnabled()) { balloon->setStop(true); balloon->setStoppedTimer(time); } } } // Pone en marcha todos los globos void Game::startAllBalloons() { for (auto balloon : balloons) { if ((balloon->isEnabled()) && (!balloon->isBeingCreated())) { balloon->setStop(false); balloon->setStoppedTimer(0); } } } // Obtiene el número de globos activos int Game::countBalloons() { int num = 0; for (auto balloon : balloons) { if (balloon->isEnabled()) { num++; } } return num; } // Vacia del vector de globos los globos que ya no sirven void Game::freeBalloons() { if (balloons.empty() == false) { for (int i = balloons.size() - 1; i >= 0; --i) { if (balloons[i]->isEnabled() == false) { delete balloons[i]; balloons.erase(balloons.begin() + i); } } } } // Comprueba la colisión entre el jugador y los globos activos bool Game::checkPlayerBalloonCollision(Player *player) { for (auto balloon : balloons) { if ((balloon->isEnabled()) && !(balloon->isStopped()) && !(balloon->isInvulnerable())) { if (checkCollision(player->getCollider(), balloon->getCollider())) { return true; } } } return false; } // Comprueba la colisión entre el jugador y los items void Game::checkPlayerItemCollision(Player *player) { if (!player->isPlaying()) { return; } for (auto item : items) { if (item->isEnabled()) { if (checkCollision(player->getCollider(), item->getCollider())) { switch (item->getClass()) { case ITEM_POINTS_1_DISK: player->addScore(1000); updateHiScore(); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n1000Sprite->getWidth() / 2), player->getPosY(), n1000Sprite.get()); JA_PlaySound(itemPickUpSound); break; case ITEM_POINTS_2_GAVINA: player->addScore(2500); updateHiScore(); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n2500Sprite->getWidth() / 2), player->getPosY(), n2500Sprite.get()); JA_PlaySound(itemPickUpSound); break; case ITEM_POINTS_3_PACMAR: player->addScore(5000); updateHiScore(); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n5000Sprite->getWidth() / 2), player->getPosY(), n5000Sprite.get()); JA_PlaySound(itemPickUpSound); break; case ITEM_CLOCK: enableTimeStopItem(); JA_PlaySound(itemPickUpSound); break; case ITEM_COFFEE: if (player->getCoffees() == 2) { player->addScore(5000); updateHiScore(); createItemScoreSprite(item->getPosX() + (item->getWidth() / 2) - (n5000Sprite->getWidth() / 2), player->getPosY(), n5000Sprite.get()); } player->giveExtraHit(); JA_PlaySound(itemPickUpSound); break; case ITEM_COFFEE_MACHINE: player->setPowerUp(); JA_PlaySound(itemPickUpSound); coffeeMachineEnabled = false; break; default: break; } item->disable(); } } } } // Comprueba y procesa la colisión entre las balas y los globos void Game::checkBulletBalloonCollision() { for (auto bullet : bullets) { for (auto balloon : balloons) { if (balloon->isEnabled() && (!balloon->isInvulnerable()) && bullet->isEnabled()) { if (checkCollision(balloon->getCollider(), bullet->getCollider())) { // Otorga los puntos correspondientes al globo al jugador que disparó la bala Player *player = getPlayer(bullet->getOwner()); if (!player) { return; } player->incScoreMultiplier(); player->addScore(Uint32(balloon->getScore() * player->getScoreMultiplier() * difficultyScoreMultiplier)); updateHiScore(); // Suelta el item si se da el caso const int droppeditem = dropItem(); if ((droppeditem != ITEM_NULL) && !(demo.recording)) { if (droppeditem != ITEM_COFFEE_MACHINE) { createItem(droppeditem, balloon->getPosX(), balloon->getPosY()); JA_PlaySound(itemDropSound); } else { createItem(droppeditem, player->getPosX(), 0); coffeeMachineEnabled = true; } } // Explota el globo popBalloon(balloon); // Sonido de explosión JA_PlaySound(balloonSound); // Deshabilita la bala bullet->disable(); break; } } } } } // Mueve las balas activas void Game::moveBullets() { for (auto bullet : bullets) { if (bullet->isEnabled()) { if (bullet->move() == BulletMoveStatus::OUT) { Player *player = getPlayer(bullet->getOwner()); player->decScoreMultiplier(); } } } } // Pinta las balas activas void Game::renderBullets() { for (auto bullet : bullets) { if (bullet->isEnabled()) { bullet->render(); } } } // Crea un objeto bala void Game::createBullet(int x, int y, BulletType kind, bool poweredUp, int owner) { Bullet *b = new Bullet(x, y, kind, poweredUp, owner, &(param.game.playArea.rect), bulletTexture.get()); bullets.push_back(b); } // Vacia el vector de balas void Game::freeBullets() { if (bullets.empty() == false) { for (int i = bullets.size() - 1; i >= 0; --i) { if (bullets[i]->isEnabled() == false) { delete bullets[i]; bullets.erase(bullets.begin() + i); } } } } // Actualiza los items void Game::updateItems() { for (auto item : items) { if (item->isEnabled()) { item->update(); if (item->isOnFloor()) { JA_PlaySound(coffeeMachineSound); screen->shake(); } } } } // Pinta los items activos void Game::renderItems() { for (auto item : items) { item->render(); } } // Devuelve un item al azar y luego segun sus probabilidades int Game::dropItem() { const int luckyNumber = rand() % 100; const int item = rand() % 6; switch (item) { case 0: if (luckyNumber < helper.itemPoints1Odds) { return ITEM_POINTS_1_DISK; } break; case 1: if (luckyNumber < helper.itemPoints2Odds) { return ITEM_POINTS_2_GAVINA; } break; case 2: if (luckyNumber < helper.itemPoints3Odds) { return ITEM_POINTS_3_PACMAR; } break; case 3: if (luckyNumber < helper.itemClockOdds) { return ITEM_CLOCK; } break; case 4: if (luckyNumber < helper.itemCoffeeOdds) { helper.itemCoffeeOdds = ITEM_COFFEE_ODDS; return ITEM_COFFEE; } else { if (helper.needCoffee) { helper.itemCoffeeOdds++; } } break; case 5: if (luckyNumber < helper.itemCoffeeMachineOdds) { helper.itemCoffeeMachineOdds = ITEM_COFFEE_MACHINE_ODDS; if ((!coffeeMachineEnabled) && (helper.needCoffeeMachine)) { return ITEM_COFFEE_MACHINE; } } else { if (helper.needCoffeeMachine) { helper.itemCoffeeMachineOdds++; } } break; default: break; } return ITEM_NULL; } // Crea un objeto item void Game::createItem(int kind, float x, float y) { Item *item = new Item(kind, x, y, &(param.game.playArea.rect), itemTextures[kind - 1], itemAnimations[kind - 1]); items.push_back(item); } // Vacia el vector de items void Game::freeItems() { if (items.empty() == false) { for (int i = items.size() - 1; i >= 0; --i) { if (items[i]->isEnabled() == false) { delete items[i]; items.erase(items.begin() + i); } } } } // Crea un objeto SmartSprite para mostrar la puntuación al coger un objeto void Game::createItemScoreSprite(int x, int y, SmartSprite *sprite) { SmartSprite *ss = new SmartSprite(nullptr); smartSprites.push_back(ss); // Crea una copia del objeto *ss = *sprite; ss->setPosX(x); ss->setPosY(y); ss->setDestX(x); ss->setDestY(y - 25); ss->setEnabled(true); ss->setEnabledCounter(100); } // Vacia el vector de smartsprites void Game::freeSmartSprites() { if (smartSprites.empty() == false) { for (int i = smartSprites.size() - 1; i >= 0; --i) { if (smartSprites[i]->hasFinished()) { delete smartSprites[i]; smartSprites.erase(smartSprites.begin() + i); } } } } // Crea un SmartSprite para arrojar el item café al recibir un impacto void Game::throwCoffee(int x, int y) { SmartSprite *ss = new SmartSprite(itemTextures[4]); smartSprites.push_back(ss); ss->setPosX(x - 8); ss->setPosY(y - 8); ss->setWidth(param.game.itemSize); ss->setHeight(param.game.itemSize); ss->setVelX(-1.0f + ((rand() % 5) * 0.5f)); ss->setVelY(-4.0f); ss->setAccelX(0.0f); ss->setAccelY(0.2f); ss->setDestX(x + (ss->getVelX() * 50)); ss->setDestY(param.game.height + 1); ss->setEnabled(true); ss->setEnabledCounter(1); ss->setSpriteClip(0, param.game.itemSize, param.game.itemSize, param.game.itemSize); ss->setRotate(true); ss->setRotateSpeed(10); ss->setRotateAmount(90.0); } // Actualiza los SmartSprites void Game::updateSmartSprites() { for (auto ss : smartSprites) { ss->update(); } } // Pinta los SmartSprites activos void Game::renderSmartSprites() { for (auto ss : smartSprites) { ss->render(); } } // Acciones a realizar cuando el jugador muere void Game::killPlayer(Player *player) { if (!player->isPlaying() || player->isInvulnerable()) { // Si no está jugando o tiene inmunidad, no hace nada return; } // Si tiene cafes if (player->hasExtraHit()) { // Lo pierde player->removeExtraHit(); throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2)); JA_PlaySound(coffeeOutSound); screen->shake(); } else { // Si no tiene cafes, muere if (!demo.enabled) { JA_PauseMusic(); } stopAllBalloons(10); JA_PlaySound(playerCollisionSound); screen->shake(); JA_PlaySound(coffeeOutSound); player->setStatusPlaying(playerStatus::DYING); if (!demo.enabled) { // En el modo DEMO ni se para la musica ni se añade la puntuación a la tabla allPlayersAreNotPlaying() ? JA_StopMusic() : JA_ResumeMusic(); } } } // Calcula y establece el valor de amenaza en funcion de los globos activos void Game::evaluateAndSetMenace() { menaceCurrent = 0; for (auto balloon : balloons) { if (balloon->isEnabled()) { menaceCurrent += balloon->getMenace(); } } } // Obtiene el valor de la variable int Game::getMenace() { return menaceCurrent; } // Establece el valor de la variable void Game::setTimeStopped(bool value) { timeStopped = value; } // Obtiene el valor de la variable bool Game::isTimeStopped() { return timeStopped; } // Establece el valor de la variable void Game::setTimeStoppedCounter(int value) { timeStoppedCounter = value; } // Incrementa el valor de la variable void Game::incTimeStoppedCounter(int value) { timeStoppedCounter += value; } // Actualiza y comprueba el valor de la variable void Game::updateTimeStoppedCounter() { if (isTimeStopped()) { if (timeStoppedCounter > 0) { timeStoppedCounter--; stopAllBalloons(TIME_STOPPED_COUNTER); } else { disableTimeStopItem(); } } } // Actualiza la variable enemyDeployCounter void Game::updateEnemyDeployCounter() { if (enemyDeployCounter > 0) { enemyDeployCounter--; } } // Actualiza el juego void Game::update() { // Comprueba que la diferencia de ticks sea mayor a la velocidad del juego if (SDL_GetTicks() - ticks > ticksSpeed) { // Actualiza el contador de ticks ticks = SDL_GetTicks(); // Actualiza el contador de juego counter++; if (demo.enabled) { // Incrementa el contador de la demo if (demo.counter < TOTAL_DEMO_DATA) { demo.counter++; } // Activa el fundido antes de acabar con los datos de la demo if (demo.counter == TOTAL_DEMO_DATA - 200) { fade->setType(FADE_RANDOM_SQUARE); fade->activate(); } // Si ha terminado el fundido, cambia de sección if (fade->hasEnded()) { section::name = section::NAME_HI_SCORE_TABLE; return; } } #ifdef RECORDING // Solo mira y guarda el input en cada update checkInput(); // Incrementa el contador de la demo if (demo.counter < TOTAL_DEMO_DATA) { demo.counter++; } // Si se ha llenado el vector con datos, sale del programa else { section::name = section::NAME_QUIT; return; } #endif #ifdef DEBUG if (autoPopBalloons && !gameCompleted) { balloonsPopped++; increaseStageCurrentPower(1); } #endif if (!paused) { // Actualiza el objeto fade fade->update(); // Actualiza las variables del jugador updatePlayers(); // Actualiza el marcador checkPlayersStatusPlaying(); updateScoreboard(); // Actualiza el fondo updateBackground(); // Mueve los globos updateBalloons(); // Actualiza el objeto encargado de las explosiones explosions->update(); // Mueve las balas moveBullets(); // Actualiza los items updateItems(); // Comprueba si hay cambio de fase y actualiza las variables updateStage(); // Actualiza el estado de muerte updateGameOver(); // Actualiza los SmartSprites updateSmartSprites(); // Actualiza los contadores de estado y efectos updateTimeStoppedCounter(); updateEnemyDeployCounter(); // Actualiza el ayudante updateHelper(); // Comprueba las colisiones entre globos y balas checkBulletBalloonCollision(); // Comprueba el nivel de amenaza para ver si se han de crear nuevos enemigos updateMenace(); // Actualiza la velocidad de los enemigos updateBalloonSpeed(); // Actualiza el tramo final de juego, una vez completado updateGameCompleted(); // Vacia los vectores freeBullets(); freeBalloons(); freeItems(); freeSmartSprites(); } // Comprueba si la música ha de estar sonando checkMusicStatus(); // Actualiza el objeto screen screen->update(); // Dibuja los graficos de la zona de juego en la textura fillCanvas(); } } // Actualiza el fondo void Game::updateBackground() { // Si el juego está completado, se reduce la velocidad de las nubes if (gameCompleted) { if (balloonsPopped > 400) { balloonsPopped -= 25; } else { balloonsPopped = 200; } } // Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego const float cloudsInitialSpeed = 0.05f; const float cloudsFinalSpeed = 2.00f - cloudsInitialSpeed; const float cloudsSpeed = (-cloudsInitialSpeed) + (-cloudsFinalSpeed * ((float)balloonsPopped / (float)totalPowerToCompleteGame)); background->setCloudsSpeed(cloudsSpeed); // Calcula la transición de los diferentes fondos const float gradientNumber = std::min(((float)balloonsPopped / 1250.0f), 3.0f); const float percent = gradientNumber - (int)gradientNumber; background->setGradientNumber((int)gradientNumber); background->setTransition(percent); // Actualiza el objeto background->update(); } // Dibuja los elementos de la zona de juego en su textura void Game::fillCanvas() { // Dibujamos el contenido de la zona de juego en su textura SDL_Texture *temp = SDL_GetRenderTarget(renderer); SDL_SetRenderTarget(renderer, canvas); // Dibuja los objetos background->render(); renderItems(); renderSmartSprites(); explosions->render(); renderBalloons(); renderBullets(); renderMessages(); renderPlayers(); // Deja el renderizador apuntando donde estaba SDL_SetRenderTarget(renderer, temp); } // Dibuja el juego void Game::render() { // Prepara para empezar a dibujar en la textura de juego screen->start(); // Copia la textura con la zona de juego a la pantalla SDL_RenderCopy(renderer, canvas, nullptr, ¶m.game.playArea.rect); // Dibuja el marcador scoreboard->render(); // Dibuja el fade fade->render(); // Vuelca el contenido del renderizador en pantalla screen->blit(); } // Gestiona el nivel de amenaza void Game::updateMenace() { if (gameCompleted) { return; } const stage_t stage = enemyFormations->getStage(currentStage); const float percent = currentPower / stage.powerToComplete; const int difference = stage.maxMenace - stage.minMenace; // Aumenta el nivel de amenaza en función de la puntuación menaceThreshold = stage.minMenace + (difference * percent); // Si el nivel de amenza es inferior al umbral if (menaceCurrent < menaceThreshold) { // Crea una formación de enemigos deployEnemyFormation(); // Recalcula el nivel de amenaza con el nuevo globo evaluateAndSetMenace(); } } // Gestiona la entrada durante el juego void Game::checkInput() { // Comprueba si se pulsa el botón de pausa for (int i = 0; i < input->getNumControllers(); ++i) { // Comprueba si se va a pausar el juego if (input->checkModInput(input_service, input_pause, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_USE_GAMECONTROLLER, i)) { pause(!paused); return; } } // Modo Demo activo if (demo.enabled) { int i = 0; for (auto player : players) { if (player->isPlaying()) { // Comprueba direcciones if (demo.dataFile[i][demo.counter].left == 1) { player->setInput(input_left); } else if (demo.dataFile[i][demo.counter].right == 1) { player->setInput(input_right); } else if (demo.dataFile[i][demo.counter].noInput == 1) { player->setInput(input_null); } // Comprueba botones if (demo.dataFile[i][demo.counter].fire == 1) { if (player->canFire()) { player->setInput(input_fire_center); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::UP, player->isPowerUp(), player->getId()); player->setFireCooldown(10); } } else if (demo.dataFile[i][demo.counter].fireLeft == 1) { if (player->canFire()) { player->setInput(input_fire_left); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::LEFT, player->isPowerUp(), player->getId()); player->setFireCooldown(10); } } else if (demo.dataFile[i][demo.counter].fireRight == 1) { if (player->canFire()) { player->setInput(input_fire_right); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::RIGHT, player->isPowerUp(), player->getId()); player->setFireCooldown(10); } } // Si se pulsa cualquier tecla, se sale del modo demo if (input->checkAnyButtonPressed()) { section::name = section::NAME_TITLE; return; } } i++; } } // Modo Demo no activo else { #ifdef RECORDING // Resetea el teclado demo.keys.left = 0; demo.keys.right = 0; demo.keys.noInput = 0; demo.keys.fire = 0; demo.keys.fireLeft = 0; demo.keys.fireRight = 0; #endif for (auto player : players) { const int controllerIndex = player->getController(); const bool autofire = player->isPowerUp() || options.game.autofire; if (player->isPlaying()) { // Input a la izquierda if (input->checkInput(input_left, INPUT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { player->setInput(input_left); #ifdef RECORDING demo.keys.left = 1; #endif } else { // Input a la derecha if (input->checkInput(input_right, INPUT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { player->setInput(input_right); #ifdef RECORDING demo.keys.right = 1; #endif } else { // Ninguno de los dos inputs anteriores player->setInput(input_null); #ifdef RECORDING demo.keys.noInput = 1; #endif } } // Comprueba el input de disparar al centro if (input->checkInput(input_fire_center, autofire, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { if (player->canFire()) { player->setInput(input_fire_center); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::UP, player->isPowerUp(), player->getId()); player->setFireCooldown(10); // Reproduce el sonido de disparo JA_PlaySound(bulletSound); #ifdef RECORDING demo.keys.fire = 1; #endif } } // Comprueba el input de disparar a la izquierda else if (input->checkInput(input_fire_left, autofire, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { if (player->canFire()) { player->setInput(input_fire_left); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::LEFT, player->isPowerUp(), player->getId()); player->setFireCooldown(10); // Reproduce el sonido de disparo JA_PlaySound(bulletSound); #ifdef RECORDING demo.keys.fireLeft = 1; #endif } } // Comprueba el input de disparar a la derecha else if (input->checkInput(input_fire_right, autofire, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { if (player->canFire()) { player->setInput(input_fire_right); createBullet(player->getPosX() + (player->getWidth() / 2) - 4, player->getPosY() + (player->getHeight() / 2), BulletType::RIGHT, player->isPowerUp(), player->getId()); player->setFireCooldown(10); // Reproduce el sonido de disparo JA_PlaySound(bulletSound); #ifdef RECORDING demo.keys.fireRight = 1; #endif } } #ifdef RECORDING if (demo.recording) { if (demo.counter < TOTAL_DEMO_DATA) { demo.dataFile[0][demo.counter] = demo.keys; } } #endif } else if (player->isContinue() || player->isWaiting()) { // Si no está jugando, el botón de start le permite continuar jugando if (input->checkInput(input_start, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { player->setStatusPlaying(playerStatus::PLAYING); } // Si está continuando, los botones de fuego hacen decrementar el contador const bool fire1 = input->checkInput(input_fire_left, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index); const bool fire2 = input->checkInput(input_fire_center, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index); const bool fire3 = input->checkInput(input_fire_right, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index); if (fire1 || fire2 || fire3) { player->decContinueCounter(); } } else if (player->isEnteringName()) { const bool fire1 = input->checkInput(input_fire_left, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index); const bool fire2 = input->checkInput(input_fire_center, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index); const bool fire3 = input->checkInput(input_fire_right, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index); if (fire1 || fire2 || fire3) { if (player->getRecordNamePos() == 7) { player->setInput(input_start); addScoreToScoreBoard(player->getRecordName(), player->getScore()); player->setStatusPlaying(playerStatus::CONTINUE); } else { player->setInput(input_right); } } else if (input->checkInput(input_up, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { player->setInput(input_up); } else if (input->checkInput(input_down, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { player->setInput(input_down); } else if (input->checkInput(input_left, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { player->setInput(input_left); } else if (input->checkInput(input_right, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { player->setInput(input_right); } else if (input->checkInput(input_start, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].deviceType, options.controller[controllerIndex].index)) { player->setInput(input_start); addScoreToScoreBoard(player->getRecordName(), player->getScore()); player->setStatusPlaying(playerStatus::CONTINUE); } } } } // Comprueba el input para el resto de objetos screen->checkInput(); // Comprueba los inputs que se pueden introducir en cualquier sección del juego globalInputs::check(); } // Pinta diferentes mensajes en la pantalla void Game::renderMessages() { // GetReady if ((counter < STAGE_COUNTER) && (!demo.enabled)) { textNokiaBig2->write((int)getReadyBitmapPath[counter], param.game.playArea.centerY - 8, lang::getText(75), -2); } // Time Stopped if (timeStopped) { if ((timeStoppedCounter > 100) || (timeStoppedCounter % 10 > 4)) { textNokia2->writeDX(TXT_CENTER, param.game.playArea.centerX, param.game.playArea.firstQuarterY, lang::getText(36) + std::to_string(timeStoppedCounter / 10), -1, noColor, 1, shdwTxtColor); } if (timeStoppedCounter > 100) { if (timeStoppedCounter % 30 == 0) { // JA_PlaySound(clockSound, false); JA_PlaySound(clockSound); } } else { if (timeStoppedCounter % 15 == 0) { // JA_PlaySound(clockSound, false); JA_PlaySound(clockSound); } } } // STAGE NUMBER if (stageBitmapCounter < STAGE_COUNTER) { const int stageNum = enemyFormations->getStage(currentStage).number; std::string text; if (stageNum == 10) { // Ultima fase text = lang::getText(79); } else { // X fases restantes text = std::to_string(11 - stageNum) + lang::getText(38); } if (!gameCompleted) { // Escribe el número de fases restantes textNokiaBig2->writeDX(TXT_CENTER, param.game.playArea.centerX, stageBitmapPath[stageBitmapCounter], text, -2, noColor, 2, shdwTxtColor); } else { // Escribe el texto de juego completado text = lang::getText(50); textNokiaBig2->writeDX(TXT_CENTER, param.game.playArea.centerX, stageBitmapPath[stageBitmapCounter], text, -2, noColor, 1, shdwTxtColor); textNokia2->writeDX(TXT_CENTER, param.game.playArea.centerX, stageBitmapPath[stageBitmapCounter] + textNokiaBig2->getCharacterSize() + 2, lang::getText(76), -1, noColor, 1, 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 && !demo.enabled) { JA_PauseMusic(); } } // Deshabilita el efecto del item de detener el tiempo void Game::disableTimeStopItem() { timeStopped = false; setTimeStoppedCounter(0); startAllBalloons(); if (JA_GetMusicState() == JA_MUSIC_PAUSED && !demo.enabled) { JA_ResumeMusic(); } } // Comprueba si la música ha de estar sonando void Game::checkMusicStatus() { // Si la música no está sonando if ((JA_GetMusicState() == JA_MUSIC_INVALID) || (JA_GetMusicState() == JA_MUSIC_STOPPED)) { // Si se ha completado el juego o los jugadores han terminado, detiene la música gameCompleted || allPlayersAreGameOver() ? JA_StopMusic() : JA_PlayMusic(music); } } // Bucle para el juego void Game::run() { while (section::name == section::NAME_GAME) { #ifndef RECORDING checkInput(); #endif update(); checkEvents(); // Tiene que ir antes del render render(); } // Vuelve a dejar el sonido como estaba if (demo.enabled) { JA_EnableSound(options.audio.sound.enabled); } else { JA_StopMusic(); } } // Indica si se puede crear una powerball bool Game::canPowerBallBeCreated() { if ((!powerBallEnabled) && (calculateScreenPower() > POWERBALL_SCREENPOWER_MINIMUM) && (powerBallCounter == 0)) { return true; } return false; } // Calcula el poder actual de los globos en pantalla int Game::calculateScreenPower() { int power = 0; for (auto balloon : balloons) { if (balloon->isEnabled()) { power += balloon->getPower(); } } return power; } // Inicializa las variables que contienen puntos de ruta para mover objetos void Game::initPaths() { // Vector con los valores del seno para 360 grados float sin[360]; for (int i = 0; i < 360; ++i) { sin[i] = SDL_sinf((float)i * 3.14f / 180.0f); } // Letrero de STAGE # const int firstPart = STAGE_COUNTER / 4; // 50 const int secondPart = firstPart * 3; // 150 const int centerPoint = param.game.playArea.centerY - (BLOCK * 2); const int distance = (param.game.playArea.rect.h) - (param.game.playArea.centerY - 16); for (int i = 0; i < STAGE_COUNTER; ++i) { if (i < firstPart) { stageBitmapPath[i] = (sin[(int)((i * 1.8f) + 90)] * (distance) + centerPoint); } else if (i < secondPart) { stageBitmapPath[i] = (int)centerPoint; } else { stageBitmapPath[i] = (sin[(int)(((i - 149) * 1.8f) + 90)] * (centerPoint + 17) - 17); } } // Letrero de GetReady const int size = textNokiaBig2->lenght(lang::getText(75), -2); const float start1 = param.game.playArea.rect.x - size; const float finish1 = param.game.playArea.centerX - (size / 2); const float start2 = finish1; const float finish2 = param.game.playArea.rect.w; const float distance1 = finish1 - start1; const float distance2 = finish2 - start2; for (int i = 0; i < STAGE_COUNTER; ++i) { if (i < firstPart) { getReadyBitmapPath[i] = sin[(int)(i * 1.8f)]; getReadyBitmapPath[i] *= distance1; getReadyBitmapPath[i] -= size; } else if (i < secondPart) { getReadyBitmapPath[i] = (int)finish1; } else { getReadyBitmapPath[i] = sin[(int)((i - 150) * 1.8f)]; getReadyBitmapPath[i] *= distance2; getReadyBitmapPath[i] += finish1; } } } // Actualiza el tramo final de juego, una vez completado void Game::updateGameCompleted() { if (gameCompleted) { gameCompletedCounter++; } if (gameCompletedCounter == GAME_COMPLETED_END) { // section::options = SUBSECTION_GAME_GAMEOVER; section::name = section::NAME_TITLE; section::options = section::OPTIONS_TITLE_1; } } // Actualiza las variables de ayuda void Game::updateHelper() { // Solo ofrece ayuda cuando la amenaza es elevada if (menaceCurrent > 15) { for (auto player : players) { if (player->getCoffees() == 0) { helper.needCoffee = true; } else { helper.needCoffee = false; } if (!player->isPowerUp()) { helper.needCoffeeMachine = true; } else { helper.needCoffeeMachine = false; } } } else { helper.needCoffee = false; helper.needCoffeeMachine = false; } } // Comprueba si todos los jugadores han terminado de jugar bool Game::allPlayersAreWaiting() { bool success = true; for (auto player : players) { success &= player->isWaiting(); } return success; } // Comprueba si todos los jugadores han terminado de jugar bool Game::allPlayersAreGameOver() { bool success = true; for (auto player : players) { success &= player->isGameOver(); } return success; } // Comprueba si todos los jugadores han terminado de jugar bool Game::allPlayersAreNotPlaying() { bool success = true; for (auto player : players) { success &= !player->isPlaying(); } return success; } // Comprueba los eventos que hay en cola void Game::checkEvents() { while (SDL_PollEvent(eventHandler.get()) != 0) { // Evento de salida de la aplicación if (eventHandler->type == SDL_QUIT) { section::name = section::NAME_QUIT; break; } else if (eventHandler->type == SDL_WINDOWEVENT) { switch (eventHandler->window.event) { case SDL_WINDOWEVENT_FOCUS_LOST: if (!demo.enabled) { pause(true); } break; case SDL_WINDOWEVENT_FOCUS_GAINED: pause(false); break; case SDL_WINDOWEVENT_SIZE_CHANGED: reloadTextures(); break; default: break; } } #ifdef DEBUG else if (eventHandler->type == SDL_KEYDOWN) { switch (eventHandler->key.keysym.sym) { // CREA UNA POWERBALL case SDLK_1: createPowerBall(); break; // CREA DOS BALLON4 case SDLK_2: { const int set = 0; const stage_t stage = enemyFormations->getStage(0); const int numEnemies = stage.enemyPool->set[set]->numberOfEnemies; for (int i = 0; i < numEnemies; ++i) { createBalloon(stage.enemyPool->set[set]->init[i].x, stage.enemyPool->set[set]->init[i].y, stage.enemyPool->set[set]->init[i].kind, stage.enemyPool->set[set]->init[i].velX, enemySpeed, stage.enemyPool->set[set]->init[i].creationCounter); } } break; // ACTIVA EL MODO PARA PASAR EL JUEGO AUTOMATICAMENTE case SDLK_3: autoPopBalloons = !autoPopBalloons; screen->showNotification("autoPopBalloons " + boolToString(autoPopBalloons)); break; default: break; } } #endif } } // Carga las animaciones void Game::loadAnimations(std::string filePath, std::vector *buffer) { std::ifstream file(filePath); std::string line; if (file) { #ifdef VERBOSE std::cout << "Animation loaded: " << filePath.substr(filePath.find_last_of("\\/") + 1).c_str() << std::endl; #endif while (std::getline(file, line)) { buffer->push_back(line); } file.close(); } } // Elimina todos los objetos contenidos en vectores void Game::deleteAllVectorObjects() { for (auto player : players) { delete player; }; players.clear(); for (auto ballon : balloons) { delete ballon; }; balloons.clear(); for (auto bullet : bullets) { delete bullet; }; bullets.clear(); for (auto item : items) { delete item; }; items.clear(); for (auto smartSprite : smartSprites) { delete smartSprite; }; smartSprites.clear(); } // Recarga las texturas void Game::reloadTextures() { for (auto texture : itemTextures) { texture->reLoad(); } for (auto texture : balloonTextures) { texture->reLoad(); } for (auto texture : player1Textures) { texture->reLoad(); } for (auto texture : player2Textures) { texture->reLoad(); } bulletTexture->reLoad(); gameTextTexture->reLoad(); background->reloadTextures(); } // Actualiza el marcador void Game::updateScoreboard() { for (auto player : players) { scoreboard->setScore(player->getScoreBoardPanel(), player->getScore()); scoreboard->setMult(player->getScoreBoardPanel(), player->getScoreMultiplier()); } // Resto de marcador scoreboard->setStage(enemyFormations->getStage(currentStage).number); scoreboard->setPower((float)currentPower / (float)enemyFormations->getStage(currentStage).powerToComplete); scoreboard->setHiScore(hiScore.score); scoreboard->setHiScoreName(hiScore.name); // Lógica del marcador scoreboard->update(); } // Pausa el juego void Game::pause(bool value) { paused = value; screen->attenuate(paused); } // Añade una puntuación a la tabla de records void Game::addScoreToScoreBoard(std::string name, int score) { const auto entry = (hiScoreEntry_t){trim(name), score}; auto manager = std::make_unique(&options.game.hiScoreTable); manager->add(entry); manager->saveToFile(asset->get("score.bin")); } // Comprueba el estado de los jugadores void Game::checkPlayersStatusPlaying() { if (demo.enabled) { return; } for (auto player : players) { switch (player->getStatusPlaying()) { case playerStatus::PLAYING: scoreboard->setMode(player->getScoreBoardPanel(), scoreboardMode::SCORE); break; case playerStatus::CONTINUE: scoreboard->setMode(player->getScoreBoardPanel(), scoreboardMode::CONTINUE); scoreboard->setContinue(player->getScoreBoardPanel(), player->getContinueCounter()); break; case playerStatus::WAITING: scoreboard->setMode(player->getScoreBoardPanel(), scoreboardMode::WAITING); break; case playerStatus::ENTERING_NAME: scoreboard->setMode(player->getScoreBoardPanel(), scoreboardMode::ENTER_NAME); scoreboard->setRecordName(player->getScoreBoardPanel(), player->getRecordName()); scoreboard->setSelectorPos(player->getScoreBoardPanel(), player->getRecordNamePos()); break; case playerStatus::DYING: break; case playerStatus::DIED: { const playerStatus nextPlayerStatus = IsEligibleForHighScore(player->getScore()) ? playerStatus::ENTERING_NAME : playerStatus::CONTINUE; demo.enabled ? player->setStatusPlaying(playerStatus::WAITING) : player->setStatusPlaying(nextPlayerStatus); break; } case playerStatus::GAME_OVER: scoreboard->setMode(player->getScoreBoardPanel(), scoreboardMode::GAME_OVER); break; default: break; } } } // Comprueba si la puntuación entra en la tabla de mejores puntuaciones bool Game::IsEligibleForHighScore(int score) { return score > options.game.hiScoreTable.back().score; } // Obtiene un jugador a partir de su "id" Player *Game::getPlayer(int id) { for (auto player : players) { if (player->getId() == id) { return player; } } return nullptr; } // Obtiene un controlador a partir del "id" del jugador int Game::getController(int playerId) { for (int i = 0; i < (int)options.controller.size(); ++i) { if (options.controller[i].playerId == playerId) { return i; } } return -1; } // Termina void Game::quit(section::options_e code) { if (screen->notificationsAreActive()) { section::name = section::NAME_QUIT; section::options = code; } else { screen->showNotification(lang::getText(94)); } }