Commits

Teemu Piippo committed 9fa7285

Added vote summaries for checking for recent votes with custom defined votes. The summary includes IPs if the vote command would have used them.

Comments (0)

Files changed (4)

 static	void			callvote_EndVote( void );
 static	ULONG			callvote_CountPlayersWhoVotedYes( void );
 static	ULONG			callvote_CountPlayersWhoVotedNo( void );
-static	bool			callvote_CheckForFlooding( FString &Command, FString &Parameters, ULONG ulPlayer );
+static	bool			callvote_CheckForFlooding( FString &Command, FString &Parameters, voteapplyinput_t vInput, ULONG ulPlayer );
 static	bool			callvote_CheckValidity( ULONG ulVoteType, FString &Parameters, FString Reason, voteapplyinput_t &Input );
 
 //*****************************************************************************
 				// If the vote passed, execute the command string.
 				if (( g_bVotePassed ) && ( !g_bVoteCancelled ) && ( !NETWORK_InClientMode( ) ))
 				{
-#if 0
-					// [BB, RC] If the vote is a kick vote, we have to rewrite g_VoteCommand to both use the stored IP, and temporarily ban it.
-					// [Dusk] Write the kick reason into the ban reason. Also
-					// check the first five characters, instead of four, to
-					// make "kickfromgame" not pass this.
-					FString KickReason;
-					KickReason.Format ("Vote kick, %lu to %lu (%s).",
-						CALLVOTE_CountVotes( true ),
-						CALLVOTE_CountVotes( false ),
-						!g_VoteReason.IsEmpty( ) ? g_VoteReason.GetChars( ) : "No reason given");
-
-					if ( strncmp( g_VoteCommand, "kick ", 5 ) == 0 )
-						g_VoteCommand.Format( "addban %s 10min \"%s\"",
-							NETWORK_AddressToString( g_KickVoteVictimAddress ), KickReason.GetChars( ));
-					// [Dusk] kickfromgame needs the reason as well. Append it to the command.
-					else if ( strncmp( g_VoteCommand, "kickfromgame", 12 ) == 0 )
-						g_VoteCommand.Format( "%s \"%s\"",
-							g_VoteCommand.GetChars( ), KickReason.GetChars( ));
-
-					AddCommandString( (char *)g_VoteCommand.GetChars( ));
-#endif // 0
-
 					// [Dusk] Retrieve the stored input and build the command from it.
 					g_VoteCommandInput.yes = CALLVOTE_CountVotes( true );
 					g_VoteCommandInput.no = CALLVOTE_CountVotes( false );
 
 	pVoteType = VOTEDEFS_VoteTypeByIndex( ulVoteType );
 	if ( !pVoteType )
+	{
+		Printf ("No such vote type %lu!\n", ulVoteType);
 		return;
+	}
 
 	Command = pVoteType->name;
 
 
 	// Check and make sure all the parameters are valid.
 	if ( callvote_CheckValidity( ulVoteType, Parameters, Reason, vInput ) == false )
+	{
+		Printf ("Validity check failed\n");
 		return;
+	}
 
 	// Prevent excessive re-voting.
-	if (( NETWORK_GetState( ) == NETSTATE_SERVER ) && callvote_CheckForFlooding( Command, Parameters, ulPlayer ) == false )
+	if (( NETWORK_GetState( ) == NETSTATE_SERVER ) && callvote_CheckForFlooding( Command, Parameters, vInput, ulPlayer ) == false )
 		return;
 
 	// Play the announcer sound for this.
 		VoteRecord.tTimeCalled = tNow;
 		VoteRecord.Address = SERVER_GetClient( g_ulVoteCaller )->Address;
 		VoteRecord.ulVoteType = VOTEDEFS_VoteIndexByName( Command );
+		VoteRecord.fsSummary = VOTEDEFS_VoteSummary( vInput );
 
 		if ( VoteRecord.ulVoteType == VOTECMD_KICK )
 			VoteRecord.KickAddress = g_KickVoteVictimAddress; 
 	if ( ulPlayer == g_ulVoteCaller && ( NETWORK_GetState( ) == NETSTATE_SERVER ) && !bYes )
 	{
 		// [BB] If a player canceled his own vote, don't prevent others from making this type of vote again.
-		g_PreviousVotes.back( ).ulVoteType = NUM_VOTECMDS;
+		// [Dusk] Use the votedefs function to get the number of votes defined
+		g_PreviousVotes.back( ).ulVoteType = VOTEDEFS_NumVoteTypes ();
 
 		SERVER_Printf( PRINT_HIGH, "Vote caller cancelled the vote.\n" );
 		g_bVoteCancelled = true;
 
 //*****************************************************************************
 //
-static bool callvote_CheckForFlooding( FString &Command, FString &Parameters, ULONG ulPlayer )
+static bool callvote_CheckForFlooding( FString &Command, FString &Parameters, voteapplyinput_t vInput, ULONG ulPlayer )
 {
+	// [Dusk] The server manages the vote cache
+	if ( NETWORK_GetState( ) == NETSTATE_CLIENT )
+		return true;
+
 	NETADDRESS_s	Address = SERVER_GetClient( ulPlayer )->Address;
 	ULONG			ulVoteType = VOTEDEFS_VoteIndexByName( Command );
 	time_t tNow;
 	if ( sv_limitnumvotes == false )
 		return true;
 
+	// [Dusk] Generate a summary of the current vote for checking
+	FString fsCurrentSummary = VOTEDEFS_VoteSummary( vInput );
+
 	// Run through the vote cache (backwards, from recent to old) and search for grounds on which to reject the vote.
 	for( std::list<VOTE_s>::reverse_iterator i = g_PreviousVotes.rbegin(); i != g_PreviousVotes.rend(); ++i )
 	{
 		}
 
 		// Specific votes ("map map30") that fail can't be re-proposed for ## minutes.
+#if 0
 		if (( ulVoteType == i->ulVoteType ) && ( !i->bPassed ) && (( tNow - i->tTimeCalled ) < VOTE_LITERALREVOTE_INTERVAL * MINUTE ))
 		{
 			int iMinutesLeft = static_cast<int>( 1 + ( i->tTimeCalled + VOTE_LITERALREVOTE_INTERVAL * MINUTE - tNow ) / MINUTE );
 				return false;
 			}
 		}
+#endif // 0
+		// [Dusk] Use the vote's summary for the check, it has all the stuff we need.
+		// In addition, I think that the player is well aware what vote "this specific
+		// vote" means, after all he's calling it in the first place...
+		int iMinutesLeft = static_cast<int>( 1 + ( i->tTimeCalled + VOTE_LITERALREVOTE_INTERVAL * MINUTE - tNow ) / MINUTE );
+		// Printf ("%s <-> %s\n", i->fsSummary.GetChars(), fsCurrentSummary.GetChars());
+		if (( ulVoteType == i->ulVoteType ) && ( !i->bPassed ) && ( iMinutesLeft > 0 ) &&
+			!i->fsSummary.CompareNoCase( fsCurrentSummary ) )
+		{
+			SERVER_PrintfPlayer( PRINT_HIGH, ulPlayer, "That specific vote was recently called, "
+				"and failed. You must wait %d minute%s to call it again.\n", iMinutesLeft,
+				( iMinutesLeft == 1 ? "" : "s" ));
+			return false;
+		}
 	}
 
 	return true;
 		if ( NETWORK_GetState( ) == NETSTATE_SERVER )
 			SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ),
 				"Vote call error: %s\n", vResults.error.GetChars( ));
