/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: Patrick Boenzli */ #define DEBUG_MODULE_GAME_RULES #include #include "multiplayer_team_deathmatch.h" #include "util/loading/load_param.h" #include "util/loading/factory.h" #include "render2D/image_plane.h" #include "state.h" #include "class_list.h" #include "player.h" #include "playable.h" #include "space_ships/space_ship.h" #include "shared_network_data.h" #include "terrain.h" #include "class_list.h" #include "space_ships/space_ship.h" #include "network_game_manager.h" #include "event_handler.h" #include "glgui.h" #include "story_entity.h" #include "shell_command.h" #include "spawning_point.h" using namespace std; CREATE_FACTORY(MultiplayerTeamDeathmatch, CL_MULTIPLAYER_TEAM_DEATHMATCH); /** * constructor */ MultiplayerTeamDeathmatch::MultiplayerTeamDeathmatch(const TiXmlElement* root) : NetworkGameRules(root) { this->setClassID(CL_MULTIPLAYER_TEAM_DEATHMATCH, "MultiplayerTeamDeathmatch"); this->bLocalPlayerDead = false; this->deathTimeout = 10.0f; // 5 seconds this->timeout = 0.0f; this->numTeams = 2; this->currentGameState = GAMESTATE_PRE_GAME; this->gameStateTimer = 3.0f; this->bShowTeamChange = false; this->box = NULL; this->table = NULL; this->statsBox = NULL; this->localPlayer = State::getPlayer(); if( root != NULL) this->loadParams(root); subscribeEvent( ES_GAME, SDLK_o ); subscribeEvent( ES_GAME, SDLK_TAB ); subscribeEvent( ES_GAME, SDLK_F1 ); subscribeEvent( ES_MENU, KeyMapper::PEV_FIRE1 ); this->input = new OrxGui::GLGuiInputLine(); this->input->setAbsCoor2D(180, 5); this->input->connect(SIGNAL(input, enterPushed), this, SLOT(MultiplayerTeamDeathmatch, onInputEnter)); } /** * decontsructor */ MultiplayerTeamDeathmatch::~MultiplayerTeamDeathmatch() { unsubscribeEvent( ES_GAME, SDLK_o ); unsubscribeEvent( ES_GAME, SDLK_TAB ); unsubscribeEvent( ES_GAME, SDLK_F1 ); unsubscribeEvent( ES_MENU, KeyMapper::PEV_FIRE1 ); if ( this->input ) { delete this->input; this->input = NULL; } } void MultiplayerTeamDeathmatch::loadParams(const TiXmlElement* root) { GameRules::loadParams(root) ; LoadParam(root, "death-penalty-timeout", this, MultiplayerTeamDeathmatch, setDeathPenaltyTimeout) .describe("sets the time in seconds a player has to wait for respawn"); LoadParam(root, "max-kills", this, MultiplayerTeamDeathmatch, setMaxKills) .describe("sets the maximal kills for winning condition"); LoadParam(root, "num-teams", this, MultiplayerTeamDeathmatch, setNumTeams) .describe("sets number of teams"); } /** * time tick * @param dt time */ void MultiplayerTeamDeathmatch::tick(float dt) { tickStatsTable(); //on client side hostId is -1 until hanshake finished if ( SharedNetworkData::getInstance()->getHostID() < 0 ) return; if ( currentGameState == GAMESTATE_PRE_GAME || currentGameState == GAMESTATE_GAME ) { if ( PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() ) && box == NULL && (PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() )->getPreferedTeamId() == TEAM_NOTEAM || bShowTeamChange ) ) { EventHandler::getInstance()->pushState( ES_MENU ); OrxGui::GLGuiHandler::getInstance()->activateCursor(); box = new OrxGui::GLGuiBox(); box->setAbsCoor2D( 300, 100 ); OrxGui::GLGuiPushButton * buttonSpectator = new OrxGui::GLGuiPushButton("Spectator"); box->pack( buttonSpectator ); buttonSpectator->connect(SIGNAL(buttonSpectator, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonSpectator)); OrxGui::GLGuiPushButton * buttonRandom = new OrxGui::GLGuiPushButton("Random"); box->pack( buttonRandom ); buttonRandom->connect(SIGNAL(buttonRandom, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonRandom)); OrxGui::GLGuiPushButton * buttonTeam0 = new OrxGui::GLGuiPushButton("Blue Team"); box->pack( buttonTeam0 ); buttonTeam0->connect(SIGNAL(buttonTeam0, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonTeam0)); OrxGui::GLGuiPushButton * buttonTeam1 = new OrxGui::GLGuiPushButton("Red Team"); box->pack( buttonTeam1 ); buttonTeam1->connect(SIGNAL(buttonTeam1, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonTeam1)); if ( bShowTeamChange ) { OrxGui::GLGuiPushButton * buttonCancel = new OrxGui::GLGuiPushButton("Cancel"); box->pack( buttonCancel ); buttonCancel->connect(SIGNAL(buttonCancel, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonCancel)); } OrxGui::GLGuiPushButton * buttonExit = new OrxGui::GLGuiPushButton("Exit"); box->pack( buttonExit ); buttonExit->connect(SIGNAL(buttonExit, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonExit)); box->showAll(); } } if ( box != NULL && PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() ) && PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() )->getPreferedTeamId() != TEAM_NOTEAM && !bShowTeamChange ) { delete box; box = NULL; OrxGui::GLGuiHandler::getInstance()->deactivateCursor( true ); EventHandler::getInstance()->popState(); } if ( box != NULL ) { OrxGui::GLGuiHandler::getInstance()->tick( dt ); } assignPlayable(); if ( !SharedNetworkData::getInstance()->isGameServer() ) return; //handle kills while ( this->killList.begin() != this->killList.end() ) { onKill( this->killList.begin()->getVictim(), this->killList.begin()->getKiller() ); this->killList.erase( this->killList.begin() ); } gameStateTimer -= dt; //PRINTF(0)("TICK %f\n", gameStateTimer); if ( currentGameState != GAMESTATE_GAME && gameStateTimer < 0 ) nextGameState(); this->currentGameState = NetworkGameManager::getInstance()->getGameState(); if ( currentGameState == GAMESTATE_GAME ) { handleTeamChanges(); } this->calculateTeamScore(); this->checkGameRules(); // is the local player dead and inactive if( unlikely(this->bLocalPlayerDead)) { this->timeout += dt; PRINTF(0)("TICK DEATH: %f of %f\n", this->timeout, this->deathTimeout); // long enough dead? if( this->timeout >= this->deathTimeout) { this->timeout = 0.0f; // respawn PRINTF(0)("RESPAWN\n"); (State::getPlayer())->getPlayable()->respawn(); } } } /** * draws the stuff */ void MultiplayerTeamDeathmatch::draw() { if( unlikely( this->bLocalPlayerDead)) { } } /** * check the game rules for consistency */ void MultiplayerTeamDeathmatch::checkGameRules() { if ( !SharedNetworkData::getInstance()->isGameServer() ) return; // check for max killing count for ( int i = 0; i= maxKills ) { nextGameState(); } } } /** * find group for new player * @return group id */ int MultiplayerTeamDeathmatch::getTeamForNewUser() { return TEAM_NOTEAM; } ClassID MultiplayerTeamDeathmatch::getPlayableClassId( int userId, int team ) { if ( team == TEAM_NOTEAM || team == TEAM_SPECTATOR ) return CL_SPECTATOR; if ( team == 0 || team == 1 ) return CL_FPS_PLAYER; assert( false ); } std::string MultiplayerTeamDeathmatch::getPlayableModelFileName( int userId, int team, ClassID classId ) { if ( team == 0 ) return "models/creatures/doom_guy.md2"; else if ( team == 1 ) return "models/creatures/doom_guy.md2"; else return ""; } /** * calculate team score */ void MultiplayerTeamDeathmatch::calculateTeamScore( ) { teamScore.clear(); for ( int i = 0; i * list = ClassList::getList( CL_PLAYER_STATS ); if ( !list ) return; for ( std::list::const_iterator it = list->begin(); it != list->end(); it++ ) { PlayerStats & stats = *dynamic_cast(*it); if ( stats.getTeamId() >= 0 ) { teamScore[stats.getTeamId()] += stats.getScore(); } } } /** * get team for player who choose to join random team * @return smallest team */ int MultiplayerTeamDeathmatch::getRandomTeam( ) { std::map playersInTeam; for ( int i = 0; i * list = ClassList::getList( CL_PLAYER_STATS ); if ( !list ) return 0; for ( std::list::const_iterator it = list->begin(); it != list->end(); it++ ) { PlayerStats & stats = *dynamic_cast(*it); if ( stats.getTeamId() >= 0 ) { playersInTeam[stats.getTeamId()]++; } } int minPlayers = 0xFFFF; int minTeam = -1; for ( int i = 0; isetGameState( GAMESTATE_GAME ); return; } if ( currentGameState == GAMESTATE_GAME ) { NetworkGameManager::getInstance()->setGameState( GAMESTATE_POST_GAME ); return; } if ( currentGameState == GAMESTATE_POST_GAME ) { State::getCurrentStoryEntity()->stop(); this->bShowTeamChange = false; return; } } void MultiplayerTeamDeathmatch::handleTeamChanges( ) { const std::list * list = ClassList::getList( CL_PLAYER_STATS ); if ( !list ) return; //first server players with choices for ( std::list::const_iterator it = list->begin(); it != list->end(); it++ ) { PlayerStats & stats = *dynamic_cast(*it); if ( stats.getTeamId() != stats.getPreferedTeamId() ) { if ( stats.getPreferedTeamId() == TEAM_SPECTATOR || ( stats.getPreferedTeamId() >= 0 && stats.getPreferedTeamId() < numTeams ) ) { teamChange( stats.getUserId() ); } } } //now serve player who want join a random team for ( std::list::const_iterator it = list->begin(); it != list->end(); it++ ) { PlayerStats & stats = *dynamic_cast(*it); if ( stats.getTeamId() != stats.getPreferedTeamId() ) { if ( stats.getPreferedTeamId() == TEAM_RANDOM ) { stats.setPreferedTeamId( getRandomTeam() ); teamChange( stats.getUserId() ); } } } } void MultiplayerTeamDeathmatch::teamChange( int userId ) { assert( PlayerStats::getStats( userId ) ); PlayerStats & stats = *(PlayerStats::getStats( userId )); stats.setTeamId( stats.getPreferedTeamId() ); Playable * oldPlayable = stats.getPlayable(); ClassID playableClassId = getPlayableClassId( userId, stats.getPreferedTeamId() ); std::string playableModel = getPlayableModelFileName( userId, stats.getPreferedTeamId(), playableClassId ); std::string playableTexture = getPlayableModelTextureFileName( userId, stats.getPreferedTeamId(), playableClassId ); BaseObject * bo = Factory::fabricate( playableClassId ); assert( bo != NULL ); assert( bo->isA( CL_PLAYABLE ) ); Playable & playable = *(dynamic_cast(bo)); if ( playableTexture != "" ) playable.loadMD2Texture( playableTexture ); if ( playableModel != "" ) playable.loadModel( playableModel ); playable.setOwner( userId ); playable.setUniqueID( SharedNetworkData::getInstance()->getNewUniqueID() ); playable.setSynchronized( true ); stats.setTeamId( stats.getPreferedTeamId() ); stats.setPlayableClassId( playableClassId ); stats.setPlayableUniqueId( playable.getUniqueID() ); stats.setModelFileName( playableModel ); this->respawnPlayable( &playable, stats.getPreferedTeamId(), 0.0f ); if ( oldPlayable ) { //if ( userId == SharedNetworkData::getInstance()->getHostID() ) // State::getPlayer()->setPlayable( NULL ); delete oldPlayable; } } void MultiplayerTeamDeathmatch::onButtonExit( ) { State::getCurrentStoryEntity()->stop(); this->bShowTeamChange = false; } void MultiplayerTeamDeathmatch::onButtonRandom( ) { NetworkGameManager::getInstance()->prefereTeam( TEAM_RANDOM ); this->bShowTeamChange = false; } void MultiplayerTeamDeathmatch::onButtonTeam0( ) { NetworkGameManager::getInstance()->prefereTeam( 0 ); this->bShowTeamChange = false; } void MultiplayerTeamDeathmatch::onButtonTeam1( ) { NetworkGameManager::getInstance()->prefereTeam( 1 ); this->bShowTeamChange = false; } void MultiplayerTeamDeathmatch::onButtonSpectator( ) { NetworkGameManager::getInstance()->prefereTeam( TEAM_SPECTATOR ); this->bShowTeamChange = false; } void MultiplayerTeamDeathmatch::assignPlayable( ) { if ( PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() ) ) PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() )->getPlayable(); } /** * function that processes events from the handler * @param event: the event * @todo replace SDLK_o with something from KeyMapper */ void MultiplayerTeamDeathmatch::process( const Event & event ) { if ( event.type == SDLK_o ) { if ( event.bPressed ) this->bShowTeamChange = true; } else if ( event.type == SDLK_F1 ) { if ( this->statsBox && !this->bLocalPlayerDead && event.bPressed ) { PRINTF(0)("hide stats\n"); this->hideStats(); } else if ( !this->statsBox && event.bPressed ) { PRINTF(0)("show stats\n"); this->showStats(); } } else if ( event.type == SDLK_TAB ) { if ( currentGameState == GAMESTATE_GAME && event.bPressed && !EventHandler::getInstance()->isPressed( SDLK_RALT ) && !EventHandler::getInstance()->isPressed( SDLK_LALT ) ) { EventHandler::getInstance()->pushState( ES_MENU ); OrxGui::GLGuiHandler::getInstance()->activateCursor(); OrxGui::GLGuiHandler::getInstance()->deactivateCursor(); input->show(); input->giveMouseFocus(); input->setText("say "); } } else if ( this->bLocalPlayerDead && statsBox && event.type == KeyMapper::PEV_FIRE1 ) { this->hideStats(); } } void MultiplayerTeamDeathmatch::onButtonCancel( ) { this->bShowTeamChange = false; } /** * this method is called by NetworkGameManger when he recieved a chat message * @param userId senders user id * @param message message string * @param messageType some int */ void MultiplayerTeamDeathmatch::handleChatMessage( int userId, const std::string & message, int messageType ) { std::string name = "unknown"; if ( PlayerStats::getStats( userId ) ) { name = PlayerStats::getStats( userId )->getNickName(); } PRINTF(0)("CHATMESSAGE %s (%d): %s\n", name.c_str(), userId, message.c_str() ); State::getPlayer()->hud().notifyUser(name + ": " + message); } void MultiplayerTeamDeathmatch::onInputEnter( const std::string & text ) { EventHandler::getInstance()->popState(); input->breakMouseFocus(); input->hide(); input->setText(""); std::string command = text; //HACK insert " in say commands so user doesn't have to type them if ( command.length() >= 4 && command[0] == 's' && command[1] == 'a' && command[2] == 'y' && command[3] == ' ' ) { command.insert( 4, "\"" ); command = command + "\""; } OrxShell::ShellCommand::execute( command ); } /** * show table with frags */ void MultiplayerTeamDeathmatch::showStats( ) { statsBox = new OrxGui::GLGuiBox(); statsBox->setAbsCoor2D( 100, 100 ); this->table = new OrxGui::GLGuiTable(10,5); statsBox->pack( this->table ); statsBox->showAll(); } /** * hide table with frags */ void MultiplayerTeamDeathmatch::hideStats( ) { if ( statsBox ) { delete statsBox; statsBox = NULL; } } /** * fill stats table with values */ void MultiplayerTeamDeathmatch::tickStatsTable( ) { if ( !this->statsBox ) return; std::vector headers; headers.push_back("Blue Team"); headers.push_back(""); headers.push_back(""); headers.push_back("Red Team"); headers.push_back(""); this->table->setHeader(headers); ScoreList scoreList = PlayerStats::getScoreList(); char st[10]; int i = 0; i = 2; for ( TeamScoreList::const_iterator it = scoreList[0].begin(); it != scoreList[0].end(); it++ ) { this->table->setEntry( i, 0, it->name ); snprintf( st, 10, "%d", it->score ); this->table->setEntry( i, 1, st ); this->table->setEntry( i, 2, "" ); i++; } i = 2; for ( TeamScoreList::const_iterator it = scoreList[1].begin(); it != scoreList[1].end(); it++ ) { this->table->setEntry( i, 3, it->name ); snprintf( st, 10, "%d", it->score ); this->table->setEntry( i, 4, st ); i++; } } /** * this function is called when a player kills another one or himself * @param killedUserId * @param userId */ void MultiplayerTeamDeathmatch::onKill( WorldEntity * victim, WorldEntity * killer ) { if ( !victim ) return; if ( !killer ) return; int killerUserId = killer->getOwner(); int victimUserId = victim->getOwner(); PlayerStats & victimStats = *PlayerStats::getStats( victimUserId ); PlayerStats & killerStats = *PlayerStats::getStats( killerUserId ); if ( killerStats.getPlayable() != killer || victimStats.getPlayable() != victim ) return; //check for suicide if ( killerUserId != victimUserId ) { //check for teamkill if ( victimStats.getTeamId() != killerStats.getTeamId() ) { killerStats.setScore( killerStats.getScore() + 1 ); } else { killerStats.setScore( killerStats.getScore() - 1 ); } } else killerStats.setScore( killerStats.getScore() - 1 ); if ( victimUserId == SharedNetworkData::getInstance()->getHostID() ) { this->bLocalPlayerDead = true; this->showStats(); } this->respawnPlayable( victimStats.getPlayable(), victimStats.getTeamId(), 3.0f ); } /** * this function is called on player respawn * @param userId */ void MultiplayerTeamDeathmatch::onRespawn( int userId ) { if ( userId == SharedNetworkData::getInstance()->getHostID() ) { this->bLocalPlayerDead = false; this->hideStats(); } } /** * this function is called on player respawn * @param we */ void MultiplayerTeamDeathmatch::registerSpawn( WorldEntity * we ) { onRespawn( we->getOwner() ); } void MultiplayerTeamDeathmatch::respawnPlayable( Playable * playable, int teamId, float delay ) { const std::list * list = ClassList::getList( CL_SPAWNING_POINT ); assert( list ); std::vector spList; for ( std::list::const_iterator it = list->begin(); it != list->end(); it++ ) { SpawningPoint * sp = dynamic_cast(*it); if ( sp->getTeamId() == teamId ) spList.push_back( sp ); } if ( spList.size() == 0 ) { for ( std::list::const_iterator it = list->begin(); it != list->end(); it++ ) { SpawningPoint * sp = dynamic_cast(*it); if ( sp->getTeamId() < 0 ) spList.push_back( sp ); } } assert( spList.size() != 0 ); int n = (int)((float)spList.size() * (float)rand()/(float)RAND_MAX); spList[n]->pushEntity( playable, delay ); } std::string MultiplayerTeamDeathmatch::getPlayableModelTextureFileName( int userId, int team, ClassID classId ) { if ( classId == CL_FPS_PLAYER ) { return "maps/doom_guy.png"; } return ""; }