Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/branches/network/src/util/multiplayer_team_deathmatch.cc @ 8782

Last change on this file since 8782 was 8782, checked in by rennerc, 18 years ago

MultiplayerTeamDeathmatch: added functions to handle kills/respawns

File size: 18.6 KB
Line 
1/*
2   orxonox - the future of 3D-vertical-scrollers
3
4   Copyright (C) 2004 orx
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11### File Specific:
12   main-programmer: Patrick Boenzli
13*/
14
15#define DEBUG_MODULE_GAME_RULES
16
17#include <map>
18
19#include "multiplayer_team_deathmatch.h"
20
21#include "util/loading/load_param.h"
22#include "util/loading/factory.h"
23
24#include "render2D/image_plane.h"
25#include "state.h"
26#include "class_list.h"
27
28#include "player.h"
29#include "playable.h"
30#include "space_ships/space_ship.h"
31
32
33#include "shared_network_data.h"
34#include "terrain.h"
35#include "class_list.h"
36#include "space_ships/space_ship.h"
37
38#include "network_game_manager.h"
39
40#include "event_handler.h"
41
42#include "glgui.h"
43
44#include "story_entity.h"
45
46#include "shell_command.h"
47
48
49using namespace std;
50
51
52CREATE_FACTORY(MultiplayerTeamDeathmatch, CL_MULTIPLAYER_TEAM_DEATHMATCH);
53
54
55/**
56 * constructor
57 */
58MultiplayerTeamDeathmatch::MultiplayerTeamDeathmatch(const TiXmlElement* root)
59  : NetworkGameRules(root)
60{
61  this->setClassID(CL_MULTIPLAYER_TEAM_DEATHMATCH, "MultiplayerTeamDeathmatch");
62
63  this->bLocalPlayerDead = false;
64  this->deathTimeout = 10.0f;     // 5 seconds
65  this->timeout = 0.0f;
66  this->numTeams = 2;
67  this->currentGameState = GAMESTATE_PRE_GAME;
68  this->gameStateTimer = 10.0f;
69  this->bShowTeamChange = false;
70 
71  this->box = NULL;
72  this->table = NULL;
73  this->statsBox = NULL;
74
75  this->localPlayer = State::getPlayer();
76
77  if( root != NULL)
78    this->loadParams(root);
79 
80  subscribeEvent( ES_GAME, SDLK_o );
81  subscribeEvent( ES_GAME, SDLK_TAB );
82  subscribeEvent( ES_GAME, SDLK_F1 );
83  subscribeEvent( ES_MENU, SDLK_F1 );
84  subscribeEvent( ES_MENU, KeyMapper::PEV_FIRE1 );
85 
86  this->notifier = new OrxGui::GLGuiNotifier();
87  this->notifier->show();
88  this->notifier->setAbsCoor2D(5, 30);
89  this->notifier->setFadeAge( 6.0 );
90  this->notifier->setHideAge( 8.0 );
91  this->input = new OrxGui::GLGuiInputLine();
92  this->input->setAbsCoor2D(180, 5);
93  this->input->connect(SIGNAL(input, enterPushed), this, SLOT(MultiplayerTeamDeathmatch, onInputEnter));
94}
95
96/**
97 * decontsructor
98 */
99MultiplayerTeamDeathmatch::~MultiplayerTeamDeathmatch()
100{
101  unsubscribeEvent( ES_GAME, SDLK_o );
102  unsubscribeEvent( ES_GAME, SDLK_TAB );
103  unsubscribeEvent( ES_GAME, SDLK_F1 );
104  unsubscribeEvent( ES_MENU, SDLK_F1 );
105  unsubscribeEvent( ES_MENU, KeyMapper::PEV_FIRE1 );
106 
107  if ( this->notifier )
108  {
109    delete this->notifier;
110    this->notifier = NULL;
111  }
112 
113  if ( this->input )
114  {
115    delete this->input;
116    this->input = NULL;
117  }
118}
119
120
121
122void MultiplayerTeamDeathmatch::loadParams(const TiXmlElement* root)
123{
124  GameRules::loadParams(root) ;
125
126  LoadParam(root, "death-penalty-timeout", this, MultiplayerTeamDeathmatch, setDeathPenaltyTimeout)
127      .describe("sets the time in seconds a player has to wait for respawn");
128
129  LoadParam(root, "max-kills", this, MultiplayerTeamDeathmatch, setMaxKills)
130      .describe("sets the maximal kills for winning condition");
131
132  LoadParam(root, "num-teams", this, MultiplayerTeamDeathmatch, setNumTeams)
133      .describe("sets number of teams");
134
135}
136
137
138/**
139 * called when the player enters the game
140 * @param player the spawned player
141 */
142void MultiplayerTeamDeathmatch::onPlayerSpawn()
143{
144  this->bLocalPlayerDead = false;
145}
146
147
148/**
149 * when the player is killed
150 * @param player the killed player
151 */
152void MultiplayerTeamDeathmatch::onPlayerDeath()
153{
154  this->bLocalPlayerDead = true;
155}
156
157
158/**
159 * time tick
160 * @param dt time
161 */
162void MultiplayerTeamDeathmatch::tick(float dt)
163{
164  tickStatsTable();
165  //on client side hostId is -1 until hanshake finished
166  if ( SharedNetworkData::getInstance()->getHostID() < 0 )
167    return;
168 
169  if ( currentGameState == GAMESTATE_PRE_GAME || currentGameState == GAMESTATE_GAME )
170  {
171    if ( PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() )
172         && box == NULL
173         &&  (PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() )->getPreferedTeamId() == TEAM_NOTEAM
174         || bShowTeamChange )
175         
176       )
177    {
178      EventHandler::getInstance()->pushState( ES_MENU );
179     
180      OrxGui::GLGuiHandler::getInstance()->activateCursor();
181     
182      box = new OrxGui::GLGuiBox();
183      box->setAbsCoor2D( 300, 100 );
184     
185      OrxGui::GLGuiPushButton * buttonSpectator = new OrxGui::GLGuiPushButton("Spectator");
186      box->pack( buttonSpectator );
187      buttonSpectator->connect(SIGNAL(buttonSpectator, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonSpectator));
188     
189      OrxGui::GLGuiPushButton * buttonRandom = new OrxGui::GLGuiPushButton("Random");
190      box->pack( buttonRandom );
191      buttonRandom->connect(SIGNAL(buttonRandom, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonRandom));
192     
193      OrxGui::GLGuiPushButton * buttonTeam0 = new OrxGui::GLGuiPushButton("Blue Team");
194      box->pack( buttonTeam0 );
195      buttonTeam0->connect(SIGNAL(buttonTeam0, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonTeam0));
196     
197      OrxGui::GLGuiPushButton * buttonTeam1 = new OrxGui::GLGuiPushButton("Red Team");
198      box->pack( buttonTeam1 );
199      buttonTeam1->connect(SIGNAL(buttonTeam1, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonTeam1));
200     
201      if ( bShowTeamChange )
202      {
203        OrxGui::GLGuiPushButton * buttonCancel = new OrxGui::GLGuiPushButton("Cancel");
204        box->pack( buttonCancel );
205        buttonCancel->connect(SIGNAL(buttonCancel, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonCancel));
206      }
207     
208      OrxGui::GLGuiPushButton * buttonExit = new OrxGui::GLGuiPushButton("Exit");
209      box->pack( buttonExit );
210      buttonExit->connect(SIGNAL(buttonExit, released), this, SLOT(MultiplayerTeamDeathmatch, onButtonExit));
211     
212      box->showAll();
213    }
214  }
215
216  if ( box != NULL
217       && PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() )
218       && PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() )->getPreferedTeamId() != TEAM_NOTEAM
219       && !bShowTeamChange
220     )
221  {
222    delete box;
223    box = NULL;
224     
225    OrxGui::GLGuiHandler::getInstance()->deactivateCursor( true );
226     
227    EventHandler::getInstance()->popState();
228  }
229 
230  if ( box != NULL )
231  {
232    OrxGui::GLGuiHandler::getInstance()->tick( dt );
233  }
234 
235  assignPlayable();
236 
237  if ( !SharedNetworkData::getInstance()->isGameServer() )
238    return;
239 
240  gameStateTimer -= dt;
241  //PRINTF(0)("TICK %f\n", gameStateTimer);
242 
243  if ( currentGameState != GAMESTATE_GAME && gameStateTimer < 0 )
244    nextGameState();
245 
246  this->currentGameState = NetworkGameManager::getInstance()->getGameState();
247 
248  if ( currentGameState == GAMESTATE_GAME )
249  {
250    handleTeamChanges();
251  }
252 
253  this->calculateTeamScore();
254 
255  this->checkGameRules();
256
257  // is the local player dead and inactive
258  if( unlikely(this->bLocalPlayerDead))
259  {
260    this->timeout += dt;
261    PRINTF(0)("TICK DEATH: %f of %f\n", this->timeout, this->deathTimeout);
262    // long enough dead?
263    if( this->timeout >= this->deathTimeout)
264    {
265      this->timeout = 0.0f;
266      // respawn
267      PRINTF(0)("RESPAWN\n");
268      (State::getPlayer())->getPlayable()->respawn();
269    }
270  }
271}
272
273
274/**
275 * draws the stuff
276 */
277void MultiplayerTeamDeathmatch::draw()
278{
279  if( unlikely( this->bLocalPlayerDead))
280  {
281
282  }
283}
284
285
286/**
287 * check the game rules for consistency
288 */
289void MultiplayerTeamDeathmatch::checkGameRules()
290{
291  if ( !SharedNetworkData::getInstance()->isGameServer() )
292    return;
293 
294  // check for max killing count
295  for ( int i = 0; i<numTeams; i++ )
296  {
297    if ( teamScore[i] >= maxKills )
298    {
299      //team i wins
300      //TODO
301    }
302  }
303}
304
305/**
306 * find group for new player
307 * @return group id
308 */
309int MultiplayerTeamDeathmatch::getTeamForNewUser()
310{
311  return TEAM_NOTEAM;
312}
313
314ClassID MultiplayerTeamDeathmatch::getPlayableClassId( int userId, int team )
315{
316  if ( team == TEAM_NOTEAM || team == TEAM_SPECTATOR )
317    return CL_SPECTATOR;
318 
319  if ( team == 0 || team == 1 )
320    return CL_SPACE_SHIP;
321 
322  assert( false );
323}
324
325std::string MultiplayerTeamDeathmatch::getPlayableModelFileName( int userId, int team, ClassID classId )
326{
327  if ( team == 0 )
328    return "models/ships/reap_#.obj";
329  else if ( team == 1 )
330    return "models/ships/fighter.obj";
331  else
332    return "";
333}
334
335/**
336 * calculate team score
337 */
338void MultiplayerTeamDeathmatch::calculateTeamScore( )
339{
340  teamScore.clear();
341 
342  for ( int i = 0; i<numTeams; i++ )
343    teamScore[i] = 0;
344 
345   
346  const std::list<BaseObject*> * list = ClassList::getList( CL_PLAYER_STATS );
347 
348  if ( !list )
349    return;
350 
351  for ( std::list<BaseObject*>::const_iterator it = list->begin(); it != list->end(); it++ )
352  {
353    PlayerStats & stats = *dynamic_cast<PlayerStats*>(*it);
354
355    if ( stats.getTeamId() >= 0 )
356    {
357      teamScore[stats.getTeamId()] += stats.getScore();
358    }
359  }
360}
361
362/**
363 * get team for player who choose to join random team
364 * @return smallest team
365 */
366int MultiplayerTeamDeathmatch::getRandomTeam( )
367{
368  std::map<int,int> playersInTeam;
369 
370  for ( int i = 0; i<numTeams; i++ )
371    playersInTeam[i] = 0;
372 
373  const std::list<BaseObject*> * list = ClassList::getList( CL_PLAYER_STATS );
374 
375  if ( !list )
376    return 0;
377 
378  for ( std::list<BaseObject*>::const_iterator it = list->begin(); it != list->end(); it++ )
379  {
380    PlayerStats & stats = *dynamic_cast<PlayerStats*>(*it);
381
382    if ( stats.getTeamId() >= 0 )
383    {
384      playersInTeam[stats.getTeamId()]++;
385    }
386  }
387 
388 
389  int minPlayers = 0xFFFF;
390  int minTeam = -1;
391 
392  for ( int i = 0; i<numTeams; i++ )
393  {
394    if ( playersInTeam[i] < minPlayers )
395    {
396      minTeam = i;
397      minPlayers = playersInTeam[i];
398    }
399  }
400 
401  assert( minTeam != -1 );
402 
403  return minTeam;
404}
405
406void MultiplayerTeamDeathmatch::nextGameState( )
407{
408  if ( currentGameState == GAMESTATE_PRE_GAME )
409  {
410    NetworkGameManager::getInstance()->setGameState( GAMESTATE_GAME );
411   
412    return;
413  }
414 
415  if ( currentGameState == GAMESTATE_GAME )
416  {
417    NetworkGameManager::getInstance()->setGameState( GAMESTATE_POST_GAME );
418   
419    return;
420  }
421 
422  if ( currentGameState == GAMESTATE_POST_GAME )
423  {
424    //TODO end game
425   
426    return;
427  }
428}
429
430void MultiplayerTeamDeathmatch::handleTeamChanges( )
431{
432  const std::list<BaseObject*> * list = ClassList::getList( CL_PLAYER_STATS );
433 
434  if ( !list )
435    return;
436 
437  //first server players with choices
438  for ( std::list<BaseObject*>::const_iterator it = list->begin(); it != list->end(); it++ )
439  {
440    PlayerStats & stats = *dynamic_cast<PlayerStats*>(*it);
441
442    if ( stats.getTeamId() != stats.getPreferedTeamId() )
443    {
444      if ( stats.getPreferedTeamId() == TEAM_SPECTATOR || ( stats.getPreferedTeamId() >= 0 && stats.getPreferedTeamId() < numTeams ) )
445      {
446        teamChange( stats.getUserId() );
447      }
448    }
449  }
450 
451  //now serve player who want join a random team
452  for ( std::list<BaseObject*>::const_iterator it = list->begin(); it != list->end(); it++ )
453  {
454    PlayerStats & stats = *dynamic_cast<PlayerStats*>(*it);
455
456    if ( stats.getTeamId() != stats.getPreferedTeamId() )
457    {
458      if ( stats.getPreferedTeamId() == TEAM_RANDOM )
459      {
460        stats.setPreferedTeamId( getRandomTeam() );
461        teamChange( stats.getUserId() );
462      }
463    }
464  }
465}
466
467void MultiplayerTeamDeathmatch::teamChange( int userId )
468{
469  assert( PlayerStats::getStats( userId ) );
470  PlayerStats & stats = *(PlayerStats::getStats( userId ));
471 
472  stats.setTeamId( stats.getPreferedTeamId() );
473 
474  Playable * oldPlayable = stats.getPlayable();
475 
476 
477  ClassID playableClassId = getPlayableClassId( userId, stats.getPreferedTeamId() );
478  std::string playableModel = getPlayableModelFileName( userId, stats.getPreferedTeamId(), playableClassId );
479 
480  BaseObject * bo = Factory::fabricate( playableClassId );
481 
482  assert( bo != NULL );
483  assert( bo->isA( CL_PLAYABLE ) );
484 
485  Playable & playable = *(dynamic_cast<Playable*>(bo));
486 
487  playable.loadModel( playableModel );
488  playable.setOwner( userId );
489  playable.setUniqueID( SharedNetworkData::getInstance()->getNewUniqueID() );
490  playable.setSynchronized( true );
491 
492  stats.setTeamId( stats.getPreferedTeamId() );
493  stats.setPlayableClassId( playableClassId );
494  stats.setPlayableUniqueId( playable.getUniqueID() );
495  stats.setModelFileName( playableModel );
496 
497  if ( oldPlayable )
498  {
499    //if ( userId == SharedNetworkData::getInstance()->getHostID() )
500    //  State::getPlayer()->setPlayable( NULL );
501    delete oldPlayable;
502  }
503}
504
505void MultiplayerTeamDeathmatch::onButtonExit( )
506{
507  State::getCurrentStoryEntity()->stop();
508  this->bShowTeamChange = false;
509}
510
511void MultiplayerTeamDeathmatch::onButtonRandom( )
512{
513  NetworkGameManager::getInstance()->prefereTeam( TEAM_RANDOM );
514  this->bShowTeamChange = false;
515}
516
517void MultiplayerTeamDeathmatch::onButtonTeam0( )
518{
519  NetworkGameManager::getInstance()->prefereTeam( 0 );
520  this->bShowTeamChange = false;
521}
522
523void MultiplayerTeamDeathmatch::onButtonTeam1( )
524{
525  NetworkGameManager::getInstance()->prefereTeam( 1 );
526  this->bShowTeamChange = false;
527}
528
529void MultiplayerTeamDeathmatch::onButtonSpectator( )
530{
531  NetworkGameManager::getInstance()->prefereTeam( TEAM_SPECTATOR );
532  this->bShowTeamChange = false;
533}
534
535void MultiplayerTeamDeathmatch::assignPlayable( )
536{
537  if ( PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() ) )
538    PlayerStats::getStats( SharedNetworkData::getInstance()->getHostID() )->getPlayable();
539}
540
541  /**
542   * function that processes events from the handler
543   * @param event: the event
544   * @todo replace SDLK_o with something from KeyMapper
545   */
546void MultiplayerTeamDeathmatch::process( const Event & event )
547{
548  if ( event.type == SDLK_o )
549  {
550    if ( event.bPressed )
551      this->bShowTeamChange = true;
552  } else if ( event.type == SDLK_F1 )
553  {
554    if ( !this->statsBox && event.bPressed )
555    {
556      PRINTF(0)("show stats\n");
557      this->showStats();
558    }
559    if ( this->statsBox && !this->bLocalPlayerDead && event.bPressed && this->table->rowCount() != 0 && this->table->columnCount() != 0 )
560    {
561      PRINTF(0)("hide stats\n");
562      this->hideStats();
563    }
564  }
565  else if ( event.type == SDLK_TAB )
566  {
567    if ( !event.bPressed )
568    {
569      EventHandler::getInstance()->pushState( ES_MENU );
570      OrxGui::GLGuiHandler::getInstance()->activateCursor();
571      OrxGui::GLGuiHandler::getInstance()->deactivateCursor();
572      input->show();
573      input->giveFocus();
574      input->setText("say ");
575    }
576  }
577  else if ( this->bLocalPlayerDead && statsBox && event.type == KeyMapper::PEV_FIRE1 )
578  {
579    this->hideStats();
580  }
581}
582
583void MultiplayerTeamDeathmatch::onButtonCancel( )
584{
585  this->bShowTeamChange = false;
586}
587
588
589
590/**
591 * this method is called by NetworkGameManger when he recieved a chat message
592 * @param userId senders user id
593 * @param message message string
594 * @param messageType some int
595 */
596void MultiplayerTeamDeathmatch::handleChatMessage( int userId, const std::string & message, int messageType )
597{
598  std::string name = "unknown";
599 
600  if ( PlayerStats::getStats( userId ) )
601  {
602    name = PlayerStats::getStats( userId )->getNickName();
603  }
604 
605  PRINTF(0)("CHATMESSAGE %s (%d): %s\n", name.c_str(), userId, message.c_str() );
606  notifier->pushNotifyMessage(name + ": " + message);
607}
608
609void MultiplayerTeamDeathmatch::onInputEnter( const std::string & text )
610{
611  EventHandler::getInstance()->popState();
612  input->breakFocus();
613  input->hide();
614  input->setText("");
615
616  std::string command = text;
617 
618  //HACK insert " in say commands so user doesn't have to type them
619  if ( command.length() >= 4 && command[0] == 's' && command[1] == 'a' && command[2] == 'y' && command[3] == ' ' )
620  {
621    command.insert( 4, "\"" );
622    command = command + "\"";
623  }
624
625  OrxShell::ShellCommand::execute( command );
626}
627
628/**
629 * show table with frags
630 */
631void MultiplayerTeamDeathmatch::showStats( )
632{
633  EventHandler::getInstance()->pushState( ES_MENU );
634  statsBox = new OrxGui::GLGuiBox();
635  statsBox->setAbsCoor2D( 300, 100 );
636 
637  this->table = new OrxGui::GLGuiTable(0,0);
638
639  statsBox->pack( this->table );
640
641  statsBox->showAll();
642}
643
644/**
645 * hide table with frags
646 */
647void MultiplayerTeamDeathmatch::hideStats( )
648{
649    if ( statsBox )
650    {
651      delete statsBox;
652      statsBox = NULL;
653    }
654     
655    EventHandler::getInstance()->popState();
656}
657
658/**
659 * fill stats table with values
660 */
661void MultiplayerTeamDeathmatch::tickStatsTable( )
662{
663  if ( !this->statsBox )
664    return;
665
666  std::vector<std::string> headers;
667  headers.push_back("Red Team");
668  headers.push_back("");
669  headers.push_back("");
670  headers.push_back("Blue Team");
671  headers.push_back("");
672  this->table->setHeader(headers);
673 
674  std::map<int,std::string> fragsTeam0;
675  std::map<int,std::string> fragsTeam1;
676 
677  const std::list<BaseObject*> * list = ClassList::getList( CL_PLAYER_STATS );
678 
679  if ( !list )
680    return;
681 
682  for ( std::list<BaseObject*>::const_iterator it = list->begin(); it != list->end(); it++ )
683  {
684    PlayerStats & stats = *dynamic_cast<PlayerStats*>(*it);
685   
686    if ( stats.getTeamId() == 0 || stats.getTeamId() == 1 )
687    {
688      if ( stats.getTeamId() == 0 )
689        fragsTeam0[stats.getScore()] = stats.getNickName();
690      else
691        fragsTeam1[stats.getScore()] = stats.getNickName();
692    }
693  }
694 
695  char st[10];
696  int i = 0;
697 
698 
699  i = 2;
700  for ( std::map<int,std::string>::const_iterator it = fragsTeam0.begin(); it != fragsTeam0.end(); it++ )
701  {
702    this->table->setEntry( 0, i, it->second );
703    snprintf( st, 10, "%d", it->first );
704    this->table->setEntry( 1, i, st );
705    this->table->setEntry( 2, i, "" );
706    i++;
707  }
708 
709  i = 2;
710  for ( std::map<int,std::string>::const_iterator it = fragsTeam1.begin(); it != fragsTeam1.end(); it++ )
711  {
712    this->table->setEntry( 3, i, it->second );
713    snprintf( st, 10, "%d", it->first );
714    this->table->setEntry( 4, i, st );
715    i++;
716  }
717}
718
719/**
720 * this function is called when a player kills another one or himself
721 * @param killedUserId
722 * @param userId
723 */
724void MultiplayerTeamDeathmatch::onKill( int killedUserId, int userId )
725{
726  assert( PlayerStats::getStats( killedUserId ) );
727  assert( PlayerStats::getStats( userId ) );
728 
729  PlayerStats & killedStats = *PlayerStats::getStats( killedUserId );
730  PlayerStats & stats = *PlayerStats::getStats( userId );
731 
732  if ( killedUserId != userId )
733    stats.setScore( stats.getScore() + 1 );
734  else
735    stats.setScore( stats.getScore() - 1 );
736 
737  killedStats.getPlayable()->toList( OM_DEAD );
738 
739  if ( killedUserId == SharedNetworkData::getInstance()->getHostID() )
740  {
741    this->bLocalPlayerDead = true;
742    this->showStats();
743  }
744 
745  //TODO respawn killed palyer
746 
747}
748
749/**
750 * this function is called on player respawn
751 * @param userId
752 */
753void MultiplayerTeamDeathmatch::onRespawn( int userId )
754{
755  if ( userId == SharedNetworkData::getInstance()->getHostID() )
756  {
757    this->bLocalPlayerDead = false;
758    this->hideStats();
759  }
760}
761
Note: See TracBrowser for help on using the repository browser.