#include "director.h" #include // for SDL_Init, SDL_Quit, SDL_INIT_EV... #include // for AUDIO_S16 #include // for SDL_BLENDMODE_BLEND #include // for SDL_GetError #include // for SDL_CONTROLLER_BUTTON_B, SDL_CO... #include // for SDL_SetHint, SDL_HINT_RENDER_SC... #include // for SDL_SCANCODE_ESCAPE, SDL_SCANCO... #include // for Uint32 #include // for SDL_GetTicks #include // for errno, EEXIST, EACCES, ENAMETOO... #include // for printf, perror #include // for strcmp #include // for mkdir, stat, S_IRWXU #include // for getuid #include // for exit, EXIT_FAILURE, srand #include // for basic_ostream, operator<<, basi... #include // for cout #include // for basic_string, operator+, char_t... #include // for vector #include #include "asset.h" // for Asset, assetType #include "const.h" // for SECTION_PROG_LOGO, GAMECANVAS_H... #include "game.h" // for Game #include "input.h" // for Input, inputs_e, INPUT_USE_GAME... #include "intro.h" // for Intro #include "jail_audio.h" // for JA_Init #include "lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK #include "logo.h" // for Logo #include "screen.h" // for FILTER_NEAREST, Screen, FILTER_... #include "title.h" // for Title #include "utils.h" // for options_t, input_t, boolToString #ifndef _WIN32 #include #endif // Constructor Director::Director(int argc, const char *argv[]) { std::cout << "Game start" << std::endl; // Inicializa variables section = new section_t(); section->name = SECTION_PROG_LOGO; // Inicializa las opciones del programa initOptions(); // Comprueba los parametros del programa checkProgramArguments(argc, argv); // Crea la carpeta del sistema donde guardar datos createSystemFolder("jailgames"); #ifndef DEBUG createSystemFolder("jailgames/coffee_crisis"); #else createSystemFolder("jailgames/coffee_crisis_debug"); #endif // Crea el objeto que controla los ficheros de recursos asset = new Asset(executablePath); asset->setVerbose(options->console); // Si falta algún fichero no inicia el programa if (!setFileList()) { exit(EXIT_FAILURE); } // Carga el fichero de configuración loadConfigFile(); // Inicializa SDL initSDL(); // Inicializa JailAudio initJailAudio(); // Crea los objetos lang = new Lang(asset); lang->setLang(options->language); input = new Input(asset->get("gamecontrollerdb.txt")); initInput(); screen = new Screen(window, renderer, asset, options); } Director::~Director() { saveConfigFile(); delete asset; delete input; delete screen; delete lang; delete options; delete section; SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); std::cout << "\nBye!" << std::endl; } // Inicializa el objeto input void Director::initInput() { // Establece si ha de mostrar mensajes input->setVerbose(options->console); // Busca si hay un mando conectado input->discoverGameController(); // Teclado - Movimiento del jugador input->bindKey(input_up, SDL_SCANCODE_UP); input->bindKey(input_down, SDL_SCANCODE_DOWN); input->bindKey(input_left, SDL_SCANCODE_LEFT); input->bindKey(input_right, SDL_SCANCODE_RIGHT); input->bindKey(input_fire_left, SDL_SCANCODE_Q); input->bindKey(input_fire_center, SDL_SCANCODE_W); input->bindKey(input_fire_right, SDL_SCANCODE_E); // Teclado - Otros input->bindKey(input_accept, SDL_SCANCODE_RETURN); input->bindKey(input_cancel, SDL_SCANCODE_ESCAPE); input->bindKey(input_pause, SDL_SCANCODE_ESCAPE); input->bindKey(input_exit, SDL_SCANCODE_ESCAPE); input->bindKey(input_window_dec_size, SDL_SCANCODE_F1); input->bindKey(input_window_inc_size, SDL_SCANCODE_F2); input->bindKey(input_window_fullscreen, SDL_SCANCODE_F3); // Mando - Movimiento del jugador input->bindGameControllerButton(input_up, SDL_CONTROLLER_BUTTON_DPAD_UP); input->bindGameControllerButton(input_down, SDL_CONTROLLER_BUTTON_DPAD_DOWN); input->bindGameControllerButton(input_left, SDL_CONTROLLER_BUTTON_DPAD_LEFT); input->bindGameControllerButton(input_right, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); input->bindGameControllerButton(input_fire_left, SDL_CONTROLLER_BUTTON_X); input->bindGameControllerButton(input_fire_center, SDL_CONTROLLER_BUTTON_Y); input->bindGameControllerButton(input_fire_right, SDL_CONTROLLER_BUTTON_B); // Mando - Otros input->bindGameControllerButton(input_accept, SDL_CONTROLLER_BUTTON_B); input->bindGameControllerButton(input_cancel, SDL_CONTROLLER_BUTTON_A); #ifdef GAME_CONSOLE input->bindGameControllerButton(input_pause, SDL_CONTROLLER_BUTTON_BACK); input->bindGameControllerButton(input_exit, SDL_CONTROLLER_BUTTON_START); #else input->bindGameControllerButton(input_pause, SDL_CONTROLLER_BUTTON_START); input->bindGameControllerButton(input_exit, SDL_CONTROLLER_BUTTON_BACK); #endif } // Inicializa JailAudio void Director::initJailAudio() { JA_Init(48000, AUDIO_S16, 2); } // Arranca SDL y crea la ventana bool Director::initSDL() { // Indicador de éxito bool success = true; // Inicializa SDL if (SDL_Init(SDL_INIT_EVERYTHING) < 0) // if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) < 0) { if (options->console) { std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl; } success = false; } else { // Inicia el generador de numeros aleatorios std::srand(static_cast(SDL_GetTicks())); // Establece el filtro de la textura if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, std::to_string(options->filter).c_str())) { if (options->console) { std::cout << "Warning: Nearest texture filtering not enabled!\n"; } } // Crea la ventana int incW = 0; int incH = 0; if (options->borderEnabled) { incW = options->borderWidth * 2; incH = options->borderHeight * 2; } window = SDL_CreateWindow(WINDOW_CAPTION, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (options->gameWidth + incW) * options->windowSize, (options->gameHeight + incH) * options->windowSize, SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI); if (window == nullptr) { if (options->console) { std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl; } success = false; } else { // Crea un renderizador para la ventana. El vsync se activa en funcion de las opciones // Crea un renderizador para la ventana. El vsync se activa en funcion de las opciones // Uint32 flags = SDL_RENDERER_SOFTWARE; // Uint32 flags = SDL_RENDERER_ACCELERATED; Uint32 flags = 0; if (options->vSync) { flags = flags | SDL_RENDERER_PRESENTVSYNC; } renderer = SDL_CreateRenderer(window, -1, flags); if (renderer == nullptr) { if (options->console) { std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl; } success = false; } else { // Inicializa el color de renderizado SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF); // Establece el tamaño del buffer de renderizado SDL_RenderSetLogicalSize(renderer, options->gameWidth, options->gameHeight); // Establece el modo de mezcla SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); } } } if (options->console) { std::cout << std::endl; } return success; } // Crea el indice de ficheros bool Director::setFileList() { #ifdef MACOS_BUNDLE const std::string prefix = "/../Resources"; #else const std::string prefix = ""; #endif // Ficheros de configuración asset->add(systemFolder + "/config.txt", t_data, false, true); asset->add(systemFolder + "/score.bin", t_data, false, true); asset->add(prefix + "/data/config/demo.bin", t_data); asset->add(prefix + "/data/config/gamecontrollerdb.txt", t_data); // Musicas asset->add(prefix + "/data/music/intro.ogg", t_music); asset->add(prefix + "/data/music/playing.ogg", t_music); asset->add(prefix + "/data/music/title.ogg", t_music); // Sonidos asset->add(prefix + "/data/sound/balloon.wav", t_sound); asset->add(prefix + "/data/sound/bubble1.wav", t_sound); asset->add(prefix + "/data/sound/bubble2.wav", t_sound); asset->add(prefix + "/data/sound/bubble3.wav", t_sound); asset->add(prefix + "/data/sound/bubble4.wav", t_sound); asset->add(prefix + "/data/sound/bullet.wav", t_sound); asset->add(prefix + "/data/sound/coffeeout.wav", t_sound); asset->add(prefix + "/data/sound/hiscore.wav", t_sound); asset->add(prefix + "/data/sound/itemdrop.wav", t_sound); asset->add(prefix + "/data/sound/itempickup.wav", t_sound); asset->add(prefix + "/data/sound/menu_move.wav", t_sound); asset->add(prefix + "/data/sound/menu_select.wav", t_sound); asset->add(prefix + "/data/sound/player_collision.wav", t_sound); asset->add(prefix + "/data/sound/stage_change.wav", t_sound); asset->add(prefix + "/data/sound/title.wav", t_sound); asset->add(prefix + "/data/sound/clock.wav", t_sound); asset->add(prefix + "/data/sound/powerball.wav", t_sound); // Texturas asset->add(prefix + "/data/gfx/balloon1.png", t_bitmap); asset->add(prefix + "/data/gfx/balloon1.ani", t_data); asset->add(prefix + "/data/gfx/balloon2.png", t_bitmap); asset->add(prefix + "/data/gfx/balloon2.ani", t_data); asset->add(prefix + "/data/gfx/balloon3.png", t_bitmap); asset->add(prefix + "/data/gfx/balloon3.ani", t_data); asset->add(prefix + "/data/gfx/balloon4.png", t_bitmap); asset->add(prefix + "/data/gfx/balloon4.ani", t_data); asset->add(prefix + "/data/gfx/bullet.png", t_bitmap); asset->add(prefix + "/data/gfx/game_buildings.png", t_bitmap); asset->add(prefix + "/data/gfx/game_clouds.png", t_bitmap); asset->add(prefix + "/data/gfx/game_grass.png", t_bitmap); asset->add(prefix + "/data/gfx/game_power_meter.png", t_bitmap); asset->add(prefix + "/data/gfx/game_sky_colors.png", t_bitmap); asset->add(prefix + "/data/gfx/game_text.png", t_bitmap); asset->add(prefix + "/data/gfx/intro.png", t_bitmap); asset->add(prefix + "/data/gfx/logo.png", t_bitmap); asset->add(prefix + "/data/gfx/menu_game_over.png", t_bitmap); asset->add(prefix + "/data/gfx/menu_game_over_end.png", t_bitmap); asset->add(prefix + "/data/gfx/item_points1_disk.png", t_bitmap); asset->add(prefix + "/data/gfx/item_points1_disk.ani", t_data); asset->add(prefix + "/data/gfx/item_points2_gavina.png", t_bitmap); asset->add(prefix + "/data/gfx/item_points2_gavina.ani", t_data); asset->add(prefix + "/data/gfx/item_points3_pacmar.png", t_bitmap); asset->add(prefix + "/data/gfx/item_points3_pacmar.ani", t_data); asset->add(prefix + "/data/gfx/item_clock.png", t_bitmap); asset->add(prefix + "/data/gfx/item_clock.ani", t_data); asset->add(prefix + "/data/gfx/item_coffee.png", t_bitmap); asset->add(prefix + "/data/gfx/item_coffee.ani", t_data); asset->add(prefix + "/data/gfx/item_coffee_machine.png", t_bitmap); asset->add(prefix + "/data/gfx/item_coffee_machine.ani", t_data); asset->add(prefix + "/data/gfx/title_bg_tile.png", t_bitmap); asset->add(prefix + "/data/gfx/title_coffee.png", t_bitmap); asset->add(prefix + "/data/gfx/title_crisis.png", t_bitmap); asset->add(prefix + "/data/gfx/title_dust.png", t_bitmap); asset->add(prefix + "/data/gfx/title_dust.ani", t_data); asset->add(prefix + "/data/gfx/title_gradient.png", t_bitmap); asset->add(prefix + "/data/gfx/player_head.ani", t_data); asset->add(prefix + "/data/gfx/player_body.ani", t_data); asset->add(prefix + "/data/gfx/player_legs.ani", t_data); asset->add(prefix + "/data/gfx/player_death.ani", t_data); asset->add(prefix + "/data/gfx/player_fire.ani", t_data); asset->add(prefix + "/data/gfx/player_bal1_head.png", t_bitmap); asset->add(prefix + "/data/gfx/player_bal1_body.png", t_bitmap); asset->add(prefix + "/data/gfx/player_bal1_legs.png", t_bitmap); asset->add(prefix + "/data/gfx/player_bal1_death.png", t_bitmap); asset->add(prefix + "/data/gfx/player_bal1_fire.png", t_bitmap); asset->add(prefix + "/data/gfx/player_arounder_head.png", t_bitmap); asset->add(prefix + "/data/gfx/player_arounder_body.png", t_bitmap); asset->add(prefix + "/data/gfx/player_arounder_legs.png", t_bitmap); asset->add(prefix + "/data/gfx/player_arounder_death.png", t_bitmap); asset->add(prefix + "/data/gfx/player_arounder_fire.png", t_bitmap); // Fuentes asset->add(prefix + "/data/font/8bithud.png", t_font); asset->add(prefix + "/data/font/8bithud.txt", t_font); asset->add(prefix + "/data/font/nokia.png", t_font); asset->add(prefix + "/data/font/nokia_big2.png", t_font); asset->add(prefix + "/data/font/nokia.txt", t_font); asset->add(prefix + "/data/font/nokia2.png", t_font); asset->add(prefix + "/data/font/nokia2.txt", t_font); asset->add(prefix + "/data/font/nokia_big2.txt", t_font); asset->add(prefix + "/data/font/smb2_big.png", t_font); asset->add(prefix + "/data/font/smb2_big.txt", t_font); asset->add(prefix + "/data/font/smb2.png", t_font); asset->add(prefix + "/data/font/smb2.txt", t_font); // Textos asset->add(prefix + "/data/lang/es_ES.txt", t_lang); asset->add(prefix + "/data/lang/en_UK.txt", t_lang); asset->add(prefix + "/data/lang/ba_BA.txt", t_lang); // Menus asset->add(prefix + "/data/menu/title.men", t_data); asset->add(prefix + "/data/menu/title_gc.men", t_data); asset->add(prefix + "/data/menu/options.men", t_data); asset->add(prefix + "/data/menu/options_gc.men", t_data); asset->add(prefix + "/data/menu/pause.men", t_data); asset->add(prefix + "/data/menu/gameover.men", t_data); asset->add(prefix + "/data/menu/player_select.men", t_data); return asset->check(); } // Inicializa las opciones del programa void Director::initOptions() { // Crea el puntero a la estructura de opciones options = new options_t; // Pone unos valores por defecto para las opciones de control options->input.clear(); input_t inp; inp.id = 0; inp.name = "KEYBOARD"; inp.deviceType = INPUT_USE_KEYBOARD; options->input.push_back(inp); inp.id = 0; inp.name = "GAME CONTROLLER"; inp.deviceType = INPUT_USE_GAMECONTROLLER; options->input.push_back(inp); // Opciones de video options->gameWidth = GAMECANVAS_WIDTH; options->gameHeight = GAMECANVAS_HEIGHT; options->videoMode = 0; options->windowSize = 3; options->filter = FILTER_NEAREST; options->vSync = true; options->integerScale = true; options->keepAspect = true; options->borderWidth = 0; options->borderHeight = 0; options->borderEnabled = false; // Opciones varios options->playerSelected = 0; options->difficulty = DIFFICULTY_NORMAL; options->language = ba_BA; options->console = false; } // Comprueba los parametros del programa void Director::checkProgramArguments(int argc, const char *argv[]) { // Establece la ruta del programa executablePath = argv[0]; // Comprueba el resto de parametros for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "--console") == 0) { options->console = true; } } } // Crea la carpeta del sistema donde guardar datos void Director::createSystemFolder(const std::string &folder) { #ifdef _WIN32 systemFolder = std::string(getenv("APPDATA")) + "/" + folder; #elif __APPLE__ struct passwd *pw = getpwuid(getuid()); const char *homedir = pw->pw_dir; systemFolder = std::string(homedir) + "/Library/Application Support" + "/" + folder; #elif __linux__ struct passwd *pw = getpwuid(getuid()); const char *homedir = pw->pw_dir; systemFolder = std::string(homedir) + "/.config/" + folder; { // Intenta crear ".config", per si no existeix std::string config_base_folder = std::string(homedir) + "/.config"; int ret = mkdir(config_base_folder.c_str(), S_IRWXU); if (ret == -1 && errno != EEXIST) { printf("ERROR CREATING CONFIG BASE FOLDER."); exit(EXIT_FAILURE); } } #endif struct stat st = {0}; if (stat(systemFolder.c_str(), &st) == -1) { errno = 0; #ifdef _WIN32 int ret = mkdir(systemFolder.c_str()); #else int ret = mkdir(systemFolder.c_str(), S_IRWXU); #endif if (ret == -1) { switch (errno) { case EACCES: printf("the parent directory does not allow write"); exit(EXIT_FAILURE); case EEXIST: printf("pathname already exists"); exit(EXIT_FAILURE); case ENAMETOOLONG: printf("pathname is too long"); exit(EXIT_FAILURE); default: perror("mkdir"); exit(EXIT_FAILURE); } } } } // Carga el fichero de configuración bool Director::loadConfigFile() { // Indicador de éxito en la carga bool success = true; // Variables para manejar el fichero const std::string filePath = "config.txt"; std::string line; std::ifstream file(asset->get(filePath)); // Si el fichero se puede abrir if (file.good()) { // Procesa el fichero linea a linea if (options->console) { std::cout << "Reading file " << filePath << std::endl; } while (std::getline(file, line)) { // Comprueba que la linea no sea un comentario if (line.substr(0, 1) != "#") { // Encuentra la posición del caracter '=' int pos = line.find("="); // Procesa las dos subcadenas if (!setOptions(options, line.substr(0, pos), line.substr(pos + 1, line.length()))) { if (options->console) { std::cout << "Warning: file " << filePath << std::endl; std::cout << "Unknown parameter " << line.substr(0, pos).c_str() << std::endl; } success = false; } } } // Cierra el fichero if (options->console) { std::cout << "Closing file " << filePath << std::endl; } file.close(); } // El fichero no existe else { // Crea el fichero con los valores por defecto saveConfigFile(); } // Normaliza los valores const bool a = options->videoMode == 0; const bool b = options->videoMode == SDL_WINDOW_FULLSCREEN; const bool c = options->videoMode == SDL_WINDOW_FULLSCREEN_DESKTOP; if (!(a || b || c)) { options->videoMode = 0; } if (options->windowSize < 1 || options->windowSize > 4) { options->windowSize = 3; } if (options->language < 0 || options->language > MAX_LANGUAGES) { options->language = en_UK; } return success; } // Guarda el fichero de configuración bool Director::saveConfigFile() { bool success = true; // Crea y abre el fichero de texto std::ofstream file(asset->get("config.txt")); if (file.good()) { if (options->console) { std::cout << asset->get("config.txt") << " open for writing" << std::endl; } } else { if (options->console) { std::cout << asset->get("config.txt") << " can't be opened" << std::endl; } } // Opciones g´raficas file << "## VISUAL OPTIONS\n"; if (options->videoMode == 0) { file << "videoMode=0\n"; } else if (options->videoMode == SDL_WINDOW_FULLSCREEN) { file << "videoMode=SDL_WINDOW_FULLSCREEN\n"; } else if (options->videoMode == SDL_WINDOW_FULLSCREEN_DESKTOP) { file << "videoMode=SDL_WINDOW_FULLSCREEN_DESKTOP\n"; } file << "windowSize=" + std::to_string(options->windowSize) + "\n"; if (options->filter == FILTER_NEAREST) { file << "filter=FILTER_NEAREST\n"; } else { file << "filter=FILTER_LINEAL\n"; } file << "vSync=" + boolToString(options->vSync) + "\n"; file << "integerScale=" + boolToString(options->integerScale) + "\n"; file << "keepAspect=" + boolToString(options->keepAspect) + "\n"; file << "borderEnabled=" + boolToString(options->borderEnabled) + "\n"; file << "borderWidth=" + std::to_string(options->borderWidth) + "\n"; file << "borderHeight=" + std::to_string(options->borderHeight) + "\n"; // Otras opciones del programa file << "\n## OTHER OPTIONS\n"; file << "language=" + std::to_string(options->language) + "\n"; file << "difficulty=" + std::to_string(options->difficulty) + "\n"; file << "input0=" + std::to_string(options->input[0].deviceType) + "\n"; file << "input1=" + std::to_string(options->input[1].deviceType) + "\n"; // Cierra el fichero file.close(); return success; } void Director::runLogo() { auto logo = std::make_unique(renderer, screen, asset, input, section); logo->run(); } void Director::runIntro() { auto intro = std::make_unique(renderer, screen, asset, input, lang, section); intro->run(); } void Director::runTitle() { auto title = std::make_unique(renderer, screen, input, asset, options, lang, section); title->run(); } void Director::runGame() { const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2; auto game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, options, section); game->run(); } int Director::run() { // Bucle principal while (section->name != SECTION_PROG_QUIT) { switch (section->name) { case SECTION_PROG_LOGO: runLogo(); break; case SECTION_PROG_INTRO: runIntro(); break; case SECTION_PROG_TITLE: runTitle(); break; case SECTION_PROG_GAME: runGame(); break; } } return 0; } // Asigna variables a partir de dos cadenas bool Director::setOptions(options_t *options, std::string var, std::string value) { // Indicador de éxito en la asignación bool success = true; // Opciones de video if (var == "videoMode") { if (value == "SDL_WINDOW_FULLSCREEN_DESKTOP") { options->videoMode = SDL_WINDOW_FULLSCREEN_DESKTOP; } else if (value == "SDL_WINDOW_FULLSCREEN") { options->videoMode = SDL_WINDOW_FULLSCREEN; } else { options->videoMode = 0; } } else if (var == "windowSize") { options->windowSize = std::stoi(value); if ((options->windowSize < 1) || (options->windowSize > 4)) { options->windowSize = 3; } } else if (var == "filter") { if (value == "FILTER_LINEAL") { options->filter = FILTER_LINEAL; } else { options->filter = FILTER_NEAREST; } } else if (var == "vSync") { options->vSync = stringToBool(value); } else if (var == "integerScale") { options->integerScale = stringToBool(value); } else if (var == "keepAspect") { options->keepAspect = stringToBool(value); } else if (var == "borderEnabled") { options->borderEnabled = stringToBool(value); } else if (var == "borderWidth") { options->borderWidth = std::stoi(value); } else if (var == "borderHeight") { options->borderHeight = std::stoi(value); } // Opciones varias else if (var == "language") { options->language = std::stoi(value); } else if (var == "difficulty") { options->difficulty = std::stoi(value); } else if (var == "input0") { options->input[0].deviceType = std::stoi(value); } else if (var == "input1") { options->input[1].deviceType = std::stoi(value); } // Lineas vacias o que empiezan por comentario else if (var == "" || var.substr(0, 1) == "#") { } else { success = false; } return success; }