1 /*
2
3 *************************************************************************
4
5 ArmageTron -- Just another Tron Lightcycle Game in 3D.
6 Copyright (C) 2000 Manuel Moos (manuel@moosnet.de)
7
8 **************************************************************************
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 ***************************************************************************
25
26 */
27
28 #include "rSDL.h"
29
30 #include "eVoter.h"
31
32 #include "tMemManager.h"
33 #include "tSysTime.h"
34 #include "tDirectories.h"
35
36 #include "uMenu.h"
37
38 #include "nConfig.h"
39 #include "nServerInfo.h"
40
41 #include "rConsole.h"
42
43 #include "ePlayer.h"
44 #include "eGrid.h"
45
46 #ifndef DEDICATED
47 // use server controlled votes (just for the client, to avoid UPGRADE messages)
48 static bool se_useServerControlledKick = false;
49 static nSettingItem< bool > se_usc( "VOTE_USE_SERVER_CONTROLLED_KICK", se_useServerControlledKick );
50 #endif
51
52 // basic vote timeout value
53 static unsigned short se_votingItemID = 0;
54 static float se_votingTimeout = 300.0f;
55 static nSettingItem< float > se_vt( "VOTING_TIMEOUT", se_votingTimeout );
56
57 // additional timeout for every voter present
58 static float se_votingTimeoutPerVoter = 0.0f;
59 static nSettingItem< float > se_vtp( "VOTING_TIMEOUT_PER_VOTER", se_votingTimeoutPerVoter );
60
61 static float se_votingStartDecay = 60.0f;
62 static nSettingItem< float > se_vsd( "VOTING_START_DECAY", se_votingStartDecay );
63
64 static float se_votingDecay = 60.0f;
65 static nSettingItem< float > se_vd( "VOTING_DECAY", se_votingDecay );
66
67 // spam level of issuing a vote
68 static float se_votingSpamIssue = 1.0f;
69 static nSettingItem< float > se_vsi( "VOTING_SPAM_ISSUE", se_votingSpamIssue );
70
71 // spam level of getting your vote rejected
72 static float se_votingSpamReject = 5.0f;
73 static nSettingItem< float > se_vsr( "VOTING_SPAM_REJECT", se_votingSpamReject );
74
75 static bool se_allowVoting = false;
76 static tSettingItem< bool > se_av( "ALLOW_VOTING", se_allowVoting );
77
78 static bool se_allowVotingSpectator = false;
79 static tSettingItem< bool > se_avo( "ALLOW_VOTING_SPECTATOR", se_allowVotingSpectator );
80
81 // number of rounds to suspend
82 static int se_suspendRounds = 5;
83 static tSettingItem< int > se_sr( "VOTING_SUSPEND_ROUNDS", se_suspendRounds );
84
85 static int se_minVoters = 3;
86 static tSettingItem< int > se_mv( "MIN_VOTERS", se_minVoters );
87
88 // the number set here always acts as votes against a change.
89 static int se_votingBias = 0;
90 static tSettingItem< int > se_vb( "VOTING_BIAS", se_votingBias );
91
92 // the number set here always acts as additional votes against a kick vote.
93 static int se_votingBiasKick = 0;
94 static tSettingItem< int > se_vbKick( "VOTING_BIAS_KICK", se_votingBiasKick );
95
96 // the number set here always acts as additional votes against a suspend vote.
97 static int se_votingBiasSuspend = 0;
98 static tSettingItem< int > se_vbSuspend( "VOTING_BIAS_SUSPEND", se_votingBiasSuspend );
99
100 // the number set here always acts as additional votes against a suspend vote.
101 static int se_votingBiasInclude = 0;
102 static tSettingItem< int > se_vbInclude( "VOTING_BIAS_INCLUDE", se_votingBiasInclude );
103
104 // the number set here always acts as additional votes against a command vote.
105 static int se_votingBiasCommand = 0;
106 static tSettingItem< int > se_vbCommand( "VOTING_BIAS_COMMAND", se_votingBiasCommand );
107
108 // voting privacy level. -2 means total disclosure, +2 total secrecy.
109 static int se_votingPrivacy = 1;
110 static tSettingItem< int > se_vp( "VOTING_PRIVACY", se_votingPrivacy );
111
112 // maximum number of concurrent votes
113 static int se_maxVotes = 5;
114 static tSettingItem< int > se_maxVotesSI( "MAX_VOTES", se_maxVotes );
115
116 // maximum number of concurrent votes per voter
117 static int se_maxVotesPerVoter = 2;
118 static tSettingItem< int > se_maxVotesPerVoterSI( "MAX_VOTES_PER_VOTER", se_maxVotesPerVoter );
119
120 // time between kick votes against the same target in seconds
121 static int se_minTimeBetweenKicks = 300;
122 static tSettingItem< int > se_minTimeBetweenKicksSI( "VOTING_KICK_TIME", se_minTimeBetweenKicks );
123
124 // time between harmful votes against the same target in seconds
125 static int se_minTimeBetweenHarms = 180;
126 static tSettingItem< int > se_minTimeBetweenHarmsSI( "VOTING_HARM_TIME", se_minTimeBetweenHarms );
127
128 // time between name changes and you being allowed to issue votes again
129 static int se_votingMaturity = 300;
130 static tSettingItem< int > se_votingMaturitySI( "VOTING_MATURITY", se_votingMaturity );
131
132 #ifdef KRAWALL_SERVER
133 // minimal access level for kick votes
134 static tAccessLevel se_accessLevelVoteKick = tAccessLevel_Program;
135 static tSettingItem< tAccessLevel > se_accessLevelVoteKickSI( "ACCESS_LEVEL_VOTE_KICK", se_accessLevelVoteKick );
136 static tAccessLevelSetter se_accessLevelVoteKickSILevel( se_accessLevelVoteKickSI, tAccessLevel_Owner );
137
138 // minimal access level for suspend votes
139 static tAccessLevel se_accessLevelVoteSuspend = tAccessLevel_Program;
140 static tSettingItem< tAccessLevel > se_accessLevelVoteSuspendSI( "ACCESS_LEVEL_VOTE_SUSPEND", se_accessLevelVoteSuspend );
141 static tAccessLevelSetter se_accessLevelVoteSuspendSILevel( se_accessLevelVoteSuspendSI, tAccessLevel_Owner );
142
143 // minimal access level for include votes
144 static tAccessLevel se_accessLevelVoteInclude = tAccessLevel_Moderator;
145 static tSettingItem< tAccessLevel > se_accessLevelVoteIncludeSI( "ACCESS_LEVEL_VOTE_INCLUDE", se_accessLevelVoteInclude );
146 static tAccessLevelSetter se_accessLevelVoteIncludeSILevel( se_accessLevelVoteIncludeSI, tAccessLevel_Owner );
147
148 // minimal access level for include votes
149 static tAccessLevel se_accessLevelVoteIncludeExecute = tAccessLevel_Moderator;
150 static tSettingItem< tAccessLevel > se_accessLevelVoteIncludeExecuteSI( "ACCESS_LEVEL_VOTE_INCLUDE_EXECUTE", se_accessLevelVoteIncludeExecute );
151 static tAccessLevelSetter se_accessLevelVoteIncludeExecuteSILevel( se_accessLevelVoteIncludeExecuteSI, tAccessLevel_Owner );
152
153 // minimal access level for direct command votes
154 static tAccessLevel se_accessLevelVoteCommand = tAccessLevel_Moderator;
155 static tSettingItem< tAccessLevel > se_accessLevelVoteCommandSI( "ACCESS_LEVEL_VOTE_COMMAND", se_accessLevelVoteCommand );
156 static tAccessLevelSetter se_accessLevelVoteCommandSILevel( se_accessLevelVoteCommandSI, tAccessLevel_Owner );
157
158 // access level direct command votes will be executed with (minimal level is,
159 // however, the access level of the vote submitter)
160 static tAccessLevel se_accessLevelVoteCommandExecute = tAccessLevel_Moderator;
161 static tSettingItem< tAccessLevel > se_accessLevelVoteCommandExecuteSI( "ACCESS_LEVEL_VOTE_COMMAND_EXECUTE", se_accessLevelVoteCommandExecute );
162 static tAccessLevelSetter se_accessLevelVoteCommandExecuteSILevel( se_accessLevelVoteCommandExecuteSI, tAccessLevel_Owner );
163
164 #endif
165
166 static REAL se_defaultVotesSuspendLength = 3;
167 static tSettingItem< REAL > se_defaultVotesSuspendLenght_Conf( "VOTES_SUSPEND_DEFAULT", se_defaultVotesSuspendLength );
168 static REAL se_votesSuspendTimeout = 0;
169
se_GetVoter(const nMessage & m)170 static eVoter* se_GetVoter( const nMessage& m )
171 {
172 return eVoter::GetVoter( m.SenderID(), true );
173 }
174
eVoterPlayerInfo()175 eVoterPlayerInfo::eVoterPlayerInfo(): suspended_(0), silenced_(0){}
176
se_GetAccessLevel(int userID)177 static tAccessLevel se_GetAccessLevel( int userID )
178 {
179 tAccessLevel ret = tAccessLevel_Default;
180
181 // scan players of given user ID
182 for ( int i = se_PlayerNetIDs.Len()-1; i>=0; --i )
183 {
184 ePlayerNetID* p = se_PlayerNetIDs(i);
185
186 if ( p->Owner() == userID )
187 {
188 if( p->GetAccessLevel() < ret )
189 {
190 ret = p->GetAccessLevel();
191 }
192 }
193 }
194
195 return ret;
196 }
197
198 static nVersionFeature serverControlledVotesBroken( 10 );
199 static nVersionFeature serverControlledVotes( 15 );
200
201
202 // something to vote on
203 class eVoteItem: public tListMember
204 {
205 friend class eMenuItemVote;
206 public:
207 // constructors/destructor
eVoteItem(void)208 eVoteItem( void ): creationTime_( tSysTimeFloat() ), user_( 0 ), id_( ++se_votingItemID ), menuItem_( 0 ), total_( 0 )
209 {
210 items_.Add( this );
211 }
212
213 virtual ~eVoteItem( void );
214
FillFromMessage(nMessage & m)215 bool FillFromMessage( nMessage& m )
216 {
217 // cloak the ID of the sener for privacy
218 nCurrentSenderID cloak;
219 if ( se_votingPrivacy > 1 )
220 cloak.SetID(0);
221
222 if ( !DoFillFromMessage( m ) )
223 return false;
224
225 if ( sn_GetNetState() == nSERVER )
226 {
227 if ( !CheckValid( m.SenderID() ) )
228 return false;
229 }
230
231 ReBroadcast( m.SenderID() );
232
233 return true;
234 }
235
236
ReBroadcast(int exceptTo)237 void ReBroadcast( int exceptTo )
238 {
239 // rebroadcast message to all non-voters that may be able to vote
240 if ( sn_GetNetState() == nSERVER )
241 {
242 // prepare message
243 tOutput voteMessage;
244 voteMessage.SetTemplateParameter( 1, suggestor_->Name( user_ ) );
245 voteMessage.SetTemplateParameter( 2, GetDescription() );
246 voteMessage << "$vote_submitted";
247
248 // print it
249 if ( se_votingPrivacy <= -1 )
250 {
251 sn_ConsoleOut( voteMessage ); // broadcast it
252 }
253 else
254 {
255 if ( exceptTo > 0 )
256 {
257 sn_ConsoleOut( voteMessage, exceptTo ); // inform submitter
258 }
259
260 if ( se_votingPrivacy <= 1 )
261 {
262 con << voteMessage; // print it for the server admin
263 }
264 }
265
266 // create messages for old and new clients
267 tJUST_CONTROLLED_PTR< nMessage > retNew = this->CreateMessage();
268 tJUST_CONTROLLED_PTR< nMessage > retLegacy = this->CreateMessageLegacy();
269
270 // set so every voter ony gets each vote once
271 std::set< eVoter * > sentTo;
272 total_ = 1;
273
274 for ( int i = MAXCLIENTS; i > 0; --i )
275 {
276 eVoter * voter = eVoter::GetVoter( i );
277 if ( sn_Connections[ i ].socket && i != exceptTo && 0 != voter &&
278 sentTo.find(voter) == sentTo.end() )
279 {
280
281 if ( serverControlledVotes.Supported( i ) )
282 {
283 sentTo.insert(voter);
284 retNew->Send( i );
285 total_++;
286 }
287 else if ( retLegacy )
288 {
289 sentTo.insert(voter);
290 retLegacy->Send( i );
291 total_++;
292 }
293 }
294 }
295 // item->SendMessage();
296
297 if ( suggestor_ )
298 suggestor_->Spam( exceptTo, se_votingSpamIssue, tOutput("$spam_vote_kick_issue") );
299 }
300
301 con << tOutput( "$vote_new", GetDescription() );
302
303 this->Evaluate();
304 }
305
CreateMessage(void) const306 nMessage* CreateMessage( void ) const
307 {
308 nMessage* m = tNEW( nMessage )( this->DoGetDescriptor() );
309 this->DoFillToMessage( *m );
310 return m;
311 }
312
CreateMessageLegacy(void) const313 nMessage* CreateMessageLegacy( void ) const
314 {
315 nDescriptor * descriptor = this->DoGetDescriptorLegacy();
316 if ( descriptor )
317 {
318 nMessage* m = tNEW( nMessage )( *descriptor );
319 this->DoFillToMessageLegacy( *m );
320 return m;
321 }
322 else
323 {
324 return 0;
325 }
326 }
327
SendMessage(void) const328 void SendMessage( void ) const
329 {
330 this->CreateMessage()->BroadCast();
331 }
332
333 // message sending
334 void Vote( bool accept ); // called on the clients to accept or decline the vote
335
AcceptNewVote(eVoter * voter,int senderID)336 static bool AcceptNewVote( eVoter * voter, int senderID ) // check if a new voting item should be accepted
337 {
338 // cloak the ID of the sener for privacy
339 nCurrentSenderID cloak;
340 if ( se_votingPrivacy > 0 )
341 cloak.SetID(0);
342
343 int i;
344
345 // let old messages time out
346 for ( i = items_.Len()-1; i>=0; --i )
347 {
348 items_[i]->Evaluate();
349 }
350
351 // always accept in client mode
352 if ( sn_GetNetState() == nCLIENT )
353 return true;
354
355 // check if voting is allowed
356 if ( !voter )
357 {
358 return false;
359 }
360
361 // reject voting
362 if ( !se_allowVoting )
363 {
364 tOutput message("$vote_disabled");
365 sn_ConsoleOut( message, senderID );
366 return false;
367 }
368
369 // spawn spectator voters
370 for ( i = MAXCLIENTS; i > 0; --i )
371 {
372 if ( sn_Connections[ i ].socket )
373 eVoter::GetVoter( i );
374 }
375
376 // enough voters online?
377 if ( eVoter::voters_.Len() < se_minVoters )
378 {
379 tOutput message("$vote_toofew");
380 sn_ConsoleOut( message, senderID );
381 return false;
382 }
383
384 // check for spam
385 if ( voter->IsSpamming( senderID ) )
386 {
387 return false;
388 }
389
390 // count number of votes by the voter
391 int voteCount = 0;
392 for ( i = items_.Len()-1; i>=0; --i )
393 {
394 eVoteItem * other = items_[i];
395 if ( other->suggestor_ == voter )
396 voteCount ++;
397 }
398 if ( voteCount >= se_maxVotesPerVoter )
399 {
400 tOutput message("$vote_overflow");
401 sn_ConsoleOut( message, senderID );
402 return false;
403 }
404
405 if ( items_.Len() < se_maxVotes )
406 {
407 return true;
408 }
409 else
410 {
411 tOutput message("$vote_overflow");
412 sn_ConsoleOut( message, senderID );
413 return false;
414 }
415 }
416
AcceptNewVote(nMessage const & m)417 static bool AcceptNewVote( nMessage const & m ) // check if a new voting item should be accepted
418 {
419 return AcceptNewVote( se_GetVoter( m ), m.SenderID() );
420 }
421
RemoveVoter(eVoter * voter)422 void RemoveVoter( eVoter* voter )
423 {
424 // remove voter from the lists
425 for ( int res = 1; res >= 0; --res )
426 this->voters_[ res ].Remove( voter );
427 }
428
RemoveVoterCompletely(eVoter * voter)429 void RemoveVoterCompletely( eVoter* voter )
430 {
431 RemoveVoter( voter );
432 if ( suggestor_ == voter )
433 {
434 suggestor_ = 0;
435 user_ = 0;
436 }
437 }
438
439 // message receival
GetControlMessage(nMessage & m)440 static void GetControlMessage( nMessage& m ) // handles a voting message
441 {
442 if ( sn_GetNetState() == nSERVER )
443 {
444 unsigned short id;
445 m.Read( id );
446
447 bool result;
448 m >> result;
449 result = result ? 1 : 0;
450
451 for ( int i = items_.Len()-1; i>=0; --i )
452 {
453 eVoteItem* vote = items_[i];
454 if ( vote->id_ == id )
455 {
456 // found the vote; find the voter
457 tCONTROLLED_PTR( eVoter ) voter = se_GetVoter( m );
458 if ( voter )
459 {
460 // prepare message
461 tOutput voteMessage;
462 voteMessage.SetTemplateParameter( 1, voter->Name( m.SenderID() ) );
463 voteMessage.SetTemplateParameter( 2, vote->GetDescription() );
464 if ( result )
465 voteMessage << "$vote_vote_for";
466 else
467 voteMessage << "$vote_vote_against";
468
469 // print it
470 if ( se_votingPrivacy <= -2 )
471 sn_ConsoleOut( voteMessage ); // broadcast it
472 else if ( se_votingPrivacy <= 0 )
473 con << voteMessage; // print it for the server admin
474 else
475 {
476 sn_ConsoleOut( voteMessage, m.SenderID() );
477 }
478
479 // remove him from the lists
480 vote->RemoveVoter( voter );
481
482 // insert hum
483 vote->voters_[ result ].Insert( voter );
484 }
485
486 // are enough votes cast?
487 vote->Evaluate();
488 return;
489 }
490 }
491 }
492 }
493
494 // information
GetStats(int & pro,int & con,int & total) const495 void GetStats( int& pro, int& con, int& total ) const // returns voting statistics about this item
496 {
497 pro = voters_[1].Len();
498 con = voters_[0].Len();
499 total = total_;
500 }
501
502 // message
BroadcastMessage(const tOutput & message) const503 void BroadcastMessage( const tOutput& message ) const
504 {
505 if ( sn_GetNetState() == nSERVER )
506 {
507 tOutput m;
508 m.SetTemplateParameter( 1, this->GetDescription() );
509 m.Append( message );
510
511 sn_ConsoleOut( m );
512 }
513 }
514
515 // access level required for this kind of vote
DoGetAccessLevel() const516 virtual tAccessLevel DoGetAccessLevel() const
517 {
518 return tAccessLevel_Default;
519 }
520
521 // return vote-specific extra bias
DoGetExtraBias() const522 virtual int DoGetExtraBias() const
523 {
524 return 0;
525 }
526
527 // evaluation
Evaluate()528 virtual void Evaluate() // check if this voting item is to be kept around
529 {
530 int pro, con, total;
531
532 GetStats( pro, con, total );
533
534 if ( sn_GetNetState() == nSERVER )
535 {
536 // see if there are enough voters
537 if ( total <= se_minVoters )
538 {
539 this->BroadcastMessage( tOutput("$vote_toofew") );
540 delete this;
541 return;
542 }
543 }
544
545 int bias = se_votingBias + DoGetExtraBias();
546
547 // apply bias
548 con += bias;
549 total += bias;
550
551 // reduce number of total voters
552 if ( se_votingDecay > 0 )
553 {
554 int reduce = int( ( tSysTimeFloat() - this->creationTime_ - se_votingStartDecay ) / se_votingDecay );
555 if ( reduce > 0 )
556 {
557 total -= reduce;
558 }
559 }
560
561 if ( sn_GetNetState() == nSERVER )
562 {
563 // see if the vote has been rejected
564 if ( con >= pro && con * 2 >= total )
565 {
566 if ( this->suggestor_ )
567 this->suggestor_->Spam( user_, se_votingSpamReject, tOutput("$spam_vote_rejected") );
568
569 this->BroadcastMessage( tOutput( "$vote_rejected" ) );
570 delete this;
571 return;
572 }
573
574 // see if the vote has been accepted
575 if ( pro >= con && pro * 2 > total )
576 {
577 this->BroadcastMessage( tOutput( "$vote_accepted" ) );
578
579 this->DoExecute();
580
581 delete this;
582 return;
583 }
584 }
585
586 // see if the voting has timed out
587 int relevantNumVoters = sn_GetNetState() == nCLIENT ? se_PlayerNetIDs.Len() + MAXCLIENTS : eVoter::voters_.Len(); // the number of voters (overestimate the value on the client)
588 if ( this->creationTime_ < tSysTimeFloat() - se_votingTimeout - se_votingTimeoutPerVoter * relevantNumVoters )
589 {
590 this->BroadcastMessage( tOutput( "$vote_timeout" ) );
591
592 delete this;
593 return;
594 }
595 }
596
597 // accessors
GetItems()598 static const tList< eVoteItem >& GetItems() { return items_; } // returns the list of all items
GetSuggestor() const599 inline eVoter* GetSuggestor() const { return suggestor_; } // returns the voter that suggested the item
GetDescription() const600 inline tString GetDescription() const{ return this->DoGetDescription(); } // returns the description of the voting item
GetDetails() const601 inline tString GetDetails() const{ return this->DoGetDetails(); } // returns the detailed description of the voting item
602
GetID()603 unsigned short GetID(){ return id_; }
604 void UpdateMenuItem(); // update the menu item about a status change
605
606 // checks whether the vote is a valid vote to make
CheckValid(int senderID)607 bool CheckValid( int senderID )
608 {
609 if ( sn_GetNetState() != nSERVER )
610 {
611 return true;
612 }
613
614 if ( se_votesSuspendTimeout > tSysTimeFloat() )
615 {
616 sn_ConsoleOut(tOutput("$vote_rejected_voting_suspended"),
617 senderID );
618
619 return false;
620 }
621
622 // fill suggestor
623 if ( !suggestor_ )
624 {
625 suggestor_ = eVoter::GetVoter( senderID );
626 if ( !suggestor_ )
627 return false;
628
629 // add suggestor to supporters
630 this->voters_[1].Insert( suggestor_ );
631
632 user_ = senderID;
633 }
634
635 // check access level
636 tAccessLevel accessLevel = se_GetAccessLevel( senderID );
637 if ( accessLevel < tCurrentAccessLevel::GetAccessLevel() )
638 {
639 accessLevel = tCurrentAccessLevel::GetAccessLevel();
640 }
641
642 tAccessLevel required = DoGetAccessLevel();
643 if ( accessLevel > required )
644 {
645 sn_ConsoleOut(tOutput("$player_vote_accesslevel",
646 tCurrentAccessLevel::GetName( accessLevel ),
647 tCurrentAccessLevel::GetName( required ) ),
648 senderID );
649
650 return false;
651 }
652
653 return DoCheckValid( senderID );
654 }
655
Update()656 virtual void Update() //!< update description and details
657 {}
658 protected:
DoFillFromMessage(nMessage & m)659 virtual bool DoFillFromMessage( nMessage& m )
660 {
661 // get user
662 user_ = m.SenderID();
663
664 // get originator of vote
665 if(sn_GetNetState()!=nSERVER)
666 {
667 m.Read( id_ );
668 }
669
670 return true;
671 }
672
DoCheckValid(int senderID)673 virtual bool DoCheckValid( int senderID ){ return true; }
674
DoFillToMessage(nMessage & m) const675 virtual void DoFillToMessage( nMessage& m ) const
676 {
677 if(sn_GetNetState()==nSERVER)
678 {
679 // write our message ID
680 m.Write( id_ );
681 }
682 }
683
684 protected:
DoGetDetails() const685 virtual tString DoGetDetails() const // returns the detailed description of the voting item
686 {
687 tString ret;
688 if ( se_votingPrivacy <= -1 && bool( suggestor_ ) )
689 ret << tOutput( "$vote_submitter_text", suggestor_->Name( user_ ) ) << " ";
690
691 return ret;
692 }
693 static tList< eVoteItem > items_; // list of vote items
694 private:
DoGetDescriptorLegacy() const695 virtual nDescriptor * DoGetDescriptorLegacy() const // returns the creation descriptor
696 {
697 return 0;
698 }
699
DoFillToMessageLegacy(nMessage & m) const700 virtual void DoFillToMessageLegacy( nMessage& m ) const
701 {
702 return DoFillToMessage( m );
703 }
704
705 virtual nDescriptor& DoGetDescriptor() const = 0; // returns the creation descriptor
706 virtual tString DoGetDescription() const = 0; // returns the description of the voting item
707 virtual void DoExecute() = 0; // called when the voting was successful
708
709 nTimeAbsolute creationTime_; // time the vote was cast
710 tCONTROLLED_PTR( eVoter ) suggestor_; // the voter suggesting the vote
711 unsigned int user_; // user suggesting the vote
712 tArray< tCONTROLLED_PTR( eVoter ) > voters_[2]; // array of voters approving or disapproving of the vote
713 unsigned short id_; // running id of voting item
714 eMenuItemVote *menuItem_; // menu item
715 int total_; // total number of voters aware of this item
716
717 eVoteItem& operator=( const eVoteItem& );
718 eVoteItem( const eVoteItem& );
719 };
720
721 tList< eVoteItem > eVoteItem::items_; // list of vote items
722
se_CancelAllVotes(bool announce)723 void se_CancelAllVotes( bool announce )
724 {
725 if ( sn_GetNetState() == nCLIENT )
726 {
727 return;
728 }
729
730 if (announce)
731 {
732 sn_ConsoleOut( tOutput( "$vote_cancel_all" ) );
733 }
734
735 tList< eVoteItem > const & items = eVoteItem::GetItems();
736
737 while ( items.Len() )
738 {
739 delete items(0);
740 }
741 }
742
se_CancelAllVotes(std::istream &)743 void se_CancelAllVotes( std::istream & )
744 {
745 se_CancelAllVotes ( true );
746 }
747
748 static tConfItemFunc se_cancelAllVotes_conf( "VOTES_CANCEL", &se_CancelAllVotes );
749
750
751
752
753
se_votesSuspend(REAL minutes,bool announce,std::istream & s)754 void se_votesSuspend( REAL minutes, bool announce, std::istream & s )
755 {
756 if ( sn_GetNetState() == nCLIENT )
757 {
758 return;
759 }
760
761 if ( minutes > 0)
762 {
763 s >> minutes;
764 }
765
766 se_CancelAllVotes( false );
767
768 se_votesSuspendTimeout = tSysTimeFloat() + ( minutes * 60 );
769
770 if ( announce && minutes > 0 )
771 {
772 sn_ConsoleOut( tOutput( "$voting_suspended", minutes ) );
773 }
774 else if ( announce && minutes <= 0 )
775 {
776 sn_ConsoleOut( tOutput( "$voting_unsuspended" ) );
777 }
778
779 }
780
se_SuspendVotes(std::istream & s)781 void se_SuspendVotes( std::istream & s )
782 {
783 se_votesSuspend( se_defaultVotesSuspendLength, true, s );
784 }
se_UnSuspendVotes(std::istream & s)785 void se_UnSuspendVotes( std::istream & s )
786 {
787 se_votesSuspend( 0, true, s );
788 }
789
790 static tConfItemFunc se_suspendVotes_conf( "VOTES_SUSPEND", &se_SuspendVotes );
791 static tConfItemFunc se_unSuspendVotes_conf( "VOTES_UNSUSPEND", &se_UnSuspendVotes );
792
793
794 static nDescriptor vote_handler(230,eVoteItem::GetControlMessage,"vote cast");
795
796 // called on the clients to accept or decline the vote
Vote(bool accept)797 void eVoteItem::Vote( bool accept )
798 {
799 tJUST_CONTROLLED_PTR< nMessage > m = tNEW( nMessage )( vote_handler );
800 *m << id_;
801 *m << accept;
802 m->BroadCast();
803
804 delete this;
805 }
806
807 //nDescriptor& eVoteItem::DoGetDescriptor() const; // returns the creation descriptor
808 //tString eVoteItem::DoGetDescription() const; // returns the description of the voting item
809 //void DoExecute(); // called when the voting was successful
810
811 // ****************************************************************************************
812 // ****************************************************************************************
813
814 // voting decision
815 enum Vote
816 {
817 Vote_Approve,
818 Vote_Reject,
819 Vote_DontMind
820 };
821
822 #ifdef _MSC_VER
823 #pragma warning ( disable: 4355 )
824 #endif
825
826 // menu item to silence selected players
827 class eMenuItemVote: public uMenuItemSelection< Vote >
828 {
829 friend class eVoteItem;
830 friend class eVoteItemServerControlled;
831
832 public:
eMenuItemVote(uMenu * m,eVoteItem * v)833 eMenuItemVote(uMenu *m, eVoteItem* v )
834 : uMenuItemSelection< Vote >( m, tOutput(""), tOutput("$vote_help"), vote_ )
835 , item_( v )
836 , vote_ ( Vote_DontMind )
837 , reject_ ( *this, "$vote_reject" , "$vote_reject_help" , Vote_Reject )
838 , dontMind_ ( *this, "$vote_dont_mind" , "$vote_dont_mind_help" , Vote_DontMind )
839 , approve_ ( *this, "$vote_approve" , "$vote_approve_help" , Vote_Approve )
840 {
841 tASSERT( v );
842
843 if ( v )
844 {
845 v->menuItem_ = this;
846 v->UpdateMenuItem();
847 }
848 }
849
~eMenuItemVote()850 ~eMenuItemVote()
851 {
852 if ( item_ )
853 {
854 item_->menuItem_ = 0;
855
856 switch ( vote_ )
857 {
858 case Vote_Approve:
859 item_->Vote( true );
860 break;
861 case Vote_Reject:
862 item_->Vote( false );
863 break;
864 default:
865 break;
866 }
867 }
868 }
869
870 private:
871 eVoteItem* item_; // vote item
872 Vote vote_; // result
873 uSelectEntry< Vote > reject_, dontMind_, approve_; // selection entries
874 };
875
876 // **************************************************************************************
877 // **************************************************************************************
878
879 static void se_HandleServerVoteChanged( nMessage& m );
880 static nDescriptor server_vote_expired_handler(233,se_HandleServerVoteChanged,"Server controlled vote expired");
881
882 // something to vote on: completely controlled by the server
883 class eVoteItemServerControlled: public virtual eVoteItem
884 {
885 public:
886 // constructors/destructor
eVoteItemServerControlled()887 eVoteItemServerControlled()
888 : description_( "No Info" )
889 , details_( "No Info" )
890 , expired_( false )
891 {
892 }
893
eVoteItemServerControlled(tString const & description,tString const & details)894 eVoteItemServerControlled( tString const & description, tString const & details )
895 : description_( description )
896 , details_( details )
897 , expired_( false )
898 {}
899
~eVoteItemServerControlled()900 ~eVoteItemServerControlled()
901 {
902 if ( sn_GetNetState() == nSERVER )
903 {
904 expired_ = true;
905 SendChanged();
906 }
907 }
908
s_HandleChanged(nMessage & m)909 static void s_HandleChanged( nMessage & m )
910 {
911 unsigned short id;
912 m.Read( id );
913 for ( int i = items_.Len()-1; i>=0; --i )
914 {
915 eVoteItem* vote = items_[i];
916 if ( vote->GetID() == id )
917 {
918 eVoteItemServerControlled * vote2 = dynamic_cast< eVoteItemServerControlled * >( vote );
919 if ( vote2 )
920 vote2->HandleChanged( m );
921 }
922 }
923 }
924
HandleChanged(nMessage & m)925 void HandleChanged( nMessage & m )
926 {
927 unsigned short expired;
928 m.Read( expired );
929 expired_ = expired;
930 m >> description_;
931 m >> details_;
932
933 Update();
934 UpdateMenuItem();
935 }
936
SendChanged()937 void SendChanged()
938 {
939 tJUST_CONTROLLED_PTR< nMessage > m = tNEW( nMessage )( server_vote_expired_handler );
940 *m << GetID();
941 *m << (unsigned short)expired_;
942 *m << description_;
943 *m << details_;
944 m->BroadCast();
945 }
946 protected:
947
DoFillFromMessage(nMessage & m)948 virtual bool DoFillFromMessage( nMessage& m )
949 {
950 m >> description_;
951 m >> details_;
952 return eVoteItem::DoFillFromMessage( m );
953 }
954
DoFillToMessage(nMessage & m) const955 virtual void DoFillToMessage( nMessage& m ) const
956 {
957 m << description_;
958 m << details_;
959 eVoteItem::DoFillToMessage( m );
960 }
961
DoExecute()962 virtual void DoExecute(){} // called when the voting was successful
963 protected:
964 virtual nDescriptor& DoGetDescriptor() const; // returns the creation descriptor
965
Evaluate()966 virtual void Evaluate()
967 {
968 // update clients (i.e. if a player to be kicked changed his name)
969 if ( sn_GetNetState() == nSERVER )
970 {
971 Update();
972 SendChanged();
973 }
974
975 if ( expired_ )
976 delete this;
977 else
978 eVoteItem::Evaluate();
979 }
980
DoGetDescription() const981 virtual tString DoGetDescription() const // returns the description of the voting item
982 {
983 return expired_ ? tString("Expired vote") : description_;
984 }
985
DoGetDetails() const986 virtual tString DoGetDetails() const // returns the detailed description of the voting item
987 {
988 return expired_ ? tString("Expired vote") : details_;
989 }
990 protected:
991 mutable tString description_; //!< the description of the vote
992 mutable tString details_; //!< details on the vote
993 private:
994 bool expired_; //!< flag set when the vote expired on the server
995 };
996
se_HandleServerVoteChanged(nMessage & m)997 static void se_HandleServerVoteChanged( nMessage& m )
998 {
999 eVoteItemServerControlled::s_HandleChanged( m );
1000 }
1001
se_HandleNewServerVote(nMessage & m)1002 static void se_HandleNewServerVote( nMessage& m )
1003 {
1004 if ( sn_GetNetState() != nCLIENT || eVoteItem::AcceptNewVote( m ) )
1005 {
1006 // accept message
1007 eVoteItem* item = tNEW( eVoteItemServerControlled )();
1008 if ( !item->FillFromMessage( m ) )
1009 delete item;
1010 }
1011 }
1012
1013 static nDescriptor new_server_vote_handler(232,se_HandleNewServerVote,"Server controlled vote");
1014
1015 // returns the creation descriptor
DoGetDescriptor() const1016 nDescriptor& eVoteItemServerControlled::DoGetDescriptor() const
1017 {
1018 return new_server_vote_handler;
1019 }
1020
1021 // *******************************************************************************************
1022 // *******************************************************************************************
1023
1024 class nMachineObserver: public nMachineDecorator
1025 {
1026 public:
nMachineObserver(nMachine & machine)1027 nMachineObserver( nMachine & machine )
1028 : nMachineDecorator( machine ), machine_( &machine ){}
1029
GetMachine()1030 nMachine * GetMachine()
1031 {
1032 return machine_;
1033 }
1034 protected:
OnDestroy()1035 virtual void OnDestroy()
1036 {
1037 machine_ = 0;
1038 }
1039 private:
1040 nMachine * machine_;
1041 };
1042
1043 static tString se_voteKickToServer("");
1044 static int se_voteKickToPort = 4534;
1045 static tSettingItem< tString > se_voteKickToServerConf( "VOTE_KICK_TO_SERVER", se_voteKickToServer );
1046 static tSettingItem< int > se_voteKickToPortConf( "VOTE_KICK_TO_PORT", se_voteKickToPort );
1047
1048 // minimal previous harmful votes (kick, silence, suspend) before
1049 // a successful kick vote really results in a kick. Before that, the result is a
1050 // suspension.
1051 static int se_kickMinHarm = 0;
1052 static tSettingItem< int > se_kickMinHarmSI( "VOTING_KICK_MINHARM", se_kickMinHarm );
1053
1054 // reason given on vote kicks
1055 static tString se_voteKickReason("");
1056 static tConfItemLine se_voteKickReasonConf( "VOTE_KICK_REASON", se_voteKickReason );
1057
se_VoteKickUser(int user)1058 void se_VoteKickUser( int user )
1059 {
1060 if ( user == 0 )
1061 {
1062 return;
1063 }
1064
1065 tString reason;
1066 if ( se_voteKickReason.Len() >= 2 )
1067 {
1068 reason = se_voteKickReason;
1069 }
1070 else
1071 {
1072 reason = tOutput("$voted_kill_kick");
1073 }
1074
1075 if ( se_voteKickToServer.Len() < 2 )
1076 {
1077 sn_KickUser( user, reason );
1078 }
1079 else
1080 {
1081 // kick player to default destination
1082 nServerInfoRedirect redirect( se_voteKickToServer, se_voteKickToPort );
1083 sn_KickUser( user, reason, 1, &redirect );
1084 }
1085 }
1086
se_VoteKickPlayer(ePlayerNetID * p)1087 void se_VoteKickPlayer( ePlayerNetID * p )
1088 {
1089 if ( !p )
1090 {
1091 return;
1092 }
1093
1094 se_VoteKickUser( p->Owner() );
1095 }
1096
1097 // something to vote on: harming a player
1098 class eVoteItemHarm: public virtual eVoteItem
1099 {
1100 public:
1101 // constructors/destructor
eVoteItemHarm(ePlayerNetID * player=0)1102 eVoteItemHarm( ePlayerNetID* player = 0 )
1103 : player_( player )
1104 , machine_(NULL)
1105 , name_( "(Player who already left)" )
1106 {}
1107
~eVoteItemHarm()1108 ~eVoteItemHarm()
1109 {
1110 delete machine_;
1111 machine_ = NULL;
1112 }
1113
1114 // returns the player that is to be harmed
GetPlayer() const1115 ePlayerNetID * GetPlayer() const
1116 {
1117 ePlayerNetID const * player = player_;
1118 return const_cast< ePlayerNetID * >( player );
1119 }
1120 protected:
1121 // this is a good spot to put in legacy hooks
DoGetDescriptorLegacy() const1122 virtual nDescriptor * DoGetDescriptorLegacy() const
1123 {
1124 return &eVoteItemHarm::DoGetDescriptor();
1125 }
1126
DoFillToMessageLegacy(nMessage & m) const1127 virtual void DoFillToMessageLegacy( nMessage& m ) const
1128 {
1129 return eVoteItemHarm::DoFillToMessage( m );
1130 }
1131
DoFillFromMessage(nMessage & m)1132 virtual bool DoFillFromMessage( nMessage& m )
1133 {
1134 // read player ID
1135 unsigned short id;
1136 m.Read(id);
1137 tJUST_CONTROLLED_PTR< ePlayerNetID > p=dynamic_cast<ePlayerNetID *>(nNetObject::ObjectDangerous(id));
1138 player_ = p;
1139
1140 return eVoteItem::DoFillFromMessage( m );
1141 }
1142
DoCheckValid(int senderID)1143 virtual bool DoCheckValid( int senderID )
1144 {
1145 // always accept votes from server
1146 if ( sn_GetNetState() == nCLIENT && senderID == 0 )
1147 {
1148 return true;
1149 }
1150
1151 eVoter * sender = eVoter::GetVoter( senderID );
1152
1153 double time = tSysTimeFloat();
1154
1155 // check whether the issuer is allowed to start a vote
1156 if ( sender && player_ && player_->GetTimeCreated() + se_votingMaturity > time )
1157 {
1158 REAL timeLeft = player_->GetTimeCreated() + se_votingMaturity - time;
1159 tOutput message( "$vote_maturity", timeLeft );
1160 sn_ConsoleOut( message, senderID );
1161 return false;
1162 }
1163
1164 // prevent the sender from changing his name for confusion
1165 if ( sender )
1166 sender->lastNameChangePreventor_ = time;
1167
1168 // check if player is protected from kicking
1169 if ( player_ && sn_GetNetState() != nCLIENT )
1170 {
1171 // check whether the player is on the server
1172 if ( player_->Owner() == 0 )
1173 {
1174 sn_ConsoleOut( tOutput( "$vote_kick_local", player_->GetName() ), senderID );
1175 return false;
1176 }
1177
1178 name_ = player_->GetName();
1179 eVoter * voter = eVoter::GetVoter( player_->Owner() );
1180 if ( voter )
1181 {
1182 machine_ = tNEW( nMachineObserver )( voter->machine_ );
1183
1184 if ( time < voter->lastHarmVote_ + se_minTimeBetweenHarms )
1185 {
1186 tOutput message("$vote_redundant");
1187 sn_ConsoleOut( message, senderID );
1188 return false;
1189 }
1190 else
1191 {
1192 voter->lastHarmVote_ = time;
1193 voter->lastNameChangePreventor_ = time;
1194 }
1195
1196 // count harmful votes
1197 voter->harmCount_++;
1198 }
1199 }
1200
1201 return eVoteItem::DoCheckValid( senderID );
1202 }
1203
DoFillToMessage(nMessage & m) const1204 virtual void DoFillToMessage( nMessage& m ) const
1205 {
1206 if ( player_ )
1207 m.Write( player_->ID() );
1208 else
1209 m.Write( 0 );
1210
1211 eVoteItem::DoFillToMessage( m );
1212 }
1213
1214 protected:
1215 virtual nDescriptor& DoGetDescriptor() const; // returns the creation descriptor
1216
1217 // get the language string prefix
1218 virtual char const * DoGetPrefix() const = 0;
1219
DoGetDescription() const1220 virtual tString DoGetDescription() const // returns the description of the voting item
1221 {
1222 // get name from player
1223 if ( player_ )
1224 name_ = player_->GetName();
1225
1226 return tString( tOutput( tString("$") + DoGetPrefix() + "_player_text", name_ ) );
1227 }
1228
DoGetDetails() const1229 virtual tString DoGetDetails() const // returns the detailed description of the voting item
1230 {
1231 // get name from player
1232 if ( player_ )
1233 name_ = player_->GetName();
1234
1235 return eVoteItem::DoGetDetails() + tString( tOutput( tString("$") + DoGetPrefix() + "_player_details_text", name_ ) );
1236 }
1237
GetMachine() const1238 nMachine * GetMachine() const
1239 {
1240 if ( !machine_ )
1241 {
1242 return 0;
1243 }
1244 else
1245 {
1246 return machine_->GetMachine();
1247 }
1248 }
1249 private:
1250 nObserverPtr< ePlayerNetID > player_; // keep player referenced
1251 nMachineObserver * machine_; // pointer to the machine of the player to be kicked
1252 mutable tString name_; // the name of the player to be kicked
1253 };
1254
1255 // something to vote on: kicking a player
1256 class eVoteItemKick: public virtual eVoteItemHarm
1257 {
1258 public:
1259 // constructors/destructor
eVoteItemKick(ePlayerNetID * player)1260 eVoteItemKick( ePlayerNetID* player )
1261 : eVoteItemHarm( player )
1262 {}
1263
~eVoteItemKick()1264 ~eVoteItemKick()
1265 {}
1266
1267 protected:
1268 // get the language string prefix
DoGetPrefix() const1269 virtual char const * DoGetPrefix() const{ return "kick"; }
1270
1271 #ifdef KRAWALL_SERVER
1272 // access level required for this kind of vote
DoGetAccessLevel() const1273 virtual tAccessLevel DoGetAccessLevel() const
1274 {
1275 return se_accessLevelVoteKick;
1276 }
1277 #endif
1278
1279 // return vote-specific extra bias
DoGetExtraBias() const1280 virtual int DoGetExtraBias() const
1281 {
1282 return se_votingBiasKick;
1283 }
1284
DoCheckValid(int senderID)1285 virtual bool DoCheckValid( int senderID )
1286 {
1287 ePlayerNetID * player = GetPlayer();
1288
1289 // check if player is protected from kicking
1290 if ( player && sn_GetNetState() != nCLIENT )
1291 {
1292 eVoter * voter = eVoter::GetVoter( player->Owner() );
1293 if ( voter )
1294 {
1295 double time = tSysTimeFloat();
1296 if ( time < voter->lastKickVote_ + se_minTimeBetweenKicks )
1297 {
1298 tOutput message("$vote_redundant");
1299 sn_ConsoleOut( message, senderID );
1300 return false;
1301 }
1302 else
1303 {
1304 voter->lastKickVote_ = time;
1305 voter->lastNameChangePreventor_ = time;
1306 }
1307 }
1308 }
1309
1310 return eVoteItemHarm::DoCheckValid( senderID );
1311 }
1312
DoExecute()1313 virtual void DoExecute() // called when the voting was successful
1314 {
1315 ePlayerNetID * player = GetPlayer();
1316 nMachine * machine = GetMachine();
1317 if ( player )
1318 {
1319 // kick the player, he is online
1320 se_VoteKickPlayer( player );
1321 }
1322 else if ( machine )
1323 {
1324 // the player left. Inform the machine that he would have gotten kicked.
1325 // kick all players that connected from that machine
1326 bool kick = false;
1327 for ( int user = MAXCLIENTS; user > 0; --user )
1328 {
1329 if ( &nMachine::GetMachine( user ) == machine )
1330 {
1331 se_VoteKickUser( user );
1332 kick = true;
1333 }
1334 }
1335
1336 // if no user could be kicked, notify at least the machine that
1337 // somebody would have been kicked.
1338 if ( !kick )
1339 {
1340 machine->OnKick();
1341 }
1342 }
1343 }
1344
1345 private:
1346 };
1347
1348 // harming vote items, server controlled
1349 class eVoteItemHarmServerControlled: public virtual eVoteItemServerControlled, public virtual eVoteItemHarm
1350 {
1351 public:
1352 // constructors/destructor
eVoteItemHarmServerControlled(ePlayerNetID * player=0)1353 eVoteItemHarmServerControlled( ePlayerNetID* player = 0 )
1354 : eVoteItemHarm( player )
1355 {}
1356
~eVoteItemHarmServerControlled()1357 ~eVoteItemHarmServerControlled()
1358 {}
1359 protected:
DoFillFromMessage(nMessage & m)1360 virtual bool DoFillFromMessage( nMessage& m )
1361 {
1362 // delegate
1363 bool ret = eVoteItemHarm::DoFillFromMessage( m );
1364
1365 // fill in description
1366 Update();
1367
1368 return ret;
1369 }
1370
DoFillToMessage(nMessage & m) const1371 virtual void DoFillToMessage( nMessage& m ) const
1372 {
1373 // should never be called on the client
1374 tASSERT( sn_GetNetState() != nCLIENT );
1375
1376 eVoteItemServerControlled::DoFillToMessage( m );
1377 }
1378 private:
Update()1379 virtual void Update() //!< update description and details
1380 {
1381 description_ = eVoteItemHarm::DoGetDescription();
1382 details_ = eVoteItemHarm::DoGetDetails();
1383 }
1384
DoGetDescriptor() const1385 virtual nDescriptor& DoGetDescriptor() const // returns the creation descriptor
1386 {
1387 return eVoteItemServerControlled::DoGetDescriptor();
1388 }
1389
DoGetDescription() const1390 virtual tString DoGetDescription() const // returns the description of the voting item
1391 {
1392 return eVoteItemServerControlled::DoGetDescription();
1393 }
1394
DoGetDetails() const1395 virtual tString DoGetDetails() const // returns the detailed description of the voting item
1396 {
1397 return eVoteItemServerControlled::DoGetDetails();
1398 }
1399 };
1400
1401 // remove vote items, server controlled
1402 class eVoteItemSuspend: public virtual eVoteItemHarmServerControlled
1403 {
1404 public:
1405 // constructors/destructor
eVoteItemSuspend(ePlayerNetID * player=0)1406 eVoteItemSuspend( ePlayerNetID* player = 0 )
1407 : eVoteItemHarm( player )
1408 {}
1409
~eVoteItemSuspend()1410 ~eVoteItemSuspend()
1411 {}
1412 protected:
1413 // get the language string prefix
DoGetPrefix() const1414 virtual char const * DoGetPrefix() const{ return "suspend"; }
1415
1416 #ifdef KRAWALL_SERVER
1417 // access level required for this kind of vote
DoGetAccessLevel() const1418 virtual tAccessLevel DoGetAccessLevel() const
1419 {
1420 return se_accessLevelVoteSuspend;
1421 }
1422 #endif
1423
1424 // return vote-specific extra bias
DoGetExtraBias() const1425 virtual int DoGetExtraBias() const
1426 {
1427 return se_votingBiasSuspend;
1428 }
1429
DoExecute()1430 virtual void DoExecute() // called when the voting was successful
1431 {
1432 ePlayerNetID * player = GetPlayer();
1433 if ( player )
1434 {
1435 player->Suspend( se_suspendRounds );
1436 }
1437 }
1438 };
1439
1440 // kick vote items, server controlled
1441 class eVoteItemKickServerControlled: public virtual eVoteItemHarmServerControlled, public virtual eVoteItemKick
1442 {
1443 public:
1444 // constructors/destructor
eVoteItemKickServerControlled(bool fromMenu,ePlayerNetID * player)1445 eVoteItemKickServerControlled( bool fromMenu, ePlayerNetID* player )
1446 : eVoteItemHarm( player ), eVoteItemKick( player ), fromMenu_( fromMenu )
1447 {}
1448
~eVoteItemKickServerControlled()1449 ~eVoteItemKickServerControlled()
1450 {}
1451 protected:
DoCheckValid(int senderID)1452 virtual bool DoCheckValid( int senderID )
1453 {
1454 // check whether enough harmful votes were collected already
1455 ePlayerNetID * p = GetPlayer();
1456 if ( p && !p->GetVoter() )
1457 {
1458 p->CreateVoter();
1459 }
1460
1461 if ( fromMenu_ && p && p->GetVoter() && p->GetVoter()->HarmCount() < se_kickMinHarm )
1462 {
1463 // try to transfor the vote to a suspension
1464 eVoteItem * item = tNEW ( eVoteItemSuspend )( p );
1465
1466 // let item check its validity
1467 if ( !item->CheckValid( senderID ) )
1468 {
1469 delete item;
1470 }
1471 else
1472 {
1473 // no objection? Broadcast it to everyone.
1474 item->Update();
1475 item->ReBroadcast( senderID );
1476 }
1477
1478 // and cancel this item here.
1479 return false;
1480 }
1481
1482 // no transformation needed or transformation failed. Proceed as usual.
1483 return eVoteItemHarm::DoCheckValid( senderID );
1484 }
1485
DoExecute()1486 virtual void DoExecute() // called when the voting was successful
1487 {
1488 eVoteItemKick::DoExecute();
1489 }
1490 private:
1491 bool fromMenu_; // flag set if the vote came from the menu
1492 };
1493
se_HandleKickVote(nMessage & m)1494 static void se_HandleKickVote( nMessage& m )
1495 {
1496 // set high default access level for menu issued kick votes, the true access level
1497 // is taken from the highest level player from the sending client later
1498 tCurrentAccessLevel level( tAccessLevel_Owner, true );
1499
1500 // accept message
1501 if ( eVoteItem::AcceptNewVote( m ) )
1502 {
1503 eVoteItemHarm* item = tNEW( eVoteItemKickServerControlled )( true, 0 );
1504 if ( !item->FillFromMessage( m ) )
1505 {
1506 delete item;
1507 return;
1508 }
1509 }
1510 }
1511
1512 static nDescriptor kill_vote_handler(231,se_HandleKickVote,"Kick vote");
1513
1514 // returns the creation descriptor
DoGetDescriptor() const1515 nDescriptor& eVoteItemHarm::DoGetDescriptor() const
1516 {
1517 return kill_vote_handler;
1518 }
1519
se_SendKick(ePlayerNetID * p)1520 static void se_SendKick( ePlayerNetID* p )
1521 {
1522 eVoteItemKick kick( p );
1523 kick.SendMessage();
1524 }
1525
1526 #ifdef KRAWALL_SERVER
1527
1528 // console with filter for redirection to anyone with a certain access level
1529 class eAccessConsoleFilter: public tConsoleFilter
1530 {
1531 public:
eAccessConsoleFilter(tAccessLevel level)1532 eAccessConsoleFilter( tAccessLevel level )
1533 :level_( level )
1534 {
1535 }
1536
Send()1537 void Send()
1538 {
1539 bool canSee[ MAXCLIENTS+1 ];
1540 for( int i = MAXCLIENTS; i>=0; --i )
1541 {
1542 canSee[i] = false;
1543 }
1544
1545 // look which clients have someone who can see the message
1546 for ( int i = se_PlayerNetIDs.Len()-1; i>=0; --i )
1547 {
1548 ePlayerNetID* player = se_PlayerNetIDs(i);
1549 if ( player->GetAccessLevel() <= level_ )
1550 {
1551 canSee[ player->Owner() ] = true;
1552 }
1553 }
1554
1555 // and send it
1556 for( int i = MAXCLIENTS; i>=0; --i )
1557 {
1558 if ( canSee[i] )
1559 {
1560 sn_ConsoleOut( message_, i );
1561 }
1562 }
1563
1564 message_.Clear();
1565 }
1566
~eAccessConsoleFilter()1567 ~eAccessConsoleFilter()
1568 {
1569 Send();
1570 }
1571 private:
1572 // we want to come first, the admins should get unfiltered output
DoGetPriority() const1573 virtual int DoGetPriority() const{ return -100; }
1574
1575 // don't actually filter; take line and add it to the message sent to the admin
DoFilterLine(tString & line)1576 virtual void DoFilterLine( tString &line )
1577 {
1578 //tColoredString message;
1579 message_ << tColoredString::ColorString(1,.3,.3) << "RA: " << tColoredString::ColorString(1,1,1) << line << "\n";
1580
1581 // don't let message grow indefinitely
1582 if (message_.Len() > 600)
1583 { Send();
1584 }
1585 }
1586
1587 tAccessLevel level_; // the access level required
1588 tColoredString message_; // the console message for the remote administrator
1589 };
1590
1591 // include vote items
1592 class eVoteItemInclude: public eVoteItemServerControlled
1593 {
1594 public:
1595 // constructors/destructor
eVoteItemInclude(tString const & file,tAccessLevel submitterLevel)1596 eVoteItemInclude( tString const & file, tAccessLevel submitterLevel )
1597 : eVoteItemServerControlled()
1598 , file_( file )
1599 {
1600 description_ = tOutput( "$vote_include_text", file );
1601 file_ = tString( "vote/" ) + file_;
1602 details_ = tOutput( "$vote_include_details_text", file_ );
1603
1604 if ( submitterLevel > se_accessLevelVoteIncludeExecute )
1605 {
1606 submitterLevel = se_accessLevelVoteIncludeExecute;
1607 }
1608 level_ = submitterLevel;
1609 }
1610
~eVoteItemInclude()1611 ~eVoteItemInclude()
1612 {}
1613 protected:
1614 // access level required for this kind of vote
DoGetAccessLevel() const1615 virtual tAccessLevel DoGetAccessLevel() const
1616 {
1617 return se_accessLevelVoteInclude;
1618 }
1619
1620 // return vote-specific extra bias
DoGetExtraBias() const1621 virtual int DoGetExtraBias() const
1622 {
1623 return se_votingBiasInclude;
1624 }
1625
Open(std::ifstream & s,int userToNotify)1626 bool Open( std::ifstream & s, int userToNotify )
1627 {
1628 bool ret = tConfItemBase::OpenFile( s, file_, tConfItemBase::All );
1629
1630 if ( ret )
1631 {
1632 return true;
1633 }
1634 else
1635 {
1636 con << tOutput( "$vote_include_error", file_ );
1637 sn_ConsoleOut( tOutput( "$vote_include_error", file_ ), userToNotify );
1638 return false;
1639 }
1640 }
1641
DoCheckValid(int senderID)1642 virtual bool DoCheckValid( int senderID )
1643 {
1644 std::ifstream s;
1645 return ( Open( s, senderID ) && eVoteItemServerControlled::DoCheckValid( senderID ) );
1646 }
1647
DoExecute()1648 virtual void DoExecute() // called when the voting was successful
1649 {
1650 // set the access level for the following operation
1651 tCurrentAccessLevel accessLevel( level_, true );
1652
1653 // load contents of voted file for real
1654 std::ifstream s;
1655 if ( Open( s, 0 ) )
1656 {
1657 sn_ConsoleOut( tOutput( "$vote_include_message", file_, tCurrentAccessLevel::GetName( level_ ) ) );
1658 eAccessConsoleFilter filter( level_ );
1659 tConfItemBase::ReadFile( s );
1660 }
1661 }
1662
1663 tString file_; //!< the file to include (inside the vote/ subdirectory)
1664 tAccessLevel level_; //!< the level to execute the file with
1665 };
1666
1667 // command vote items
1668 class eVoteItemCommand: public eVoteItemServerControlled
1669 {
1670 public:
1671 // constructors/destructor
eVoteItemCommand(tString const & command,tAccessLevel submitterLevel)1672 eVoteItemCommand( tString const & command, tAccessLevel submitterLevel )
1673 : eVoteItemServerControlled()
1674 , command_( command )
1675 {
1676 description_ = tOutput( "$vote_command_text", command );
1677 details_ = tOutput( "$vote_command_details_text", command );
1678
1679 if ( submitterLevel > se_accessLevelVoteCommandExecute )
1680 {
1681 submitterLevel = se_accessLevelVoteCommandExecute;
1682 }
1683 level_ = submitterLevel;
1684 }
1685
~eVoteItemCommand()1686 ~eVoteItemCommand()
1687 {}
1688 protected:
1689 // access level required for this kind of vote
DoGetAccessLevel() const1690 virtual tAccessLevel DoGetAccessLevel() const
1691 {
1692 return se_accessLevelVoteCommand;
1693 }
1694
1695 // return vote-specific extra bias
DoGetExtraBias() const1696 virtual int DoGetExtraBias() const
1697 {
1698 return se_votingBiasCommand;
1699 }
1700
DoExecute()1701 virtual void DoExecute() // called when the voting was successful
1702 {
1703 // set the access level for the following operation
1704 tCurrentAccessLevel accessLevel( level_, true );
1705
1706 // load contents of everytime.cfg for real
1707 std::istringstream s( static_cast< char const * >( command_ ) );
1708 sn_ConsoleOut( tOutput( "$vote_command_message" ) );
1709 eAccessConsoleFilter filter( tAccessLevel_Default );
1710 tConfItemBase::LoadLine(s);
1711 }
1712
1713 tString command_; //!< the command to execute
1714 tAccessLevel level_; //!< the level to execute the file with
1715 };
1716
1717 #endif
1718
1719 // **************************************************************************************
1720 // **************************************************************************************
1721
1722
1723 // menu item to silence selected players
1724 class eMenuItemKick: public uMenuItemAction
1725 {
1726 public:
eMenuItemKick(uMenu * m,ePlayerNetID * p)1727 eMenuItemKick(uMenu *m, ePlayerNetID* p )
1728 : uMenuItemAction( m, tOutput(""),tOutput("$kick_player_help" ) )
1729 {
1730 this->name_.Clear();
1731 this->name_.SetTemplateParameter(1, p->GetName() );
1732 this->name_ << "$kick_player_text";
1733 player_ = p;
1734 }
1735
~eMenuItemKick()1736 ~eMenuItemKick()
1737 {
1738 }
1739
Enter()1740 virtual void Enter()
1741 {
1742 if(sn_GetNetState()==nSERVER)
1743 {
1744 // kill user directly
1745 se_VoteKickPlayer( player_ );
1746 }
1747 {
1748 // issue kick vote
1749 se_SendKick( player_ );
1750 }
1751
1752 // leave menu to release smart pointers
1753 this->menu->Exit();
1754 }
1755 private:
1756 tCONTROLLED_PTR( ePlayerNetID ) player_; // keep player referenced
1757 };
1758
1759
1760 // ****************************************************************************************
1761 // ****************************************************************************************
1762
1763 static nSpamProtectionSettings se_voteSpamProtection( 50.0f, "SPAM_PROTECTION_VOTE", tOutput("$vote_spam_protection") );
1764
eVoter(nMachine & machine)1765 eVoter::eVoter( nMachine & machine )
1766 : nMachineDecorator( machine ), machine_( machine ), votingSpam_( se_voteSpamProtection )
1767 {
1768 selfReference_ = this;
1769 voters_.Add( this );
1770 harmCount_ = 0;
1771 lastKickVote_ = -1E+40;
1772 lastHarmVote_ = -1E+40;
1773 lastNameChangePreventor_ = -1E+40;
1774 lastChange_ = tSysTimeFloat();
1775 }
1776
~eVoter()1777 eVoter::~eVoter()
1778 {
1779 voters_.Remove( this );
1780 }
1781
Spam(int user,REAL spamLevel,tOutput const & message)1782 void eVoter::Spam( int user, REAL spamLevel, tOutput const & message )
1783 {
1784 if ( sn_GetNetState() == nSERVER )
1785 votingSpam_.CheckSpam( spamLevel, user, message );
1786 }
1787
IsSpamming(int user)1788 bool eVoter::IsSpamming( int user )
1789 {
1790 if ( sn_GetNetState() == nSERVER )
1791 {
1792 return nSpamProtection::Level_Ok != votingSpam_.CheckSpam( 0.0f, user, tOutput("$spam_vote_kick_issue") );
1793 }
1794
1795 return false;
1796 }
1797
1798 // *******************************************************************************
1799 // *
1800 // * OnDestroy
1801 // *
1802 // *******************************************************************************
1803 //!
1804 //!
1805 // *******************************************************************************
1806
OnDestroy(void)1807 void eVoter::OnDestroy( void )
1808 {
1809 tJUST_CONTROLLED_PTR< eVoter > keepAlive( this );
1810 selfReference_ = 0;
1811 }
1812
1813
1814 // *******************************************************************************
1815 // *
1816 // * OnDestroy
1817 // *
1818 // *******************************************************************************
1819 //!
1820 //! @return the last change to players on this voter in seconds
1821 //!
1822 // *******************************************************************************
1823
Age() const1824 REAL eVoter::Age() const
1825 {
1826 return tSysTimeFloat() - lastChange_;
1827 }
1828
1829
1830
1831 // *******************************************************************************
1832 // *
1833 // * AllowNameChange
1834 // *
1835 // *******************************************************************************
1836 //! @return true if the players belonging to this voter should be allowed to rename
1837 //!
1838 // *******************************************************************************
1839
AllowNameChange(void) const1840 bool eVoter::AllowNameChange( void ) const
1841 {
1842 return tSysTimeFloat() > this->lastNameChangePreventor_ + se_minTimeBetweenKicks;
1843 }
1844
RemoveFromGame()1845 void eVoter::RemoveFromGame()
1846 {
1847 tCONTROLLED_PTR( eVoter ) keeper( this );
1848
1849 voters_.Remove( this );
1850
1851 // remove from items
1852 for ( int i = eVoteItem::GetItems().Len()-1; i>=0; --i )
1853 {
1854 eVoteItem::GetItems()( i )->RemoveVoterCompletely( this );
1855 }
1856 }
1857
KickMenu()1858 void eVoter::KickMenu() // activate player kick menu
1859 {
1860 uMenu menu( "$player_police_kick_text" );
1861
1862 int size = se_PlayerNetIDs.Len();
1863 eMenuItemKick** items = tNEW( eMenuItemKick* )[ size ];
1864
1865 int i;
1866 for ( i = size-1; i>=0; --i )
1867 {
1868 ePlayerNetID* player = se_PlayerNetIDs[ i ];
1869 if ( player->IsHuman() )
1870 {
1871 items[i] = tNEW( eMenuItemKick )( &menu, player );
1872 }
1873 else
1874 {
1875 items[i] = 0;
1876 }
1877 }
1878
1879 menu.Enter();
1880
1881 for ( i = size - 1; i>=0; --i )
1882 {
1883 if( items[i] )
1884 delete items[i];
1885 }
1886 delete[] items;
1887 }
1888
1889 #ifndef DEDICATED
se_KeepConsoleSmall()1890 static bool se_KeepConsoleSmall()
1891 {
1892 return true;
1893 }
1894 #endif
1895
1896 static uMenu* votingMenu = 0;
1897
VotingMenu()1898 void eVoter::VotingMenu() // activate voting menu ( you can vote about suggestions there )
1899 {
1900 static bool recursion = false;
1901 if ( ! recursion )
1902 {
1903
1904 // expire old items
1905 if ( !VotingPossible() )
1906 return;
1907
1908 #ifndef DEDICATED
1909 rSmallConsoleCallback SmallConsole( se_KeepConsoleSmall );
1910
1911 // count items
1912 int size = eVoteItem::GetItems().Len();
1913 if ( size == 0 )
1914 return;
1915
1916 // fill menu
1917 uMenu menu( "$voting_menu_text" );
1918
1919 eMenuItemVote** items = tNEW( eMenuItemVote* )[ size ];
1920
1921 int i;
1922 for ( i = size-1; i>=0; --i )
1923 {
1924 items[i] = tNEW( eMenuItemVote )( &menu, eVoteItem::GetItems()( i ) );
1925 }
1926
1927 // enter menu
1928 recursion = true;
1929 votingMenu = &menu;
1930 menu.Enter();
1931 votingMenu = 0;
1932 recursion = false;
1933
1934 for ( i = size - 1; i>=0; --i )
1935 {
1936 delete items[i];
1937 }
1938 delete[] items;
1939
1940 // expire old items
1941 VotingPossible();
1942 #endif
1943 }
1944 }
1945
VotingPossible()1946 bool eVoter::VotingPossible()
1947 {
1948 // expire old items
1949 for ( int i = eVoteItem::GetItems().Len()-1; i>=0; --i )
1950 {
1951 eVoteItem::GetItems()( i )->Evaluate();
1952 }
1953
1954 if ( sn_GetNetState() != nCLIENT )
1955 {
1956 return false;
1957 }
1958
1959 return eVoteItem::GetItems().Len() > 0;
1960 }
1961
GetVoter(int ID,bool complain)1962 eVoter* eVoter::GetVoter( int ID, bool complain ) // find or create the voter for the specified ID
1963 {
1964 // the server has no voter
1965 #ifdef DEDICATED
1966 if ( ID == 0 )
1967 return NULL;
1968 #endif
1969
1970 // see if there is a real player on the specified ID
1971 if ( !se_allowVotingSpectator )
1972 {
1973 bool player = false;
1974 for ( int i = se_PlayerNetIDs.Len()-1; i>=0; --i )
1975 {
1976 ePlayerNetID* p = se_PlayerNetIDs(i);
1977 if ( p->Owner() == ID && p->CurrentTeam() )
1978 player = true;
1979 }
1980 if (!player)
1981 {
1982 if ( complain )
1983 {
1984 tOutput message("$vote_disabled_spectator");
1985 sn_ConsoleOut( message, ID );
1986 }
1987 return NULL;
1988 }
1989 }
1990
1991 // get machine from network subsystem
1992 nMachine & machine = nMachine::GetMachine( ID );
1993
1994 return GetVoter( machine );
1995 }
1996
GetVoter(nMachine & machine)1997 eVoter* eVoter::GetVoter( nMachine & machine ) // find or create the voter for the specified machine
1998 {
1999 // iterate through the machine's decorators, find a voter
2000 nMachineDecorator * run = machine.GetDecorators();
2001 while ( run )
2002 {
2003 eVoter * voter = dynamic_cast< eVoter * >( run );
2004 if ( voter )
2005 {
2006 // reinsert voter into lists
2007 if ( voter->ListID() < 0 )
2008 {
2009 voters_.Add( voter );
2010 voter->lastKickVote_ = -1E30;
2011 voter->lastNameChangePreventor_ = -1E30;
2012 }
2013
2014 // return result
2015 return voter;
2016 }
2017
2018 run = run->Next();
2019 }
2020
2021 // create new voter
2022 return tNEW(eVoter)( machine );
2023 }
2024
2025 tList< eVoter > eVoter::voters_; // list of all voters
2026
se_Cleanup()2027 static void se_Cleanup()
2028 {
2029 if ( nCallbackLoginLogout::User() == 0 )
2030 {
2031 if ( votingMenu )
2032 {
2033 votingMenu->Exit();
2034 }
2035
2036 if ( !nCallbackLoginLogout::Login() && eGrid::CurrentGrid() )
2037 {
2038 // uMenu::exitToMain = true;
2039 }
2040
2041 // client login/logout: delete voting items
2042 const tList< eVoteItem >& list = eVoteItem::GetItems();
2043 while ( list.Len() > 0 )
2044 {
2045 delete list(0);
2046 }
2047 }
2048 else if ( nCallbackLoginLogout::Login() )
2049 {
2050 // new user: send pending voting items
2051 const tList< eVoteItem >& list = eVoteItem::GetItems();
2052 for ( int i = list.Len()-1; i >= 0; -- i)
2053 {
2054 eVoteItem* vote = list( i );
2055 nMessage* m = vote->CreateMessage();
2056 m->Send( nCallbackLoginLogout::User() );
2057 }
2058 }
2059 }
2060
2061
2062 static nCallbackLoginLogout se_cleanup( se_Cleanup );
2063
~eVoteItem(void)2064 eVoteItem::~eVoteItem( void )
2065 {
2066 items_.Remove( this );
2067
2068 if ( menuItem_ )
2069 {
2070 menuItem_->item_ = 0;
2071 menuItem_->title.Clear();
2072 menuItem_->helpText.Clear();
2073 menuItem_ = 0;
2074 }
2075 }
2076
UpdateMenuItem(void)2077 void eVoteItem::UpdateMenuItem( void )
2078 {
2079 if ( menuItem_ )
2080 {
2081 menuItem_->title.Clear();
2082 menuItem_->title = GetDescription();
2083
2084 menuItem_->helpText.Clear();
2085 menuItem_->helpText << tString( tOutput( "$vote_details_help", GetDetails() ) );
2086 }
2087 }
2088
2089 // *******************************************************************************************
2090 // *
2091 // * Name
2092 // *
2093 // *******************************************************************************************
2094 //!
2095 //! @param senderID the ID of the network user ( default: take any )
2096 //! @return the name of the voter ( all players of that IP )
2097 //!
2098 // *******************************************************************************************
2099
Name(int senderID) const2100 tString eVoter::Name( int senderID ) const
2101 {
2102 tString name;
2103
2104 // collect the names of all players associated with this voter
2105 for ( int i = se_PlayerNetIDs.Len()-1; i>=0; --i )
2106 {
2107 ePlayerNetID* p = se_PlayerNetIDs(i);
2108 if ( eVoter::GetVoter( p->Owner() ) == this && ( senderID < 0 || p->Owner() == senderID ) )
2109 {
2110 if ( name.Len() > 1 )
2111 name << ", ";
2112 name << p->GetName();
2113 }
2114 }
2115
2116 if ( name.Len() < 2 )
2117 name = machine_.GetIP();
2118
2119 return name;
2120 }
2121
2122 // *******************************************************************************
2123 // *
2124 // * PlayerChanged
2125 // *
2126 // *******************************************************************************
2127 //!
2128 //!
2129 // *******************************************************************************
2130
PlayerChanged(void)2131 void eVoter::PlayerChanged( void )
2132 {
2133 this->lastChange_ = tSysTimeFloat();
2134 }
2135
2136 // *******************************************************************************
2137 // *
2138 // * HandleChat
2139 // *
2140 // *******************************************************************************
2141 //! @param p the player chatting
2142 //! @param message the rest of the message after "/vote"
2143 // *******************************************************************************
2144
HandleChat(ePlayerNetID * p,std::istream & message)2145 void eVoter::HandleChat( ePlayerNetID * p, std::istream & message ) //!< handles player "/vote" command.
2146 {
2147 // cloak the ID of the sener for privacy
2148 nCurrentSenderID cloak;
2149 if ( se_votingPrivacy > 1 )
2150 cloak.SetID(0);
2151
2152 if ( !p )
2153 {
2154 return;
2155 }
2156
2157 // read command part (kick, remove, include)
2158 tString command;
2159 message >> command;
2160 tToLower( command );
2161
2162 eVoter * voter = eVoter::GetVoter( p->Owner(), true ); // can't use ePlayerNedID::GetVoter here,
2163 // as it can't show a warning,
2164 // for example if the voter has only spectators
2165 if ( !eVoteItem::AcceptNewVote( voter, p->Owner() ) )
2166 {
2167 return;
2168 }
2169
2170 eVoteItem * item = 0;
2171
2172 if ( command == "kick" )
2173 {
2174 tString name;
2175 name.ReadLine( message );
2176 ePlayerNetID * toKick = ePlayerNetID::FindPlayerByName( name, p );
2177 if ( toKick )
2178 {
2179 // accept message
2180 item = tNEW( eVoteItemKickServerControlled )( false, toKick );
2181 }
2182 }
2183 else if ( command == "suspend" )
2184 {
2185 tString name;
2186 name.ReadLine( message );
2187 ePlayerNetID * toSuspend = ePlayerNetID::FindPlayerByName( name, p );
2188 if ( toSuspend )
2189 {
2190 // accept message
2191 item = tNEW( eVoteItemSuspend )( toSuspend );
2192 }
2193 }
2194 #ifdef KRAWALL_SERVER
2195 else if ( command == "include" )
2196 {
2197 tString file;
2198 file.ReadLine( message );
2199 {
2200 // accept message
2201 item = tNEW( eVoteItemInclude )( file, p->GetAccessLevel() );
2202 }
2203 }
2204 else if ( command == "command" )
2205 {
2206 tString console;
2207 console.ReadLine( message );
2208 {
2209 // accept message
2210 item = tNEW( eVoteItemCommand )( console, p->GetAccessLevel() );
2211 }
2212 }
2213 #endif
2214 else
2215 {
2216 #ifdef KRAWALL_SERVER
2217 sn_ConsoleOut( tOutput("$vote_unknown_command", command, "suspend, kick, include, command" ), p->Owner() );
2218 #else
2219 sn_ConsoleOut( tOutput("$vote_unknown_command", command, "suspend, kick" ), p->Owner() );
2220 #endif
2221 }
2222
2223 // nothing created
2224 if ( !item )
2225 {
2226 return;
2227 }
2228
2229 // let item check its validity
2230 if ( !item->CheckValid( p->Owner() ) )
2231 {
2232 delete item;
2233 return;
2234 }
2235
2236 // no objection? Broadcast it to everyone.
2237 item->Update();
2238 item->ReBroadcast( p->Owner() );
2239 }
2240
2241