#include #include #include #include #include #include "Model.h" #include "Physics.h" #include "PhysicsBase.h" #include "EntityFactory.h" #include "AsteroidsEvents.h" #include "AsteroidEntity.h" #include namespace asteroids { static Model* ModelInstance = NULL; Engine::Variable Model::HighscoreServerName ("highscore_server_name", "asteroids.fysx.org"); Engine::Variable Model::HighscoreServerPath ("highscore_server_path", "/highscore/highscore.php?format=raw"); Engine::Variable Model::UseServerHighscore ("use_server_highscore", "false"); /* * Inherited Module functions */ int Model::OnInit (int argc, char* argv[]) { int result = Engine::ModelBase::OnInit (argc, argv); Engine::LogMessage ("Model Initialization!"); ModelInstance = this; mGameState = GameStatePaused; mLastGameState = GameStatePaused; /// \TODO use or similar for initialization of mCurrentLevelIndex mCurrentLevelIndex = 99999; if (InitLevelList() == 0) Engine::LogError ("No levels found!"); // Reset the newest highscore entry index which may be used for highlighting // the newest entry. mNewestHighscoreEntryIndex = std::numeric_limits::max(); mHighscoreIsOnline = false; // initialize event handlers and register them Engine::RegisterListener (this, EventGameOver); Engine::RegisterListener (this, EventShipExplode); mPlayerName = "Player"; mLevelAuthor = ""; mLevelTitle = ""; mLevelName = ""; mLevelParTimeSeconds = -1.f; // initialize SDL_net to be able to retrieve highscore from the internet if (SDLNet_Init() == -1) { Engine::LogError("SDLNet_Init: %s\n", SDLNet_GetError()); } Engine::RegisterListener (this, EventAccelerateStart); Engine::RegisterListener (this, EventAccelerateStop); Engine::RegisterListener (this, EventLevelComplete); return result; } void Model::OnDestroy() { Engine::ModelBase::OnDestroy(); mAsteroids.clear(); mLevelList.clear(); SaveLocalHighscoreList(); SDLNet_Quit(); } bool Model::OnReceiveEvent (const Engine::EventBasePtr &event) { switch (event->mEventType) { case EventAccelerateStart: Engine::PlaySoundLoop(Engine::GetResourceFullPath("/data/sounds/thrust.wav"), -1); break; case EventAccelerateStop: Engine::HaltSoundLoop(Engine::GetResourceFullPath("/data/sounds/thrust.wav")); break; case EventShipExplode: OnShipExplode(); break; case EventLevelComplete: OnLevelComplete (); break; case EventGameOver: return OnGameOver(); break; default: Engine::LogWarning ("Received Event with type %d but don't know what to do with it!", event->mEventType); break; } return false; } /* * Module specific functions */ void Model::Process () { if (mLastGameState == mGameState) { if (mGameState == GameStateRunning) { // Only if we are still controlling the ship we increase the level // timer if (Engine::GetPlayerEntityId() != Engine::NullEntityId) mLevelTimeSeconds += Engine::GetFrameDuration(); Engine::ModelBase::Process(); } return; } // when we are here we know that something has changed so we need to take // some action. Engine::LogDebug ("Switching from %s->%s", GetStringGameState(mLastGameState), GetStringGameState(mGameState)); // ... and we have to set the last game state to the current gamestate // otherwise we end up in an infinite loop of performing the switching // action. mLastGameState = mGameState; } unsigned int Model::InitLevelList () { std::string level_dir_name = Engine::GetResourceFullPath("/data/levels/"); Engine::LogDebug ("Searching for levels in %s", level_dir_name.c_str()); mLevelList.clear(); boost::filesystem::path level_dir(level_dir_name); if (!boost::filesystem::exists(level_dir)) { Engine::LogError ("Could not init level list: \todo %s does not exist!"); } if (!boost::filesystem::is_directory(level_dir)) { Engine::LogError ("Could not init level list: \todo %s is not a directory!"); } boost::filesystem::directory_iterator end_iter; for (boost::filesystem::directory_iterator dir_iter(level_dir); dir_iter != end_iter; ++dir_iter) { if (boost::filesystem::is_regular_file (dir_iter->status())) { std::string level_relative_path (level_dir_name); level_relative_path += dir_iter->path().filename(); mLevelList.push_back (level_relative_path); Engine::LogDebug ("Found level %s", mLevelList[mLevelList.size()-1].c_str()); } } std::sort(mLevelList.begin(), mLevelList.end()); return mLevelList.size(); } void Model::ParseHighscoreStream (std::istream &highscore_stream) { std::string line; while (getline(highscore_stream, line)) { std::string name; unsigned int points; std::string::size_type delimiter = line.find ('\t'); if (delimiter == std::string::npos) break; name = line.substr(0, delimiter); std::istringstream points_stream(line.substr(delimiter + 1, line.size())); points_stream >> points; Engine::LogDebug ("Read Highscore Entry Name: %s Points: %d", name.c_str(), points); AddLocalHighscoreEntry (name, points); } } void Model::LoadHighscoreList () { mHighscoreList.clear(); if (mHighscoreList.size() < 10) { AddLocalHighscoreEntry ("Imperator", 1000000); AddLocalHighscoreEntry ("Darth Vader", 800000); AddLocalHighscoreEntry ("Luke Skywalker", 600000); AddLocalHighscoreEntry ("Han Solo", 400000); AddLocalHighscoreEntry ("Princess Leia", 200000); AddLocalHighscoreEntry ("C3PO", 100000); AddLocalHighscoreEntry ("R2-D2", 50000); AddLocalHighscoreEntry ("Chewy", 10000); AddLocalHighscoreEntry ("Mr. Ewok", 5000); AddLocalHighscoreEntry ("Jabba the Hutt", 1000); } mHighscoreIsOnline = false; if (Model::UseServerHighscore.GetBoolValue()) { Engine::LogDebug ("Retrieving Highscore from server"); std::stringstream global_highscore_stream; if (PullGlobalHighscore(global_highscore_stream)) { ParseHighscoreStream(global_highscore_stream); mHighscoreIsOnline = true; return; } Engine::LogMessage ("Could not load highscore from server, falling back to local."); } LoadLocalHighscoreList(); SaveLocalHighscoreList(); } void Model::LoadLocalHighscoreList () { std::string highscore_filename = Engine::GetUserDirFullPath("/highscore.dat").c_str(); boost::filesystem::path highscore_file(highscore_filename); // if the file does not exist, we create it and write standard values into // it. if (!boost::filesystem::exists(highscore_file)) { Engine::LogDebug ("Local highscore file not found!"); return; } if (!boost::filesystem::is_regular_file(highscore_file)) { Engine::LogError ("Could not load highscore file: %s is not a regular file!", highscore_file.filename().c_str()); } Engine::LogDebug ("Loading highscore file '%s'", highscore_filename.c_str()); std::ifstream score_stream (highscore_filename.c_str()); ParseHighscoreStream (score_stream); score_stream.close(); } void Model::SaveLocalHighscoreList () { Engine::LogDebug ("Saving local highscore file"); std::ofstream highscore_file (Engine::GetUserDirFullPath("/highscore.dat").c_str()); std::list::iterator iter = mHighscoreList.begin(); while (iter != mHighscoreList.end()) { highscore_file << iter->name << "\t" << iter->points << std::endl; iter++; } highscore_file.close(); } bool highscore_cmp (Model::HighscoreEntry a, Model::HighscoreEntry b) { return a.points > b.points; } /** \brief Addes an entry to the highscore list and takes care that the list stays valid * * \TODO Re-think usage of mNewestHighscoreEntryIndex variable in this function */ unsigned int Model::AddLocalHighscoreEntry(const std::string &name, const unsigned int points) { HighscoreEntry entry; entry.name = name; entry.points = points; Engine::LogDebug ("Adding entry %s points = %d", name.c_str(), points); unsigned int counter = 0; std::list::iterator iter = mHighscoreList.begin(); while (iter != mHighscoreList.end()) { if (name == iter->name && points == iter->points) break; if (points >= iter->points) { mHighscoreList.insert (iter, entry); mNewestHighscoreEntryIndex = counter; break; } iter++; counter ++; } if (mHighscoreList.size() < 10) { mHighscoreList.push_back(entry); mNewestHighscoreEntryIndex = mHighscoreList.size(); return mHighscoreList.size(); } while (mHighscoreList.size() > 10) { mHighscoreList.pop_back(); } if (counter < 10) return counter; mNewestHighscoreEntryIndex = 99999; return 99999; } bool Model::PullGlobalHighscore(std::stringstream &highscore_stream) { highscore_stream.str(""); IPaddress server_address; if (SDLNet_ResolveHost (&server_address, Model::HighscoreServerName.GetStringValue().c_str(), 80) == -1) { Engine::LogWarning ("Could not retrieve global highscore list: host name resolution for server %s failed!", Model::HighscoreServerName.GetStringValue().c_str()); return false; } char ip_address_char[4]; memcpy (&ip_address_char[0], &server_address.host, sizeof(char) * 4); int ip_address[4]; ip_address[0] = static_cast(ip_address_char[0]); ip_address[1] = static_cast(ip_address_char[1]); ip_address[2] = static_cast(ip_address_char[2]); ip_address[3] = static_cast(ip_address_char[3]); if (ip_address[0] < 0) ip_address[0] += 256; if (ip_address[1] < 0) ip_address[1] += 256; if (ip_address[2] < 0) ip_address[2] += 256; if (ip_address[3] < 0) ip_address[3] += 256; Engine::LogMessage ("Pulling global highscore from server %s (%d.%d.%d.%d)", Model::HighscoreServerName.GetStringValue().c_str(), ip_address[0], ip_address[1], ip_address[2], ip_address[3] ); TCPsocket server_socket = SDLNet_TCP_Open (&server_address); if (!server_socket) { Engine::LogError ("SDL_net tcp open: %s", SDLNet_GetError()); return false; } std::string http_query_string; http_query_string = std::string ("GET ") + Model::HighscoreServerPath.GetStringValue() + std::string(" HTTP/1.1\r\nHost: asteroids.fysx.org\r\nConnection: close\r\n\r\n"); int bytes_sent; bytes_sent = SDLNet_TCP_Send (server_socket, http_query_string.c_str(), http_query_string.size()); if (bytes_sent != http_query_string.size()) { Engine::LogError ("SDL_net tcp send: %s", SDLNet_GetError()); return false; } char receive_buffer[255]; std::string http_result; bool receiving = true; while (receiving) { // reset the buffer bzero (&receive_buffer[0], 255); // read the data int received_bytes = SDLNet_TCP_Recv (server_socket, receive_buffer, 255); if (received_bytes <= 0) { receiving = false; } http_result.append (receive_buffer, received_bytes); } // we have to strip the whitespaces twice to cut out the content http_result = strip_whitespaces (http_result); http_result = http_result.substr (http_result.find ("\n\r"), http_result.size()); http_result = strip_whitespaces (http_result); Engine::LogDebug ("Received Highscore: %s", http_result.c_str()); SDLNet_TCP_Close (server_socket); highscore_stream.str(http_result); return true; } bool Model::SubmitGlobalHigscoreEntry (const std::string &name, const unsigned int points) { IPaddress server_address; if (SDLNet_ResolveHost (&server_address, Model::HighscoreServerName.GetStringValue().c_str(), 80) == -1) { Engine::LogWarning ("Cannot submit highscore entry: host name resolution for server %s failed!", Model::HighscoreServerName.GetStringValue().c_str()); return false; } char ip_address_char[4]; memcpy (&ip_address_char[0], &server_address.host, sizeof(char) * 4); int ip_address[4]; ip_address[0] = static_cast(ip_address_char[0]); ip_address[1] = static_cast(ip_address_char[1]); ip_address[2] = static_cast(ip_address_char[2]); ip_address[3] = static_cast(ip_address_char[3]); if (ip_address[0] < 0) ip_address[0] += 256; if (ip_address[1] < 0) ip_address[1] += 256; if (ip_address[2] < 0) ip_address[2] += 256; if (ip_address[3] < 0) ip_address[3] += 256; Engine::LogDebug ("Submitting highscore player_name='%s' score_value='%d' to server %s (%d.%d.%d.%d)", name.c_str(), points, Model::HighscoreServerName.GetStringValue().c_str(), ip_address[0], ip_address[1], ip_address[2], ip_address[3] ); TCPsocket server_socket = SDLNet_TCP_Open (&server_address); if (!server_socket) { Engine::LogError ("SDL_net tcp open: %s", SDLNet_GetError()); return false; } std::stringstream points_stringstream; points_stringstream << points; std::stringstream hash_input; hash_input << name << ":" << points << ":" << sha256_hash ("asteroids rule"); std::string key = sha256_hash (hash_input.str()); std::string http_query_string; http_query_string = std::string ("GET ") + Model::HighscoreServerPath.GetStringValue() + std::string("&player_name=") + name + std::string("&score_value=") + points_stringstream.str() + std::string("&key=") + key + std::string(" HTTP/1.1\r\nHost: asteroids.fysx.org\r\nConnection: close\r\n\r\n"); int bytes_sent; bytes_sent = SDLNet_TCP_Send (server_socket, http_query_string.c_str(), http_query_string.size()); if (bytes_sent != http_query_string.size()) { Engine::LogError ("SDL_net tcp send: %s", SDLNet_GetError()); return false; } char receive_buffer[255]; std::string http_result; bool receiving = true; while (receiving) { // reset the buffer bzero (&receive_buffer[0], 255); // read the data int received_bytes = SDLNet_TCP_Recv (server_socket, receive_buffer, 255); if (received_bytes <= 0) { receiving = false; } http_result.append (receive_buffer, received_bytes); } // we have to strip the whitespaces twice to cut out the content http_result = strip_whitespaces (http_result); http_result = http_result.substr (http_result.find ("\n\r"), http_result.size()); http_result = strip_whitespaces (http_result); SDLNet_TCP_Close (server_socket); if (http_result == "OK") { Engine::LogDebug ("Submission successful: %s", http_result.c_str()); LoadHighscoreList(); return true; } else { Engine::LogMessage ("Submission unsuccessful: %s", http_result.c_str()); } return false; } void Model::SubmitHighscoreEntry (const std::string &name, const unsigned int points) { if (Model::UseServerHighscore.GetBoolValue()) { if (SubmitGlobalHigscoreEntry (name, points)) { return; } Engine::LogWarning ("Could not send highscore entry to server!"); } // if it did not succeed or we do not want to use the global highscore // fall back to the local highscore system. AddLocalHighscoreEntry (name, points); SaveLocalHighscoreList(); } int Model::DoLoadLevel (const char* filename) { Engine::LogMessage ("Loading level from %s", filename); std::fstream level_file (filename, std::ios::in); if (!level_file) { Engine::LogError ("Unable to open file %s for reading!", filename); exit (-1); } ClearEntities(); mAsteroids.clear(); std::string entity_type_str; int entity_count = 0; SetLevelTitle (""); SetLevelAuthor (""); mLevelTimeSeconds = 0.f; mLevelParTimeSeconds = -1.f; mLevelPoints = 0; mLevelTimeBonusPoints = 0; while (level_file >> entity_type_str) { if (entity_type_str[0] == '#') { getline (level_file, entity_type_str); Engine::LogDebug ("Read Comment: %s", entity_type_str.c_str()); continue; } else if (entity_type_str == "Title") { std::string level_title; getline (level_file, level_title); level_title = strip_whitespaces (level_title); SetLevelTitle (level_title); continue; } else if (entity_type_str == "Author") { std::string level_author; getline (level_file, level_author); level_author = strip_whitespaces (level_author); SetLevelAuthor (level_author); continue; } else if (entity_type_str == "ParTime") { std::string partime_string; getline (level_file, partime_string); partime_string = strip_whitespaces (partime_string); std::stringstream partime_stream (partime_string); float partime = -1.; partime_stream >> partime; if (partime > 0.) mLevelParTimeSeconds = partime; continue; } GameEntityType entity_type = GameEntityTypeUnknown; if (entity_type_str == "GameEntityTypeShip") entity_type = GameEntityTypeShip; else if (entity_type_str == "GameEntityTypeAsteroid") entity_type = GameEntityTypeAsteroid; else { Engine::LogError ("Unknown Entity type: %s", entity_type_str.c_str()); exit (-1); } Engine::EntityBase* entity = CreateEntity (entity_type); bool is_player; level_file >> is_player; if (is_player) { mPlayerEntityId = entity->mId; Engine::LogDebug ("Entity with id '%d' is player", entity->mId); } level_file >> entity->mPhysicState->mPosition[0]; level_file >> entity->mPhysicState->mPosition[1]; level_file >> entity->mPhysicState->mPosition[2]; level_file >> entity->mPhysicState->mOrientation[0]; level_file >> entity->mPhysicState->mOrientation[1]; level_file >> entity->mPhysicState->mOrientation[2]; level_file >> entity->mPhysicState->mVelocity[0]; level_file >> entity->mPhysicState->mVelocity[1]; level_file >> entity->mPhysicState->mVelocity[2]; level_file >> entity->mPhysicState->mAngleVelocity; entity_count ++; } if (mLevelParTimeSeconds == -1.f) { Engine::LogWarning ("Level %s has no par time set. Setting to 120s.", filename); mLevelParTimeSeconds = 120.f; } level_file.close(); Engine::LogDebug ("%d Entities loaded!", mEntities.size()); return 0; } int Model::DoSaveLevel (const char* filename) { Engine::LogMessage ("Saving level to %s", filename); std::fstream level_file (filename, std::ios::out); if (!level_file) { Engine::LogError ("Unable to open file %s for writing!", filename); exit (-1); } level_file << "# Format" << std::endl; level_file << "# " << std::endl; if (GetLevelTitle() != "") { level_file << "Title " << mLevelTitle << std::endl; } if (GetLevelAuthor() != "") { level_file << "Author " << mLevelAuthor << std::endl; } std::map::iterator iter = mEntities.begin(); unsigned int player_id = GetPlayerEntityId(); for (iter = mEntities.begin(); iter != mEntities.end(); iter++) { Engine::EntityBase* game_entity = iter->second; level_file << GetStringGameEntityType((GameEntityType)game_entity->mType) << "\t" // this stores the player id << (game_entity->mId == player_id) << "\t" << game_entity->mPhysicState->mPosition[0] << "\t" << game_entity->mPhysicState->mPosition[1] << "\t" << game_entity->mPhysicState->mPosition[2] << "\t" << game_entity->mPhysicState->mOrientation[0] << "\t" << game_entity->mPhysicState->mOrientation[1] << "\t" << game_entity->mPhysicState->mOrientation[2] << "\t" << game_entity->mPhysicState->mVelocity[0] << "\t" << game_entity->mPhysicState->mVelocity[1] << "\t" << game_entity->mPhysicState->mVelocity[2] << "\t" << game_entity->mPhysicState->mAngleVelocity << "\t" << std::endl; } level_file.close(); return 0; } void Model::LoadLevelFromIndex (unsigned int level_index) { if (level_index < mLevelList.size()) { mCurrentLevelIndex = level_index; DoLoadLevel(mLevelList[mCurrentLevelIndex].c_str()); } } void Model::ReloadLevel () { Engine::LogDebug ("Reloading level %d", mCurrentLevelIndex + 1); if (mCurrentLevelIndex < 0 || mCurrentLevelIndex >= mLevelList.size()) Engine::LogError ("Invalid level index: %u", mCurrentLevelIndex); DoLoadLevel(mLevelList[mCurrentLevelIndex].c_str()); } void Model::ProceedToNextLevel () { Engine::LogDebug ("Proceeding to next level %d", mCurrentLevelIndex + 1); mCurrentLevelIndex++; if (mCurrentLevelIndex == mLevelList.size()) { Engine::EventBasePtr gameover_event (new Engine::EventBase()); gameover_event->mEventType = EventGameOver; QueueEvent (gameover_event); } else { DoLoadLevel(mLevelList[mCurrentLevelIndex].c_str()); } } void Model::SetGameState (const unsigned int &state) { mLastGameState = mGameState; mGameState = state; } bool Model::OnGameOver() { Engine::LogDebug ("Points = %d lowest = %d", mPoints, mHighscoreList.back().points ); SubmitHighscoreEntry (mPlayerName, mPoints); if (mPoints > mHighscoreList.back().points) { Engine::LogMessage ("New Highscore!"); } return false; }; void Model::OnNewGame() { ClearEntities(); mNewestHighscoreEntryIndex = std::numeric_limits::max(); mPlayerLives = 3; mCurrentLevelIndex = 0; mPoints = 0; mLevelPoints = 0; DoLoadLevel (mLevelList[mCurrentLevelIndex].c_str()); } void Model::OnShipExplode () { Engine::PlaySound(Engine::GetResourceFullPath("/data/sounds/ship_destroyed.wav")); mPlayerLives --; if (mPlayerLives == 0) { Engine::EventBasePtr gameover_event (new Engine::EventBase()); gameover_event->mEventType = EventGameOver; QueueEvent (gameover_event); } else { Engine::EventBasePtr playerdied_event (new Engine::EventBase()); playerdied_event->mEventType = EventPlayerDied; QueueEvent (playerdied_event); } } void Model::OnLevelComplete() { // calculate the bonus points float level_time = roundf(mLevelTimeSeconds); float level_par_time = roundf(mLevelParTimeSeconds); int bonus_points = 0; if (level_time <= level_par_time) { // secret time bonus formula float a = 1.8, b = -4.2, c = 2; float t_n = mLevelTimeSeconds / mLevelParTimeSeconds; float bonus_points_f = roundf ( (a * t_n * t_n + b * t_n + c) * static_cast( mLevelPoints )); if (bonus_points_f < 0.) bonus_points_f = 0.; bonus_points_f = roundf (bonus_points_f / 10.) * 10. + 100.; mLevelTimeBonusPoints = static_cast (bonus_points_f); Engine::LogMessage("Bonus Points: t_n = %f bonus_points_f = %f bonus_points = %u", t_n, bonus_points_f, mLevelTimeBonusPoints); } else { mLevelTimeBonusPoints = 0; } mLevelPoints += mLevelTimeBonusPoints; mPoints += mLevelPoints; } void Model::OnCreateEntity (const int type, const unsigned int id) { GameEntityType entity_type = (GameEntityType) type; if (entity_type == GameEntityTypeAsteroid) { mAsteroids.push_back (id); } } void Model::OnKillEntity (const Engine::EntityBase *entity) { GameEntityType entity_type = (GameEntityType) entity->mType; if (entity_type == GameEntityTypeAsteroid) { Engine::PlaySound(Engine::GetResourceFullPath("/data/sounds/rock_destroyed.wav")); unsigned int i; const AsteroidEntity *asteroid = static_cast(entity); if (GetPlayerEntityId() != Engine::NullEntityId) { mLevelPoints += 150 + asteroid->mSubAsteroidsCount * 75; } for (i = 0; i < mAsteroids.size(); i++) { if (mAsteroids.at(i) == entity->mId) { std::vector::iterator entity_iter = mAsteroids.begin() + i; mAsteroids.erase (entity_iter); break; } } if (mAsteroids.size() == 0) { Engine::EventBasePtr level_complete_event (new Engine::EventBase()); level_complete_event->mEventType = EventLevelComplete; QueueEvent (level_complete_event); } } } float Model::GetWorldWidth () { return static_cast(mPhysics)->GetWorldWidth(); } float Model::GetWorldHeight () { return static_cast(mPhysics)->GetWorldHeight(); } }