+		Printf ("Vote call error: %s\n", vResults.error.GetChars( ));
 		return false;
 	}
 
 		}
 	}
 }
+
+//*****************************************************************************
+// [Dusk] Dump the recent votes
+CCMD ( dumprecentvotes )
+{
+	time_t tNow;
+	time( &tNow );
+	for( std::list<VOTE_s>::reverse_iterator i = g_PreviousVotes.rbegin(); i != g_PreviousVotes.rend(); ++i )
+	{
+		if (!VOTEDEFS_VoteTypeByIndex( i->ulVoteType ))
+			continue;
+
+		Printf( "Type: %s, called by %s %ld minutes ago\nSummary: %s\nResolution: %s\n",
+			VOTEDEFS_VoteTypeByIndex( i->ulVoteType )->name.GetChars( ),
+			NETWORK_AddressToStringIgnorePort( i->Address ),
+			( i->tTimeCalled - tNow ) / MINUTE,
+			i->fsSummary.GetChars( ),
+			i->bPassed ? "passed" : "failed" );
+	}
+}
 	// Was it passed?
 	bool			bPassed;
 
+	// [Dusk] The "summary" of this vote
+	FString			fsSummary;
+
 } VOTE_s;
 
 //*****************************************************************************
 		type.name = votename;
 		type.numParms = 0;
 		type.autogenCommand = false;
-		for (int i = 0; i < MAX_VOTEDEF_PARMS; i++)
+		for (int i = 0; i < MAX_VOTEDEF_PARMS; i++) {
 			type.parmTypes[i] = VOTEPARM_END;
+			type.parmFlags[i] = 0;
+		}
 		
 		if (sc.CheckToken (':')) {
 			// We're inheriting this vote type from another. Copy the properties
 			}
 		}
 		
