Attached Files | votedef.diff [^] (79,646 bytes) 2014-11-01 02:32 [Show Content] [Hide Content]diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -799,6 +799,7 @@
bots.cpp #ST
browser.cpp #ST
callvote.cpp #ST
+ callvotetype.cpp #ZA
campaign.cpp #ST
c_bind.cpp
c_cmds.cpp
diff --git a/src/callvote.cpp b/src/callvote.cpp
--- a/src/callvote.cpp
+++ b/src/callvote.cpp
@@ -65,13 +65,20 @@
#include "sv_main.h"
#include "v_video.h"
#include "maprotation.h"
+#include "deathmatch.h"
+#include "duel.h"
+#include "lastmanstanding.h"
+#include "team.h"
+#include "sv_ban.h"
+#include "callvotetype.h"
+#include "p_acs.h"
#include <list>
//*****************************************************************************
// VARIABLES
static VOTESTATE_e g_VoteState;
-static FString g_VoteCommand;
+static FString g_VoteDisplay;
static FString g_VoteReason;
static ULONG g_ulVoteCaller;
static ULONG g_ulVoteCountdownTicks = 0;
@@ -83,6 +90,8 @@
static ULONG g_ulPlayersWhoVotedNo[(MAXPLAYERS / 2) + 1];
static NETADDRESS_s g_KickVoteVictimAddress;
static std::list<VOTE_s> g_PreviousVotes;
+static VoteStateData g_VoteStateData;
+static FString g_CurrentVoteSummary;
//*****************************************************************************
// PROTOTYPES
@@ -90,10 +99,8 @@
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_CheckValidity( FString &Command, FString &Parameters );
-static ULONG callvote_GetVoteType( const char *pszCommand );
-static bool callvote_IsKickVote( const ULONG ulVoteType );
+static bool callvote_CheckForFlooding( ULONG& Command, FString& Parameters, ULONG ulPlayer );
+static void callvote_ExecuteVote( const VoteStateData& input );
//*****************************************************************************
// FUNCTIONS
@@ -150,27 +157,16 @@
g_PreviousVotes.back( ).bPassed = g_bVotePassed;
// If the vote passed, execute the command string.
- if (( g_bVotePassed ) && ( !g_bVoteCancelled ) &&
- ( NETWORK_GetState( ) != NETSTATE_CLIENT ) &&
- ( CLIENTDEMO_IsPlaying( ) == false ))
+ if (( g_bVotePassed )
+ && ( g_bVoteCancelled == false )
+ && ( NETWORK_InClientMode() == false ))
{
- // [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, [BB] but only if it's not empty.
- // [BB] "forcespec" votes need a similar handling.
- if ( ( strncmp( g_VoteCommand, "kick", 4 ) == 0 ) || ( strncmp( g_VoteCommand, "forcespec", 9 ) == 0 ) )
- {
- if ( strncmp( g_VoteCommand, "kick", 4 ) == 0 )
- g_VoteCommand.Format( "addban %s 10min \"Vote kick", NETWORK_AddressToString( g_KickVoteVictimAddress ) );
- else
- g_VoteCommand.Format( "kickfromgame_idx %d \"Vote forcespec", SERVER_FindClientByAddress ( g_KickVoteVictimAddress ) );
- g_VoteCommand.AppendFormat( ", %d to %d", static_cast<int>(callvote_CountPlayersWhoVotedYes( )), static_cast<int>(callvote_CountPlayersWhoVotedNo( )) );
- if ( g_VoteReason.IsNotEmpty() )
- g_VoteCommand.AppendFormat ( " (%s)", g_VoteReason.GetChars( ) );
- g_VoteCommand += ".\"";
- }
+ VoteValidateResult output;
+ g_VoteStateData.yes = callvote_CountPlayersWhoVotedYes( );
+ g_VoteStateData.no = callvote_CountPlayersWhoVotedNo( );
+ callvote_ExecuteVote( g_VoteStateData );
+ }
- AddCommandString( (char *)g_VoteCommand.GetChars( ));
- }
// Reset the module.
CALLVOTE_ClearVote( );
}
@@ -181,8 +177,11 @@
//*****************************************************************************
//
-void CALLVOTE_BeginVote( FString Command, FString Parameters, FString Reason, ULONG ulPlayer )
+void CALLVOTE_BeginVote( ULONG Command, FString Parameters, FString Reason, ULONG ulPlayer )
{
+ VoteValidateResult validateResult;
+ VoteClass* votetype = VOTEDEF_FindTypeByIndex( Command );
+
// Don't allow a vote in the middle of another vote.
if ( g_VoteState != VOTESTATE_NOVOTE )
{
@@ -191,47 +190,68 @@
return;
}
+ g_VoteStateData.type = votetype;
+ g_VoteStateData.arg = Parameters;
+ g_VoteStateData.caller = SERVER_GetCurrentClient( );
+ g_VoteStateData.yes = 0;
+ g_VoteStateData.no = 0;
+ g_VoteStateData.reason = Reason;
+
// Check and make sure all the parameters are valid.
- if ( callvote_CheckValidity( Command, Parameters ) == false )
+ if (( VOTEDEF_Validate( g_VoteStateData, validateResult ) == false )
+ && ( NETWORK_GetState() == NETSTATE_SERVER ))
+ {
+ SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient(),
+ "Cannot call that vote because %s\n", validateResult.error.GetChars() );
+ return;
+ }
+
+ if ( votetype == NULL )
return;
// Prevent excessive re-voting.
if (( NETWORK_GetState( ) == NETSTATE_SERVER ) && callvote_CheckForFlooding( Command, Parameters, ulPlayer ) == false )
return;
+ if ( votetype->argumentType() == VOTEARG_Player )
+ {
+ g_VoteStateData.targetAddress = SERVER_GetClient( validateResult.argument )->Address;
+ g_VoteStateData.targetAddressKnown = true;
+ }
+
+ g_CurrentVoteSummary = VOTEDEF_MakeVoteSummary( g_VoteStateData );
+
// Play the announcer sound for this.
ANNOUNCER_PlayEntry( cl_announcer, "VoteNow" );
- // Create the vote console command.
- g_VoteCommand = Command;
- g_VoteCommand += " ";
- g_VoteCommand += Parameters;
+ // [TP] How to display the vote?
+ g_VoteDisplay = votetype->name();
+
+ if ( votetype->hasArgument() )
+ g_VoteDisplay += " (" + Parameters + ")";
+
g_ulVoteCaller = ulPlayer;
g_VoteReason = Reason.Left(25);
// Create the record of the vote for flood prevention.
{
VOTE_s VoteRecord;
- VoteRecord.fsParameter = Parameters;
- time_t tNow;
- time( &tNow );
- VoteRecord.tTimeCalled = tNow;
+ time( &VoteRecord.tTimeCalled );
+ VoteRecord.VoteType = g_VoteStateData.type;
VoteRecord.Address = SERVER_GetClient( g_ulVoteCaller )->Address;
- VoteRecord.ulVoteType = callvote_GetVoteType( Command );
-
- if ( callvote_IsKickVote ( VoteRecord.ulVoteType ) )
- VoteRecord.KickAddress = g_KickVoteVictimAddress;
-
+ VoteRecord.Summary = g_CurrentVoteSummary;
g_PreviousVotes.push_back( VoteRecord );
}
// Display the message in the console.
{
- FString ReasonBlurb = ( g_VoteReason.Len( )) ? ( ", reason: \"" + g_VoteReason + "\"" ) : "";
+ FString ReasonBlurb = ( g_VoteReason.Len( )) ? ( ": \"" + g_VoteReason + "\"" ) : "";
+ FString IPString;
if ( NETWORK_GetState( ) == NETSTATE_SERVER )
- Printf( "%s\\c- (%s) has called a vote (\"%s\"%s).\n", players[ulPlayer].userinfo.netname, NETWORK_AddressToString( SERVER_GetClient( ulPlayer )->Address ), g_VoteCommand.GetChars(), ReasonBlurb.GetChars() );
- else
- Printf( "%s\\c- has called a vote (\"%s\"%s).\n", players[ulPlayer].userinfo.netname, g_VoteCommand.GetChars(), ReasonBlurb.GetChars() );
+ IPString.Format( " (%s)", NETWORK_AddressToString( SERVER_GetClient( ulPlayer )->Address ));
+
+ Printf( "%s%s\\c- has called a %s vote%s.\n", players[ulPlayer].userinfo.netname,
+ IPString.GetChars(), g_VoteDisplay.GetChars(), ReasonBlurb.GetChars() );
}
g_VoteState = VOTESTATE_INVOTE;
@@ -251,10 +271,11 @@
ULONG ulIdx;
g_VoteState = VOTESTATE_NOVOTE;
- g_VoteCommand = "";
+ g_VoteDisplay = "";
g_ulVoteCaller = MAXPLAYERS;
g_ulVoteCountdownTicks = 0;
g_ulShowVoteScreenTicks = 0;
+ g_VoteStateData = VoteStateData();
for ( ulIdx = 0; ulIdx < (( MAXPLAYERS / 2 ) + 1 ); ulIdx++ )
{
@@ -373,7 +394,7 @@
if ( ulPlayer == g_ulVoteCaller && ( NETWORK_GetState( ) == NETSTATE_SERVER ))
{
// [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;
+ g_PreviousVotes.back( ).VoteType = NULL;
SERVER_Printf( PRINT_HIGH, "Vote caller cancelled the vote.\n" );
g_bVoteCancelled = true;
@@ -437,7 +458,7 @@
{
return ( true );
}
-
+
SERVERCOMMANDS_PlayerVote( ulPlayer, false );
ulNumYes = callvote_CountPlayersWhoVotedYes( );
@@ -499,14 +520,108 @@
//*****************************************************************************
//
+// [TP] Executes the called vote
+//
+static void callvote_ExecuteVote( const VoteStateData& input )
+{
+ VoteValidateResult data;
+
+ // [TP] Validate again before executing it.
+ if ( VOTEDEF_Validate( input, data ) == false )
+ {
+ SERVER_Printf( PRINT_HIGH, TEXTCOLOR_ORANGE "Cannot execute vote: %s\n", data.error.GetChars() );
+ return;
+ }
+
+ if ( input.type->isNative() )
+ {
+ // [TP] Write the kick reason into the ban reason, [BB] but only if it's not empty.
+ FString votekickmessage;
+ votekickmessage.Format( "%s: %d to %d: %s",
+ input.type->nativeType() == VOTETYPE_Kick ? "Vote kick" : "Voted to be forced to spectate",
+ input.yes, input.no,
+ ( input.reason.IsNotEmpty() ? input.reason.GetChars() : "No reason given" ));
+
+ switch ( input.type->nativeType() )
+ {
+ case VOTETYPE_Kick:
+ {
+ // [BB, RC] If the vote is a kick vote, we have to use stored IP and temporarily ban it.
+ std::string response;
+ SERVERBAN_BanIP ( input.targetAddress, 10 * MINUTE, votekickmessage, response );
+ Printf( "[VOTEKICK] %s\n", response.c_str() );
+ break;
+ }
+
+ case VOTETYPE_ForceSpec:
+ SERVER_KickPlayerFromGame( data.argument, votekickmessage );
+ break;
+
+ case VOTETYPE_ChangeMap:
+ strncpy( level.nextmap, data.stringArgument, 8 );
+ level.flags |= LEVEL_CHANGEMAPCHEAT;
+ G_ExitLevel( 0, false );
+ break;
+
+ case VOTETYPE_Map:
+ {
+ // [TP] There's enough stuff in the map ccmd that this needs to be done..
+ // This should be safe since the votedef code ensures that the argument
+ // is a valid map name and no valid map name should break this.
+ char command[64];
+ sprintf( command, "map \"%s\"", data.stringArgument.GetChars() );
+ AddCommandString( command );
+ break;
+ }
+
+ case VOTETYPE_FragLimit:
+ fraglimit = data.argument;
+ break;
+
+ case VOTETYPE_DuelLimit:
+ duellimit = data.argument;
+ break;
+
+ case VOTETYPE_WinLimit:
+ winlimit = data.argument;
+ break;
+
+ case VOTETYPE_PointLimit:
+ pointlimit = data.argument;
+ break;
+
+ case VOTETYPE_TimeLimit:
+ timelimit = data.floatArgument;
+ break;
+
+ case VOTETYPE_NumNatives:
+ break; // impossible
+ }
+ }
+ else
+ {
+ DWORD argvalue = data.argument;
+
+ if (( input.type->argumentType() == VOTEARG_String )
+ || ( input.type->argumentType() == VOTEARG_Level ))
+ {
+ argvalue = ACS_PushAndReturnDynamicString( data.stringArgument, NULL, 0 );
+ }
+
+ Printf( "[VOTE] -> Script %d (%s)\n", input.type->scriptNumber(), input.arg.GetChars() );
+ int callerid = input.caller;
+ AActor* mo = PLAYER_IsValidPlayerWithMo( callerid ) ? players[callerid].mo : NULL;
+ P_StartScript( mo, NULL, input.type->scriptNumber(), NULL, false, argvalue, 0, 0, 1, false );
+ }
+}
+
+//*****************************************************************************
+//
void CALLVOTE_EndVote( bool bPassed )
{
// This is a client-only function.
- if (( NETWORK_GetState( ) != NETSTATE_CLIENT ) &&
- ( CLIENTDEMO_IsPlaying( ) == false ))
- {
+ if ( NETWORK_InClientMode() == false )
return;
- }
g_bVotePassed = bPassed;
callvote_EndVote( );
@@ -516,7 +631,7 @@
//
const char *CALLVOTE_GetCommand( void )
{
- return ( g_VoteCommand.GetChars( ));
+ return ( g_VoteDisplay.GetChars( ));
}
//*****************************************************************************
@@ -651,10 +766,9 @@
//*****************************************************************************
//
-static bool callvote_CheckForFlooding( FString &Command, FString &Parameters, ULONG ulPlayer )
+static bool callvote_CheckForFlooding( ULONG &Command, FString &Parameters, ULONG ulPlayer )
{
NETADDRESS_s Address = SERVER_GetClient( ulPlayer )->Address;
- ULONG ulVoteType = callvote_GetVoteType( Command );
time_t tNow;
time( &tNow );
@@ -670,11 +784,18 @@
// 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 )
{
+ VoteClass* type = i->VoteType;
+
+ if ( type == NULL )
+ continue;
+
// One *type* of vote per voter per ## minutes (excluding kick votes if they passed).
- if ( !( callvote_IsKickVote ( i->ulVoteType ) && i->bPassed ) && NETWORK_CompareAddress( i->Address, Address, true ) && ( ulVoteType == i->ulVoteType ) && (( tNow - i->tTimeCalled ) < VOTER_VOTETYPE_INTERVAL * MINUTE ))
+ if ( !(( type->flags() & VOTEF_NoLimitIfPassed ) && i->bPassed ) && NETWORK_CompareAddress( i->Address, Address, true ) &&
+ ( g_VoteStateData.type == i->VoteType ) && (( tNow - i->tTimeCalled ) < VOTER_VOTETYPE_INTERVAL * MINUTE ))
{
int iMinutesLeft = static_cast<int>( 1 + ( i->tTimeCalled + VOTER_VOTETYPE_INTERVAL * MINUTE - tNow ) / MINUTE );
- SERVER_PrintfPlayer( PRINT_HIGH, ulPlayer, "You must wait %d minute%s to call another %s vote.\n", iMinutesLeft, ( iMinutesLeft == 1 ? "" : "s" ), Command.GetChars() );
+ SERVER_PrintfPlayer( PRINT_HIGH, ulPlayer, "You must wait %d minute%s to call another %s vote.\n",
+ iMinutesLeft, ( iMinutesLeft == 1 ? "" : "s" ), type->name().GetChars() );
return false;
}
@@ -682,28 +803,21 @@
if ( NETWORK_CompareAddress( i->Address, Address, true ) && (( tNow - i->tTimeCalled ) < VOTER_NEWVOTE_INTERVAL * MINUTE ))
{
int iMinutesLeft = static_cast<int>( 1 + ( i->tTimeCalled + VOTER_NEWVOTE_INTERVAL * MINUTE - tNow ) / MINUTE );
- SERVER_PrintfPlayer( PRINT_HIGH, ulPlayer, "You must wait %d minute%s to call another vote.\n", iMinutesLeft, ( iMinutesLeft == 1 ? "" : "s" ));
+ SERVER_PrintfPlayer( PRINT_HIGH, ulPlayer, "You must wait %d minute%s to call another vote.\n",
+ iMinutesLeft, ( iMinutesLeft == 1 ? "" : "s" ));
return false;
}
// Specific votes ("map map30") that fail can't be re-proposed for ## minutes.
- if (( ulVoteType == i->ulVoteType ) && ( !i->bPassed ) && (( tNow - i->tTimeCalled ) < VOTE_LITERALREVOTE_INTERVAL * MINUTE ))
+ // [TP] Using generalized summaries. This takes care of the IP checking for instance.
+ if (( g_CurrentVoteSummary == i->Summary ) && ( !i->bPassed ) && (( tNow - i->tTimeCalled ) < VOTE_LITERALREVOTE_INTERVAL * MINUTE ))
{
- int iMinutesLeft = static_cast<int>( 1 + ( i->tTimeCalled + VOTE_LITERALREVOTE_INTERVAL * MINUTE - tNow ) / MINUTE );
+ int iMinutesLeft ( 1 + ( i->tTimeCalled + VOTE_LITERALREVOTE_INTERVAL * MINUTE - tNow ) / MINUTE );
- // Kickvotes (can't give the IP to clients!).
- if ( callvote_IsKickVote ( i->ulVoteType ) && ( !i->bPassed ) && NETWORK_CompareAddress( i->KickAddress, g_KickVoteVictimAddress, true ))
- {
- SERVER_PrintfPlayer( PRINT_HIGH, ulPlayer, "That specific player was recently on voted to be kicked or forced to spectate, but the vote failed. You must wait %d minute%s to call it again.\n", iMinutesLeft, ( iMinutesLeft == 1 ? "" : "s" ));
- return false;
- }
-
- // Other votes.
- if ( ( callvote_IsKickVote ( i->ulVoteType ) == false ) && ( stricmp( i->fsParameter.GetChars(), Parameters.GetChars() ) == 0 ))
- {
- SERVER_PrintfPlayer( PRINT_HIGH, ulPlayer, "That specific vote (\"%s %s\") was recently called, and failed. You must wait %d minute%s to call it again.\n", Command.GetChars(), Parameters.GetChars(), iMinutesLeft, ( iMinutesLeft == 1 ? "" : "s" ));
- return false;
- }
+ 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;
}
}
@@ -711,167 +825,6 @@
}
//*****************************************************************************
-//
-static bool callvote_CheckValidity( FString &Command, FString &Parameters )
-{
- // Get the type of vote this is.
- ULONG ulVoteCmd = callvote_GetVoteType( Command.GetChars( ));
- if ( ulVoteCmd == NUM_VOTECMDS )
- return ( false );
-
- // Check for any illegal characters.
- if ( callvote_IsKickVote ( ulVoteCmd ) == false )
- {
- int i = 0;
- while ( Parameters.GetChars()[i] != '\0' )
- {
- if ( Parameters.GetChars()[i] == ';' || Parameters.GetChars()[i] == ' ' )
- {
- if ( NETWORK_GetState( ) == NETSTATE_SERVER )
- SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ), "That vote command contained illegal characters.\n" );
- return ( false );
- }
- i++;
- }
- }
-
- // Then, make sure the parameter for each vote is valid.
- int parameterInt = atoi( Parameters.GetChars() );
- switch ( ulVoteCmd )
- {
- case VOTECMD_KICK:
- case VOTECMD_KICKFROMGAME:
- {
- if ( NETWORK_GetState( ) == NETSTATE_SERVER )
- {
- // Store the player's IP so he can't get away.
- ULONG ulIdx = SERVER_GetPlayerIndexFromName( Parameters.GetChars( ), true, false );
- if ( ulIdx < MAXPLAYERS )
- {
- if ( static_cast<LONG>(ulIdx) == SERVER_GetCurrentClient( ))
- {
- SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ), "You cannot call a vote to kick or to force to spectate yourself!\n" );
- return ( false );
- }
- // [BB] Don't allow anyone to kick somebody who is on the admin list. [K6] ...or is logged into RCON.
- if ( SERVER_GetAdminList()->isIPInList( SERVER_GetClient( ulIdx )->Address )
- || SERVER_GetClient( ulIdx )->bRCONAccess )
- {
- SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ), "This player is a server admin and thus can't be kicked or forced to spectate!\n" );
- return ( false );
- }
- g_KickVoteVictimAddress = SERVER_GetClient( ulIdx )->Address;
- return ( true );
- }
- else
- {
- SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ), "That player doesn't exist.\n" );
- return ( false );
- }
- }
- }
- break;
- case VOTECMD_MAP:
- case VOTECMD_CHANGEMAP:
-
- // Don't allow the command if the map doesn't exist.
- if ( !P_CheckIfMapExists( Parameters.GetChars( )))
- {
- if ( NETWORK_GetState( ) == NETSTATE_SERVER )
- SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ), "That map does not exist.\n" );
- return ( false );
- }
-
- // Don't allow us to leave the map rotation.
- if ( NETWORK_GetState( ) == NETSTATE_SERVER )
- {
- // [BB] Regardless of sv_maprotation, if the server has maps in the rotation,
- // assume players are restricted to these maps.
- if ( ( MAPROTATION_GetNumEntries() > 0 ) && ( MAPROTATION_IsMapInRotation( Parameters.GetChars( ) ) == false ) )
- {
- SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ), "That map is not in the map rotation.\n" );
- return ( false );
- }
- }
- break;
- case VOTECMD_FRAGLIMIT:
- case VOTECMD_WINLIMIT:
- case VOTECMD_DUELLIMIT:
-
- // Parameteter be between 0 and 255.
- if (( parameterInt < 0 ) || ( parameterInt >= 256 ))
- {
- if ( NETWORK_GetState( ) == NETSTATE_SERVER )
- SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ), "%s parameters must be between 0 and 255.\n", Command.GetChars() );
- return ( false );
- }
- else if ( parameterInt == 0 )
- {
- if (( Parameters.GetChars()[0] != '0' ) || ( Parameters.Len() != 1 ))
- return ( false );
- }
- Parameters.Format( "%d", parameterInt );
- break;
- case VOTECMD_TIMELIMIT:
- case VOTECMD_POINTLIMIT:
-
- // Parameteter must be between 0 and 65535.
- if (( parameterInt < 0 ) || ( parameterInt >= 65536 ))
- {
- if ( NETWORK_GetState( ) == NETSTATE_SERVER )
- SERVER_PrintfPlayer( PRINT_HIGH, SERVER_GetCurrentClient( ), "%s parameters must be between 0 and 65535.\n", Command.GetChars() );
- return ( false );
- }
- else if ( parameterInt == 0 )
- {
- if (( Parameters.GetChars()[0] != '0' ) || ( Parameters.Len() != 1 ))
- return ( false );
- }
- Parameters.Format( "%d", parameterInt );
- break;
- default:
-
- return ( false );
- }
-
- // Passed all checks!
- return ( true );
-}
-
-//*****************************************************************************
-//
-static ULONG callvote_GetVoteType( const char *pszCommand )
-{
- if ( stricmp( "kick", pszCommand ) == 0 )
- return VOTECMD_KICK;
- else if ( stricmp( "forcespec", pszCommand ) == 0 )
- return VOTECMD_KICKFROMGAME;
- else if ( stricmp( "map", pszCommand ) == 0 )
- return VOTECMD_MAP;
- else if ( stricmp( "changemap", pszCommand ) == 0 )
- return VOTECMD_CHANGEMAP;
- else if ( stricmp( "fraglimit", pszCommand ) == 0 )
- return VOTECMD_FRAGLIMIT;
- else if ( stricmp( "timelimit", pszCommand ) == 0 )
- return VOTECMD_TIMELIMIT;
- else if ( stricmp( "winlimit", pszCommand ) == 0 )
- return VOTECMD_WINLIMIT;
- else if ( stricmp( "duellimit", pszCommand ) == 0 )
- return VOTECMD_DUELLIMIT;
- else if ( stricmp( "pointlimit", pszCommand ) == 0 )
- return VOTECMD_POINTLIMIT;
-
- return NUM_VOTECMDS;
-}
-
-//*****************************************************************************
-//
-static bool callvote_IsKickVote( const ULONG ulVoteType )
-{
- return ( ( ulVoteType == VOTECMD_KICK ) || ( ulVoteType == VOTECMD_KICKFROMGAME ) );
-}
-
-//*****************************************************************************
// CONSOLE COMMANDS/VARIABLES
CUSTOM_CVAR( Int, sv_minvoters, 1, CVAR_ARCHIVE )
@@ -881,7 +834,6 @@
}
CVAR( Int, sv_nocallvote, 0, CVAR_ARCHIVE ); // 0 - everyone can call votes. 1 - nobody can. 2 - only players can.
CVAR( Bool, sv_nokickvote, false, CVAR_ARCHIVE );
-CVAR( Bool, sv_noforcespecvote, false, CVAR_ARCHIVE );
CVAR( Bool, sv_nomapvote, false, CVAR_ARCHIVE );
CVAR( Bool, sv_nochangemapvote, false, CVAR_ARCHIVE );
CVAR( Bool, sv_nofraglimitvote, false, CVAR_ARCHIVE );
@@ -895,6 +847,8 @@
CCMD( callvote )
{
ULONG ulVoteCmd;
+ FString arg, reason;
+ VoteClass* votetype;
// Don't allow a vote unless the player is a client.
if ( NETWORK_GetState( ) != NETSTATE_CLIENT )
@@ -906,7 +860,7 @@
if ( CLIENT_GetConnectionState( ) != CTS_ACTIVE )
return;
- if ( argv.argc( ) < 3 )
+ if ( argv.argc( ) < 2 )
{
Printf( "callvote <command> <parameters> [reason]: Calls a vote\n" );
return;
@@ -919,22 +873,33 @@
return;
}
- ulVoteCmd = callvote_GetVoteType( argv[1] );
- if ( ulVoteCmd == NUM_VOTECMDS )
+ if (( votetype = VOTEDEF_FindTypeByName( argv[1] )) == NULL )
{
- Printf( "Invalid callvote command.\n" );
+ Printf( "No such vote type '%s'.\nTry use one of: %s\n", argv[1],
+ VOTEDEF_GetVoteTypeList().GetChars() );
return;
}
- if ( argv.argc( ) >= 4 )
- CLIENTCOMMANDS_CallVote( ulVoteCmd, argv[2], argv[3] );
+ // [TP] We may need a second argument
+ if ( votetype->hasArgument() )
+ {
+ if ( argv.argc() < 3 )
+ {
+ Printf( "You need to supply an argument to call a %s vote:\nUsage: %s",
+ votetype->name().GetChars(), votetype->usage().GetChars());
+ return;
+ }
+
+ arg = argv[2];
+ reason = ( argv.argc() >= 4 ) ? argv[3] : "";
+ }
else
- CLIENTCOMMANDS_CallVote( ulVoteCmd, argv[2], "" );
-/*
- g_lBytesSent += g_LocalBuffer.cursize;
- if ( g_lBytesSent > g_lMaxBytesSent )
- g_lMaxBytesSent = g_lBytesSent;
-*/
+ {
+ arg = "";
+ reason = ( argv.argc() >= 3 ) ? argv[2] : "";
+ }
+
+ CLIENTCOMMANDS_CallVote( votetype->index(), arg, reason );
NETWORK_LaunchPacket( CLIENT_GetLocalBuffer( ), CLIENT_GetServerAddress( ));
NETWORK_ClearBuffer( CLIENT_GetLocalBuffer( ));
}
@@ -1010,3 +975,25 @@
}
}
}
+
+//*****************************************************************************
+//
+// [TP] What has g_PreviousVotes eaten?
+//
+CCMD ( recentvotes )
+{
+ for( std::list<VOTE_s>::reverse_iterator i = g_PreviousVotes.rbegin(); i != g_PreviousVotes.rend(); ++i )
+ {
+ Printf( "- " );
+
+ if ( i->VoteType == NULL )
+ Printf( "<canceled>" );
+ else
+ Printf( "%s", i->VoteType->name().GetChars() );
+
+ char timestring[256];
+ strftime( timestring, sizeof timestring, "%F %T", localtime( &i->tTimeCalled ));
+
+ Printf( ", called by %s on %s\n", NETWORK_AddressToString( i->Address ), timestring );
+ }
+}
diff --git a/src/callvote.h b/src/callvote.h
--- a/src/callvote.h
+++ b/src/callvote.h
@@ -66,21 +66,7 @@
#define VOTE_LITERALREVOTE_INTERVAL 10
#define VOTE_LONGEST_INTERVAL 10 // Sets when old votes are removed from the flood cache. Set to the longest interval of the above.
-//*****************************************************************************
-enum
-{
- VOTECMD_KICK,
- VOTECMD_KICKFROMGAME,
- VOTECMD_MAP,
- VOTECMD_CHANGEMAP,
- VOTECMD_FRAGLIMIT,
- VOTECMD_TIMELIMIT,
- VOTECMD_WINLIMIT,
- VOTECMD_DUELLIMIT,
- VOTECMD_POINTLIMIT,
-
- NUM_VOTECMDS
-};
+class VoteClass;
//*****************************************************************************
typedef enum
@@ -102,18 +88,15 @@
// Time that this vote was called.
time_t tTimeCalled;
- // The type of vote (see NUM_VOTECMDS).
- ULONG ulVoteType;
-
- // Parameter of the vote ("map01", "50", etc).
- FString fsParameter;
-
- // For kick votes: the address being kicked.
- NETADDRESS_s KickAddress;
-
// Was it passed?
bool bPassed;
+ // [TP] The type of this vote
+ VoteClass* VoteType;
+
+ // [TP] Unique summary for this vote (contains type and argument and whatever else)
+ FString Summary;
+
} VOTE_s;
//*****************************************************************************
@@ -122,7 +105,7 @@
void CALLVOTE_Construct( void );
void CALLVOTE_Tick( void );
//void CALLVOTE_Render( void );
-void CALLVOTE_BeginVote( FString Command, FString Parameters, FString Reason, ULONG ulPlayer );
+void CALLVOTE_BeginVote( ULONG Command, FString Parameters, FString Reason, ULONG ulPlayer );
void CALLVOTE_ClearVote( void );
bool CALLVOTE_VoteYes( ULONG ulPlayer );
bool CALLVOTE_VoteNo( ULONG ulPlayer );
@@ -144,7 +127,6 @@
EXTERN_CVAR( Int, sv_minvoters );
EXTERN_CVAR( Int, sv_nocallvote )
EXTERN_CVAR( Bool, sv_nokickvote );
-EXTERN_CVAR( Bool, sv_noforcespecvote );
EXTERN_CVAR( Bool, sv_nomapvote );
EXTERN_CVAR( Bool, sv_nochangemapvote );
EXTERN_CVAR( Bool, sv_nofraglimitvote );
diff --git a/src/callvotetype.cpp b/src/callvotetype.cpp
new file mode 100644
--- /dev/null
+++ b/src/callvotetype.cpp
@@ -0,0 +1,623 @@
+/*
+ * Zandronum source code
+ * Copyright (C) 2012-2014 Teemu Piippo
+ * Copyright (C) 2012-2014 Zandronum Development Team
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of the Skulltag Development Team nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * 4. Redistributions in any form must be accompanied by information on how to
+ * obtain complete source code for the software and any accompanying
+ * software that uses the software. The source code must either be included
+ * in the distribution or be available for no more than the cost of
+ * distribution plus a nominal fee, and must be freely redistributable
+ * under reasonable conditions. For an executable file, complete source
+ * code means the source code for all modules it contains. It does not
+ * include source code for modules or files that typically accompany the
+ * major components of the operating system on which the executable file
+ * runs.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Filename: votedef.cpp
+ *
+ * Description: VOTEDEF parser, vote type manager
+ *
+ * -----------------------------------------------------------------------------
+ */
+
+#include "doomdef.h"
+#include "info.h"
+#include "tarray.h"
+#include "callvotetype.h"
+#include "network.h"
+#include "sv_main.h"
+#include "g_level.h"
+#include "v_text.h"
+#include "c_dispatch.h"
+#include "maprotation.h"
+#include "c_cvars.h"
+#include "i_system.h"
+#include "doomerrors.h"
+#include "p_acs.h"
+
+static TArray<VoteClass*> g_Defs;
+static NETADDRESS_s g_StoredIPAddress;
+
+const char* g_ParameterTokens[] =
+{
+ "INT",
+ "FLOAT",
+ "PLAYER",
+ "MAP",
+ "STRING",
+ NULL
+};
+
+const char* g_PropertyTokens[] =
+{
+ "ARGUMENT",
+ "SCRIPTNUMBER",
+ "DESCRIPTION",
+ "FORBIDCVAR",
+ "ARGUMENTLABEL",
+ "MENURANGE",
+ "MENUINCREMENT",
+ NULL
+};
+
+const char* g_FlagTokens[] =
+{
+ "NOPASSEDLIMIT",
+ "NOTONSELF",
+ "NOTONADMIN",
+ "PLAYERASINDEX",
+ "NOTONSPECTATORS",
+ "MENUSLIDER",
+ "MAPINROTATION",
+ NULL
+};
+
+const char* g_NativeNames[] =
+{
+ "KICK",
+ "FORCESPEC",
+ "CHANGEMAP",
+ "MAP",
+ "FRAGLIMIT",
+ "TIMELIMIT",
+ "POINTLIMIT",
+ "DUELLIMIT",
+ "WINLIMIT",
+ NULL
+};
+
+enum VotedefProperty
+{
+ VTPROP_Argument,
+ VTPROP_ScriptNumber,
+ VTPROP_Description,
+ VTPROP_ForbidCVar,
+ VTPROP_ArgumentLabel,
+ VTPROP_MenuRange,
+ VTPROP_MenuIncrement,
+};
+
+//
+// [TP] Vote definition constructor
+//
+VoteClass::VoteClass( const FString& name, NativeVoteType native ) :
+ _name( name ),
+ _scriptNumber( 0 ),
+ _flags( 0 ),
+ _argumentType( VOTEARG_NumArgTypes ),
+ _minimum( INT_MAX ),
+ _maximum( INT_MAX ),
+ _native( native ),
+ _index( g_Defs.Size() ),
+ _menuMinimum( 0.0f ),
+ _menuMaximum( 0.0f ),
+ _menuIncrement( 1.0f ) {}
+
+//
+// [TP] Vote definition destructor
+//
+VoteClass::~VoteClass() {}
+
+//
+// [TP] Initializes custom vote types.
+//
+void VOTEDEF_Construct()
+{
+ if ( Wads.CheckNumForName( "VOTEDEF" ) == -1 )
+ return;
+
+ // [TP] Startup message. :)
+ Printf( "VOTEDEF_Construct: Loading vote type definitions.\n" );
+ atterm( VOTEDEF_Destruct );
+
+ int lump, lastlump;
+ lastlump = 0;
+
+ while (( lump = Wads.FindLump( "VOTEDEF", &lastlump )) != -1 )
+ VOTEDEF_ParseLump( lump );
+}
+
+//
+// [TP] De-initializes the votedef stuff.
+//
+void VOTEDEF_Destruct()
+{
+ for ( unsigned i = 0; i < g_Defs.Size(); ++i )
+ delete g_Defs[i];
+
+ g_Defs.Clear();
+}
+
+static double VOTEDEF_GetNumeric( FScanner& sc )
+{
+ if ( sc.CheckToken( TK_FloatConst ) == false )
+ sc.MustGetToken( TK_IntConst );
+
+ return sc.Float;
+}
+
+//
+// [TP] Parse a single VOTEDEF lump.
+//
+void VOTEDEF_ParseLump( int lump )
+{
+ FScanner sc( lump );
+
+ while ( sc.GetToken() )
+ {
+ bool isnative = false;
+ NativeVoteType native = VOTETYPE_NumNatives;
+
+ // [TP] 'votetype' is the only command we have right now.
+ if ( sc.Compare( "VOTETYPE" ) == false )
+ sc.ScriptError( "Expected `votetype`, got %s", sc.String );
+
+ // [TP] See if this vote type is native.
+ if ( sc.CheckToken( TK_Native ))
+ isnative = true;
+
+ sc.MustGetAnyToken();
+ FString votename = sc.String;
+ FString forbidCVarName;
+
+ if ( isnative )
+ native = NativeVoteType( sc.MustMatchString( g_NativeNames ));
+
+ // [TP] Check that it's not already defined
+ if ( VOTEDEF_FindTypeByName( votename ))
+ sc.ScriptError( "Vote type `%s` is already defined", votename.GetChars() );
+
+ VoteClass* type = new VoteClass( votename, native );
+
+ bool gotMenuRange = false;
+ sc.MustGetToken( '{' );
+
+ while ( sc.CheckToken( '}' ) == false )
+ {
+ // [TP] The file shouldn't end in the middle of a block
+ if ( sc.GetToken() == false )
+ sc.ScriptError( "Unexpected end of file, did you miss a '}'?" );
+
+ // [TP] Try flags
+ int flagindex;
+ if (( flagindex = sc.MatchString( g_FlagTokens )) != -1 )
+ {
+ sc.MustGetToken( ';' );
+ type->_flags |= ( 1 << flagindex );
+ continue;
+ }
+
+ // [TP] Try properties
+ int prop = sc.MustMatchString( g_PropertyTokens );
+ sc.MustGetToken( '=' );
+
+ switch ( VotedefProperty( prop ))
+ {
+ case VTPROP_Argument:
+ sc.MustGetAnyToken();
+ type->_argumentType = VoteArgumentType( sc.MustMatchString( g_ParameterTokens ));
+
+ // [TP] Int and float types must have a value range.
+ if (( type->argumentType() == VOTEARG_Int )
+ || ( type->argumentType() == VOTEARG_Float ))
+ {
+ sc.MustGetToken( '(' );
+ type->_minimum = VOTEDEF_GetNumeric( sc );
+ sc.MustGetToken( ',' );
+ type->_maximum = VOTEDEF_GetNumeric( sc );
+ sc.MustGetToken( ')' );
+ }
+ break;
+
+ case VTPROP_ForbidCVar:
+ sc.MustGetAnyToken();
+ forbidCVarName = sc.String;
+ break;
+
+ case VTPROP_ScriptNumber:
+ sc.MustGetToken( TK_IntConst );
+ type->_scriptNumber = sc.Number;
+
+ if ( type->scriptNumber() != clamp( type->scriptNumber(), 1, 999 ) )
+ sc.ScriptError( "Script number must be between values 1 and 999." );
+ break;
+
+ case VTPROP_Description:
+ sc.MustGetAnyToken();
+ type->_description = sc.String;
+ break;
+
+ case VTPROP_ArgumentLabel:
+ sc.MustGetToken( TK_StringConst );
+ type->_argumentLabel = sc.String;
+ break;
+
+ case VTPROP_MenuRange:
+ type->_menuMinimum = VOTEDEF_GetNumeric( sc );
+ sc.MustGetToken( ',' );
+ type->_menuMaximum = VOTEDEF_GetNumeric( sc );
+ gotMenuRange = true;
+ break;
+
+ case VTPROP_MenuIncrement:
+ type->_menuIncrement = VOTEDEF_GetNumeric( sc );
+ break;
+ }
+
+ sc.MustGetToken( ';' );
+ }
+
+ if ( type->isNative() == false
+ && type->_scriptNumber == 0 )
+ {
+ sc.ScriptError( "Non-native vote type '%s' needs a script number.",
+ type->_name.GetChars() );
+ }
+
+ // [TP] Generate the forbid CVar name if it's not given.
+ if ( forbidCVarName.IsEmpty() )
+ forbidCVarName = "sv_no" + type->name() + "vote";
+
+ // [TP] If the CVar does not exist, create it now.
+ if (( type->_forbidCVar = FindCVar( forbidCVarName, NULL )) == NULL )
+ {
+ FBoolCVar* var = new FBoolCVar( forbidCVarName, false, CVAR_AUTO | CVAR_ARCHIVE );
+ var->SetArchiveBit();
+ type->_forbidCVar = var;
+ }
+
+ // [TP] Put a colon after the argument label if it's not empty.
+ // If it is empty, we want it to be an empty string.
+ if ( type->_argumentLabel.IsNotEmpty() )
+ type->_argumentLabel += ":";
+
+ // [TP] Fill in default menu range if not explicitly given
+ if ( gotMenuRange == false )
+ {
+ type->_menuMinimum = type->_minimum;
+ type->_menuMaximum = type->_maximum;
+ }
+
+ // [TP] If we use integral values, ensure the numbers given aren't floating point
+ if ( type->argumentType() == VOTEARG_Int )
+ {
+ const double epsilon = 0.0000000001;
+ float values[] = { type->minimum(), type->maximum(), type->menuMinimum(),
+ type->menuMaximum(), type->menuIncrement() };
+ const char* valueNames[] = { "Minimum", "Maximum", "Menu minimum",
+ "Menu maximum", "Menu increment" };
+
+ for( int i = 0; i < int( sizeof values / sizeof *values ); ++i )
+ {
+ if ( fabsf( values[i] - floor( values[i] )) > epsilon )
+ {
+ sc.ScriptError( "%s of vote type '%s' is not an integer",
+ valueNames[i], type->name().GetChars() );
+ }
+ }
+ }
+
+ g_Defs.Push( type );
+ }
+}
+
+//
+// [TP] An error function for VOTEDEF_Validate
+//
+static void STACK_ARGS VOTEDEF_ValidateError( const char* fmtstr, ... ) GCCPRINTF( 1, 2 );
+static void STACK_ARGS VOTEDEF_ValidateError( const char* fmtstr, ... )
+{
+ va_list va;
+ char errortext[1024];
+ va_start( va, fmtstr );
+ vsprintf( errortext, fmtstr, va );
+ va_end( va );
+ throw FString( errortext );
+}
+
+//
+// [TP] Apply given input data, performs validity checks and compiles some data
+// needed for vote execution.
+//
+bool VOTEDEF_Validate( VoteStateData const& input, VoteValidateResult& result )
+{
+ try
+ {
+ VoteClass* votetype = input.type;
+
+ if ( votetype == NULL )
+ VOTEDEF_ValidateError( "a bad vote class was given" );
+
+ // [TP] Reset the result
+ result.error = "";
+ result.stringArgument = "";
+ result.argument = 0;
+ result.floatArgument = 0.0;
+
+ // [TP] Check that we got the proper parameter
+ if ( votetype->hasArgument() )
+ {
+ if ( input.arg.IsEmpty() )
+ VOTEDEF_ValidateError( "the vote argument was not given" );
+
+ // [TP] The variable contens in various types
+ DWORD valueAsIntegral = atol( input.arg );
+ double valueAsDouble = atof( input.arg );
+ int idx = 0;
+
+ switch ( votetype->argumentType() )
+ {
+ case VOTEARG_Int:
+ if (( input.arg.IsInt() == false )
+ || ( valueAsIntegral < DWORD( votetype->minimum() ))
+ || ( valueAsIntegral > DWORD( votetype->maximum() )))
+ {
+ VOTEDEF_ValidateError( "%s is not an integral number between %d and %d",
+ input.arg.GetChars(), DWORD( votetype->minimum() ), DWORD( votetype->maximum() ));
+ }
+
+ result.argument = valueAsIntegral;
+ break;
+
+ case VOTEARG_Float:
+ if (( input.arg.IsFloat() == false )
+ || ( valueAsDouble < votetype->minimum() )
+ || ( valueAsDouble > votetype->maximum() ))
+ {
+ VOTEDEF_ValidateError( "%s is not a number between %f and %f",
+ input.arg.GetChars(), votetype->minimum(), votetype->maximum() );
+ }
+
+ // [TP] The argument needs to be converted to fixed point
+ result.argument = valueAsDouble * FRACUNIT;
+ result.floatArgument = valueAsDouble;
+ break;
+
+ case VOTEARG_Player:
+ // [TP] Only need to test the player index of the target address isn't already known
+ if (( votetype->nativeType() != VOTETYPE_Kick ) || ( input.targetAddressKnown == false ))
+ {
+ // [TP] Find the client by name, unless PLAYERINDEX is given
+ if ( votetype->flags() & VOTEF_PlayerAsIndex )
+ {
+ if ( input.arg.IsInt() == false )
+ VOTEDEF_ValidateError( "'%s' is not a valid player index", input.arg.GetChars() );
+
+ idx = valueAsIntegral;
+ }
+ else
+ {
+ idx = SERVER_GetPlayerIndexFromName( input.arg, true, false );
+ }
+
+ // [TP] Must be a valid player
+ if ( SERVER_IsValidClient( idx ) == false )
+ VOTEDEF_ValidateError( "there is no player #%d", idx );
+
+ // [TP] Check if this is to be done on spectators
+ if (( votetype->flags() & VOTEF_NotOnSpectators ) && ( players[idx].bSpectating ))
+ VOTEDEF_ValidateError( "%s is a spectator", players[idx].userinfo.netname );
+
+ // [BB] Don't allow anyone to kick somebody who is on the admin list.
+ if (( votetype->flags() & VOTEF_NotOnAdmin ) && ( VOTEDEF_IsPlayerAdmin( idx )))
+ VOTEDEF_ValidateError( "you may not do this on %s", players[idx].userinfo.netname );
+
+ // [TP] Not self if NOTSELF is set
+ if (( votetype->flags() & VOTEF_NotOnSelf ) && ( idx == input.caller ))
+ VOTEDEF_ValidateError( "you are %s!", players[idx].userinfo.netname );
+ }
+
+ result.argument = idx;
+ break;
+
+ case VOTEARG_Level:
+ if ( P_CheckIfMapExists( input.arg ) == false )
+ VOTEDEF_ValidateError( "there is no such map '%s'", input.arg.GetChars() );
+
+ if (( votetype->flags() & VOTEF_MapInRotation )
+ && ( NETWORK_GetState() == NETSTATE_SERVER )
+ && ( sv_maprotation )
+ && ( MAPROTATION_IsMapInRotation( input.arg ) == false ))
+ {
+ VOTEDEF_ValidateError( "the map '%s' is not in rotation", input.arg.GetChars() );
+ }
+
+ result.stringArgument = input.arg;
+ break;
+
+ case VOTEARG_String:
+ if ( input.arg.Len() >= MAX_NETWORK_STRING )
+ VOTEDEF_ValidateError( "the argument is too long" );
+
+ result.stringArgument = input.arg;
+ break;
+
+ case VOTEARG_NumArgTypes:
+ break;
+ }
+ }
+
+ // [TP] Check that we won't run a client-side script as a result of this vote.
+ if ( votetype->isNative() == false )
+ {
+ FBehavior* module = NULL;
+ const ScriptPtr* scriptdata = FBehavior::StaticFindScript( votetype->scriptNumber(), module );
+
+ if (( scriptdata != NULL ) && ( scriptdata->Flags & SCRIPTF_ClientSide ))
+ VOTEDEF_ValidateError( "the vote would call the client-side script %d", votetype->scriptNumber() );
+ }
+ }
+ catch ( FString& err )
+ {
+ result.error = err;
+ return false;
+ }
+
+ return true;
+}
+
+//
+// [TP] Make a record of this vote for later comparing
+//
+FString VOTEDEF_MakeVoteSummary( const VoteStateData& input )
+{
+ FString record = input.type->name() + " ";
+
+ switch ( input.type->argumentType() )
+ {
+ case VOTEARG_Int:
+ case VOTEARG_Float:
+ case VOTEARG_Level:
+ record += input.arg;
+ break;
+
+ case VOTEARG_Player:
+ // [TP] Store the IP address of this player was this a kick vote or not,
+ // as this way we can have something unique to the player.
+ // This way a player index vote will not fail just because someone
+ // left and another person took his index.
+ record += NETWORK_AddressToStringIgnorePort( input.targetAddress );
+ break;
+
+ default:
+ break;
+ }
+
+ return record;
+}
+
+//
+// [TP] Get usage info of calling this vote
+//
+FString VoteClass::usage() const
+{
+ FString cmd = "callvote " + name();
+
+ if ( hasArgument() )
+ {
+ FString arg = g_ParameterTokens[argumentType()];
+ arg.ToLower();
+ cmd.AppendFormat( " <%s>", arg.GetChars() );
+ }
+
+ cmd += " [reason]";
+ return cmd;
+}
+
+//
+// [TP] Find a vote type by index number
+//
+VoteClass* VOTEDEF_FindTypeByIndex( int index )
+{
+ if (( index < 0 ) || ( index >= int( g_Defs.Size() )))
+ return NULL;
+
+ return g_Defs[index];
+}
+
+//
+// [TP] Find a vote type by name
+//
+VoteClass* VOTEDEF_FindTypeByName( FString name )
+{
+ for ( int i = 0; i < int( g_Defs.Size() ); i++ )
+ {
+ if ( g_Defs[i]->name().CompareNoCase( name ) == 0 )
+ return g_Defs[i];
+ }
+
+ return NULL;
+}
+
+int VOTEDEF_NumVoteTypes()
+{
+ return g_Defs.Size();
+}
+
+//
+// [TP] Is the given player in the adminlist?
+//
+bool VOTEDEF_IsPlayerAdmin( int idx )
+{
+ NETADDRESS_s address = SERVER_GetClient( idx )->Address;
+ return SERVER_GetAdminList()->isIPInList( address );
+}
+
+//
+// [TP] Gets a comma-delimeted list of all vote type names
+//
+FString VOTEDEF_GetVoteTypeList()
+{
+ FString result;
+
+ for ( int i = 0; i < VOTEDEF_NumVoteTypes(); i++ )
+ {
+ if ( i != 0 )
+ result += ", ";
+
+ VoteClass* votetype = VOTEDEF_FindTypeByIndex( i );
+ result += votetype->name();
+ }
+
+ return result;
+}
+
+//
+// [TP] List all vote types and information on how to use them.
+//
+CCMD( listvotetypes )
+{
+ for ( int i = 0; i < VOTEDEF_NumVoteTypes(); i++ )
+ {
+ VoteClass* votetype = VOTEDEF_FindTypeByIndex( i );
+ FString message, description;
+ message.Format( "\\c[Orange]%s\\c-", votetype->usage().GetChars() );
+
+ // [TP] Add the description, if we have it.
+ if (( description = votetype->description() ).IsNotEmpty() )
+ message.AppendFormat( ": %s", description.GetChars() );
+
+ Printf( "%s\n", message.GetChars() );
+ }
+}
diff --git a/src/callvotetype.h b/src/callvotetype.h
new file mode 100644
--- /dev/null
+++ b/src/callvotetype.h
@@ -0,0 +1,282 @@
+/*
+ * Zandronum source code
+ * Copyright (C) 2012-2014 Teemu Piippo
+ * Copyright (C) 2012-2014 Zandronum Development Team
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of the Skulltag Development Team nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * 4. Redistributions in any form must be accompanied by information on how to
+ * obtain complete source code for the software and any accompanying
+ * software that uses the software. The source code must either be included
+ * in the distribution or be available for no more than the cost of
+ * distribution plus a nominal fee, and must be freely redistributable
+ * under reasonable conditions. For an executable file, complete source
+ * code means the source code for all modules it contains. It does not
+ * include source code for modules or files that typically accompany the
+ * major components of the operating system on which the executable file
+ * runs.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Filename: callvotetype.h
+ *
+ * Description: VOTEDEF declarations
+ *
+ * -----------------------------------------------------------------------------
+ */
+
+#ifndef VOTEDEF_H
+#define VOTEDEF_H
+
+#include "zstring.h"
+#include "basictypes.h"
+#include "networkshared.h"
+#include "doomdef.h"
+
+class FBaseCVar;
+class VoteClass;
+
+enum NativeVoteType
+{
+ VOTETYPE_Kick,
+ VOTETYPE_ForceSpec,
+ VOTETYPE_ChangeMap,
+ VOTETYPE_Map,
+ VOTETYPE_FragLimit,
+ VOTETYPE_TimeLimit,
+ VOTETYPE_PointLimit,
+ VOTETYPE_DuelLimit,
+ VOTETYPE_WinLimit,
+
+ VOTETYPE_NumNatives, // i.e. not native
+};
+
+//
+// Vote parameter types
+//
+enum VoteArgumentType
+{
+ VOTEARG_Int,
+ VOTEARG_Float,
+ VOTEARG_Player,
+ VOTEARG_Level,
+ VOTEARG_String,
+
+ VOTEARG_NumArgTypes, // i.e. no argument
+};
+
+//
+// Flags for vote classes
+//
+enum
+{
+ // Vote can be called without delay as long as it passes
+ VOTEF_NoLimitIfPassed = ( 1 << 0 ),
+
+ // Player argument must not be the caller
+ VOTEF_NotOnSelf = ( 1 << 1 ),
+
+ // Player argument must not be an admin
+ VOTEF_NotOnAdmin = ( 1 << 2 ),
+
+ // Player argument is given as an index instead of a name
+ VOTEF_PlayerAsIndex = ( 1 << 3 ),
+
+ // Player given must be in game
+ VOTEF_NotOnSpectators = ( 1 << 4 ),
+
+ // Use a slider in the call vote menu (int and float only)
+ VOTEF_MenuSlider = ( 1 << 5 ),
+
+ // If a map argument, map must be in rotation
+ VOTEF_MapInRotation = ( 1 << 6 ),
+
+ VOTEF_NumFlags = 7
+};
+
+//
+// A bunch of data for the state of a vote
+//
+struct VoteStateData
+{
+ VoteClass* type; // Type of vote (note: only is guaranteed non-NULL if validate passes)
+ FString arg; // Vote argument
+ FString reason; // Reason string
+ int yes; // Count of yes votes
+ int no; // Count of no votes
+ int caller; // Index of vote caller
+ NETADDRESS_s targetAddress; // For a vote with a player argument, what's the target address?
+ bool targetAddressKnown; // Is the targetAddress valid?
+
+ VoteStateData() :
+ type( NULL ),
+ yes( 0 ),
+ no( 0 ),
+ targetAddressKnown( false ) {}
+};
+
+//
+// Structure for result from VOTEDEF_Validate
+//
+struct VoteValidateResult
+{
+ FString command; // Resulting command string (for natives)
+ FString error; // Failed? Why?
+ DWORD argument; // Processed argument
+ FString stringArgument; // For a string or map argument, what's the string?
+ double floatArgument; // For a float argument, what's the floating-point value?
+};
+
+//
+// A type of vote
+//
+class VoteClass
+{
+ FString _name; // Name of the type
+ int _scriptNumber; // Script number to execute
+ DWORD _flags; // Flags of this vote
+ VoteArgumentType _argumentType; // Argument type
+ FString _argumentLabel; // Label of the argument in menus
+ FString _description; // Description string
+ FBaseCVar* _forbidCVar; // CVar that forbids the use of this vote
+ float _minimum; // Minimum input value
+ float _maximum; // Maximum input value
+ NativeVoteType _native; // Native vote type, these have special behavior.
+ int _index; // The index of this type
+ float _menuMinimum; // Minimum value for menus
+ float _menuMaximum; // Maximum value for menus
+ float _menuIncrement; // Increment value for menus
+
+ friend void VOTEDEF_ParseLump( int );
+
+public:
+ VoteClass( const FString& name, NativeVoteType native );
+ ~VoteClass();
+
+ inline VoteArgumentType argumentType() const;
+ const FString& argumentLabel() const;
+ inline const FString& description() const;
+ inline DWORD flags() const;
+ inline FBaseCVar* forbidCVar() const;
+ inline bool hasArgument() const;
+ inline int index() const;
+ inline bool isNative() const;
+ FString usage() const;
+ inline float maximum() const;
+ inline float menuIncrement() const;
+ inline float menuMaximum() const;
+ inline float menuMinimum() const;
+ inline float minimum() const;
+ inline const FString& name() const;
+ inline NativeVoteType nativeType() const;
+ inline int scriptNumber() const;
+};
+
+void VOTEDEF_Construct();
+void VOTEDEF_Destruct();
+FString VOTEDEF_GetVoteTypeList();
+VoteClass* VOTEDEF_FindTypeByIndex( int index );
+VoteClass* VOTEDEF_FindTypeByName( FString name );
+FString VOTEDEF_MakeVoteSummary( VoteStateData const& input );
+int VOTEDEF_NumVoteTypes();
+void VOTEDEF_ParseLump( int lump );
+bool VOTEDEF_IsPlayerAdmin( int idx );
+bool VOTEDEF_Validate( const VoteStateData& input, VoteValidateResult& result );
+
+inline int VoteClass::index() const
+{
+ return _index;
+}
+
+inline int VoteClass::scriptNumber() const
+{
+ return _scriptNumber;
+}
+
+inline NativeVoteType VoteClass::nativeType() const
+{
+ return _native;
+}
+
+inline const FString& VoteClass::name() const
+{
+ return _name;
+}
+
+inline VoteArgumentType VoteClass::argumentType() const
+{
+ return _argumentType;
+}
+
+inline DWORD VoteClass::flags() const
+{
+ return _flags;
+}
+
+inline FBaseCVar* VoteClass::forbidCVar() const
+{
+ return _forbidCVar;
+}
+
+inline float VoteClass::minimum() const
+{
+ return _minimum;
+}
+
+inline float VoteClass::maximum() const
+{
+ return _maximum;
+}
+
+inline const FString& VoteClass::description() const
+{
+ return _description;
+}
+
+inline bool VoteClass::isNative() const
+{
+ return _native != VOTETYPE_NumNatives;
+}
+
+inline bool VoteClass::hasArgument() const
+{
+ return _argumentType != VOTEARG_NumArgTypes;
+}
+
+inline const FString& VoteClass::argumentLabel() const
+{
+ return _argumentLabel;
+}
+
+inline float VoteClass::menuMinimum() const
+{
+ return _menuMinimum;
+}
+
+inline float VoteClass::menuMaximum() const
+{
+ return _menuMaximum;
+}
+
+inline float VoteClass::menuIncrement() const
+{
+ return _menuIncrement;
+}
+
+#endif // VOTEDEF_H
diff --git a/src/cl_main.cpp b/src/cl_main.cpp
--- a/src/cl_main.cpp
+++ b/src/cl_main.cpp
@@ -9997,7 +9997,7 @@
//
static void client_CallVote( BYTESTREAM_s *pByteStream )
{
- FString command;
+ ULONG ulCommand;
FString parameters;
FString reason;
ULONG ulVoteCaller;
@@ -10006,7 +10006,7 @@
ulVoteCaller = NETWORK_ReadByte( pByteStream );
// Read in the command.
- command = NETWORK_ReadString( pByteStream );
+ ulCommand = NETWORK_ReadLong( pByteStream );
// Read in the parameters.
parameters = NETWORK_ReadString( pByteStream );
@@ -10015,7 +10015,7 @@
reason = NETWORK_ReadString( pByteStream );
// Begin the vote!
- CALLVOTE_BeginVote( command, parameters, reason, ulVoteCaller );
+ CALLVOTE_BeginVote( ulCommand, parameters, reason, ulVoteCaller );
}
//*****************************************************************************
diff --git a/src/d_main.cpp b/src/d_main.cpp
--- a/src/d_main.cpp
+++ b/src/d_main.cpp
@@ -147,6 +147,7 @@
#include "g_shared/pwo.h"
#include "win32/g15/g15.h"
+#include "callvotetype.h"
EXTERN_CVAR(Bool, hud_althud)
void DrawHUD();
@@ -2737,6 +2738,9 @@
// [BB] At the moment Skulltag still doesn't use the new ZDoom TeamLibrary class.
TEAMINFO_Init ();
+ // [Dusk] Initialize vote definitions
+ VOTEDEF_Construct();
+
FActorInfo::StaticInit ();
// [GRB] Initialize player class list
diff --git a/src/g_level.cpp b/src/g_level.cpp
--- a/src/g_level.cpp
+++ b/src/g_level.cpp
@@ -261,7 +261,6 @@
//
//
//==========================================================================
-
CCMD (map)
{
if (argv.argc() > 1)
diff --git a/src/m_menu.h b/src/m_menu.h
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -142,6 +142,7 @@
browserslot,
txslider,
mnnumber,
+ votetype, // [Dusk]
} itemtype;
diff --git a/src/m_options.cpp b/src/m_options.cpp
--- a/src/m_options.cpp
+++ b/src/m_options.cpp
@@ -115,6 +115,7 @@
// [ZZ] PWO header file
#include "g_shared/pwo.h"
+#include "callvotetype.h"
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
@@ -201,6 +202,12 @@
static LONG g_lSavedColor;
static bool g_bSwitchColorBack;
+// [BB]
+static TArray<valuestring_t> SkirmishLevels;
+
+// [BB]
+void InitSkirmishLevelList();
+
value_t YesNo[2] = {
{ 0.0, "No" },
{ 1.0, "Yes" }
@@ -1827,8 +1834,10 @@
if ( !bAllowBots && ( players[lPlayer].bIsBot ))
return ( false );
+/*
if ( lPlayer == consoleplayer )
return ( false );
+*/
return ( true );
}
@@ -1946,221 +1955,26 @@
//====================================================================================
//
-// Call Vote-->Kick Player Menu
-//
-//====================================================================================
-
-CVAR( String, menu_votereason, "", 0 );
-
-void kickplayermenu_Kick( void );
-
-//*****************************************************************************
-//
-static menuitem_t kickplayermenu_Items[] =
-{
- { discretes,"Player", {&menu_playerslider_idx}, {1.0}, {0.0}, {0.0}, {NULL} },
- { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} },
- { string, "Reason for kicking:",{&menu_votereason}, {0.0}, {0.0}, {0.0}, {NULL} },
- { more, "Kick!", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)kickplayermenu_Kick} },
-};
-
-// [BB, RC] Line number of the "Player:" entry from kickplayermenu_Items. If the line number is changed, the value has to be adjusted.
-#define KICKPLAYER_SLIDER_LOCATION 0
-
-//*****************************************************************************
-//
-menu_t KickPlayerMenu = {
- "KICK A PLAYER",
- 0,
- countof(kickplayermenu_Items),
- 0,
- kickplayermenu_Items,
- 0,
- 0,
-};
-
-//*****************************************************************************
-//
-void kickplayermenu_Kick( void )
-{
- // Clean the name of color codes.
- FString Name = AvailablePlayers[menu_playerslider_idx].name.GetChars( );
- V_RemoveColorCodes( Name );
- V_EscapeBacklashes( Name );
-
- FString Reason = menu_votereason.GetGenericRep( CVAR_String ).String;
- V_EscapeBacklashes( Reason );
-
- // Execute the command.
- char szString[256];
- sprintf( szString, "callvote kick \"%s\" \"%s\"", Name.Left( 96 ).GetChars(), Reason.Left( 25 ).GetChars());
- AddCommandString( szString );
- M_ClearMenus( );
-}
-
-//*****************************************************************************
-//
-void kickplayermenu_Show( void )
-{
- if ( SERVER_CountPlayers( false ) < 2 )
- {
- M_ClearMenus( );
- M_StartMessage( "There is nobody else here to kick!\n\npress any key.", NULL );
- return;
- }
-
- // Set up the player selection slider.
- playerslider_BuildList( false );
- kickplayermenu_Items[KICKPLAYER_SLIDER_LOCATION].b.numvalues = static_cast<float>(AvailablePlayers.Size());
- kickplayermenu_Items[KICKPLAYER_SLIDER_LOCATION].e.valuestrings = &AvailablePlayers[0];
-
- M_SwitchMenu( &KickPlayerMenu );
-}
-
-//====================================================================================
-//
-// Call Vote-->Map Menu
-//
-//====================================================================================
-
-CVAR( String, menu_mapvotemap, "", 0 );
-CVAR( Bool, menu_mapvoteintermission, false, 0 );
-
-//*****************************************************************************
-//
-void mapvotemenu_Vote( void )
-{
- // Sanitize the inputs.
- FString Map = menu_mapvotemap.GetGenericRep( CVAR_String ).String;
- FString Reason = menu_votereason.GetGenericRep( CVAR_String ).String;
- V_EscapeBacklashes( Map );
- V_EscapeBacklashes( Reason );
-
- if ( !Map.Len( ) )
- {
- Printf( "You didn't specify a map!\n" );
- return;
- }
-
- // Execute the command.
- char szString[256];
- sprintf( szString, "callvote %s %s \"%s\"", ( menu_mapvoteintermission.GetGenericRep( CVAR_Bool ).Bool ? "changemap" : "map" ), Map.Left( 128 ).GetChars(), Reason.Left( 25 ).GetChars() );
- AddCommandString( szString );
- M_ClearMenus( );
-}
-
-//*****************************************************************************
-//
-static menuitem_t mapvotemenu_Items[] =
-{
- { string, "Map", {&menu_mapvotemap}, {0.0}, {0.0}, {0.0}, {NULL} },
- { discrete, "Intermission", {&menu_mapvoteintermission}, {2.0}, {0.0}, {0.0}, {YesNo} },
- { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} },
- { string, "Reason for change:",{&menu_votereason}, {0.0}, {0.0}, {0.0}, {NULL} },
- { more, "Vote!", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)mapvotemenu_Vote} },
-};
-
-//*****************************************************************************
-//
-menu_t MapVoteMenu = {
- "CHANGE MAP",
- 0,
- countof(mapvotemenu_Items),
- 0,
- mapvotemenu_Items,
- 0,
- 0,
-};
-
-//*****************************************************************************
-//
-void mapvotemenu_Show( void )
-{
- M_SwitchMenu( &MapVoteMenu );
-}
-
-//====================================================================================
-//
-// Call Vote-->Limit Menu
-//
-//====================================================================================
-
-//*****************************************************************************
-//
-value_t limitvote_Types[5] = {
- { 0.0, "fraglimit" },
- { 1.0, "timelimit" },
- { 2.0, "winlimit" },
- { 3.0, "duellimit" },
- { 4.0, "pointlimit" }
-};
-
-//*****************************************************************************
-//
-CVAR( Int, menu_limitvote_type, 0, 0 );
-CVAR( String, menu_limitvote_value, "", 0 );
-
-//*****************************************************************************
-//
-void limitvotemenu_Vote( void )
-{
- // Sanitize the inputs.
- int iVoteType = menu_limitvote_type.GetGenericRep( CVAR_Int ).Int;
- int iLimit = atoi( menu_limitvote_value.GetGenericRep( CVAR_String ).String );
- FString Reason = menu_votereason.GetGenericRep( CVAR_String ).String;
- V_EscapeBacklashes( Reason );
-
- if ( iVoteType >= 5 || iVoteType < 0 )
- return;
-
- // Execute the command.
- char szString[512];
- sprintf( szString, "callvote %s %d \"%s\"", limitvote_Types[iVoteType].name, iLimit, Reason.Left( 25 ).GetChars() );
- AddCommandString( szString );
- M_ClearMenus( );
-}
-
-//*****************************************************************************
-//
-static menuitem_t limitvotemenu_Items[] =
-{
- { discrete, "Type of limit", {&menu_limitvote_type}, {5.0}, {0.0}, {0.0}, {limitvote_Types} },
- { string, "New value", {&menu_limitvote_value}, {0.0}, {0.0}, {0.0}, {NULL} },
- { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} },
- { string, "Reason for change:",{&menu_votereason}, {0.0}, {0.0}, {0.0}, {NULL} },
- { more, "Vote!", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)limitvotemenu_Vote} },
-};
-
-//*****************************************************************************
-//
-menu_t limitvoteMenu = {
- "CHANGE LIMIT",
- 0,
- countof(limitvotemenu_Items),
- 0,
- limitvotemenu_Items,
- 0,
- 0,
-};
-
-//*****************************************************************************
-//
-void limitvotemenu_Show( void )
-{
- M_SwitchMenu( &limitvoteMenu );
-}
-
-//====================================================================================
-//
// Call Vote Menu
//
//====================================================================================
+// [TP]
+CVAR( Int, menu_votetype, 0, 0 )
+CVAR( Int, menu_voteargument, 0, 0 )
+CVAR( Float, menu_votefloatargument, 0, 0 )
+CVAR( String, menu_votestringargument, "", 0 )
+CVAR( String, menu_votereason, "", 0 )
+static void M_VoteTypeChanged( bool first );
+static void votemenu_go();
+
static menuitem_t CallVoteItems[] =
{
- { more, "Kick a player", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)kickplayermenu_Show} },
- { more, "Change the map", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)mapvotemenu_Show} },
- { more, "Change a limit", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)limitvotemenu_Show} },
+ { votetype, "Vote action", {&menu_votetype}, {0.0}, {100.0}, {1.0}, {NULL} },
+ { string, "<dynamic text>", {&menu_voteargument}, {0.0}, {0.0}, {0.0}, {NULL} },
+ { string, "Reason for vote", {&menu_votereason}, {0.0}, {0.0}, {0.0}, {NULL} },
+ { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} },
+ { more, "Call the vote", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *) votemenu_go} },
};
menu_t CallVoteMenu = {
@@ -2173,6 +1987,182 @@
0,
};
+//*****************************************************************************
+//
+void M_CallVote( void )
+{
+ // Don't allow a vote unless the player is a client.
+ if (( NETWORK_GetState( ) != NETSTATE_CLIENT )
+ || ( CLIENT_GetConnectionState() != CTS_ACTIVE ))
+ {
+ M_ClearMenus();
+ M_StartMessage( "You must be in a multiplayer game to call a vote.\n\npress any key.", NULL );
+ return;
+ }
+
+ // [TP] Init the skirmish level list as we're using it for map votes
+ InitSkirmishLevelList();
+ M_VoteTypeChanged( true );
+ M_SwitchMenu( &CallVoteMenu );
+}
+
+//*****************************************************************************
+//
+// [TP] Hmm. Now that votes are generalized the argument passed is of variable
+// type. Thus, we have to change the argument field dynamically based on the type
+// of the argument of the vote we scrolled to.
+//
+// This also resets the argument, first because the argument of one type may be
+// out of bounds or otherwise be of completely different meaning for another.
+//
+static void M_VoteTypeChanged( bool first )
+{
+ VoteClass* def = VOTEDEF_FindTypeByIndex( menu_votetype );
+
+ if ( def == NULL )
+ return;
+
+ VoteArgumentType argtype = def->argumentType();
+ menuitem_t* item = &CallVoteItems[1];
+ item->label = def->argumentLabel().GetChars();
+
+ switch ( argtype )
+ {
+ case VOTEARG_Player:
+ // Set up the player selection slider.
+ playerslider_BuildList( false );
+ item->type = discretes;
+ item->a.intcvar = &menu_voteargument;
+ item->b.numvalues = float( AvailablePlayers.Size() );
+ item->e.valuestrings = &AvailablePlayers[0];
+
+ if ( item->label[0] == '\0' )
+ item->label = "Player";
+
+ if ( first == false )
+ menu_voteargument = AvailablePlayers[0].value;
+ break;
+
+ case VOTEARG_Int:
+ case VOTEARG_Float:
+ item->type = ( def->flags() & VOTEF_MenuSlider ) ? slider : number;
+ item->a.cvar = &menu_votefloatargument;
+ item->b.min = def->menuMinimum();
+ item->c.max = def->menuMaximum();
+ item->d.step = def->menuIncrement();
+ item->e.values = NULL;
+
+ if ( item->label[0] == '\0' )
+ item->label = "Value";
+
+ if ( first == false )
+ menu_votefloatargument = def->minimum();
+ break;
+
+ case VOTEARG_Level:
+ item->type = discretes;
+ item->a.intcvar = &menu_voteargument;
+ item->b.numvalues = float( SkirmishLevels.Size() );
+ item->e.valuestrings = ( SkirmishLevels.Size() > 0 ) ? &SkirmishLevels[0] : NULL;
+
+ if ( item->label[0] == '\0' )
+ item->label = "Level:";
+
+ if ( first == false )
+ menu_voteargument = 0;
+ break;
+
+ case VOTEARG_String:
+ item->type = string;
+ item->a.stringcvar = &menu_votestringargument;
+ item->b.min = 0.0f;
+ item->c.max = 0.0f;
+ item->d.step = 0.0f;
+ item->e.values = NULL;
+ break;
+
+ case VOTEARG_NumArgTypes:
+ item->type = redtext;
+ item->label = "";
+ break;
+ }
+}
+
+static void votemenu_go()
+{
+ VoteClass* def = VOTEDEF_FindTypeByIndex( menu_votetype );
+
+ if ( def == NULL )
+ return;
+
+ VoteStateData in;
+ VoteValidateResult out;
+ in.type = VOTEDEF_FindTypeByIndex( menu_votetype );
+ in.caller = consoleplayer;
+
+ // [TP] Determine the argument to callvote.
+ if ( def->hasArgument() )
+ {
+ if ( def->argumentType() == VOTEARG_Level )
+ {
+ // [TP] If this is a map, we need to pass the string argument
+ in.arg = wadlevelinfos[menu_voteargument].mapname;
+ }
+ else if (( def->argumentType() == VOTEARG_Player )
+ && (( def->flags() & VOTEF_PlayerAsIndex ) == 0 ))
+ {
+ // [TP] A player's name is wanted - pass that.
+ in.arg = players[menu_voteargument].userinfo.netname;
+ V_ColorizeString( in.arg );
+ V_RemoveColorCodes( in.arg );
+ }
+ else if ( def->argumentType() == VOTEARG_Float )
+ {
+ in.arg.Format( "%f", float( menu_votefloatargument ));
+
+ // Remove trailing zeroes
+ if ( in.arg.IndexOf( "." ) != -1 )
+ {
+ while ( in.arg[in.arg.Len() - 1] == '0' )
+ in.arg = FString( in.arg.GetChars(), in.arg.Len() - 1 );
+
+ if ( in.arg[in.arg.Len() - 1] == '.' )
+ in.arg = FString( in.arg.GetChars(), in.arg.Len() - 1 );
+ }
+ }
+ else if ( def->argumentType() == VOTEARG_String )
+ {
+ in.arg = menu_votestringargument;
+ }
+ else if ( def->argumentType() == VOTEARG_Int )
+ {
+ in.arg.Format( "%d", int( menu_votefloatargument ));
+ }
+ else
+ {
+ in.arg.Format( "%d", int( menu_voteargument ));
+ }
+ }
+
+ M_ClearMenus();
+
+ // [TP] Validate the vote command now
+ if ( VOTEDEF_Validate( in, out ))
+ {
+ CLIENTCOMMANDS_CallVote( menu_votetype, in.arg, menu_votereason );
+ NETWORK_LaunchPacket( CLIENT_GetLocalBuffer( ), CLIENT_GetServerAddress( ));
+ NETWORK_ClearBuffer( CLIENT_GetLocalBuffer( ));
+ }
+ else
+ {
+ // [TP] Failed - tell the user why.
+ static char errortext[256];
+ snprintf( errortext, sizeof errortext, "Couldn't call that because %s\n\npress any key.",
+ out.error.GetChars() );
+ M_StartMessage( errortext, NULL );
+ }
+}
+
/*=======================================
*
* Multiplayer Menu
@@ -2186,6 +2176,7 @@
void M_StartBrowserMenu( void );
void M_Spectate( void );
void M_CallVote( void );
+void M_VoteTypeChanged( bool first );
void M_ChangeTeam( void );
void M_Skirmish( void );
@@ -2199,7 +2190,7 @@
{ more, "Spectate", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)M_Spectate} },
{ more, "Switch teams", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)M_ChangeTeam} },
{ redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} },
- { more, "Call a vote", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)M_CallVote} },
+ { more, "Call a vote", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)M_CallVote} },
{ more, "Ignore a player", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)ignoreplayermenu_Show} },
{ redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} },
{ discrete, "Allow skins", {&cl_skins}, {3.0}, {0.0}, {0.0}, {AllowSkinVals} },
@@ -2239,22 +2230,6 @@
//*****************************************************************************
//
-void M_CallVote( void )
-{
- // Don't allow a vote unless the player is a client.
- if ( NETWORK_GetState( ) != NETSTATE_CLIENT )
- {
- M_ClearMenus( );
- M_StartMessage( "You must be in a multiplayer game to vote.\n\npress any key.", NULL );
-
- return;
- }
-
- M_SwitchMenu( &CallVoteMenu );
-}
-
-//*****************************************************************************
-//
bool M_SkulltagVersionDrawer( void )
{
ULONG ulTextHeight;
@@ -3278,9 +3253,6 @@
#define SKIRMISHITEMS_LEVEL_INDEX 0
// [BB]
-static TArray<valuestring_t> SkirmishLevels;
-
-// [BB]
void InitSkirmishLevelList()
{
SkirmishLevels.Clear();
@@ -5070,6 +5042,24 @@
screen->DrawText( SmallFont, CR_GREY, x, y, szString, DTA_CleanNoMove_1, true, TAG_DONE );
}
break;
+
+ // [TP]
+ case votetype:
+ {
+ VoteClass* def = VOTEDEF_FindTypeByIndex( *item->a.intcvar );
+ FString text;
+
+ if ( def == NULL )
+ text = "UNKNOWN";
+ else if ( def->description().IsNotEmpty() )
+ text = def->description();
+ else
+ text = def->name();
+
+ screen->DrawText( SmallFont, CR_GREY, x, y, text.GetChars(), DTA_CleanNoMove_1, true, TAG_DONE );
+ }
+ break;
+
default:
break;
}
@@ -5931,6 +5921,17 @@
S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE);
break;
+ // [TP]
+ case votetype:
+ {
+ FIntCVar& cv = *item->a.intcvar;
+ int num = VOTEDEF_NumVoteTypes();
+ cv = ( cv + num - 1 ) % num;
+ }
+ M_VoteTypeChanged( false );
+ S_Sound( CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE );
+ break;
+
default:
break;
}
@@ -6363,6 +6364,16 @@
S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE);
break;
+ // [TP]
+ case votetype:
+ {
+ FIntCVar& cv = *item->a.intcvar;
+ cv = ( cv + 1 ) % VOTEDEF_NumVoteTypes();
+ }
+ M_VoteTypeChanged( false );
+ S_Sound( CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE );
+ break;
+
default:
break;
}
diff --git a/src/network.cpp b/src/network.cpp
--- a/src/network.cpp
+++ b/src/network.cpp
@@ -310,6 +310,8 @@
lumpsToAuthenticateMode.push_back( ALL_LUMPS );
lumpsToAuthenticate.push_back( "MAPINFO" );
lumpsToAuthenticateMode.push_back( ALL_LUMPS );
+ lumpsToAuthenticate.push_back( "VOTEDEF" );
+ lumpsToAuthenticateMode.push_back( ALL_LUMPS );
FString checksum, longChecksum;
bool noProtectedLumpsAutoloaded = true;
diff --git a/src/sv_ban.cpp b/src/sv_ban.cpp
--- a/src/sv_ban.cpp
+++ b/src/sv_ban.cpp
@@ -600,6 +600,15 @@
//*****************************************************************************
//
+void SERVERBAN_BanIP ( NETADDRESS_s address, time_t duration, FString const& comment, std::string& message )
+{
+ time_t expiration;
+ time ( &expiration );
+ expiration += duration;
+ g_ServerBans.addEntry( NETWORK_AddressToStringIgnorePort ( address ), NULL, comment.GetChars(), message, expiration );
+ serverban_KickBannedPlayers();
+}
+
CCMD( addban )
{
if ( argv.argc( ) < 3 )
diff --git a/src/sv_ban.h b/src/sv_ban.h
--- a/src/sv_ban.h
+++ b/src/sv_ban.h
@@ -67,6 +67,7 @@
IPList *SERVERBAN_GetBanList( void );
IPList *SERVERBAN_GetBanExemptionList( void );
void SERVERBAN_BanPlayer( ULONG ulPlayer, const char *pszBanLength, const char *pszBanReason );
+void SERVERBAN_BanIP ( NETADDRESS_s address, time_t duration, FString const& comment, std::string& message );
//--------------------------------------------------------------------------------------------------------------------------------------------------
//-- EXTERNAL CONSOLE VARIABLES --------------------------------------------------------------------------------------------------------------------
diff --git a/src/sv_commands.cpp b/src/sv_commands.cpp
--- a/src/sv_commands.cpp
+++ b/src/sv_commands.cpp
@@ -3525,17 +3525,17 @@
//*****************************************************************************
//*****************************************************************************
//
-void SERVERCOMMANDS_CallVote( ULONG ulPlayer, FString Command, FString Parameters, FString Reason, ULONG ulPlayerExtra, ULONG ulFlags )
+void SERVERCOMMANDS_CallVote( ULONG ulPlayer, ULONG Command, FString Parameters, FString Reason, ULONG ulPlayerExtra, ULONG ulFlags )
{
if ( PLAYER_IsValidPlayer( ulPlayer ) == false )
return;
- NetCommand command ( SVC_CALLVOTE );
- command.addByte ( ulPlayer );
- command.addString ( Command.GetChars() );
- command.addString ( Parameters.GetChars() );
- command.addString ( Reason.GetChars() );
- command.sendCommandToClients ( ulPlayerExtra, ulFlags );
+ NetCommand command( SVC_CALLVOTE );
+ command.addByte( ulPlayer );
+ command.addLong( Command );
+ command.addString( Parameters );
+ command.addString( Reason );
+ command.sendCommandToClients( ulPlayerExtra, ulFlags );
}
//*****************************************************************************
diff --git a/src/sv_commands.h b/src/sv_commands.h
--- a/src/sv_commands.h
+++ b/src/sv_commands.h
@@ -273,7 +273,7 @@
void SERVERCOMMANDS_StopSectorSequence( sector_t *pSector, ULONG ulPlayerExtra = MAXPLAYERS, ULONG ulFlags = 0 );
// Voting commands. These handle the voting.
-void SERVERCOMMANDS_CallVote( ULONG ulPlayer, FString Command, FString Parameters, FString Reason, ULONG ulPlayerExtra = MAXPLAYERS, ULONG ulFlags = 0 );
+void SERVERCOMMANDS_CallVote( ULONG ulPlayer, ULONG Command, FString Parameters, FString Reason, ULONG ulPlayerExtra = MAXPLAYERS, ULONG ulFlags = 0 );
void SERVERCOMMANDS_PlayerVote( ULONG ulPlayer, bool bVoteYes, ULONG ulPlayerExtra = MAXPLAYERS, ULONG ulFlags = 0 );
void SERVERCOMMANDS_VoteEnded( bool bVotePassed, ULONG ulPlayerExtra = MAXPLAYERS, ULONG ulFlags = 0 );
diff --git a/src/sv_main.cpp b/src/sv_main.cpp
--- a/src/sv_main.cpp
+++ b/src/sv_main.cpp
@@ -117,6 +117,7 @@
#include "po_man.h"
#include "network/cl_auth.h"
#include "network/sv_auth.h"
+#include "callvotetype.h"
//*****************************************************************************
// MISC CRAP THAT SHOULDN'T BE HERE BUT HAS TO BE BECAUSE OF SLOPPY CODING
@@ -6048,68 +6049,22 @@
}
// Check if the specific type of vote is allowed.
- bool bVoteAllowed = false;
- switch ( ulVoteCmd )
- {
- case VOTECMD_KICK:
-
- bVoteAllowed = !sv_nokickvote;
- sprintf( szCommand, "kick" );
- break;
-
- case VOTECMD_KICKFROMGAME:
-
- bVoteAllowed = !sv_noforcespecvote;
- sprintf( szCommand, "forcespec" );
- break;
-
- case VOTECMD_MAP:
-
- bVoteAllowed = !sv_nomapvote;
- sprintf( szCommand, "map" );
- break;
- case VOTECMD_CHANGEMAP:
-
- bVoteAllowed = !sv_nochangemapvote;
- sprintf( szCommand, "changemap" );
- break;
- case VOTECMD_FRAGLIMIT:
-
- bVoteAllowed = !sv_nofraglimitvote;
- sprintf( szCommand, "fraglimit" );
- break;
- case VOTECMD_TIMELIMIT:
-
- bVoteAllowed = !sv_notimelimitvote;
- sprintf( szCommand, "timelimit" );
- break;
- case VOTECMD_WINLIMIT:
-
- bVoteAllowed = !sv_nowinlimitvote;
- sprintf( szCommand, "winlimit" );
- break;
- case VOTECMD_DUELLIMIT:
-
- bVoteAllowed = !sv_noduellimitvote;
- sprintf( szCommand, "duellimit" );
- break;
- case VOTECMD_POINTLIMIT:
-
- bVoteAllowed = !sv_nopointlimitvote;
- sprintf( szCommand, "pointlimit" );
- break;
- default:
-
- return ( false );
- }
-
- // Begin the vote, if that type is allowed.
- if ( bVoteAllowed )
- CALLVOTE_BeginVote( szCommand, Parameters, Reason, g_lCurrentClient );
- else
- SERVER_PrintfPlayer( PRINT_HIGH, g_lCurrentClient, "%s votes are disabled on this server.\n", szCommand );
-
- return ( false );
+ // [Dusk] Generalized for custom vote types.
+ VoteClass* votetype = VOTEDEF_FindTypeByIndex( ulVoteCmd );
+
+ if( votetype == NULL )
+ return false; // Unknown vote type
+
+ if( votetype->forbidCVar()->GetGenericRep( CVAR_Bool ).Bool )
+ {
+ SERVER_PrintfPlayer( PRINT_HIGH, g_lCurrentClient, "%s votes are disabled on this server.\n",
+ votetype->name().GetChars( ));
+ return false;
+ }
+
+ // Begin the vote
+ CALLVOTE_BeginVote( ulVoteCmd, Parameters, Reason, g_lCurrentClient );
+ return false;
}
//*****************************************************************************
diff --git a/wadsrc/static/votedef.txt b/wadsrc/static/votedef.txt
new file mode 100644
--- /dev/null
+++ b/wadsrc/static/votedef.txt
@@ -0,0 +1,108 @@
+//
+// Kick a player from the server and ban for 10 minutes
+//
+VoteType native kick
+{
+ Argument = player;
+ ForbidCVar = "sv_nokickvote";
+ Description = "Kick a player";
+ NotOnSelf;
+ NotOnAdmin;
+ NoPassedLimit;
+}
+
+//
+// Remove a player from game to spectators
+//
+VoteType native forcespec
+{
+ Argument = player;
+ ForbidCVar = "sv_noforcespecvote";
+ Description = "Force to spectate";
+ NotOnSelf;
+ NotOnAdmin;
+ NotOnSpectators;
+ NoPassedLimit;
+}
+
+//
+// Set the frag limit
+//
+VoteType native fraglimit
+{
+ Description = "Set frag limit";
+ Argument = int( 0, 65535 );
+ ArgumentLabel = "New limit";
+ MenuRange = 0, 100;
+ ForbidCVar = "sv_nofraglimitvote";
+}
+
+//
+// Set the LMS win limit
+//
+VoteType native winlimit
+{
+ Description = "Set LMS win limit";
+ Argument = int( 0, 255 );
+ ArgumentLabel = "New limit";
+ MenuRange = 0, 100;
+ ForbidCVar = "sv_nowinlimitvote";
+}
+
+//
+// Set the time limit
+//
+VoteType native timelimit
+{
+ Argument = float( 0, 255 );
+ Description = "Set time limit";
+ ArgumentLabel = "New limit";
+ MenuRange = 0, 100;
+ ForbidCVar = "sv_notimelimitvote";
+}
+
+//
+// Set the point limit
+//
+VoteType native pointlimit
+{
+ Description = "Set point limit";
+ Argument = int( 0, 65535 );
+ ArgumentLabel = "New limit";
+ MenuRange = 0, 100;
+ ForbidCVar = "sv_nopointlimitvote";
+}
+
+//
+// Set the duel win limit
+//
+VoteType native duellimit
+{
+ Description = "Set duel win limit";
+ Argument = int( 0, 255 );
+ ArgumentLabel = "New limit";
+ MenuRange = 0, 100;
+ ForbidCVar = "sv_noduellimitvote";
+}
+
+//
+// Change to a given level gracefully with intermission
+//
+VoteType native changemap
+{
+ Description = "Change level";
+ ForbidCVar = "sv_nochangemapvote";
+ Argument = map;
+ MapInRotation;
+}
+
+//
+// Restart the game on a given level
+//
+VoteType native map
+{
+ Description = "Restart game";
+ ForbidCVar = "sv_nomapvote";
+ Argument = map;
+ MapInRotation;
+}
|