#include #include #include #include #include "manager.hpp" #include "resource_manager.hpp" #include "utility.hpp" #include "objects/player.hpp" #include "objects/block.hpp" #include "objects/gravity_block.hpp" #include "objects/switch_block.hpp" #include "objects/finish_block.hpp" #include "objects/kill_block.hpp" #include "states/level.hpp" #ifdef _WIN32 #include #else #include #endif /** * Définition des variables et fonctions globales internes * (accessibles uniquement dans ce fichier) */ namespace { /** * Constante de gravité */ const float GRAVITY = 235; /** * Constante de déplacement des objets à déplacement manuel */ const float MOVE = 200; /** * Numéro actuel de version du format de fichier */ const unsigned int VERSION_NUMBER = 0; /** * Dictionnaire associant les types d'objets * à des instances qui seront utilisées pour la * construction d'autres objets de ces types */ std::map> object_type_map = { {Player::TYPE_ID, Player::load}, {Block::TYPE_ID, Block::load}, {GravityBlock::TYPE_ID, GravityBlock::load}, {FinishBlock::TYPE_ID, FinishBlock::load}, {KillBlock::TYPE_ID, KillBlock::load}, {SwitchBlock::TYPE_ID, SwitchBlock::load} }; } Level::Level(Manager& manager) : State(manager), gravity_direction(Utility::Direction::SOUTH), previously_muted_state(!ResourceManager::get().isMuted()) { // métadonnées par défaut setName(sf::String("Nouveau niveau")); setPath((ResourceManager::get().getLevelsPath() / "new_level.dat").string()); setTotalTime(30); // zone de jeu par défaut int size = 8; zone.push_back(sf::Vector2f(-size * Manager::GRID, -size * Manager::GRID)); zone.push_back(sf::Vector2f(size * Manager::GRID, -size * Manager::GRID)); zone.push_back(sf::Vector2f(size * Manager::GRID, size * Manager::GRID)); zone.push_back(sf::Vector2f(-size * Manager::GRID, size * Manager::GRID)); // ressources par défaut setMusic(""); setBackground(""); // ajout des boutons d'action de la barre d'action action_toolbar.addButton( "back", Utility::Direction::EAST, 0, std::bind(&Manager::popState, &getManager()) ); mute_button = action_toolbar.addButton( "music", Utility::Direction::EAST, 1, []() { // on inverse l'état muet ou non ResourceManager::get().setMuted( !ResourceManager::get().isMuted() ); } ); // ajout de la barre d'objets à l'écran getManager().addWidget(action_toolbar.getWindow()); } Level::~Level() { getManager().removeWidget(action_toolbar.getWindow()); } void Level::enable() { // positionnement par défaut de la caméra sf::Vector2u window_size = getManager().getWindow().getSize(); camera.setSize(window_size.x, window_size.y); camera_angle = 180.f; // on affiche la barre d'actions action_toolbar.getWindow()->Show(true); } void Level::load() { // ouverture du fichier std::ifstream file; file.open(path, std::ios::binary | std::ios::in); // on vérifie que le fichier ait correctement été ouvert en lecture if (file.fail()) { throw std::runtime_error( "Impossible de charger le niveau \"" + path + "\" " + "(" + std::string(strerror(errno)) + ")" ); } // lecture de la signature du fichier ("BAR") char read_signature[3]; file.read(read_signature, 3); if (strncmp(read_signature, "BAR", 3) != 0) { throw std::runtime_error( "Impossible de charger le niveau \"" + path + "\" " + "(en-tête invalide)" ); } // lecture de la version du fichier char read_file_version; file.read(&read_file_version, 1); if (read_file_version != VERSION_NUMBER) { throw std::runtime_error( "Impossible de charger le niveau \"" + path + "\" " + "(version non prise en charge)" ); } // lecture du nom du niveau std::string read_name; std::getline(file, read_name, '\0'); setName(read_name); // lecture du temps total du niveau int read_total_time; file.read(reinterpret_cast(&read_total_time), 4); setTotalTime(ntohl(read_total_time)); // lecture de la zone de jeu char read_control_points; file.read(&read_control_points, 1); zone.clear(); for (int i = 0; i < read_control_points; i++) { float read_pos_x, read_pos_y; file.read(reinterpret_cast(&read_pos_x), 4); file.read(reinterpret_cast(&read_pos_y), 4); read_pos_x *= Manager::GRID; read_pos_y *= Manager::GRID; zone.push_back(sf::Vector2f(read_pos_x, read_pos_y)); } // lecture des chemins de la musique et du fond std::string read_music; std::string read_background; std::getline(file, read_music, '\0'); std::getline(file, read_background, '\0'); setMusic(read_music); setBackground(read_background); // lecture des objets int read_object_count; file.read(reinterpret_cast(&read_object_count), 4); read_object_count = ntohl(read_object_count); objects.clear(); players.clear(); for (int i = 0; i < read_object_count; i++) { char read_object_type; file.read(&read_object_type, 1); // vérifie que le type est pris en charge // pour éviter une erreur de segmentation if (object_type_map.count(read_object_type) == 0) { throw std::runtime_error( "Impossible de charger le niveau \"" + path + "\" " + "(type d'objet " + std::to_string(read_object_type) + " inconnu)" ); } // lecture de l'objet addObject(object_type_map[read_object_type](file)); } } void Level::save() { std::ofstream file; file.open(path, std::ios::binary | std::ios::out); // on vérifie que le fichier ait correctement été ouvert en lecture if (file.fail()) { throw std::runtime_error( "Impossible d'enregistrer le niveau \"" + path + "\" " + "(" + std::string(strerror(errno)) + ")" ); } // écriture de la signture du fichier ("BAR") char write_signature[3] = {'B', 'A', 'R'}; file.write(write_signature, 3); // écriture de la version du fichier char write_file_version = VERSION_NUMBER; file.write(&write_file_version, 1); // écriture du nom du niveau char *write_name = (char*) name.toAnsiString().data(); file.write(write_name, name.getSize() + 1); // écriture du temps total du niveau int write_total_time = htonl(total_time); file.write(reinterpret_cast(&write_total_time), 4); // écriture de la zone de jeu char write_control_points = (char) zone.size(); file.write(&write_control_points, 1); for (int i = 0; i < write_control_points; i++) { float write_pos_x = zone[i].x / Manager::GRID; float write_pos_y = zone[i].y / Manager::GRID; file.write(reinterpret_cast(&write_pos_x), 4); file.write(reinterpret_cast(&write_pos_y), 4); } // écriture des noms de la musique et du fond char *write_music = (char*) music.data(); file.write(write_music, music.size() + 1); char *write_background = (char*) background.data(); file.write(write_background, background.size() + 1); // écriture des objets int write_object_count = htonl(objects.size()); file.write(reinterpret_cast(&write_object_count), 4); for (unsigned int i = 0; i < objects.size(); i++) { char write_object_type = objects[i]->getTypeId(); file.write(&write_object_type, 1); // écriture de l'objet objects[i]->save(file); } } std::string Level::getPath() { return path; } void Level::setPath(std::string set_path) { path = set_path; } void Level::processEvent(const sf::Event& event) { // lorsque la fenêtre est redimensionnée, mise à jour // de la taille de la caméra if (event.type == sf::Event::Resized) { camera.setSize(event.size.width, event.size.height); } } void Level::frame() { sf::RenderWindow& window = getManager().getWindow(); sf::Vector2i window_size = (sf::Vector2i) window.getSize(); // mise à jour de l'icône du mute en fonction de l'état if (previously_muted_state != ResourceManager::get().isMuted()) { if (ResourceManager::get().isMuted()) { previously_muted_state = true; mute_button->SetImage(sfg::Image::Create( *ResourceManager::get().getImage("no_music.tga") )); } else { previously_muted_state = false; mute_button->SetImage(sfg::Image::Create( *ResourceManager::get().getImage("music.tga") )); } } // positionnement de la barre d'actions action_toolbar.getWindow()->SetAllocation(sf::FloatRect( 0, 0, window_size.x, action_toolbar.getHeight() )); // animation du centre et de la rotation de la caméra sf::Vector2f cur_center = camera.getCenter(); cur_center.x = Utility::animateValue(cur_center.x, 5, goal_center.x); cur_center.y = Utility::animateValue(cur_center.y, 5, goal_center.y); camera_angle = Utility::animateValue( camera_angle, 5, std::fmod((float) gravity_direction * 90, 360) ); camera.setCenter(cur_center); camera.setRotation(camera_angle + 180); window.setView(camera); } void Level::draw() { sf::RenderWindow& window = getManager().getWindow(); sf::Vector2u window_size = window.getSize(); // efface la scène précédente window.clear(sf::Color(66, 165, 245)); // on englobe la caméra dans un rectangle circonscrit, dans // le cas où elle serait en rotation sf::Vector2f camera_corners[] = { pixelToCoords(sf::Vector2i(0, 0)), pixelToCoords(sf::Vector2i(window_size.x, 0)), pixelToCoords(sf::Vector2i(window_size.x, window_size.y)), pixelToCoords(sf::Vector2i(0, window_size.y)) }; sf::Vector2f camera_topleft = camera_corners[0]; sf::Vector2f camera_botright = camera_corners[0]; for (unsigned int i = 0; i < 4; i++) { camera_topleft.x = std::min(camera_topleft.x, camera_corners[i].x); camera_topleft.y = std::min(camera_topleft.y, camera_corners[i].y); camera_botright.x = std::max(camera_botright.x, camera_corners[i].x); camera_botright.y = std::max(camera_botright.y, camera_corners[i].y); } // on dessine le fond s'il y en a un if (background != "") { auto bg_texture = ResourceManager::get().getTexture("levels/" + background); sf::Vector2f bg_size = (sf::Vector2f) bg_texture->getSize(); background_sprite.setTexture(*bg_texture); // on regarde la position du coin où il faut commencer à // dessiner le fond, et la zone sur laquelle il faut le dessiner int x_left = std::floor(camera_topleft.x / bg_size.x); int y_top = std::floor(camera_topleft.y / bg_size.y); int x_right = std::ceil(camera_botright.x / bg_size.x); int y_bottom = std::ceil(camera_botright.y / bg_size.y); // et on dessine aux positions calculées for (int x = x_left; x < x_right; x++) { for (int y = y_top; y < y_bottom; y++) { background_sprite.setPosition(sf::Vector2f( x * (bg_size.x), y * (bg_size.y) )); window.draw(background_sprite); } } } // tri des objets par ordre d'affichage std::sort( objects.begin(), objects.end(), [](const Object::Ptr &a, const Object::Ptr &b) { sf::Vector2f a_pos = a->getPosition(); sf::Vector2f b_pos = b->getPosition(); return a_pos.x - a_pos.y + a->getLayer() < b_pos.x - b_pos.y + b->getLayer(); } ); // dessin des objets de la file d'affichage couche par couche for (auto it = objects.begin(); it != objects.end(); it++) { sf::Vector2i screen_pos = coordsToPixel((*it)->getPosition()); sf::FloatRect aabb = (*it)->getAABB(); // si l'objet est visible à l'écran, on le dessine if (screen_pos.x >= -aabb.width && screen_pos.y >= -aabb.height && screen_pos.x <= window_size.x + aabb.width && screen_pos.y <= window_size.y + aabb.height) { (*it)->draw(*this); } } } sf::String Level::getName() const { return name; } void Level::setName(sf::String set_name) { name = set_name; } int Level::getTotalTime() const { return total_time; } void Level::setTotalTime(int set_total_time) { // faisons rester le temps entre 10s et 59:59 set_total_time = std::min(set_total_time, 3599); set_total_time = std::max(set_total_time, 10); total_time = set_total_time; } std::string Level::getMusic() const { return music; } void Level::setMusic(std::string set_music) { music = set_music; } std::string Level::getBackground() const { return background; } void Level::setBackground(std::string set_background) { background = set_background; } sf::Vector2f Level::getGravity() const { switch (gravity_direction) { case Utility::Direction::NORTH: return sf::Vector2f(0, -GRAVITY); case Utility::Direction::EAST: return sf::Vector2f(GRAVITY, 0); case Utility::Direction::SOUTH: return sf::Vector2f(0, GRAVITY); case Utility::Direction::WEST: return sf::Vector2f(-GRAVITY, 0); } return sf::Vector2f(0, 0); } sf::Vector2f Level::getLeftDirection() const { switch (gravity_direction) { case Utility::Direction::NORTH: return sf::Vector2f(MOVE, 0); case Utility::Direction::EAST: return sf::Vector2f(0, MOVE); case Utility::Direction::SOUTH: return sf::Vector2f(-MOVE, 0); case Utility::Direction::WEST: return sf::Vector2f(0, -MOVE); } return sf::Vector2f(0, 0); } sf::Vector2f Level::getRightDirection() const { return -1.f * getLeftDirection(); } Utility::Direction Level::getGravityDirection() { return gravity_direction; } void Level::setGravityDirection(Utility::Direction set_gravity_direction) { gravity_direction = set_gravity_direction; } std::vector& Level::getObjects() { return objects; } const std::vector& Level::getObjects() const { return objects; } std::vector& Level::getPlayers() { return players; } const std::vector& Level::getPlayers() const { return players; } Object::Ptr Level::addObject(Object::Ptr object) { // si c'est un joueur, on le met dans le tableau des joueurs // et on lui attribue un numéro if (object->getTypeId() == Player::TYPE_ID) { // on n'autorise pas plus de deux joueurs if (players.size() >= 2) { return nullptr; } Player::Ptr player = std::dynamic_pointer_cast(object); player->setPlayerNumber(players.size()); players.push_back(std::dynamic_pointer_cast(object)); } objects.push_back(object); return object; } void Level::removeObject(Object::Ptr object) { // si c'est un joueur, on le supprime de la liste des joueurs if (object->getTypeId() == Player::TYPE_ID) { Player::Ptr player = std::dynamic_pointer_cast(object); players.erase(std::remove( players.begin(), players.end(), player ), players.end()); } // on supprime l'objet de la liste d'objets objects.erase(std::remove( objects.begin(), objects.end(), object ), objects.end()); } std::vector& Level::getZone() { return zone; } const std::vector& Level::getZone() const { return zone; } sf::View Level::getCamera() const { return camera; } void Level::setCamera(sf::View set_camera) { camera = set_camera; goal_center = set_camera.getCenter(); } sf::Vector2f Level::pixelToCoords(sf::Vector2i pixel) { sf::RenderWindow& window = getManager().getWindow(); sf::View old_view = window.getView(); window.setView(camera); sf::Vector2f coords = window.mapPixelToCoords(pixel); window.setView(old_view); return coords; } sf::Vector2i Level::coordsToPixel(sf::Vector2f coords) { sf::RenderWindow& window = getManager().getWindow(); sf::View old_view = window.getView(); window.setView(camera); sf::Vector2i pixel = window.mapCoordsToPixel(coords); window.setView(old_view); return pixel; } sf::Vector2f Level::getCenterGoal() { return goal_center; } void Level::setCenterGoal(sf::Vector2f set_center) { goal_center = set_center; } sf::Vector2f Level::getPlayerCenter() { sf::Vector2f sum; for (auto const &player : getPlayers()) { sum += player->getPosition(); } if (getPlayers().size() == 0) { return sf::Vector2f(0, 0); } return sum / (float) getPlayers().size(); }