-		unsigned parmCount = votedefs_TypeParmCount (type);
-		
 		// If the command is not given, generate one.
-		if (parmCount > 0 && type.command.IsEmpty ()) {
+		if (type.numParms > 0 && type.command.IsEmpty ()) {
 			type.command = type.name;
 			
-			for (unsigned i = 0; i < parmCount; i++) {
+			for (unsigned i = 0; i < type.numParms; i++) {
 				// Create the variable name
 				FString var;
 				var.Format ("%%%u", i);
 			type.autogenCommand = true;
 		} else {
 			// Inform the user of any unused variables
-			for (unsigned i = 0; i < parmCount; i++) {
+			for (unsigned i = 0; i < type.numParms; i++) {
 				FString needle;
 				needle.Format ("%%%u", i);
 				if (type.command.IndexOf (needle) == -1)
 	result.command.Substitute ("%no", fsNo);
 	result.command.Substitute ("%reason", input.reason.IsNotEmpty() ? input.reason : "No reason given");
 	
-	unsigned parmCount = votedefs_TypeParmCount (type);
-	
-	for (unsigned varnum = 0; varnum < parmCount; varnum++) {
+	for (unsigned varnum = 0; varnum < type.numParms; varnum++) {
 		if (input.args[varnum].IsEmpty ())
 			APPLY_ERROR ("argument %u not given", varnum);
 		
 		switch (vartype) {
 		case VOTEPARM_BYTE:
 			if (!fsval.IsInt() || lval < 0 || lval > 255)
-				APPLY_ERROR ("value of argument %u must be a number between 0 "
+				APPLY_ERROR ("argument %u must be a number between 0 "
 					"and 255, was %ld", varnum + 1, lval);
 			SUBST (sval);
 			break;
 		case VOTEPARM_SHORT:
 			if (!fsval.IsInt () || lval < 0 || lval > 65535)
-				APPLY_ERROR ("value of argument %u must be a number between 0 "
+				APPLY_ERROR ("argument %u must be a number between 0 "
 					"and 65535, was %ld", varnum + 1, lval);
 			SUBST (sval);
 			break;
 		case VOTEPARM_LONG:
 			if (!fsval.IsInt ())
-				APPLY_ERROR ("value of argument %u must be a number, was %s",
+				APPLY_ERROR ("argument %u must be a number, was %s",
 					varnum + 1, sval);
 			SUBST (sval);
 			break;
 		case VOTEPARM_FLOAT:
 			if (!fsval.IsFloat ())
-				APPLY_ERROR ("value of argument %u must be a floating "
+				APPLY_ERROR ("argument %u must be a floating "
 					"point number, was %s", varnum + 1, sval);
 			SUBST (sval);
 			break;
 			
 			// Must be valid
 			if (!SERVER_IsValidClient (idx))
-				APPLY_ERROR ("Player %s does not exist", sval);
+				APPLY_ERROR ("player %s does not exist", sval);
 			
 			// Must be in-game if PLAYERINGAME is given
 			if (CHECK_FLAG (PLAYERINGAME) && players[idx].bSpectating)
 			// in quotes.
 			FString str;
 			str.Format ("\"%s\"", sval);
-			str.Substitute (";", "\\;");
-			str.Substitute ("\"", "\\\"");
-			str.Substitute ("'", "\\'");
 			SUBST (str);
 			break;
 		}
 }
 
 // ============================================================================
+// Make a record of this vote for later comparing
+FString VOTEDEFS_VoteSummary (voteapplyinput_t input) {
+	votetype_t type = g_VoteDefs[input.type];
+	
+	FString record = type.name + " ";
+	
+	for (unsigned i = 0; i < type.numParms; i++) {
+		int vartype = type.parmTypes[i];
+		
+		FString fsval = input.args[i];
+		const char* sval = fsval.GetChars ();
+		long lval = atol (sval);
+		
+		switch (vartype) {
+		case VOTEPARM_BYTE:
+		case VOTEPARM_SHORT:
+		case VOTEPARM_LONG:
+		case VOTEPARM_FLOAT:
+		case VOTEPARM_MAP:
+			record += sval;
+			break;
+		case VOTEPARM_PLAYER: {
+			unsigned idx = (type.parmFlags[i] & 1 << VOTEPF_PLAYERINDEX) ?
+				lval : SERVER_GetPlayerIndexFromName (sval, true, false);
+			
+			// If the vote command uses the IP, store it. Otherwise an index
+			// should suffice.
+			FString needle;
+			needle.Format ("%%%u.ip", i);
+			FString locommand = type.command;
+			locommand.ToLower ();
+			if (locommand.IndexOf (needle) != -1)
+				record += NETWORK_AddressToStringIgnorePort (g_VoteDefIPTable[lval]);
+			else
+				record += sval;
+			break;
+		}
+		case VOTEPARM_STRING:
+			record += "\"";
+			record += sval;
+			record += "\"";
+			break;
+		default:
+			break;
+		}
+	}
+	
+	return record;
+}
+
+// ============================================================================
 // Store everybody's IPs so that they don't get lost when the command is
 // actually applied. The client could disconnect in the midst of a kick vote...
 void VOTEDEFS_StoreIPAddresses () {
 }
 
 // ============================================================================
+// Get usage info of calling this vote.
+FString VOTEDEFS_GetCallvoteExample (votetype_t* type) {
+	if (!type)
+		return "";
+	
+	FString cmd = "callvote ";
+	FString parm;
+	
+	// Name of command
+	cmd += type->name;
+	
+	// Parameters
+	for (unsigned i = 0; i < type->numParms; i++) {
+		parm = g_VoteDefParms[type->parmTypes[i]];
+		parm.ToLower ();
+		cmd.AppendFormat (" <%s>", parm.GetChars());
+	}
+	
+	// Reasons
+	cmd += " [reason]";
+	
+	// Add description if the type has it
+	if ( type->description.IsNotEmpty( ) )
+		cmd.AppendFormat( ": %s", type->description.GetChars( ) );
+	
+	return cmd;
+}
+
+// ============================================================================
 votetype_t* VOTEDEFS_VoteTypeByIndex (unsigned index) {
 	if (index >= g_VoteDefs.Size ())
 		return NULL;
 	return i;
 }
 
-// ============================================================================
-FString VOTEDEFS_GetCallvoteExample (votetype_t* type) {
-	if (!type)
-		return "";
-	
-	FString cmd = "callvote ";
-	FString parm;
-	
-	// Name of command
-	cmd += type->name;
-	
-	// Parameters
-	for (unsigned i = 0; i < type->numParms; i++) {
-		parm = g_VoteDefParms[type->parmTypes[i]];
-		parm.ToLower ();
-		cmd.AppendFormat (" <%s>", parm.GetChars());
-	}
-	
-	// Reasons
-	cmd += " [reason]";
-	
-	// Add description if the type has it
-	if ( type->description.IsNotEmpty( ) )
-		cmd.AppendFormat( ": %s", type->description.GetChars( ) );
-	
-	return cmd;
+unsigned VOTEDEFS_NumVoteTypes () {
+	return g_VoteDefs.Size();
 }
 
 // ============================================================================
 		Printf ("%u. %s\n", i+1, VOTEDEFS_GetCallvoteExample (&g_VoteDefs[i]).GetChars());
 }
 
+// ============================================================================
+// Debug command
+CCMD (dumpvotes) {
+	FString msg = "";
+	for (unsigned i = 0; i < g_VoteDefs.Size (); i++) {
+		votetype_t type = g_VoteDefs[i];
+		if (i)
+			msg += "======================================\n\n";
+		
+		msg.AppendFormat ("%s\nCommand: %s%s\nDescription: %s\nParms (%u):\n",
+			type.name.GetChars(), type.command.GetChars(),
+			type.autogenCommand ? " (autogenerated)" : "",
+			type.description.GetChars(), type.numParms);
+		
+		for (unsigned j = 0; j < type.numParms; j++) {
+			msg.AppendFormat ("%u. %s (%lu):", j, g_VoteDefParms[type.parmTypes[j]], type.parmFlags[j]);
+			
+			for (ULONG k = 0; k < VOTEPF_NUMFLAGS; k++)
+				if (type.parmFlags[j] & 1 << k)
+					msg.AppendFormat (" %s", g_VoteDefParmFlags[k]);
+			
+			msg += "\n";
+		}
+	}
+	
+	Printf ("%s", msg.GetChars());
+}
+
 #if 0
 void VOTEDEFS_VerifyCommand (votetype_t type) {
 	FScanner sc;
 	VOTEPF_PLAYERINDEX,		// player argument given as an index instead of a name
 	VOTEPF_PLAYERINGAME,	// player given must be in game
 	VOTEPF_MAPINROTATION,	// map parameter must be in rotation
+	VOTEPF_NUMFLAGS,		// end of vote list
 };
 
 // ============================================================================
 unsigned VOTEDEFS_VoteIndexByName (FString name);
 unsigned VOTEDEFS_VoteIndexByType (votetype_t* type);
 FString VOTEDEFS_GetCallvoteExample (votetype_t* type);
+FString VOTEDEFS_VoteSummary (voteapplyinput_t input);
+unsigned VOTEDEFS_NumVoteTypes ();
 
 #endif // __VOTEDEFS_H__