/*********************** Licensing *******************************************************
*
* Copyright 2008-2010 @ Brad Jones
* Copyright 2015-2017 @ Addons zz
* Copyright 2004-2017 @ AMX Mod X Development Team
*
* Plugin Thread: https://forums.alliedmods.net/showthread.php?t=273019
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 3 of the License, or ( at
* your option ) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*****************************************************************************************
*/
// Configuration Main Definitions
// ###############################################################################################
/**
* This version number must be synced with "githooks/GALILEO_SMA_VERSION.txt" for manual edition.
* To update them automatically, use: ./githooks/updateVersion.sh [major | minor | patch | build]
*/
new const PLUGIN_NAME[] = "Galileo";
new const PLUGIN_AUTHOR[] = "Brad Jones/Addons zz";
new const PLUGIN_VERSION[] = "v5.5.0-833";
/**
* Enables the support to Sven Coop 'mp_nextmap_cycle' cvar and vote map start by the Ham_Use
* "game_end". It will require the '' module.
*/
#define IS_TO_ENABLE_SVEN_COOP_SUPPPORT 1
/**
* Change this value from 1 to 0, to disable Re-HLDS and Re-Amx Mod X support. If you disable the
* support, and you are using the Re-HLDS and Re-Amx Mod X, you server may crash.
*/
#define IS_TO_ENABLE_RE_HLDS_RE_AMXMODX_SUPPORT 1
// Debugger Main Definitions
// ###############################################################################################
/**
* This is to view internal program data while execution. See the function 'debugMesssageLogger(...)'
* and the variable 'g_debug_level' for more information. Usage example, to enables several levels:
* #define DEBUG_LEVEL 1+2+4+16
*
* @note when the 'DEBUG_LEVEL_FAKE_VOTES' is activated, usually the voting will be approved
* because it creates also a fake players count. So, do not enable 'DEBUG_LEVEL_FAKE_VOTES'
* if you do not want the map voting starting on an empty server.
*
* 0 - Disables this feature.
*
* 1 - Normal/basic debugging/depuration.
*
* 2 - Run the NORMAL Unit Tests on the server start.
*
* 4 - Run the DELAYED Unit Tests on the server start.
*
* 8 - a) To create fake votes. See the function 'create_fakeVotes()'.
* b) Set the vote time to 5 seconds.
* c) To skip the 'pendingVoteCountdown()'.
* d) To create fake players count. See the function 'get_real_players_number()'.
*
* 16 - Enable DEBUG_LEVEL 1 and all its debugging/depuration available.
*
* 32 - Run the MANUAL test on server start.
*
* 64 - Disable the LOG() while running the Unit Tests.
*
* 127 - Enable the levels 1, 2, 4, 8, 16, 32 and 64.
*
* Default value: 0
*/
#define DEBUG_LEVEL 0
/**
* How much players use when the debugging level 'DEBUG_LEVEL_FAKE_VOTES' is enabled.
*/
#define FAKE_PLAYERS_NUMBER_FOR_DEBUGGING 3
/**
* When the debug mode `DEBUG_LEVEL` is enabled, the map_populateList(4) will show up to this maps
* loaded from the server.
*/
#define MAX_MAPS_TO_SHOW_ON_MAP_POPULATE_LIST 10
/**
* Debugging level configurations.
*/
#define DEBUG_LEVEL_NORMAL 1
#define DEBUG_LEVEL_UNIT_TEST_NORMAL 2
#define DEBUG_LEVEL_UNIT_TEST_DELAYED 4
#define DEBUG_LEVEL_FAKE_VOTES 8
#define DEBUG_LEVEL_CRITICAL_MODE 16
#define DEBUG_LEVEL_MANUAL_TEST_START 32
#define DEBUG_LEVEL_DISABLE_TEST_LOGS 64
/**
* Common strings sizes used around the plugin.
*/
#define MAX_LONG_STRING 256
#define MAX_COLOR_MESSAGE 190
#define MAX_SHORT_STRING 64
#define MAX_BIG_BOSS_STRING 512
#define MAX_NOMINATION_TRIE_KEY_SIZE 48
#define MAX_MAPNAME_LENGHT 64
#define MAX_FILE_PATH_LENGHT 128
#define MAX_PLAYER_NAME_LENGHT 48
/**
* Necessary modules.
*/
#include
#include
#include
/**
* Force the use of semicolons on every statements.
*/
#pragma semicolon 1
/**
* Global Debugging tools used on any 'DEBUG_LEVEL'.
*/
#if DEBUG_LEVEL >= DEBUG_LEVEL_NORMAL
/**
* The file on the './addons/amxmodx/logs' folder, to save the debugging text output.
*/
new const DEBUGGER_OUTPUT_LOG_FILE_NAME[] = "_galileo_log.txt";
/**
* Used to know when the Unit Tests are running.
*/
new bool:g_test_areTheUnitTestsRunning;
/**
* Allow the Manual Unit Tests to disable LOG() debugging messages when the level
* DEBUG_LEVEL_DISABLE_TEST_LOGS is enabled. Initialize it with true to allow the server to be
* logging from its first start.
*/
new bool:g_test_isToEnableLogging = true;
/**
* Write messages to the debug log file on 'addons/amxmodx/logs'.
*
* @param log_file the log file name.
* @param formatted_message the formatted message to write down to the debug log file.
*/
stock writeToTheDebugFile( const log_file[], const formatted_message[] )
{
new currentTime;
static lastRun;
currentTime = tickcount();
log_to_file( log_file, "{%.3f %d %5d %4d} %s", get_gametime(), heapspace(), currentTime, currentTime - lastRun, formatted_message );
lastRun = currentTime;
// Removes the compiler warning `warning 203: symbol is never used` with some DEBUG levels.
if( g_test_areTheUnitTestsRunning && g_test_isToEnableLogging && DEBUGGER_OUTPUT_LOG_FILE_NAME[0] ) { }
}
#endif
/**
* Setup the debugging tools when they are used/necessary.
*/
#if DEBUG_LEVEL & ( DEBUG_LEVEL_NORMAL | DEBUG_LEVEL_CRITICAL_MODE )
#define DEBUG
#define LOG(%1) debugMesssageLogger( %1 );
/**
* 0 - Disabled all debug output print.
*
* 1 - Displays basic debug messages.
*
* 2 - a) Players disconnecting and total number.
* b) Multiple time limits changes and restores.
* c) Cvars changes (mp_chattime, mp_timelimit, etc)
*
* 4 - a) Maps events.
* b) Vote choices.
* c) Nominations.
* d) Calls to map_populateList(4).
*
* 8 - a) Loaded vote choices.
* b) Minplayers-Whitelist debugging.
* c) Actions at startTheVoting(1).
*
* 16 - Runoff voting.
*
* 32 - Rounds end map voting.
*
* 64 - Debug for the color_print(...) function.
*
* 128 - Functions entrances messages.
*
* 256 - High called functions calls.
*
* 511 - Enables all debug logging levels.
*/
new g_debug_level = 1+128;
/**
* Write debug messages accordantly with the 'g_debug_level' variable.
*
* @param mode the debug mode level, see the variable 'g_debug_level' for the levels.
* @param text the debug message, if omitted its default value is ""
* @param any the variable number of formatting parameters
*
* @see the stock writeToTheDebugFile( log_file[], formatted_message[] ) for the output log
* 'DEBUGGER_OUTPUT_LOG_FILE_NAME'.
*/
stock debugMesssageLogger( const mode, const message[] = "", any:... )
{
if( mode & g_debug_level )
{
#if DEBUG_LEVEL & DEBUG_LEVEL_DISABLE_TEST_LOGS
if( g_test_isToEnableLogging )
{
static formatted_message[ MAX_BIG_BOSS_STRING ];
vformat( formatted_message, charsmax( formatted_message ), message, 3 );
writeToTheDebugFile( DEBUGGER_OUTPUT_LOG_FILE_NAME, formatted_message );
}
#else
static formatted_message[ MAX_BIG_BOSS_STRING ];
vformat( formatted_message, charsmax( formatted_message ), message, 3 );
writeToTheDebugFile( DEBUGGER_OUTPUT_LOG_FILE_NAME, formatted_message );
#endif
}
}
#else
#define LOG(%1)
#endif
// Unit Tests Main Definitions
// ###############################################################################################
/**
* Setup the Unit Tests when they are used/necessary.
*/
#if DEBUG_LEVEL & ( DEBUG_LEVEL_UNIT_TEST_NORMAL | DEBUG_LEVEL_MANUAL_TEST_START | DEBUG_LEVEL_UNIT_TEST_DELAYED )
/**
* Contains all imediates unit tests to execute.
*/
stock normalTestsToExecute()
{
LOG( 128, "I AM ENTERING ON normalTestsToExecute(0)" )
test_registerTest();
test_isInEmptyCycle();
test_mapGetNext_cases();
test_loadCurrentBlackList_cases();
test_resetRoundsScores_cases();
test_loadVoteChoices_cases();
test_nominateAndUnnominate_load();
test_RTVAndUnRTV_load();
test_negativeRTVValues_load();
test_getUniqueRandomBasic_load();
test_getUniqueRandomInt_load();
test_whatGameEndingTypeIt_load();
test_convertNumericBase_load();
test_setCorrectMenuPage_load();
test_strictValidMapsTrie_load();
test_populateListOnSeries_load();
test_GET_MAP_NAME_load();
test_GET_MAP_INFO_load();
test_SortCustomSynced2D();
test_configureTheNextMap();
test_handleServerStart();
test_endOfMapVoting();
}
/**
* Contains all delayed unit tests to execute.
*/
public dalayedTestsToExecute()
{
LOG( 128, "I AM ENTERING ON dalayedTestsToExecute(0)" )
// Currently there are none Delayed Unit Tests Created.
}
/**
* Run the manual call Unit Tests, by 'say run' and 'say_team run'.
*/
public inGameTestsToExecute( player_id )
{
LOG( 128, "I AM ENTERING ON inGameTestsToExecute(1) player_id: %d", player_id )
// Save the game cvars?
if( !g_test_areTheUnitTestsRunning ) saveServerCvarsForTesting();
for( new i = 0; i < 1000; i++ )
{
for( new i = 0; i < 1000; i++ )
{
}
// LOG( 1, "Current i is: %d", i )
}
test_negativeRTVValues_load();
// test_endOfMapVoting();
// test_handleServerStart();
// test_mapGetNext_cases();
// test_configureTheNextMap();
// test_loadCurrentBlackList_cases();
// test_SortCustomSynced2D();
// test_GET_MAP_INFO_load();
// test_GET_MAP_NAME_load();
// test_populateListOnSeries_load();
// test_setCorrectMenuPage_load();
// test_convertNumericBase_load();
// test_whatGameEndingTypeIt_load();
// test_getUniqueRandomInt_load();
// test_getUniqueRandomBasic_load();
// test_nominateAndUnnominate_load();
// test_loadVoteChoices_cases();
//test_colorChatLimits( player_id );
//test_unnominatedDisconnected( player_id );
//test_announceVoteBlockedMap_a();
//test_announceVoteBlockedMap_c();
// Restore the game cvars
if( g_test_areTheUnitTestsRunning ) printTheUnitTestsResults();
}
/**
* Wrapper to avoid passing a low used default parameter on the start of the function call.
*/
#define HELPER_MAP_FILE_LIST_LOAD(%1) helper_mapFileListLoadReplace( false, %1 );
#define HELPER_MAP_FILE_LIST_LOAD2(%1) helper_mapFileListLoadReplace2( false, %1 );
/**
* Accept all maps as valid while running the unit tests.
*/
#define IS_MAP_VALID(%1) ( isAllowedValidMapByTheUnitTests(%1) || !g_test_isToUseStrictValidMaps && IS_MAP_VALID_BSP( %1 ) )
/**
* Shorts the error message line. It requires the variable `errorMessage[ MAX_LONG_STRING ]`
* to be declared before it.
*
* Usage example:
*
* ERR( "The expected result must to be %d, instead of %d.", expected, test_result )
*/
#define ERR(%1) formatex( errorMessage, charsmax( errorMessage ), %1 );
/**
* Call the internal function to perform its task and stop the current test execution to avoid
* double failure at the test control system. This is to be used instead of setTestFailure(1)
* when 2 consecutive tests use the same `test_id`.
*
* @see the stock 'setTestFailure(3)'.
*/
#define SET_TEST_FAILURE(%1) \
{ \
if( setTestFailure( %1 ) ) \
{ \
LOG( 1, " ( SET_TEST_FAILURE ) Just returning/blocking." ) \
return; \
} \
}
/**
* Write debug messages to server's console and log file.
*
* @param message the debug message, if omitted its default value is ""
* @param any the variable number of formatting parameters
*
* @see the stock writeToTheDebugFile( log_file[], formatted_message[] ) for the output log
* 'DEBUGGER_OUTPUT_LOG_FILE_NAME'.
*/
stock print_logger( const message[] = "", any:... )
{
static formatted_message[ MAX_BIG_BOSS_STRING ];
vformat( formatted_message, charsmax( formatted_message ), message, 2 );
writeToTheDebugFile( DEBUGGER_OUTPUT_LOG_FILE_NAME, formatted_message );
}
/**
* Test unit variables related to the DEBUG_LEVEL_UNIT_TESTs.
*/
new g_test_maxDelayResult;
new g_test_testsNumber;
new g_test_failureNumber;
new g_test_lastMaxDelayResult;
new g_lastNormalTestToExecuteId;
new bool: g_test_isToUseStrictValidMaps;
new Trie: g_test_failureIdsTrie;
new Trie: g_test_strictValidMapsTrie;
new Array:g_test_failureIdsArray;
new Array:g_test_idsAndNamesArray;
new Array:g_test_failureReasonsArray;
new g_test_lastTimeStamp;
new g_test_aimedPlayersNumber;
new g_test_gameElapsedTime;
new g_test_startDayInteger;
new g_test_printedMessage[ MAX_COLOR_MESSAGE ];
new g_test_nomMapFilePath[] = "test_nomMapFilePath.txt";
new g_test_voteMapFilePath[] = "test_voteFilePathTestFile.txt";
new g_test_whiteListFilePath[] = "test_loadCurrentBlackList.txt";
new g_test_minPlayersFilePath[] = "test_minimumPlayersTestFile.txt";
#else
#define IS_MAP_VALID(%1) ( IS_MAP_VALID_BSP( %1 ) )
#endif
// In-place Main Constants
// ###############################################################################################
/**
* Defines the maximum players number, when it is not specified for olders AMXX versions.
*/
#if !defined MAX_PLAYERS
#define MAX_PLAYERS 32
#endif
/**
* Includes the Sven Coop required module for support.
*/
#if IS_TO_ENABLE_SVEN_COOP_SUPPPORT > 0
#include
new cvar_mp_nextmap_cycle;
#endif
/**
* Register the color chat necessary variables.
*/
new bool:g_isColorChatSupported;
new bool:g_isColoredChatEnabled;
/**
* On the first server start, we do not know whether the color chat is allowed/enabled. This is due
* the register register_dictionary_colored(1) to be called on plugin_init(0) and the settings being
* loaded only at plugin_cfg(0).
*
* When we put the color tags inside the plugin, we need to check whether the color chat is enabled.
*/
#define IS_COLORED_CHAT_ENABLED() \
( g_isColorChatSupported \
&& g_isColoredChatEnabled )
/**
* Accordingly to `https://wiki.alliedmods.net/Half-life_1_game_events#HLTV`, only some mods support
* this game event.
*/
#define IS_NEW_ROUND_EVENT_SUPPORTED() \
( g_isColorChatSupported || g_isDayOfDefeat )
#if AMXX_VERSION_NUM < 183
new g_user_msgid;
#endif
new cvar_coloredChatEnabled;
/**
* Switch between the AMXX 182 and 183 deprecated/bugged functions.
*/
#if AMXX_VERSION_NUM < 183
#define STR_TOKEN strtok
#else
#define STR_TOKEN strtok2
#endif
/**
* General Constants.
*/
#define MUTE_MESSAGES_SPAMMING 1
#define MAX_INTEGER 2147483647
#define MIN_INTEGER -2147483648
#define LISTMAPS_USERID 0
#define LISTMAPS_LAST_MAP 1
#define COMMAND_VOTEMAP_DISABLED 1
#define COMMAND_LISTMAPS_DISABLED 1
#define RUNOFF_ENABLED 1
#define RUNOFF_EXTEND 2
#define VOTE_MININUM_PLAYERS_REQUIRED 1
#define VOTE_MIDDLE_PLAYERS_REQUIRED 2
#define FIRST_SERVER_START 2
#define SECOND_SERVER_START 1
#define AFTER_READ_MAPCYCLE 0
#define END_OF_MAP_VOTE_ASK 1
#define END_OF_MAP_VOTE_ANNOUNCE1 2
#define END_OF_MAP_VOTE_ANNOUNCE2 4
#define END_OF_MAP_VOTE_NO_ANNOUNCEMENT 8
#define VOTE_TIME_SEC 1.0
#define VOTE_TIME_HUD1 7.0
#define VOTE_TIME_HUD2 5.0
#define VOTE_TIME_ANNOUNCE1 10.0
#define VOTE_TIME_ANNOUNCE2 5.0
#define VOTE_TIME_RUNOFF 3.0
#define VOTE_TIME_COUNT 5.5
#define RTV_CMD_STANDARD 1
#define RTV_CMD_SHORTHAND 2
#define RTV_CMD_DYNAMIC 4
#define RTV_CMD_SINGLE_PLAYER_DISABLE 8
#define RTV_CMD_EXTENSION_WAIT_DISABLE 16
#define MAPFILETYPE_SINGLE 1
#define MAPFILETYPE_GROUPS 2
#define IS_TO_RTV_WAIT_ADMIN 1
#define IS_TO_RTV_NOT_ALLOW_STAY 2
#define IS_DISABLED_VOTEMAP_EXIT 1
#define IS_DISABLED_VOTEMAP_INTRO 2
#define IS_DISABLED_VOTEMAP_RUNOFF 4
#define IS_DISABLED_VOTEMAP_EXTENSION 8
#define IS_ENABLED_VOTEMAP_NOMINATIONS 16
#define MAP_CHANGES_AT_THE_NEXT_ROUND_START 0
#define MAP_CHANGES_AT_THE_CURRENT_ROUND_END 1
#define IS_MAP_MAPCHANGE_COUNTDOWN 1
#define IS_MAP_MAPCHANGE_DROP_WEAPONS 2
#define IS_MAP_MAPCHANGE_FREEZE_PLAYERS 4
#define IS_MAP_MAPCHANGE_BUY_GRENADES 8
#define IS_MAP_MAPCHANGE_FRIENDLY_FIRE 16
#define IS_BY_TIMER 1
#define IS_BY_FRAGS 2
#define IS_BY_ROUNDS 4
#define IS_BY_WINLIMIT 8
#define IS_VOTE_IN_PROGRESS 1
#define IS_FORCED_VOTE 2
#define IS_RUNOFF_VOTE 4
#define IS_VOTE_OVER 8
#define IS_EARLY_VOTE 16
#define IS_VOTE_EXPIRED 32
#define IS_RTV_VOTE 64
#define IS_TO_LOAD_THE_FIRST_MAP_SERIES 1
#define IS_TO_LOAD_ALL_THE_MAP_SERIES 2
#define IS_TO_LOAD_EXPLICIT_MAP_SERIES 4
#define IS_TO_LOAD_ALTERNATE_MAP_SERIES 8
#define SOUND_GET_READY_TO_CHOOSE 1
#define SOUND_COUNTDOWN 2
#define SOUND_TIME_TO_CHOOSE 4
#define SOUND_RUNOFF_REQUIRED 8
#define SOUND_MAPCHANGE 16
#define HUD_CHANGELEVEL_COUNTDOWN 1
#define HUD_VOTE_VISUAL_COUNTDOWN 2
#define HUD_CHANGELEVEL_ANNOUNCE 4
#define HUD_VOTE_RESULTS_ANNOUNCE 8
#define HUD_TIMELEFT_ANNOUNCE 16
#define SHOW_STATUS_NEVER 0
#define SHOW_STATUS_AFTER_VOTE 1
#define SHOW_STATUS_AT_END 2
#define SHOW_STATUS_ALWAYS 3
#define SHOW_STATUS_ALWAYS_UNTIL_VOTE 4
#define END_AT_RIGHT_NOW 0
#define END_AT_THE_CURRENT_ROUND_END 1
#define END_AT_THE_NEXT_ROUND_END 2
#define STATUS_TYPE_COUNT 1
#define STATUS_TYPE_PERCENTAGE 2
#define ANNOUNCE_CHOICE_PLAYERS 1
#define ANNOUNCE_CHOICE_ADMINS 2
#define HIDE_AFTER_USER_VOTE_NONE_OPTION 0
#define ALWAYS_KEEP_SHOWING_NONE_OPTION 1
#define CONVERT_NONE_OPTION_TO_CANCEL_LAST_VOTE 2
#define MAX_PREFIX_SIZE 16
#define MAX_PREFIX_COUNT 32
#define MAX_OPTIONS_IN_VOTE 9
#define MAX_MENU_ITEMS_PER_PAGE 8
#define MAX_NOM_MENU_ITEMS_PER_PAGE 7
#define MAX_STANDARD_MAP_COUNT 25
#define MAX_NOM_MATCH_COUNT 1000
#define MAX_PLAYERS_COUNT MAX_PLAYERS + 1
#define SERVER_START_CURRENTMAP 1
#define SERVER_START_NEXTMAP 2
#define SERVER_START_MAPVOTE 3
#define SERVER_START_RANDOMMAP 4
#define SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR 2
#define DELAY_TO_WAIT_THE_SERVER_CVARS_TO_BE_LOADED 50.0
/**
* The value used when the voting time is set to 0.
*/
#define INFINITY_VOTING_TIME_VALUE 20
/**
* Used on the count `++g_showLastRoundHudCounter % LAST_ROUND_HUD_SHOW_INTERVAL > 6`, called each second.
*/
#define LAST_ROUND_HUD_SHOW_INTERVAL 30
/**
* Highly and directly impact on the server change level performance.
*
* If your server is hosted under an SSD (Solid State Disk), a value high as 20 will take up to 10 or 20 seconds to
* complete the change level for a mapcycle within 800 maps.
*
* If your server is hosted under an HD (Hard Disk), a value high as 20 will take up to 60 or 120 seconds to
* complete the change level for a mapcycle within 800 maps.
*/
#define MAX_NON_SEQUENCIAL_MAPS_ON_THE_SERIE 3
/**
* Define how many times the server can crash on a map, before that map to be ignored and to select
* the next map on the map cycle to be played. The counter starts on 0.
*/
#define MAX_SERVER_RESTART_ACCEPTABLE 3
/**
* The rounds number before the mp_maxrounds/mp_winlimit to be reached to start the map voting. This
* constant is equivalent to the `START_VOTEMAP_MIN_TIME` and `START_VOTEMAP_MAX_TIME` concepts.
*
* Make sure this is big enough, because the rounds could be finish pretty fast and the game may end
* before it, as this option only takes effect and the `cvar_endOnRound` and `cvar_endOfMapVoteStart`
* are not handling the map end.
*/
#define VOTE_START_ROUNDS ( IS_THE_ROUND_AVERAGE_TIME_TOO_SHORT() ? 10 : ( IS_THE_ROUND_AVERAGE_TIME_SHORT() ? 7 : 4 ) )
/**
* Calculates whether to start the map voting due map closing time.
*
* @param fragsRemaining how much frags are remaining to finish the map.
*/
#define IS_TO_START_THE_VOTE_BY_FRAGS(%1) ( %1 < ( g_fragLimitNumber > 30 ? 20 : 12 ) )
/**
* The rounds number required to be reached to allow predict if this will be the last round and
* allow to start the voting.
*/
#define MIN_ROUND_TIME_DELAY 10
#define FRAGS_BY_ROUND_AVERAGE 7
#define SECONDS_BY_ROUND_AVERAGE 70
#define MIN_VOTE_START_ROUNDS_DELAY 1
#define MAX_SAVED_ROUNDS_FOR_AVERAGE 5
/**
* Determine whether there will be a alternate vote option as `Stay Here` or `Extend Map`.
*/
#define IS_MAP_EXTENSION_ALLOWED() ( g_isMapExtensionAllowed && g_isGameFinalVoting || g_isExtendmapAllowStay && !g_isGameFinalVoting )
/**
* Every time an operation close to the call to map_manageEnd(0) need to be performed on the cvars
* `mp_timelimit`, `mp_fraglimit`, `mp_maxrounds` and `mp_winlimit`, this macro must to be used to
* retrieve the correct cvar value, otherwise it will probably get the value 0 and go nuts.
*/
#define GAME_ENDING_CONTEXT_SAVED(%1,%2) ( ( g_isGameEndingTypeContextSaved ) ? ( %1 ) : ( %2 ) )
/**
* The periodic task created on 'configureServerStart(1)' use this intervals in seconds to
* start checking for an end map voting start. Defines the interval where the periodic tasks
* as map_manageEnd(0) and vote_manageEnd(0) will be checked.
*/
#define PERIODIC_CHECKING_INTERVAL 15
#define START_VOTEMAP_MIN_TIME ( g_totalVoteTime + PERIODIC_CHECKING_INTERVAL + 3 )
#define START_VOTEMAP_MAX_TIME ( g_totalVoteTime )
// In-place Macros
// ###############################################################################################
/**
* To return `PLUGIN_HANDLED` or `PLUGIN_CONTINUE`, accordantly to the settings set by the cvar.
*
* @param true if the message/command should be blocked (PLUGIN_HANDLED), false otherwise (PLUGIN_CONTINUE).
*/
#define IS_TO_MUTE(%1) \
( ( %1 && ( get_pcvar_num( cvar_generalOptions ) & MUTE_MESSAGES_SPAMMING ) ) ? PLUGIN_HANDLED : PLUGIN_CONTINUE )
//
/**
* When there are enough rounds played and the round average time is neither even half to the vote
* total time, it is pretty pointless the try start the voting at the round start.
*/
#define IS_THE_ROUND_AVERAGE_TIME_TOO_SHORT() \
( g_totalRoundsSavedTimes > MAX_SAVED_ROUNDS_FOR_AVERAGE - 2 \
&& g_roundAverageTime < g_totalVoteTime / 2 )
//
/**
* When there are some rounds played and the round average time is just smaller than the to the vote
* total time, we need to try start the voting at the round start on round before the actual point as
* the voting will extend from one round to the other round.
*/
#define IS_THE_ROUND_AVERAGE_TIME_SHORT() \
( g_totalRoundsSavedTimes > MIN_VOTE_START_ROUNDS_DELAY \
&& g_roundAverageTime < g_totalVoteTime )
//
/**
* If this is called when the voting or the round ending is going on, it will cause the voting/round
* ending to be cut and will force the map to immediately change to the next map.
*/
#define IS_ABLE_TO_PERFORM_A_MAP_CHANGE() \
( !task_exists( TASKID_PROCESS_LAST_ROUND_COUNT ) \
|| !task_exists( TASKID_INTERMISSION_HOLD ) \
|| !( g_voteStatus & IS_VOTE_IN_PROGRESS ) \
|| !g_isTheRoundEndWhileVoting )
//
/**
* This indicates the players minimum number necessary to allow the last round to be finished when
* the time runs out.
*/
#define ARE_THERE_ENOUGH_PLAYERS_FOR_MANAGE_END() \
( get_real_players_number() >= get_pcvar_num( cvar_endOnRoundMininum ) )
//
/**
* Specifies how much time to delay the voting start after the round start.
*/
#define ROUND_VOTING_START_SECONDS_DELAY() \
( get_pcvar_num( cvar_mp_freezetime ) + PERIODIC_CHECKING_INTERVAL \
- ( get_pcvar_num( cvar_isToAskForEndOfTheMapVote ) & END_OF_MAP_VOTE_ANNOUNCE1 ? 5 : 0 ) \
+ ( g_roundAverageTime > 2 * g_totalVoteTime / 3 ? g_totalVoteTime / 5 : 1 ) )
//
/**
* Start a map voting delayed after the mp_maxrounds or mp_winlimit minimum to be reached.
*/
#define START_VOTING_BY_MIDDLE_ROUND_DELAY(%1) \
set_task( float( ROUND_VOTING_START_SECONDS_DELAY() ), %1, TASKID_START_VOTING_DELAYED );
//
/**
* Verifies if a voting is or was already processed.
*/
#define IS_END_OF_MAP_VOTING_GOING_ON() \
( g_voteStatus & ( IS_VOTE_IN_PROGRESS | IS_VOTE_OVER ) )
//
/**
* Verifies if the round time is too big. If the map time is too big or near zero, makes not sense
* to wait to start the map voting and will probably not to start the voting.
*/
#define IS_THE_ROUND_TIME_TOO_BIG(%1) \
( ( %1 > 8.0 \
&& g_roundAverageTime > 300 ) \
|| %1 < 0.5 )
//
/**
* Boolean check for the Whitelist feature. The Whitelist feature specifies the time where the maps
* are allowed to be added to the voting list as fillers after the nominations being loaded.
*/
#define IS_WHITELIST_ENABLED() \
( get_pcvar_num( cvar_whitelistMinPlayers ) == 1 \
|| get_real_players_number() < get_pcvar_num( cvar_whitelistMinPlayers ) )
//
/**
* Boolean check for the nominations minimum players controlling feature. When there are less
* players than cvar 'cvar_voteMinPlayers' value on the server, use a different map file list
* specified at the cvar 'gal_vote_minplayers_mapfile' to fill the map voting as map fillers
* instead of the cvar 'gal_vote_mapfile' map file list.
*/
#define IS_NOMINATION_MININUM_PLAYERS_CONTROL_ENABLED() \
( get_real_players_number() < get_pcvar_num( cvar_voteMinPlayers ) \
&& get_pcvar_num( cvar_nomMinPlayersControl ) )
//
/**
* When it is set the maximum voting options as 9, we need to give space to the `Stay Here` and
* `Extend` on the voting menu, calculating how many voting choices are allowed to be used,
* considering whether the voting map extension option is being showed or not.
*/
#define MAX_VOTING_CHOICES() \
( IS_MAP_EXTENSION_ALLOWED() ? \
( g_maxVotingChoices >= MAX_OPTIONS_IN_VOTE ? \
g_maxVotingChoices - 1 : g_maxVotingChoices ) : g_maxVotingChoices )
//
/**
* Return whether is to allow a crash search on this server start or not.
*/
#define IS_TO_ALLOW_A_CRASH_SEARCH(%1) \
( file_exists( modeFlagFilePath ) \
&& !( DEBUG_LEVEL & DEBUG_LEVEL_FAKE_VOTES ) )
//
/**
* Used to determine whether a map is blocked by the Whitelist feature.
*
* @param isWhitelistEnabled whether or not is to allow the Whitelist blockage.
* @param mapNameToCheck the map name to be verified if the map list checking is enabled.
*/
#define IS_WHITELIST_BLOCKING(%1,%2) \
( %1 \
&& ( ( g_blacklistTrie \
&& TrieKeyExists( g_blacklistTrie, %2 ) ) \
|| ( g_whitelistTrie \
&& !TrieKeyExists( g_whitelistTrie, %2 ) ) ) )
//
// Global Macro Expansions
// ###############################################################################################
/**
* Used to set a the voting time to a variable.
*/
#define SET_VOTING_TIME_TO(%1,%2) \
{ \
if( ( %1 = get_pcvar_num( %2 ) ) < 5 ) \
{ \
%1 = INFINITY_VOTING_TIME_VALUE; \
} \
}
/**
* Convert colored strings codes '!g for green', '!y for yellow', '!t for team'.
*
* @param string[] a string pointer to be converted.
*/
#define INSERT_COLOR_TAGS(%1) \
{ \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "!g", "^4" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "!t", "^3" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "!n", "^1" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "!y", "^1" ); \
}
/**
* Remove the colored strings codes '^4 for green', '^1 for yellow', '^3 for team' and
* '^2 for unknown'.
*
* @param string[] a string pointer to be formatted.
*/
#define REMOVE_CODE_COLOR_TAGS(%1) \
{ \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "^4", "" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "^3", "" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "^2", "" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "^1", "" ); \
}
/**
* Remove the colored strings codes '!g for green', '!y for yellow', '!t for team' and
* '!n for unknown'.
*
* @param string[] a string pointer to be formatted.
*/
#define REMOVE_LETTER_COLOR_TAGS(%1) \
{ \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "!g", "" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "!t", "" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "!n", "" ); \
replace_all( %1, MAX_COLOR_MESSAGE - 1, "!y", "" ); \
}
/**
* Print to the users chat, a colored chat message.
*
* @param player_id a player id from 1 to MAX_PLAYERS
* @param message a colored formatted string message. At the AMXX 182 it must start within
* one color code as found on REMOVE_CODE_COLOR_TAGS(1) above macro. Example:
* "^1Hi! I am a ^3 colored message".
*/
#define PRINT_COLORED_MESSAGE(%1,%2) \
{ \
message_begin( MSG_ONE_UNRELIABLE, g_user_msgid, _, %1 ); \
write_byte( %1 ); \
write_string( %2 ); \
message_end(); \
}
/**
* Get the player name. If the player is not connected, uses "Unknown Dude" as its name.
*
* @param player_id the player id
* @param name_string a string pointer to hold the player name.
*/
#define GET_USER_NAME(%1,%2) \
{ \
if( is_user_connected( %1 ) ) \
{ \
get_user_name( %1, %2, charsmax( %2 ) ); \
} \
else \
{ \
copy( %2, charsmax( %2 ), "Unknown Dude" ); \
} \
}
/**
* Helper to adjust the menus options 'back', 'next' and exit. This requires prior definition of
* the variables 'menuOptionString[ MAX_SHORT_STRING ] and 'player_id', where the player id must
* point to the player identification number which will see the menu.
*
* @param propertyConstant one of the new menu property constants
* @param menuId the menu identification number
* @param langConstantName the dictionary registered LANG constant
*/
#define SET_MENU_LANG_STRING_PROPERTY(%1,%2,%3) \
{ \
formatex( menuOptionString, charsmax( menuOptionString ), "%L", player_id, %3 ); \
menu_setprop( %2, %1, menuOptionString ); \
}
/**
* Accept a map as valid, even when they end with `.bsp`.
*/
#define IS_MAP_VALID_BSP(%1) ( is_map_valid( %1 ) || is_map_valid_bsp_check( %1 ) )
/**
* Split the map name from a string.
*
* @param textLine a string containing a map name at the first part
* @param mapName a string to save the map extracted
*/
#define GET_MAP_NAME_LEFT(%2,%3) \
{ \
STR_TOKEN( %2, %3, MAX_MAPNAME_LENGHT - 1, \
__g_getMapNameRightToken, MAX_MAPNAME_LENGHT - 1, ' ' ); \
}
/**
* Split the map info from a string.
*
* @param textLine a string containing a map info at the second part
* @param mapName a string to save the map extracted
*/
#define GET_MAP_INFO_RIGHT(%2,%3) \
{ \
STR_TOKEN( %2, __g_getMapNameRightToken, MAX_MAPNAME_LENGHT - 1, \
%3 , MAX_MAPNAME_LENGHT - 1, ' ' ); \
}
/**
* Internal variables used by the GET_MAP_NAME(3) and GET_MAP_INFO(3) macros.
* By conversion, never ever a Trie will store together the map information/info,
* only the map name as the hash key.
*/
new __g_getMapNameInputLine [ MAX_MAPNAME_LENGHT ];
new __g_getMapNameRightToken[ MAX_MAPNAME_LENGHT ];
/**
* Retrieves a map name from a Dynamic Array of maps.
*
* @param mapArray a Dynamic Array of maps
* @param mapIndex an valid index on the `mapArray` parameter
* @param mapName a string to store the map name
*/
#define GET_MAP_NAME(%1,%2,%3) \
{ \
ArrayGetString( %1, %2, __g_getMapNameInputLine, MAX_MAPNAME_LENGHT - 1 ); \
GET_MAP_NAME_LEFT( __g_getMapNameInputLine, %3 ) \
}
/**
* Retrieves a map name from a Dynamic Array of maps.
*
* @param mapArray a Dynamic Array of maps
* @param mapIndex an valid index on the `mapArray` parameter
* @param mapName a string to store the map name
*/
#define GET_MAP_INFO(%1,%2,%3) \
{ \
ArrayGetString( %1, %2, __g_getMapNameInputLine, MAX_MAPNAME_LENGHT - 1 ); \
GET_MAP_INFO_RIGHT( __g_getMapNameInputLine, %3 ) \
}
/**
* Check whether a line not a commentary, empty and if it is a valid map by IS_MAP_VALID(1).
*
* @param currentLine a string within the line to check.
*/
#define IS_IT_A_VALID_MAP_LINE(%1) \
( %1[ 0 ] \
&& %1[ 1 ] != ';' \
&& !equal( %1, "//", 2 ) )
/**
* General handler to assist object property applying and keep the code clear. This only need
* to be used with destructors/cleaners which does not support uninitialized handlers, requiring
* an if pre-checking.
*
* @param objectHandler the object handler to be called.
* @param objectIndentifation the object identification number to be destroyed.
*/
#define TRY_TO_APPLY(%1,%2) \
{ \
LOG( 128, "I AM ENTERING ON TRY_TO_APPLY(2) objectIndentifation: %d", %2 ) \
if( %2 ) \
{ \
%1( %2 ); \
} \
}
/**
* General handler to assist object property applying and keep the code clear. This only need
* to be used with cleaners and creators handlers.
*
* @param objectHandler the object handler to be called for cleaning.
* @param objectIndentifation the object identification number to be used.
* @param objectHandlerCreator the object handler to be called for creation.
*/
#define TRY_TO_CLEAN(%1,%2,%3) \
{ \
LOG( 128, "I AM ENTERING ON TRY_TO_CLEAN(3) objectIndentifation: %d", %2 ) \
if( %2 ) \
{ \
%1( %2 ); \
} \
else \
{ \
%2 = %3; \
} \
}
/**
* Wrapper to allow use the destroy_two_dimensional_array(2) as a cleaner on the TRY_TO_CLEAN(2).
*/
#define clear_two_dimensional_array(%1) destroy_two_dimensional_array( %1, false )
/**
* Check whether the menu exists, call menu_destroy(1) and set the menu to id to 0.
*
* @param menu_id_variable a variable within the player menu to be destroyed.
*/
#define DESTROY_PLAYER_NEW_MENU_TYPE(%1) \
{ \
LOG( 128, "I AM ENTERING ON DESTROY_PLAYER_NEW_MENU_TYPE(1) menu_id: %d", %1 ) \
if( %1 ) \
{ \
menu_destroy( %1 ); \
%1 = 0; \
} \
}
/**
* Check whether the menu exists, call menu_destroy(1) and set the menu to id to 0.
*
* @param menu_id_variable a variable within the player menu to be destroyed.
*/
#define TOGGLE_BIT_FLAG_ON_OFF(%1,%2) \
{ \
LOG( 256, "I AM ENTERING ON TOGGLE_BIT_FLAG_ON_OFF(2) mask: %d, flag: %d", %1, %2 ) \
%1 & %2 ? ( %1 &= ~%2 ) : ( %1 |= %2 ); \
}
/**
* Calculate which is the number of the last menu page.
*
* @param totalMenuItems how many items there are on the menu
* @param menuItemPerPage how much items there are on each menu's page
*/
#define GET_LAST_PAGE_NUMBER(%1,%2) \
( ( ( %1 + 1 ) / %2 ) \
+ ( ( ( ( %1 + 1 ) % %2 ) > 0 ) ? 1 : 0 ) );
// General Global Variables
// ###############################################################################################
/**
* Dummy value used on conditional statements to allow statements as always true or false.
*/
new const bool:g_dummy_value = false;
/**
* Task ids are 100000 apart.
*/
enum (+= 100000)
{
TASKID_RTV_REMINDER = 100000, // start with 100000
TASKID_SHOW_LAST_ROUND_HUD,
TASKID_SHOW_LAST_ROUND_MESSAGE,
TASKID_DELETE_USERS_MENUS,
TASKID_DELETE_USERS_MENUS_CARE,
TASKID_PREVENT_INFITY_GAME,
TASKID_EMPTYSERVER,
TASKID_START_VOTING_DELAYED,
TASKID_PROCESS_LAST_ROUND,
TASKID_DISPLAY_REMAINING_TIME,
TASKID_PROCESS_LAST_ROUND_COUNT,
TASKID_PROCESS_LAST_ROUNDCHANGE,
TASKID_VOTE_HANDLEDISPLAY,
TASKID_VOTE_DISPLAY,
TASKID_VOTE_EXPIRE,
TASKID_NOMINATION_PARTIAL,
TASKID_PENDING_VOTE_COUNTDOWN,
TASKID_DBG_FAKEVOTES,
TASKID_START_THE_VOTING,
TASKID_MAP_CHANGE,
TASKID_INTERMISSION_HOLD,
TASKID_FINISH_GAME_TIME_BY_HALF,
TASKID_BLOCK_NEW_VOTING_START,
TASKID_SERVER_CHANGE_LEVEL,
}
/**
* Game cvars.
*/
new cvar_mp_freezetime;
new cvar_mp_winlimit;
new cvar_mp_fraglimit;
new cvar_mp_maxrounds;
new cvar_mp_timelimit;
new cvar_mp_roundtime;
new cvar_mp_chattime;
new cvar_mp_friendlyfire;
new cvar_sv_maxspeed;
/**
* Server cvars.
*/
new cvar_extendmapAllowStayType;
new cvar_nextMapChangeAnnounce;
new cvar_nextMapChangeVotemap;
new cvar_disabledValuePointer;
new cvar_isFirstServerStart;
new cvar_isToShowVoteCounter;
new cvar_isToShowNoneOption;
new cvar_voteShowNoneOptionType;
new cvar_isExtendmapOrderAllowed;
new cvar_isToStopEmptyCycle;
new cvar_successfullLevels;
new cvar_unnominateDisconnected;
new cvar_endOnRound;
new cvar_endOnRoundChange;
new cvar_endOfMapVote;
new cvar_endOfMapVoteExpiration;
new cvar_endOfMapVoteStart;
new cvar_endOnRoundRtv;
new cvar_endOnRoundMininum;
new cvar_voteWeight;
new cvar_voteWeightFlags;
new cvar_maxMapExtendTime;
new cvar_extendmapStepMinutes;
new cvar_extendmapStepRounds;
new cvar_maxMapExtendRounds;
new cvar_extendmapStepFrags;
new cvar_maxMapExtendFrags;
new cvar_fragLimitSupport;
new cvar_extendmapAllowStay;
new cvar_isToAskForEndOfTheMapVote;
new cvar_emptyServerWaitMinutes;
new cvar_isEmptyCycleByMapChange;
new cvar_serverPlayersCount;
new cvar_emptyMapFilePath;
new cvar_rtvWaitMinutes;
new cvar_rtvWaitRounds;
new cvar_rtvWaitFrags;
new cvar_rtvWaitAdmin;
new cvar_rtvRatio;
new cvar_rtvCommands;
new cvar_cmdVotemap;
new cvar_cmdListmaps;
new cvar_listmapsPaginate;
new cvar_recentMapsBannedNumber;
new cvar_recentNomMapsAllowance;
new cvar_isOnlyRecentMapcycleMaps;
new cvar_banRecentStyle;
new cvar_voteDuration;
new cvar_nomMapFilePath;
new cvar_nomPrefixes;
new cvar_nomQtyUsed;
new cvar_nomPlayerAllowance;
new cvar_nomCleaning;
new cvar_isToShowExpCountdown;
new cvar_isEndMapCountdown;
new cvar_voteMapChoiceCount;
new cvar_voteMapChoiceNext;
new cvar_voteAnnounceChoice;
new cvar_generalOptions;
new cvar_voteUniquePrefixes;
new cvar_rtvReminder;
new cvar_serverStartAction;
new cvar_serverMoveCursor;
new cvar_gameCrashRecreationAction;
new cvar_serverTimeLimitRestart;
new cvar_serverMaxroundsRestart;
new cvar_serverWinlimitRestart;
new cvar_serverFraglimitRestart;
new cvar_runoffEnabled;
new cvar_runoffDuration;
new cvar_runoffRatio;
new cvar_runoffMapchoices;
new cvar_showVoteStatus;
new cvar_showVoteStatusType;
new cvar_isToReplaceByVoteMenu;
new cvar_soundsMute;
new cvar_hudsHide;
new cvar_voteMapFilePath;
new cvar_voteMinPlayers;
new cvar_voteMidPlayers;
new cvar_nomMinPlayersControl;
new cvar_voteMinPlayersMapFilePath;
new cvar_voteMidPlayersMapFilePath;
new cvar_whitelistType;
new cvar_whitelistMinPlayers;
new cvar_isWhiteListNomBlock;
new cvar_isWhiteListBlockOut;
new cvar_voteWhiteListMapFilePath;
new cvar_coloredChatPrefix;
/**
* Various Artists.
*/
new const MAP_FOLDER_LOAD_FLAG = '*';
new const MAP_CYCLE_LOAD_FLAG = '#';
new const GAL_VOTEMAP_MENU_COMMAND[] = "galmenu";
new const LAST_EMPTY_CYCLE_FILE_NAME[] = "lastEmptyCycleMapName.dat";
new const CURRENT_AND_NEXTMAP_FILE_NAME[] = "currentAndNextmapNames.dat";
new const LAST_CHANGE_MAP_FILE_NAME[] = "lastChangedMapName.dat";
new const RECENT_BAN_MAPS_FILE_NAME[] = "recentMaps.dat";
new const CHOOSE_MAP_MENU_NAME[] = "gal_menuChooseMap";
new const CHOOSE_MAP_MENU_QUESTION[] = "chooseMapQuestion";
new const CHOOSE_VOTEMAP_MENU_QUESTION[] = "chooseVoteMapQuestion";
new const GAME_CRASH_RECREATION_FLAG_FILE[] = "gameCrashRecreationAction.txt";
new const TO_STOP_THE_CRASH_SEARCH[] = "delete_this_to_stop_the_crash_search.txt";
new const MAPS_WHERE_THE_SERVER_CRASHED[] = "maps_where_the_server_probably_crashed.txt";
new bool:g_isDayOfDefeat;
new bool:g_isRunningSvenCoop;
new bool:g_isServerShuttingDown;
new bool:g_isMapExtensionPeriodRunning;
new bool:g_isTheRoundEndWhileVoting;
new bool:g_isTheRoundEnded;
new bool:g_isTimeToResetGame;
new bool:g_isTimeToResetRounds;
new bool:g_isUsingEmptyCycle;
new bool:g_isRunOffNeedingKeepCurrentMap;
new bool:g_isExtendmapAllowStay;
new bool:g_isToShowNoneOption;
new bool:g_isToShowSubMenu;
new bool:g_isToShowExpCountdown;
new bool:g_isToShowVoteCounter;
new bool:g_isEmptyCycleMapConfigured;
new bool:g_isTheLastGameRound;
new bool:g_isThePenultGameRound;
new bool:g_isToChangeMapOnVotingEnd;
new bool:g_isTimeToRestart;
new bool:g_isEndGameLimitsChanged;
new bool:g_isMapExtensionAllowed;
new bool:g_isGameFinalVoting;
new bool:g_isOnMaintenanceMode;
new bool:g_isToCreateGameCrashFlag;
new bool:g_isToRestoreFriendlyFire;
new Float:g_rtvWaitMinutes;
new Float:g_originalTimelimit;
new Float:g_original_sv_maxspeed;
new Float:g_originalChatTime;
/**
* Holds the Empty Cycle Map List feature maps used when the server is empty for some time to
* change the map to a popular one.
*/
new Array:g_emptyCycleMapsArray;
/**
* Stores all the player's nominations indexes within a array of the size MAX_OPTIONS_IN_VOTE,
* for the given trieKey by createPlayerNominationKey(3).
*
* Each player's nomination index is the index to the array `g_nominationLoadedMapsArray` containing
* all the nomination maps available to be nominated.
*/
new Trie:g_forwardSearchNominationsTrie;
/**
* Stores the nominator's `MapNominationsType` enum by a given map index. It is used to find out
* the data stored by the `MapNominationsType` enum for the given nominated map index.
*/
new Trie:g_reverseSearchNominationsTrie;
/**
* Enumeration used to create access to `g_reverseSearchNominationsTrie` values. It is an untagged type to
* allow it to be passed through the TrieSetArray(3).
*
* The `MapNomination_PlayerId` saves the id of the player which nominated the map.
* The `MapNomination_NominatedIndex` saves the nomination index on the `g_nominatedMapsArray`.
* The `MapNomination_NominationIndex` saves the player's personal nominations array index.
*/
enum _:MapNominationsType
{
MapNomination_PlayerId,
MapNomination_NominatedIndex,
MapNomination_NominationIndex
}
/**
* A simple list to keep track of the nominated maps managed by `g_forwardSearchNominationsTrie` and
* `g_reverseSearchNominationsTrie`.
*/
new Array:g_nominatedMapsArray;
/**
* The ban recent maps variables.
*/
new Trie: g_recentMapsTrie;
new Array:g_recentListMapsArray;
/**
* Contains the current loaded Whilelist from the array `g_whitelistFileArray` for the Whilelist Out Block
* feature `cvar_isWhiteListBlockOut`.
*/
new Array:g_whitelistArray;
/**
* Contains all the loaded valid lines from the Whitelist file contents.
*/
new Array:g_whitelistFileArray;
/**
* Contains all the loaded valid maps for the `gal_srv_move_cursor` feature. If the feature is disabled,
* it contains only the normal/usual loaded maps from the map cycle file.
*/
new Trie:g_mapcycleFileListTrie;
new Array:g_mapcycleFileListArray;
/**
* Contains all the allowed maps to be added as nominations or as voting map fillers.
*/
new Trie: g_whitelistTrie;
/**
* Contains all the blocked maps to be added as nominations or as voting map fillers.
*/
new Trie: g_blacklistTrie;
/**
* Contains all loaded nominations maps from the nomination file list.
*/
new Array:g_nominationLoadedMapsArray;
/**
* Contains all loaded nominations maps from the nomination file list, for fast search.
*/
new Trie:g_nominationLoadedMapsTrie;
/**
* Contains the paths to the voting fillers files.
*/
new Array:g_voteMinPlayerFillerPathsArray;
new Array:g_voteMidPlayerFillerPathsArray;
new Array:g_voteNorPlayerFillerPathsArray;
/**
* Contains how much maps per map group file to load.
*/
new Array:g_minMaxMapsPerGroupToUseArray;
new Array:g_midMaxMapsPerGroupToUseArray;
new Array:g_norMaxMapsPerGroupToUseArray;
/**
* Contains a Dynamic Array of Dynamic Arrays. Each one of the sub-arrays contains the maps loaded
* from the Array `g_voteMinPlayerFillerPathsArray` for each of the its paths receptively.
*
* Currently only the `g_minPlayerFillerMapGroupTrie` is used, so the others Dynamic Arrays do not
* need a Trie as pair.
*/
new Trie:g_minPlayerFillerMapGroupTrie;
new Array:g_minPlayerFillerMapGroupArrays;
new Array:g_midPlayerFillerMapGroupArrays;
new Array:g_norPlayerFillerMapGroupArrays;
/**
* Create a new type to perform the switch between the Minimum Players feature and the Normal
* Voting map filling.
*/
enum FillersFilePathType
{
fillersFilePaths_MininumPlayers,
fillersFilePaths_MiddlePlayers,
fillersFilePaths_NormalPlayers
}
/**
* Saves the partial nomination map name attempt to allow the partial nomination menu to be build
* by demand.
*/
new g_nominationPartialNameAttempt[ MAX_PLAYERS_COUNT ][ MAX_MAPNAME_LENGHT ];
/**
* Saves the last partial nomination menu map index to allow the menu to be rewind to the last
* visited page.
*/
new Array:g_partialMatchFirstPageItems[ MAX_PLAYERS_COUNT ];
/**
* Indicates whether the player already saw the first partial nomination menu page. This is useful
* when the player rewind to the first partial nomination menu page.
*/
new bool:g_isSawPartialMatchFirstPage[ MAX_PLAYERS_COUNT ];
/**
* Create a new type to perform the switch between voting by rounds, time or frags limit.
*/
enum GameEndingType
{
GameEndingType_ByNothing,
GameEndingType_ByWinLimit,
GameEndingType_ByMaxRounds,
GameEndingType_ByTimeLimit,
GameEndingType_ByFragLimit
}
/**
* This is the `GameEndingType` context returned by whatGameEndingTypeItIs(0) when the global variable
* `g_isGameEndingTypeContextSaved` is set to true by map_manageEnd(0).
*/
new bool:g_isGameEndingTypeContextSaved;
new GameEndingType:g_gameEndingTypeContextSaved;
new g_timeLeftContextSaved;
new Float:g_timeLimitContextSaved;
new g_maxRoundsContextSaved;
new g_winLimitContextSaved;
new g_fragLimitContextSaved;
/**
* Not saving these contexts on saveGameEndingTypeContext(0) will for the last round map change to fail
* on several configurations set by `cvar_endOnRound` and `cvar_endOfMapVoteStart`.
*/
new bool:g_isTheLastGameRoundContext;
new bool:g_isThePenultGameRoundContext;
/**
* The array indexes used on the saveRoundEnding(1) and restoreRoundEnding(1).
*/
enum SaveRoundEnding
{
SaveRoundEnding_LastRound,
SaveRoundEnding_RestartTime,
SaveRoundEnding_PenultRound,
SaveRoundEnding_VotingEnd,
SaveRoundEnding_WhileVoting
}
new g_totalRoundsSavedTimes;
new g_roundAverageTime;
new g_totalVoteTime;
new g_roundStartTime;
new g_originalMaxRounds;
new g_originalWinLimit;
new g_originalFragLimit;
new g_showVoteStatusType;
new g_extendmapStepRounds;
new g_extendmapStepFrags;
new g_extendmapStepMinutes;
new g_extendmapAllowStayType;
new g_showVoteStatus;
new g_voteShowNoneOptionType;
new g_pendingVoteCountdown;
new g_showLastRoundHudCounter;
new g_pendingMapVoteCountdown;
new g_lastRoundCountdown;
new g_rtvWaitAdminNumber;
new g_rtvCommands;
new g_rtvWaitRounds;
new g_rtvWaitFrags;
new g_rockedVoteCount;
new g_winLimitNumber;
new g_maxRoundsNumber;
new g_fragLimitNumber;
new g_greatestKillerFrags;
new g_timeLimitNumber;
new g_totalRoundsPlayed;
new g_whitelistNomBlockTime;
new g_totalTerroristsWins;
new g_totalCtWins;
new g_totalVoteOptions;
new g_maxVotingChoices;
new g_voteStatus;
new g_voteMapStatus;
new g_endVotingType;
new g_voteMapInvokerPlayerId;
new g_votingSecondsRemaining;
new g_totalVotesCounted;
new COLOR_RED [ 3 ]; // \r
new COLOR_WHITE [ 3 ]; // \w
new COLOR_YELLOW[ 3 ]; // \y
new COLOR_GREY [ 3 ]; // \d
new g_mapPrefixCount = 1;
new g_voteWeightFlags [ 32 ];
new g_voteStatus_symbol [ 3 ];
new g_coloredChatPrefix [ 16 ];
new g_arrayOfRunOffChoices[ MAX_OPTIONS_IN_VOTE ];
new g_voteStatusClean [ MAX_BIG_BOSS_STRING ];
new g_configsDirPath[ MAX_FILE_PATH_LENGHT ];
new g_dataDirPath [ MAX_FILE_PATH_LENGHT ];
/**
* Nextmap sub-plugin global variables.
*/
new cvar_amx_nextmap;
new cvar_mapcyclefile;
new cvar_gal_mapcyclefile;
new g_nextMapCyclePosition;
new g_invokerVoteMapNameToDecide[ MAX_MAPNAME_LENGHT ];
new g_nextMapName [ MAX_MAPNAME_LENGHT ];
new g_currentMapName [ MAX_MAPNAME_LENGHT ];
new g_playerVotedOption [ MAX_PLAYERS_COUNT ];
new g_playerVotedWeight [ MAX_PLAYERS_COUNT ];
new g_voteMapMenuPages [ MAX_PLAYERS_COUNT ];
new g_recentMapsMenuPages [ MAX_PLAYERS_COUNT ];
new g_nominationPlayersMenuPages[ MAX_PLAYERS_COUNT ];
new g_playersKills [ MAX_PLAYERS_COUNT ];
new g_arrayOfMapsWithVotesNumber[ MAX_OPTIONS_IN_VOTE ];
new bool:g_isPlayerVoted [ MAX_PLAYERS_COUNT ] = { true , ... };
new bool:g_isPlayerParticipating [ MAX_PLAYERS_COUNT ] = { true , ... };
new bool:g_isPlayerClosedTheVoteMenu[ MAX_PLAYERS_COUNT ];
new bool:g_isPlayerSeeingTheSubMenu [ MAX_PLAYERS_COUNT ];
new bool:g_isPlayerSeeingTheVoteMenu[ MAX_PLAYERS_COUNT ];
new bool:g_isPlayerCancelledVote [ MAX_PLAYERS_COUNT ];
new bool:g_answeredForEndOfMapVote [ MAX_PLAYERS_COUNT ];
new bool:g_rockedVote [ MAX_PLAYERS_COUNT ];
new g_mapPrefixes [ MAX_PREFIX_COUNT ][ MAX_PREFIX_SIZE ];
new g_votingMapNames [ MAX_OPTIONS_IN_VOTE ][ MAX_MAPNAME_LENGHT ];
new g_votingMapInfos [ MAX_OPTIONS_IN_VOTE ][ MAX_MAPNAME_LENGHT ];
new g_menuMapIndexForPlayerArrays[ MAX_PLAYERS_COUNT ][ MAX_NOM_MENU_ITEMS_PER_PAGE ];
new g_chooseMapMenuId;
new g_chooseMapQuestionMenuId;
new g_chooseVoteMapQuestionMenuId;
/**
* Called just after server activation.
*
* Good place to initialize most of the plugin, such as registering
* cvars, commands or forwards, creating data structures for later use, or
* generating and loading other required configurations.
*/
public plugin_init()
{
#if DEBUG_LEVEL & DEBUG_LEVEL_CRITICAL_MODE
g_debug_level = 1048575;
#endif
register_plugin( PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR );
LOG( 1, "^n^n^n^n^n^n^n^n^n^n^n^n%s PLUGIN VERSION %s INITIATING...", PLUGIN_NAME, PLUGIN_VERSION )
LOG( 1, "( plugin_init )" )
LOG( 1, "( plugin_init ) AMXX_VERSION_NUM: %d", AMXX_VERSION_NUM )
LOG( 1, "( plugin_init ) AMXX_VERSION_STR: %s", AMXX_VERSION_STR )
LOG( 1, "( plugin_init ) IS_TO_ENABLE_SVEN_COOP_SUPPPORT: %d", IS_TO_ENABLE_SVEN_COOP_SUPPPORT )
LOG( 1, "( plugin_init ) FAKE_PLAYERS_NUMBER_FOR_DEBUGGING: %d", FAKE_PLAYERS_NUMBER_FOR_DEBUGGING )
LOG( 1, "( plugin_init ) MAX_MAPS_TO_SHOW_ON_MAP_POPULATE_LIST: %d", MAX_MAPS_TO_SHOW_ON_MAP_POPULATE_LIST )
LOG( 1, "( plugin_init ) IS_TO_ENABLE_RE_HLDS_RE_AMXMODX_SUPPORT: %d", IS_TO_ENABLE_RE_HLDS_RE_AMXMODX_SUPPORT )
LOG( 1, "( plugin_init )" )
cvar_serverStartAction = register_cvar( "gal_srv_start" , "0" );
cvar_serverMoveCursor = register_cvar( "gal_srv_move_cursor" , "0" );
cvar_gameCrashRecreationAction = register_cvar( "gal_game_crash_recreation" , "0" );
cvar_serverTimeLimitRestart = register_cvar( "gal_srv_timelimit_restart" , "0" );
cvar_serverMaxroundsRestart = register_cvar( "gal_srv_maxrounds_restart" , "0" );
cvar_serverWinlimitRestart = register_cvar( "gal_srv_winlimit_restart" , "0" );
cvar_serverFraglimitRestart = register_cvar( "gal_srv_fraglimit_restart" , "0" );
cvar_endOfMapVote = register_cvar( "gal_endofmapvote" , "1" );
cvar_endOfMapVoteExpiration = register_cvar( "gal_endofmapvote_expiration" , "0" );
cvar_endOfMapVoteStart = register_cvar( "gal_endofmapvote_start" , "0" );
cvar_nextMapChangeAnnounce = register_cvar( "gal_nextmap_change" , "0" );
cvar_nextMapChangeVotemap = register_cvar( "gal_nextmap_votemap" , "1" );
cvar_voteMapChoiceCount = register_cvar( "gal_vote_mapchoices" , "5" );
cvar_voteMapChoiceNext = register_cvar( "gal_vote_mapchoices_next" , "1" );
cvar_voteDuration = register_cvar( "gal_vote_duration" , "30" );
cvar_voteMapFilePath = register_cvar( "gal_vote_mapfile" , "#" );
cvar_voteMinPlayers = register_cvar( "gal_vote_minplayers" , "0" );
cvar_voteMidPlayers = register_cvar( "gal_vote_midplayers" , "0" );
cvar_nomMinPlayersControl = register_cvar( "gal_nom_minplayers_control" , "0" );
cvar_voteMinPlayersMapFilePath = register_cvar( "gal_vote_minplayers_mapfile" , "" );
cvar_voteMidPlayersMapFilePath = register_cvar( "gal_vote_midplayers_mapfile" , "" );
cvar_runoffEnabled = register_cvar( "gal_runoff_enabled" , "0" );
cvar_runoffDuration = register_cvar( "gal_runoff_duration" , "20" );
cvar_runoffRatio = register_cvar( "gal_runoff_ratio" , "0.5" );
cvar_runoffMapchoices = register_cvar( "gal_runoff_mapchoices" , "2" );
cvar_nomPlayerAllowance = register_cvar( "gal_nom_playerallowance" , "0" );
cvar_nomCleaning = register_cvar( "gal_nom_cleaning" , "1" );
cvar_nomMapFilePath = register_cvar( "gal_nom_mapfile" , "*" );
cvar_nomPrefixes = register_cvar( "gal_nom_prefixes" , "1" );
cvar_nomQtyUsed = register_cvar( "gal_nom_qtyused" , "0" );
cvar_unnominateDisconnected = register_cvar( "gal_unnominate_disconnected" , "1" );
cvar_recentMapsBannedNumber = register_cvar( "gal_banrecent" , "0" );
cvar_recentNomMapsAllowance = register_cvar( "gal_recent_nom_maps" , "0" );
cvar_isOnlyRecentMapcycleMaps = register_cvar( "gal_banrecent_mapcycle" , "0" );
cvar_banRecentStyle = register_cvar( "gal_banrecentstyle" , "3" );
register_cvar( "gal_version", PLUGIN_VERSION, FCVAR_SERVER | FCVAR_SPONLY );
// print the current used debug information
#if DEBUG_LEVEL & ( DEBUG_LEVEL_NORMAL | DEBUG_LEVEL_CRITICAL_MODE )
new debug_level[ MAX_SHORT_STRING ];
formatex( debug_level, charsmax( debug_level ), "%d | %d", g_debug_level, DEBUG_LEVEL );
LOG( 1, "( plugin_init ) gal_debug_level: %s", debug_level )
register_cvar( "gal_debug_level", debug_level, FCVAR_SERVER | FCVAR_SPONLY );
#endif
cvar_rtvCommands = register_cvar( "gal_rtv_commands" , "0" );
cvar_rtvWaitMinutes = register_cvar( "gal_rtv_wait" , "10" );
cvar_rtvWaitRounds = register_cvar( "gal_rtv_wait_rounds" , "5" );
cvar_rtvWaitFrags = register_cvar( "gal_rtv_wait_frags" , "10" );
cvar_rtvWaitAdmin = register_cvar( "gal_rtv_wait_admin" , "0" );
cvar_rtvRatio = register_cvar( "gal_rtv_ratio" , "0.60" );
cvar_rtvReminder = register_cvar( "gal_rtv_reminder" , "2" );
cvar_whitelistType = register_cvar( "gal_whitelist_type" , "0" );
cvar_whitelistMinPlayers = register_cvar( "gal_whitelist_minplayers" , "0" );
cvar_isWhiteListNomBlock = register_cvar( "gal_whitelist_nom_block" , "0" );
cvar_isWhiteListBlockOut = register_cvar( "gal_whitelist_block_out" , "0" );
cvar_voteWhiteListMapFilePath = register_cvar( "gal_vote_whitelist_mapfile" , "" );
cvar_voteUniquePrefixes = register_cvar( "gal_vote_uniqueprefixes" , "0" );
cvar_extendmapStepMinutes = register_cvar( "amx_extendmap_step" , "15" );
cvar_maxMapExtendTime = register_cvar( "amx_extendmap_max" , "1" );
cvar_extendmapStepRounds = register_cvar( "amx_extendmap_step_rounds" , "20" );
cvar_maxMapExtendRounds = register_cvar( "amx_extendmap_max_rounds" , "1" );
cvar_fragLimitSupport = register_cvar( "gal_mp_fraglimit_support" , "0" );
cvar_extendmapStepFrags = register_cvar( "amx_extendmap_step_frags" , "30" );
cvar_maxMapExtendFrags = register_cvar( "amx_extendmap_max_frags" , "1" );
cvar_extendmapAllowStay = register_cvar( "amx_extendmap_allow_stay" , "0" );
cvar_extendmapAllowStayType = register_cvar( "amx_extendmap_allow_stay_type", "0" );
cvar_isExtendmapOrderAllowed = register_cvar( "amx_extendmap_allow_order" , "1" );
cvar_showVoteStatus = register_cvar( "gal_vote_showstatus" , "4" );
cvar_showVoteStatusType = register_cvar( "gal_vote_showstatustype" , "2" );
cvar_isToReplaceByVoteMenu = register_cvar( "gal_vote_replace_menu" , "1" );
cvar_isToShowNoneOption = register_cvar( "gal_vote_show_none" , "0" );
cvar_voteWeight = register_cvar( "gal_vote_weight" , "0" );
cvar_voteWeightFlags = register_cvar( "gal_vote_weightflags" , "y" );
cvar_voteShowNoneOptionType = register_cvar( "gal_vote_show_none_type" , "2" );
cvar_isToShowExpCountdown = register_cvar( "gal_vote_expirationcountdown" , "0" );
cvar_isToShowVoteCounter = register_cvar( "gal_vote_show_counter" , "1" );
cvar_voteAnnounceChoice = register_cvar( "gal_vote_announcechoice" , "0" );
cvar_generalOptions = register_cvar( "gal_general_options" , "0" );
cvar_isToAskForEndOfTheMapVote = register_cvar( "gal_endofmapvote_ask" , "0" );
cvar_cmdVotemap = register_cvar( "gal_cmd_votemap" , "1" );
cvar_cmdListmaps = register_cvar( "gal_cmd_listmaps" , "1" );
cvar_listmapsPaginate = register_cvar( "gal_listmaps_paginate" , "10" );
cvar_emptyServerWaitMinutes = register_cvar( "gal_emptyserver_wait" , "0" );
cvar_isEmptyCycleByMapChange = register_cvar( "gal_emptyserver_change" , "0" );
cvar_serverPlayersCount = register_cvar( "gal_server_players_count" , "0" );
cvar_emptyMapFilePath = register_cvar( "gal_emptyserver_mapfile" , "" );
cvar_soundsMute = register_cvar( "gal_sounds_mute" , "27" );
cvar_hudsHide = register_cvar( "gal_sounds_hud" , "31" );
cvar_coloredChatEnabled = register_cvar( "gal_colored_chat_enabled" , "0" );
cvar_coloredChatPrefix = register_cvar( "gal_colored_chat_prefix" , "" );
cvar_endOnRound = register_cvar( "gal_endonround" , "0" );
cvar_endOnRoundMininum = register_cvar( "gal_endonround_msg" , "0" );
cvar_endOnRoundRtv = register_cvar( "gal_endonround_rtv" , "0" );
cvar_endOnRoundChange = register_cvar( "gal_endonround_change" , "1" );
cvar_isEndMapCountdown = register_cvar( "gal_endonround_countdown" , "0" );
// Not a configurable cvars, these are used instead of the `localinfo`.
//
// When `cvar_isFirstServerStart` set set to 2 we are on the first server start period. If this
// is set to 1, we are on the beginning of the second server map change level.
cvar_isFirstServerStart = register_cvar( "gal_server_starting" , "2", FCVAR_SPONLY );
cvar_isToStopEmptyCycle = register_cvar( "gal_in_empty_cycle" , "0", FCVAR_SPONLY );
cvar_successfullLevels = register_cvar( "gal_successfull_levels" , "0", FCVAR_SPONLY );
// This is a general pointer used for cvars not registered on the game.
cvar_gal_mapcyclefile = register_cvar( "gal_mapcyclefile" , "" , FCVAR_SERVER );
cvar_disabledValuePointer = register_cvar( "gal_disabled_value_pointer", "0", FCVAR_SPONLY );
// This are default behaviors independent of any setting to be enabled.
configureEndGameCvars();
nextmapPluginInit();
timeleftPluginInit();
configureTheVotingMenus();
configureSpecificGameModFeature();
// Register the HLTV for the supported game mods.
if( IS_NEW_ROUND_EVENT_SUPPORTED() )
{
register_event( "HLTV", "new_round_event", "a", "1=0", "2=0");
}
// This are default behaviors independent of any setting to be enabled.
register_logevent( "game_commencing_event", 2, "0=World triggered", "1=Game_Commencing" );
register_logevent( "team_win_event", 6, "0=Team" );
register_logevent( "round_restart_event", 2, "0=World triggered", "1&Restart_Round_" );
register_logevent( "round_start_event", 2, "1=Round_Start" );
register_logevent( "round_end_event", 2, "1=Round_End" );
// This are default behaviors independent of any setting to be enabled.
register_concmd( "gal_startvote", "cmd_startVote", ADMIN_MAP );
register_concmd( "gal_cancelvote", "cmd_cancelVote", ADMIN_MAP );
register_concmd( "gal_changelevel", "cmd_changeLevel", ADMIN_MAP );
register_concmd( "gal_createmapfile", "cmd_createMapFile", ADMIN_RCON );
register_concmd( "gal_command_maintenance", "cmd_maintenanceMode", ADMIN_RCON );
register_concmd( "gal_looking_for_crashes", "cmd_lookingForCrashes", ADMIN_RCON );
LOG( 1, " I AM EXITING plugin_init(0)..." )
LOG( 1, "" )
}
/**
* Called when all plugins went through plugin_init(). When this forward is called, most plugins
* should have registered their cvars and commands already.
*/
public plugin_cfg()
{
LOG( 128, "I AM ENTERING ON plugin_cfg(0)" )
get_mapname( g_currentMapName, charsmax( g_currentMapName ) );
/**
* Register the color chat 'g_user_msgid' variable, for the AMXX 182.
*/
#if AMXX_VERSION_NUM < 183
// If some exception happened before this, all color_print(...) messages will cause native
// error 10, on the AMXX 182. It is because, the execution flow will not reach here, then
// the player "g_user_msgid" will be be initialized.
g_user_msgid = get_user_msgid( "SayText" );
#endif
// Load the initial settings
loadPluginSetttings();
// Need to be called after the `IS_COLORED_CHAT_ENABLED()` settings load on loadPluginSetttings(0).
loadLangFiles();
loadMapFiles();
LOG( 4, "" )
LOG( 4, "" )
// The 'mp_fraglimitCvarSupport(0)' could register a new cvar, hence only call 'cacheCvarsValues' them after it.
mp_fraglimitCvarSupport();
cacheCvarsValues();
resetRoundsScores();
LOG( 0, "", printTheCurrentAndNextMapNames() )
// Used to loop through all server maps looking for crashing ones
configureServerStart();
runTheServerMapCrashSearch();
// Configure the Unit Tests, when they are activate.
#if DEBUG_LEVEL & ( DEBUG_LEVEL_UNIT_TEST_NORMAL | DEBUG_LEVEL_MANUAL_TEST_START | DEBUG_LEVEL_UNIT_TEST_DELAYED )
configureTheUnitTests();
#endif
LOG( 1, " I AM EXITING plugin_cfg(0)..." )
LOG( 1, "" )
}
stock loadLangFiles()
{
LOG( 128, "I AM ENTERING ON loadLangFiles(0)" )
g_isColoredChatEnabled = get_pcvar_num( cvar_coloredChatEnabled ) != 0;
register_dictionary_colored( "galileo.txt" );
register_dictionary( "common.txt" );
register_dictionary( "cmdmenu.txt" );
register_dictionary( "mapsmenu.txt" );
register_dictionary( "adminvote.txt" );
}
stock runTheServerMapCrashSearch()
{
LOG( 128, "I AM ENTERING ON runTheServerMapCrashSearch(0)" )
new modeFlagFilePath[ MAX_FILE_PATH_LENGHT ];
formatex( modeFlagFilePath, charsmax( modeFlagFilePath ), "%s/%s", g_dataDirPath, TO_STOP_THE_CRASH_SEARCH );
if( IS_TO_ALLOW_A_CRASH_SEARCH( modeFlagFilePath ) )
{
new Float:delay;
new successfullLevels;
new currentDate[ MAX_SHORT_STRING ];
get_time( "%m/%d/%Y - %H:%M:%S", currentDate, charsmax( currentDate ) );
successfullLevels = get_pcvar_num( cvar_successfullLevels );
server_print( "^n%s", currentDate );
server_print( "The current map is: %s", g_currentMapName );
server_print( "The next map will be: %s", g_nextMapName );
server_print( "Successfully completed server change levels without crash: %d^n", successfullLevels );
// Allow the admin to connect to the server and to disable the command `gal_looking_for_crashes`.
delay = get_real_players_number() ? 100.0 : 7.0;
server_print( "The server is changing level in %d seconds!", floatround( delay, floatround_floor ) );
set_pcvar_num( cvar_mp_chattime, 3 );
set_pcvar_num( cvar_serverMoveCursor, 0 );
set_pcvar_num( cvar_successfullLevels, successfullLevels + 1 );
set_task( delay, "changeMapIntermission" );
set_pcvar_string( cvar_mapcyclefile, modeFlagFilePath );
}
}
/**
* On the first server start, we do not know whether the color chat is allowed/enabled. This is due
* the register register_dictionary_colored(1) to be called on plugin_init(0) and the settings being
* loaded only at plugin_cfg(0).
*/
stock configureSpecificGameModFeature()
{
LOG( 128, "I AM ENTERING ON configureSpecificGameModFeature(0)" )
g_isDayOfDefeat = !!is_running("dod");
g_isRunningSvenCoop = !!is_running("svencoop");
g_isColorChatSupported = ( is_running( "czero" ) || is_running( "cstrike" ) );
// Register the voting start call from the Sven Coop game.
#if IS_TO_ENABLE_SVEN_COOP_SUPPPORT > 0
if( g_isRunningSvenCoop )
{
RegisterHam( Ham_Use, "game_end", "startVotingByGameEngineCall", false );
}
#endif
if( colored_menus() )
{
copy( COLOR_RED, 2, "\r" );
copy( COLOR_WHITE, 2, "\w" );
copy( COLOR_YELLOW, 2, "\y" );
copy( COLOR_GREY, 2, "\d" );
}
}
/**
* All these cvars must to be set using the tryToSetGameModCvarNum(2), tryToSetGameModCvarString(2)
* and tryToSetGameModCvarFloat(2) functions.
*/
stock configureEndGameCvars()
{
LOG( 128, "I AM ENTERING ON configureEndGameCvars(0)" )
tryToGetGameModCvar( cvar_mp_maxrounds , "mp_maxrounds" );
tryToGetGameModCvar( cvar_mp_winlimit , "mp_winlimit" );
tryToGetGameModCvar( cvar_mp_freezetime , "mp_freezetime" );
tryToGetGameModCvar( cvar_mp_timelimit , "mp_timelimit" );
tryToGetGameModCvar( cvar_mp_roundtime , "mp_roundtime" );
tryToGetGameModCvar( cvar_mp_chattime , "mp_chattime" );
tryToGetGameModCvar( cvar_mp_friendlyfire , "mp_friendlyfire" );
tryToGetGameModCvar( cvar_sv_maxspeed , "sv_maxspeed" );
tryToGetGameModCvar( cvar_mapcyclefile , "mapcyclefile" );
#if IS_TO_ENABLE_SVEN_COOP_SUPPPORT > 0
tryToGetGameModCvar( cvar_mp_nextmap_cycle, "mp_nextmap_cycle" );
#endif
}
stock tryToGetGameModCvar( &cvar_to_get, cvar_name[] )
{
LOG( 128, "I AM ENTERING ON tryToGetGameModCvar(2) cvar_to_get: %d, cvar_name: %s", cvar_to_get, cvar_name )
if( !( cvar_to_get = get_cvar_pointer( cvar_name ) ) )
{
cvar_to_get = cvar_disabledValuePointer;
}
LOG( 1, " ( tryToGetGameModCvar ) %s is cvar_to_get: %d", cvar_name, cvar_to_get )
}
stock configureTheVotingMenus()
{
LOG( 128, "I AM ENTERING ON configureTheVotingMenus(0)" )
g_chooseMapMenuId = register_menuid( CHOOSE_MAP_MENU_NAME );
g_chooseMapQuestionMenuId = register_menuid( CHOOSE_MAP_MENU_QUESTION );
g_chooseVoteMapQuestionMenuId = register_menuid( CHOOSE_VOTEMAP_MENU_QUESTION );
register_menucmd( g_chooseMapMenuId, MENU_KEY_0 | MENU_KEY_1 |
MENU_KEY_2 | MENU_KEY_3 | MENU_KEY_4 | MENU_KEY_5 |
MENU_KEY_6 | MENU_KEY_7 | MENU_KEY_8 | MENU_KEY_9,
"vote_handleChoice" );
register_menucmd( g_chooseMapQuestionMenuId, MENU_KEY_6 | MENU_KEY_0, "handleEndOfTheMapVoteChoice" );
register_menucmd( g_chooseVoteMapQuestionMenuId, MENU_KEY_1 | MENU_KEY_3 | MENU_KEY_5, "handleVoteMapActionMenu" );
}
stock loadPluginSetttings()
{
LOG( 128, "I AM ENTERING ON loadPluginSetttings(0)" )
new writtenSize;
writtenSize = get_configsdir( g_configsDirPath, charsmax( g_configsDirPath ) );
copy( g_configsDirPath[ writtenSize ], charsmax( g_configsDirPath ) - writtenSize, "/galileo" );
writtenSize = get_datadir( g_dataDirPath, charsmax( g_dataDirPath ) );
copy( g_dataDirPath[ writtenSize ], charsmax( g_dataDirPath ) - writtenSize, "/galileo" );
if( !dir_exists( g_dataDirPath )
&& mkdir( g_dataDirPath ) )
{
LOG( 1, "AMX_ERR_NOTFOUND, Could not create: %s", g_dataDirPath )
log_error( AMX_ERR_NOTFOUND, "Could not create: %s", g_dataDirPath );
}
LOG( 1, "( loadPluginSetttings ) g_configsDirPath: %s, g_dataDirPath: %s,", g_configsDirPath, g_dataDirPath )
server_cmd( "exec %s/galileo.cfg", g_configsDirPath );
server_exec();
}
/**
* The cvars as 'mp_fraglimit' is registered only the first time the server starts. This function
* setup the 'mp_fraglimit' support on all Game Modifications.
*/
stock mp_fraglimitCvarSupport()
{
LOG( 128, "I AM ENTERING ON mp_fraglimitCvarSupport(0)" )
// mp_fraglimit
new exists_mp_fraglimit_cvar = cvar_exists( "mp_fraglimit" );
LOG( 32, "( mp_fraglimitCvarSupport ) exists_mp_fraglimit_cvar: %d", exists_mp_fraglimit_cvar )
if( exists_mp_fraglimit_cvar )
{
register_event( "DeathMsg", "client_death_event", "a" );
cvar_mp_fraglimit = get_cvar_pointer( "mp_fraglimit" );
}
else if( get_pcvar_num( cvar_fragLimitSupport ) )
{
register_event( "DeathMsg", "client_death_event", "a" );
cvar_mp_fraglimit = register_cvar( "mp_fraglimit", "0", FCVAR_SERVER );
}
else
{
cvar_mp_fraglimit = cvar_disabledValuePointer;
}
LOG( 1, "( mp_fraglimitCvarSupport ) cvar_disabledValuePointer: %d", cvar_disabledValuePointer )
LOG( 1, "( mp_fraglimitCvarSupport ) mp_fraglimit is cvar_to_get: %d", cvar_mp_fraglimit )
// re-cache later to wait load some late server configurations, as the per-map configs.
set_task( DELAY_TO_WAIT_THE_SERVER_CVARS_TO_BE_LOADED, "cacheCvarsValues" );
}
/**
* To cache some high used server cvars.
*/
public cacheCvarsValues()
{
LOG( 128, "I AM ENTERING ON cacheCvarsValues(0)" )
// RTV wait time
g_rtvWaitRounds = get_pcvar_num( cvar_rtvWaitRounds );
g_rtvWaitFrags = get_pcvar_num( cvar_rtvWaitFrags );
g_rtvWaitMinutes = get_pcvar_float( cvar_rtvWaitMinutes );
g_rtvCommands = get_pcvar_num( cvar_rtvCommands );
g_extendmapStepRounds = get_pcvar_num( cvar_extendmapStepRounds );
g_extendmapStepFrags = get_pcvar_num( cvar_extendmapStepFrags );
g_extendmapStepMinutes = get_pcvar_num( cvar_extendmapStepMinutes );
g_extendmapAllowStayType = get_pcvar_num( cvar_extendmapAllowStayType );
g_showVoteStatus = get_pcvar_num( cvar_showVoteStatus );
g_voteShowNoneOptionType = get_pcvar_num( cvar_voteShowNoneOptionType );
g_showVoteStatusType = get_pcvar_num( cvar_showVoteStatusType );
g_maxRoundsNumber = get_pcvar_num( cvar_mp_maxrounds );
g_winLimitNumber = get_pcvar_num( cvar_mp_winlimit );
g_fragLimitNumber = get_pcvar_num( cvar_mp_fraglimit );
g_timeLimitNumber = get_pcvar_num( cvar_mp_timelimit );
g_isExtendmapAllowStay = get_pcvar_num( cvar_extendmapAllowStay ) != 0;
g_isToShowNoneOption = get_pcvar_num( cvar_isToShowNoneOption ) == 1;
g_isToShowSubMenu = get_pcvar_num( cvar_isToShowNoneOption ) == 2;
g_isToShowVoteCounter = get_pcvar_num( cvar_isToShowVoteCounter ) != 0;
g_isToShowExpCountdown = get_pcvar_num( cvar_isToShowExpCountdown ) != 0;
// load the weighted votes flags and chat prefix.
get_pcvar_string( cvar_voteWeightFlags, g_voteWeightFlags, charsmax( g_voteWeightFlags ) );
get_pcvar_string( cvar_coloredChatPrefix, g_coloredChatPrefix, charsmax( g_coloredChatPrefix ) );
//Do not put it before the variable `g_isColoredChatEnabled` caching.
if( IS_COLORED_CHAT_ENABLED() )
{
INSERT_COLOR_TAGS( g_coloredChatPrefix )
}
else
{
REMOVE_LETTER_COLOR_TAGS( g_coloredChatPrefix )
}
LOG( 1, "( cacheCvarsValues ) g_coloredChatPrefix: %s", g_coloredChatPrefix )
g_maxVotingChoices = max( min( MAX_OPTIONS_IN_VOTE, get_pcvar_num( cvar_voteMapChoiceCount ) ), 2 );
// It need to be cached after loading all the cvars
g_totalVoteTime = howManySecondsLastMapTheVoting();
}
/**
* Notice that this whole algorithm is only ran at the first time the server start, to set properly
* the last where the server was before to be closed or crash.
*
* I must also to read them on the server start, as currently is being done, because I need to
* set up whether we need to change level now or not.
*/
stock configureServerStart()
{
LOG( 128, "I AM ENTERING ON configureServerStart(0)" )
new startAction;
set_task( float( PERIODIC_CHECKING_INTERVAL ), "vote_manageEnd", _, _, _, "b" );
// If these two settings are disabled, there is not need to these handlers.
if( get_pcvar_num( cvar_rtvCommands )
|| get_pcvar_num( cvar_nomPlayerAllowance ) )
{
register_clcmd( "say" , "cmd_say", -1 );
register_clcmd( "say_team", "cmd_say", -1 );
}
if( get_pcvar_num( cvar_cmdVotemap ) != COMMAND_VOTEMAP_DISABLED ) register_clcmd( "votemap" , "cmd_HL1_votemap" );
if( get_pcvar_num( cvar_cmdListmaps ) != COMMAND_LISTMAPS_DISABLED ) register_clcmd( "listmaps", "cmd_HL1_listmaps" );
if( get_pcvar_num( cvar_gameCrashRecreationAction ) )
{
g_isToCreateGameCrashFlag = true;
}
// take the defined "server start" action
startAction = get_pcvar_num( cvar_serverStartAction );
// To update the current and next map names every server start. This setup must to be run only
// at the first time the server is started.
if( startAction )
{
register_srvcmd( "quit2", "cmd_quit2" );
if( get_pcvar_num( cvar_isFirstServerStart ) == FIRST_SERVER_START )
{
new backupMapsFilePath[ MAX_FILE_PATH_LENGHT ];
formatex( backupMapsFilePath, charsmax( backupMapsFilePath ), "%s/%s", g_dataDirPath, CURRENT_AND_NEXTMAP_FILE_NAME );
// If the data file does not exists yet, we cannot handle the server start.
if( file_exists( backupMapsFilePath ) )
{
handleServerStart( backupMapsFilePath, startAction );
}
else
{
// These data, are already loaded by the configureTheNextMapSetttings(1) function call.
trim( g_nextMapName );
trim( g_currentMapName );
saveCurrentAndNextMapNames( g_currentMapName, g_nextMapName, true );
}
}
else
{
// Save the current and next map name when the server admin does something like `amx_map`, and the
// server did not crash on the selected map, the setTheCurrentAndNextMapSettings(0) cannot update
// what are the correct current and next map names, because it is only called a the plugin_end(0).
trim( g_nextMapName );
trim( g_currentMapName );
saveCurrentAndNextMapNames( g_currentMapName, g_nextMapName, true );
}
}
else
{
// The level `FIRST_SERVER_START` is only meant to be used by the `startAction`, therefore
// when the `startAction` is disabled we must to set it to the seconds level `SECOND_SERVER_START`.
set_pcvar_num( cvar_isFirstServerStart, SECOND_SERVER_START );
LOG( 2, "( configureServerStart ) IS CHANGING THE CVAR 'gal_server_starting' to '%d'.", \
get_pcvar_num( cvar_isFirstServerStart ) )
}
}
/**
* I must to set next the current and next map at plugin_end(0), because if the next map changed by
* a normal server change level, the current and next map names will not be updated. It is impossible
* to detect to which map the server was changed when the server admin does `amx_map` or any other
* command to change the level to a specific map. However we do not need to worry about such commands
* because if the admin does so, the map will be changed to the map just before they were when the
* change level command to be performed.
*
* Sadly this function is being called when the server's admin is calling `rcon quit`. It means that
* on the next time the server start, it will be on the next map instead of the current map. To fix
* this, we need to detect here if this function is being called when the server admin the command
* `rcon quit`.
*
* Therefore, we register the server command `quit` setting the global variable `g_isServerShuttingDown`
* to true and returning `PLUGIN_CONTINUE`. But this first one is not working. Looks list only Orpheu
* can hook this. For now I am registering the command `quit2` which setup the global variable
* `g_isServerShuttingDown` and call the server command `quit`.
*
* This is an example to blocking the rcon command:
*
* #include
* #include
*
* public plugin_init()
* {
* OrpheuRegisterHook( OrpheuGetFunction( "SV_Rcon" ), "On_Rcon_Pre", OrpheuHookPre )
* }
*
* public OrpheuHookReturn:On_Rcon_Pre()
* {
* g_isServerShuttingDown = true;
* return OrpheuIgnored
* }
*
* ************************************************************************************************
*
* Algorithm o detect the quit command: (Not implemented, Not finished)
*
* 1. Update only at server start the new file `lastServerStartMapName.dat`
*
* 2. So if the map name on `lastServerStartMapName.dat` is different that the current map name on
* `currentAndNextmapNames.dat`, the server crashed on change level or the server admin used the
* command `quit` on the server console.
*
* 2.a) If the server crashed on change level, we still want to go back to that map on
* `currentAndNextmapNames.dat` until `MAX_SERVER_RESTART_ACCEPTABLE`.
* 2.b) If the server admin just used the command `quit`, we want to go back to the map the
* file `lastServerStartMapName.dat`.
*
* 3. If if the map name on `lastServerStartMapName.dat` is equal that the current map name on
* `currentAndNextmapNames.dat`, the server can crashed while playing the that map. This case is
* the same on `2.a)`, we still want to come back to that map `lastServerStartMapName.dat` until
* the MAX_SERVER_RESTART_ACCEPTABLE.
*/
stock setTheCurrentAndNextMapSettings()
{
LOG( 128, "I AM ENTERING ON setTheCurrentAndNextMapSettings(0)" )
LOG( 4, "( setTheCurrentAndNextMapSettings ) g_isServerShuttingDown: %d", g_isServerShuttingDown )
if( g_isServerShuttingDown )
{
g_isServerShuttingDown = false;
}
else
{
// Must not to be run only at the first time the server is started, because the setup call to
// saveCurrentAndNextMapNames(3) does not need to be performed at the server first start as we
// are only reading the last data set, instead of setting new data to it.
if( get_pcvar_num( cvar_serverStartAction )
&& get_pcvar_num( cvar_isFirstServerStart ) != FIRST_SERVER_START )
{
new nextMapName [ MAX_MAPNAME_LENGHT ];
new currentMapName[ MAX_MAPNAME_LENGHT ];
// Remember, this is called at plugin_end(0), so the next map will became the the current map.
getNextMapName( currentMapName, charsmax( currentMapName ) );
// These data does not need to be synced/updated with `g_nextMapCyclePosition` because they
// are only used at the first time the server is started. Moreover, at the first time the
// server has started, these data will be used the find out the correct value for the
// variable `g_nextMapCyclePosition` use.
if( map_getNext( g_mapcycleFileListArray, currentMapName, nextMapName, "mapcyclefile" ) == -1 )
{
// If we cannot find a valid next map, set it as the current map. Therefore when the
// getNextMapByPosition(5) to start looking for a new next map, it will automatically take the
// first map, as is does not allow the current map to be set as the next map.
trim( currentMapName );
saveCurrentAndNextMapNames( currentMapName, currentMapName );
}
else
{
trim( nextMapName );
trim( currentMapName );
saveCurrentAndNextMapNames( currentMapName, nextMapName );
}
}
}
// This is the key that tells us if this server has been started or not. Note it is important to
// perform this switch only after the instructions call.
switch( get_pcvar_num( cvar_isFirstServerStart ) )
{
case FIRST_SERVER_START:
{
set_pcvar_num( cvar_isFirstServerStart, SECOND_SERVER_START );
}
default:
{
set_pcvar_num( cvar_isFirstServerStart, AFTER_READ_MAPCYCLE );
}
}
LOG( 2, "( setTheCurrentAndNextMapSettings ) IS CHANGING THE CVAR 'gal_server_starting' to '%d'.", \
get_pcvar_num( cvar_isFirstServerStart ) )
}
/**
* Indicates which action to take when it is detected that the server
* has been 'externally restarted'. By 'externally restarted', is mean to
* say the Computer's Operational System (Linux) or Server Manager (HLSW),
* used the server command 'quit' and reopened the server.
*
* 0 - stay on the map the server started with
*
* 1 - change to the map that was being played when the server was reset
*
* 2 - change to what would have been the next map had the server not
* been restarted ( if the next map isn't known, this acts like 3 )
*
* 3 - start an early map vote after the first two minutes
*
* 4 - change to a randomly selected map from your nominatable map list
*/
public handleServerStart( backupMapsFilePath[], startAction )
{
LOG( 128, "I AM ENTERING ON handleServerStart(2) backupMapsFilePath: %s", backupMapsFilePath )
isHandledGameCrashAction( startAction );
new mapToChange[ MAX_MAPNAME_LENGHT ];
new nextMapName[ MAX_MAPNAME_LENGHT ];
new mapCyclePosition;
new mapCyclePositionString[ 10 ];
if( startAction == SERVER_START_CURRENTMAP
|| startAction == SERVER_START_NEXTMAP )
{
new backupMapsFile = fopen( backupMapsFilePath, "rt" );
if( backupMapsFile )
{
fgets( backupMapsFile, mapToChange , charsmax( mapToChange ) );
fgets( backupMapsFile, nextMapName , charsmax( nextMapName ) );
fgets( backupMapsFile, mapCyclePositionString, charsmax( mapCyclePositionString ) );
fclose( backupMapsFile );
trim( mapToChange );
trim( nextMapName );
trim( mapCyclePositionString );
mapCyclePosition = str_to_num( mapCyclePositionString );
if( startAction == SERVER_START_NEXTMAP )
{
copy( mapToChange, charsmax( mapToChange ), nextMapName );
// If there is not found a next map, the current map name on `nextMapName` will to be
// set as the first map cycle map name.
map_getNext( g_mapcycleFileListArray, mapToChange, nextMapName, "mapcyclefile" );
}
}
else
{
doAmxxLog( "ERROR, handleServerStart: Could not open the file backupMapsFilePath ^"%s^"", backupMapsFilePath );
}
}
else if( startAction == SERVER_START_RANDOMMAP ) // pick a random map from allowable nominations
{
// If noms aren't allowed, the nomination list hasn't already been loaded
if( get_pcvar_num( cvar_nomPlayerAllowance ) == 0 )
{
new mapFilePath[ MAX_FILE_PATH_LENGHT ];
loadNominationList( mapFilePath );
}
new nominationsMapsCount = ArraySize( g_nominationLoadedMapsArray );
if( nominationsMapsCount )
{
GET_MAP_NAME( g_nominationLoadedMapsArray, random_num( 0, nominationsMapsCount - 1 ), mapToChange )
}
}
// When this is called more than `MAX_SERVER_RESTART_ACCEPTABLE` on the same mapToChange, we
// know crash trouble is probably expecting us.
configureTheMapcycleSystem( mapToChange, nextMapName, mapCyclePosition );
if( mapToChange[ 0 ] )
{
if( IS_MAP_VALID( mapToChange ) )
{
// If the default started server map is the last current map, we need to set the server
// state as already restarted.
if( equali( mapToChange, g_currentMapName ) )
{
// If we got here, the level was `FIRST_SERVER_START`, and as we are not changing
// the map, we must to set it to the next level `SECOND_SERVER_START`.
set_pcvar_num( cvar_isFirstServerStart, SECOND_SERVER_START );
LOG( 2, "( handleServerStart ) IS CHANGING THE CVAR 'gal_server_starting' to '%d'.", SECOND_SERVER_START )
}
else
{
// When the Unit Tests are running, we do not want to or can wait anything.
#if DEBUG_LEVEL & ( DEBUG_LEVEL_UNIT_TEST_NORMAL | DEBUG_LEVEL_MANUAL_TEST_START | DEBUG_LEVEL_UNIT_TEST_DELAYED )
if( g_test_areTheUnitTestsRunning )
{
serverChangeLevel( mapToChange );
}
else
{
set_task( 2.0, "serverChangeLevel", _, mapToChange, charsmax( mapToChange ) );
}
#else
// Create a small delay to let other plugins to do their stuff/breath.
set_task( 2.0, "serverChangeLevel", _, mapToChange, charsmax( mapToChange ) );
#endif
}
}
else
{
doAmxxLog( "WARNING, handleServerStart: Invalid map read from the current and next map file ^"%s^"", mapToChange );
}
}
else // startAction == SERVER_START_MAPVOTE
{
vote_manageEarlyStart();
}
}
/**
* To detect if the last MAX_SERVER_RESTART_ACCEPTABLE restarts was to the same map. If so, change
* to the next map right after it.
*
* @param mapToChange is the first map read from `currentAndNextmapNames.dat`, i.e., the supposed last current map.
*/
stock configureTheMapcycleSystem( mapToChange[], possibleNextMap[], possibleNextMapPosition )
{
LOG( 128, "I AM ENTERING ON configureTheMapcycleSystem(2) mapToChange: %s", mapToChange )
trim( mapToChange );
new restartsOnTheCurrentMap;
new crashingMap[ MAX_MAPNAME_LENGHT ];
copy( crashingMap, charsmax( crashingMap ), mapToChange );
restartsOnTheCurrentMap = getRestartsOnTheCurrentMap( mapToChange );
LOG( 4, "( configureTheMapcycleSystem ) mapToChange: %s", mapToChange )
LOG( 4, "( configureTheMapcycleSystem ) possibleNextMap: %s", possibleNextMap )
LOG( 4, "( configureTheMapcycleSystem ) restartsOnTheCurrentMap: %d", restartsOnTheCurrentMap )
// Set the new current map as the actual next map.
if( restartsOnTheCurrentMap > MAX_SERVER_RESTART_ACCEPTABLE )
{
new lastMapChangedFile;
LOG( 4, "( configureTheMapcycleSystem ) restartsOnTheCurrentMap > MAX_SERVER_RESTART_ACCEPTABLE" )
LOG( 4, "" )
setThisMapAsPossibleCrashingMap( crashingMap );
// This is the possibleCurrentMap because if the current map is restarted too much, this possibleCurrentMap
// will the the mapToChange, which in seconds will became the current map.
new possibleCurrentMap [ MAX_MAPNAME_LENGHT ];
new lastMapChangedFilePath[ MAX_FILE_PATH_LENGHT ];
// Get a new next map on the map cycle.
if( equali( mapToChange, possibleNextMap ) )
{
possibleNextMapPosition = 0;
if( ArraySize( g_mapcycleFileListArray ) > 1 )
{
GET_MAP_NAME( g_mapcycleFileListArray, 0, possibleCurrentMap )
GET_MAP_NAME( g_mapcycleFileListArray, 1, possibleNextMap )
configureTheNextMapPlugin( possibleCurrentMap, possibleNextMap, 1, true );
}
else
{
doAmxxLog( "WARNING, configureTheMapcycleSystem: Your ^"mapcyclefile^" server variable is invalid!" );
copy( possibleCurrentMap, MAX_MAPNAME_LENGHT - 1, g_currentMapName );
copy( possibleNextMap , MAX_MAPNAME_LENGHT - 1, g_currentMapName );
// If there is not any map, just to do setup it by default the first server's map.
configureTheNextMapPlugin( possibleCurrentMap, possibleNextMap, 0, true );
}
}
else
{
// I do like the map_getNext(4) behavior. I prefer using getNextMapByPosition(5).
copy( possibleCurrentMap, charsmax( possibleCurrentMap ), possibleNextMap );
// possibleNextMapPosition = map_getNext( g_mapcycleFileListArray, possibleCurrentMap, possibleNextMap, "mapcyclefile" );
possibleNextMapPosition = getNextMapByPosition( g_mapcycleFileListArray, possibleNextMap, g_nextMapCyclePosition );
// Update the current map to the next map.
copy( mapToChange, MAX_MAPNAME_LENGHT - 1, possibleCurrentMap );
configureTheNextMapPlugin( possibleCurrentMap, possibleNextMap, possibleNextMapPosition, true );
}
// Clear the old data
LOG( 4, "" )
formatex( lastMapChangedFilePath, charsmax( lastMapChangedFilePath ), "%s/%s", g_dataDirPath, LAST_CHANGE_MAP_FILE_NAME );
if( ( lastMapChangedFile = fopen( lastMapChangedFilePath, "wt" ) ) )
{
fprintf( lastMapChangedFile, "nothing_to_be_added_by^n0^n" );
fclose( lastMapChangedFile );
}
else
{
doAmxxLog( "ERROR, configureTheMapcycleSystem: Couldn't open the file to write ^"%s^"", lastMapChangedFilePath );
}
doAmxxLog( "" );
doAmxxLog( "The server is jumping to the next map after the current map due more than %d restarts on the map ^"%s^"",
MAX_SERVER_RESTART_ACCEPTABLE, crashingMap );
doAmxxLog( "" );
}
else
{
configureTheNextMapPlugin( mapToChange, possibleNextMap, possibleNextMapPosition );
LOG( 4, "( configureTheMapcycleSystem ) restartsOnTheCurrentMap < MAX_SERVER_RESTART_ACCEPTABLE" )
LOG( 4, "" )
}
}
stock setThisMapAsPossibleCrashingMap( const mapName[] )
{
LOG( 128, "I AM ENTERING ON setThisMapAsPossibleCrashingMap(1) mapName: %s", mapName )
new serverCrashedMapsFile;
new serverCrashedMapsFilePath[ MAX_FILE_PATH_LENGHT ];
formatex( serverCrashedMapsFilePath, charsmax( serverCrashedMapsFilePath ), "%s/%s", g_dataDirPath, MAPS_WHERE_THE_SERVER_CRASHED );
if( !( serverCrashedMapsFile = fopen( serverCrashedMapsFilePath, "a+" ) ) )
{
doAmxxLog( "ERROR, setThisMapAsPossibleCrashingMap: Couldn't open the file ^"%s^"", serverCrashedMapsFilePath );
}
else
{
fprintf( serverCrashedMapsFile, "%s^n", mapName );
fclose( serverCrashedMapsFile );
}
}
/**
* When we are setting the `possibleNextMapPosition` to 0, we are restarting the map cycle from its
* first position. This happens every time we complete a map cycle full loop.
*
* However, this function is only called at the first time the server started, so not setting anything
* implies on already starting the map cycle from its first position.
*/
stock configureTheNextMapPlugin( possibleCurrentMap[], possibleNextMap[], possibleNextMapPosition, bool:forceUpdateFile = false )
{
LOG( 128, "I AM ENTERING ON configureTheNextMapPlugin(4)" )
LOG( 4, "( configureTheNextMapPlugin ) forceUpdateFile: %d", forceUpdateFile )
LOG( 4, "( configureTheNextMapPlugin ) possibleNextMap: %s", possibleNextMap )
LOG( 4, "( configureTheNextMapPlugin ) possibleCurrentMap: %s", possibleCurrentMap )
LOG( 4, "( configureTheNextMapPlugin ) possibleNextMapPosition: %d", possibleNextMapPosition )
if( possibleNextMapPosition )
{
new mapcycleFilePath[ MAX_FILE_PATH_LENGHT ];
get_pcvar_string( cvar_mapcyclefile, mapcycleFilePath, charsmax( mapcycleFilePath ) );
setNextMap( possibleCurrentMap, possibleNextMap, true, forceUpdateFile );
saveCurrentMapCycleSetting( g_currentMapName, mapcycleFilePath, possibleNextMapPosition );
}
}
stock getRestartsOnTheCurrentMap( const mapToChange[] )
{
LOG( 128, "I AM ENTERING ON getRestartsOnTheCurrentMap(1) mapToChange: %s", mapToChange )
new lastMapChangedFile;
new lastMapChangedCount;
new lastMapChangedName [ MAX_MAPNAME_LENGHT ];
new lastMapChangedFilePath [ MAX_FILE_PATH_LENGHT ];
new lastMapChangedCountString[ 10 ];
formatex( lastMapChangedFilePath, charsmax( lastMapChangedFilePath ), "%s/%s", g_dataDirPath, LAST_CHANGE_MAP_FILE_NAME );
LOG( 4, "( getRestartsOnTheCurrentMap ) mapToChange: %s,", mapToChange )
LOG( 4, "( getRestartsOnTheCurrentMap ) lastMapChangedFilePath: %s", lastMapChangedFilePath )
if( ( lastMapChangedFile = fopen( lastMapChangedFilePath, "rt" ) ) )
{
fgets( lastMapChangedFile, lastMapChangedName , charsmax( lastMapChangedName ) );
fgets( lastMapChangedFile, lastMapChangedCountString, charsmax( lastMapChangedCountString ) );
fclose( lastMapChangedFile );
trim( lastMapChangedName );
trim( lastMapChangedCountString );
lastMapChangedCount = str_to_num( lastMapChangedCountString );
// If it got here, it could be opened for reading, but could not be opened for writing as in ready on files.
if( ( lastMapChangedFile = fopen( lastMapChangedFilePath, "wt" ) ) )
{
fprintf( lastMapChangedFile, "%s^n", mapToChange );
if( equali( mapToChange, lastMapChangedName ) )
{
++lastMapChangedCount;
LOG( 4, "( getRestartsOnTheCurrentMap ) mapToChange is equal to lastMapChangedName." )
}
else
{
lastMapChangedCount = 0;
LOG( 4, "( getRestartsOnTheCurrentMap ) mapToChange is not equal to lastMapChangedName." )
}
fprintf( lastMapChangedFile, "%d^n", lastMapChangedCount );
fclose( lastMapChangedFile );
}
else
{
doAmxxLog( "ERROR, getRestartsOnTheCurrentMap: Couldn't open the file to write ^"%s^"", lastMapChangedFilePath );
}
LOG( 4, "( getRestartsOnTheCurrentMap ) lastMapChangedName: %s", lastMapChangedName )
LOG( 4, "( getRestartsOnTheCurrentMap ) lastMapChangedCount: %d", lastMapChangedCount )
LOG( 4, "( getRestartsOnTheCurrentMap ) lastMapChangedCountString: %s", lastMapChangedCountString )
}
else
{
doAmxxLog( "ERROR, getRestartsOnTheCurrentMap: Couldn't open the file to read ^"%s^"", lastMapChangedFilePath );
if( ( lastMapChangedFile = fopen( lastMapChangedFilePath, "wt" ) ) )
{
fprintf( lastMapChangedFile, "nothing_to_be_added_by^n0^n" );
fclose( lastMapChangedFile );
}
else
{
doAmxxLog( "ERROR, getRestartsOnTheCurrentMap: Couldn't open the file to write ^"%s^"", lastMapChangedFilePath );
}
}
LOG( 1, " ( getRestartsOnTheCurrentMap ) Returning lastMapChangedCount: %d", lastMapChangedCount )
return lastMapChangedCount;
}
/**
* Internally set the next map on `g_nextMapName` and save to the file `currentAndNextmapNames.dat`,
* the current map name and the here provided nextMapName.
*
* @param currentMapName the current map the server is playing
* @param nextMapName the next map the server will be playing
* @param isToUpdateTheCvar true if is to change the cvar `amx_nextmap` to the `nextMapName`,
* otherwise false
* @param forceUpdateFile true if is to update current and next map names saved on the
* `currentAndNextmapNames.dat` file, otherwise false
*/
stock setNextMap( currentMapName[], nextMapName[], bool:isToUpdateTheCvar = true, bool:forceUpdateFile = false )
{
LOG( 128, "I AM ENTERING ON setNextMap(4) nextMapName: %s", nextMapName )
// While the `IS_DISABLED_VOTEMAP_EXIT` bit flag is set, we cannot allow any decisions.
if( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXIT )
{
// We do not need to open the menu here, because on the vote end context, a setNextMap(4) function
// call is always proceed by a process_last_round(2) function call, which will open the final choice
// vote menu.
copy( g_invokerVoteMapNameToDecide, charsmax( g_invokerVoteMapNameToDecide ), nextMapName );
LOG( 1, " ( setNextMap ) Just returning/blocking, g_voteMapStatus: %d", g_voteMapStatus )
return;
}
if( IS_MAP_VALID( nextMapName ) )
{
// set the queryable cvar
if( isToUpdateTheCvar
|| !( get_pcvar_num( cvar_nextMapChangeAnnounce )
&& get_pcvar_num( cvar_endOfMapVote ) ) )
{
LOG( 2, "( setNextMap ) IS CHANGING THE CVAR 'amx_nextmap' to '%s'.", nextMapName )
set_pcvar_string( cvar_amx_nextmap, nextMapName );
#if IS_TO_ENABLE_SVEN_COOP_SUPPPORT > 0
tryToSetGameModCvarString( cvar_mp_nextmap_cycle, nextMapName );
#endif
}
// Allow to send the variable `g_nextMapName` to the function setNextMap(4).
if( !equali( g_nextMapName, nextMapName ) )
{
copy( g_nextMapName, charsmax( g_nextMapName ), nextMapName );
}
// update our data file
trim( nextMapName );
trim( currentMapName );
saveCurrentAndNextMapNames( currentMapName, nextMapName, forceUpdateFile );
LOG( 2, "( setNextMap ) IS CHANGING THE global variable g_nextMapName to '%s'.", nextMapName )
}
else
{
LOG( 1, "AMX_ERR_PARAMS, %s, was tried to set a invalid next-map!", nextMapName )
log_error( AMX_ERR_PARAMS, "%s, was tried to set a invalid next-map!", nextMapName );
}
}
/**
* The parameter `forceUpdateFile` is used only when we need to set the `CURRENT_AND_NEXTMAP_FILE_NAME`
* at the first time we started the server. As by the book, we only read the `CURRENT_AND_NEXTMAP_FILE_NAME`
* data at the server start.
*
* The next map written to the file `currentAndNextmapNames.dat` is currently used for the option
* `startAction == SERVER_START_NEXTMAP` and debugging purposes.
*/
stock saveCurrentAndNextMapNames( const currentMapName[], const nextMapName[], bool:forceUpdateFile = false )
{
LOG( 128, "I AM ENTERING ON saveCurrentAndNextMapNames(3) currentMapName: %s, nextMapName: %s", currentMapName, nextMapName )
// We do not need to check whether the `cvar_serverStartAction` is enabled or not, because the
// execution flow only gets here when it is enabled.
if( get_pcvar_num( cvar_isFirstServerStart ) != FIRST_SERVER_START
|| forceUpdateFile )
{
new backupMapsFile;
new backupMapsFilePath[ MAX_FILE_PATH_LENGHT ];
get_pcvar_string( cvar_mapcyclefile, backupMapsFilePath, charsmax( backupMapsFilePath ) );
// We need to pass the `g_currentMapName` because the `currentMapName` to this call on the plugin_end(0)
// is the former next map, instead of the current map. We need it to be the former next map in case of
// a server crash, however the map cycle setting must to be save with the actual current map name.
saveCurrentMapCycleSetting( g_currentMapName, backupMapsFilePath, g_nextMapCyclePosition );
formatex( backupMapsFilePath, charsmax( backupMapsFilePath ), "%s/%s", g_dataDirPath, CURRENT_AND_NEXTMAP_FILE_NAME );
if( ( backupMapsFile = fopen( backupMapsFilePath, "wt" ) ) )
{
fprintf( backupMapsFile, "%s^n", currentMapName );
fprintf( backupMapsFile, "%s^n", nextMapName );
fprintf( backupMapsFile, "%d^n", g_nextMapCyclePosition );
fclose( backupMapsFile );
}
else
{
doAmxxLog( "ERROR, saveCurrentAndNextMapNames: Could not open the file backupMapsFilePath ^"%s^"", backupMapsFilePath );
}
}
}
/**
*
* @return true when the crashing was properly handled, false otherwise.
*/
public isHandledGameCrashAction( &startAction )
{
LOG( 128, "I AM ENTERING ON isHandledGameCrashAction(1) startAction: %d", startAction )
new gameCrashAction;
new gameCrashActionFilePath[ MAX_FILE_PATH_LENGHT ];
gameCrashAction = get_pcvar_num( cvar_gameCrashRecreationAction );
generateGameCrashActionFilePath( gameCrashActionFilePath, charsmax( gameCrashActionFilePath ) );
if( gameCrashAction
&& file_exists( gameCrashActionFilePath ) )
{
delete_file( gameCrashActionFilePath );
switch( gameCrashAction )
{
case 1: // The server will not change to the last map.
{
startAction = SERVER_START_NEXTMAP;
}
case 2: // The server will start a vote changing the map.
{
startAction = SERVER_START_MAPVOTE;
}
case 3: // The server will start a vote after the half of the time-left.
{
// disable any other server start action
startAction = 0;
// force to use only the '1/SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR' time, i.e.,
// stop creating an infinity loop of half of half...
g_isToCreateGameCrashFlag = false;
// Wait until the mp_timelimit, etc cvars, to be loaded from the configuration file.
set_task( DELAY_TO_WAIT_THE_SERVER_CVARS_TO_BE_LOADED + 10.0, "setGameToFinishAtHalfTime", TASKID_FINISH_GAME_TIME_BY_HALF );
}
}
}
}
stock generateGameCrashActionFilePath( gameCrashActionFilePath[], charsmaxGameCrashActionFilePath )
{
LOG( 128, "I AM ENTERING ON gameCrashActionFilePath(2) charsmaxGameCrashActionFilePath: %d", charsmaxGameCrashActionFilePath )
formatex( gameCrashActionFilePath, charsmaxGameCrashActionFilePath, "%s/%s", g_dataDirPath, GAME_CRASH_RECREATION_FLAG_FILE );
LOG( 1, "( generateGameCrashActionFilePath ) gameCrashActionFilePath: %s", gameCrashActionFilePath )
}
/**
* Save the mp_maxrounds, etc and set them to half of it.
*/
public setGameToFinishAtHalfTime()
{
LOG( 128, "I AM ENTERING ON setGameToFinishAtHalfTime(0)" )
saveEndGameLimits();
tryToSetGameModCvarFloat( cvar_mp_timelimit, g_originalTimelimit / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR );
tryToSetGameModCvarNum( cvar_mp_maxrounds, g_originalMaxRounds / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR );
tryToSetGameModCvarNum( cvar_mp_winlimit, g_originalWinLimit / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR );
tryToSetGameModCvarNum( cvar_mp_fraglimit, g_originalFragLimit / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR );
LOG( 2, "( setGameToFinishAtHalfTime ) IS CHANGING THE CVAR 'mp_timelimit' to '%f'.", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, "( setGameToFinishAtHalfTime ) IS CHANGING THE CVAR 'mp_fraglimit' to '%d'.", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, "( setGameToFinishAtHalfTime ) IS CHANGING THE CVAR 'mp_maxrounds' to '%d'.", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, "( setGameToFinishAtHalfTime ) IS CHANGING THE CVAR 'mp_winlimit' to '%d'.", get_pcvar_num( cvar_mp_winlimit ) )
}
/**
* Load the recent ban map from the file. If the number of valid maps loaded is lower than the
* number of map loaded to fill the vote menu, not all the maps will be loaded.
*
* This also restrict the number of maps to be write to the file `RECENT_BAN_MAPS_FILE_NAME` as if
* not all maps have been loaded here, on them will be written down to the file on
* writeRecentMapsBanList(0).
*
* @param maximumLoadMapsCount how many maps are loaded from the main map file list.
*/
public map_loadRecentBanList( maximumLoadMapsCount )
{
LOG( 128, "I AM ENTERING ON map_loadRecentBanList(1) maximumLoadMapsCount: %d", maximumLoadMapsCount )
new loadedMapsCount;
new recentMapsFilePath[ MAX_FILE_PATH_LENGHT ];
formatex( recentMapsFilePath, charsmax( recentMapsFilePath ), "%s/%s", g_dataDirPath, RECENT_BAN_MAPS_FILE_NAME );
new recentMapsFileDescriptor = fopen( recentMapsFilePath, "rt" );
if( recentMapsFileDescriptor )
{
new recentMapName[ MAX_MAPNAME_LENGHT ];
// loads 6 maps to ban on `maxRecentMapsBans`
new maxRecentMapsBans = get_pcvar_num( cvar_recentMapsBannedNumber );
// load the total voting choices on `maxVotingChoices` as 6, supposing your voting menu has 6 maps
new maxVotingChoices = g_maxVotingChoices;
// So, `maximumLoadMapsCount` is 10 (10 maps in my .txt file), therefore 10 > 6 (maxVotingChoices)
if( maximumLoadMapsCount > maxVotingChoices )
{
// 6 + 6 > 10 =: 12 > 10
if( maxRecentMapsBans + maxVotingChoices > maximumLoadMapsCount )
{
// Therefore the banned maps count will be 10 - 6 = 4
maxRecentMapsBans = maximumLoadMapsCount - maxVotingChoices;
}
}
else
{
maxRecentMapsBans = 0;
}
LOG( 4, "( map_loadRecentBanList ) maxVotingChoices: %d", maxVotingChoices )
LOG( 4, "( map_loadRecentBanList ) maxRecentMapsBans: %d", maxRecentMapsBans )
while( !feof( recentMapsFileDescriptor ) )
{
fgets( recentMapsFileDescriptor, recentMapName, charsmax( recentMapName ) );
trim( recentMapName );
if( recentMapName[ 0 ]
&& IS_MAP_VALID( recentMapName ) )
{
if( loadedMapsCount >= maxRecentMapsBans )
{
break;
}
// Avoid banning twice the same map.
if( !TrieKeyExists( g_recentMapsTrie, recentMapName ) )
{
ArrayPushString( g_recentListMapsArray, recentMapName );
TrieSetCell( g_recentMapsTrie, recentMapName, 0 );
loadedMapsCount++;
}
}
}
fclose( recentMapsFileDescriptor );
}
LOG( 1, " ( map_loadRecentBanList ) Returning loadedMapsCount: %d", loadedMapsCount )
return loadedMapsCount;
}
stock writeRecentMapsBanList( loadedMapsCount )
{
LOG( 128, "I AM ENTERING ON writeRecentMapsBanList(1) g_recentListMapsArray: %d", g_recentListMapsArray )
new recentMapName [ MAX_MAPNAME_LENGHT ];
new recentMapsFilePath[ MAX_FILE_PATH_LENGHT ];
formatex( recentMapsFilePath, charsmax( recentMapsFilePath ), "%s/%s", g_dataDirPath, RECENT_BAN_MAPS_FILE_NAME );
new recentMapsFileDescriptor = fopen( recentMapsFilePath, "wt" );
if( recentMapsFileDescriptor )
{
new bool:isOnlyRecentMapcycleMaps = get_pcvar_num( cvar_isOnlyRecentMapcycleMaps ) != 0;
LOG( 4, "( writeRecentMapsBanList ) isOnlyRecentMapcycleMaps: %s", isOnlyRecentMapcycleMaps )
// Do not ban repeated maps
if( !TrieKeyExists( g_recentMapsTrie, g_currentMapName ) )
{
// Add the current map to the ban list
if( isOnlyRecentMapcycleMaps )
{
// Only ban if the map is on the current map cycle. Not writing it to the file, means not banning.
if( TrieKeyExists( g_mapcycleFileListTrie, g_currentMapName ) )
{
fprintf( recentMapsFileDescriptor, "%s^n", g_currentMapName );
}
}
else
{
fprintf( recentMapsFileDescriptor, "%s^n", g_currentMapName );
}
}
// Add the others banned maps to the ban list
for( new mapIndex = 0; mapIndex < loadedMapsCount; ++mapIndex )
{
ArrayGetString( g_recentListMapsArray, mapIndex, recentMapName, charsmax( recentMapName ) );
if( isOnlyRecentMapcycleMaps )
{
if( TrieKeyExists( g_mapcycleFileListTrie, recentMapName ) )
{
fprintf( recentMapsFileDescriptor, "%s^n", recentMapName );
}
}
else
{
fprintf( recentMapsFileDescriptor, "%s^n", recentMapName );
}
}
fclose( recentMapsFileDescriptor );
LOG( 0, "", debugPrintRecentBanFile( recentMapsFilePath ) )
}
else
{
doAmxxLog( "WARNING, writeRecentMapsBanList: Couldn't find a valid map or the file doesn't exist ^"%s^"", recentMapsFilePath );
}
}
stock debugPrintRecentBanFile( recentMapsFilePath[] )
{
LOG( 128, "I AM ENTERING ON debugPrintRecentBanFile(1) recentMapsFilePath: %s", recentMapsFilePath )
new loadedMapName[ MAX_MAPNAME_LENGHT ];
new mapFileDescriptor = fopen( recentMapsFilePath, "rt" );
while( !feof( mapFileDescriptor ) )
{
fgets( mapFileDescriptor, loadedMapName, charsmax( loadedMapName ) );
trim( loadedMapName );
static mapCount;
if( mapCount++ < MAX_MAPS_TO_SHOW_ON_MAP_POPULATE_LIST
&& !( g_debug_level & 256 ) )
{
LOG( 4, "( debugPrintRecentBanFile ) %d, loadedMapName: %s", mapCount, loadedMapName )
}
LOG( 256, "( debugPrintRecentBanFile ) %d, loadedMapName: %s", mapCount, loadedMapName )
}
fclose( mapFileDescriptor );
return 0;
}
stock loadWhiteListFileFromFile( &Array:whitelistArray, whiteListFilePath[] )
{
LOG( 128, "I AM ENTERING ON loadWhiteListFileFromFile(2) whitelistArray: %d", whitelistArray)
LOG( 8, "( loadWhiteListFileFromFile ) whiteListFilePath: %s", whiteListFilePath )
new loadedCount;
new whiteListFileDescriptor;
new currentLine[ MAX_LONG_STRING ];
TRY_TO_CLEAN( ArrayClear, whitelistArray, ArrayCreate( MAX_LONG_STRING ) )
if( !( whiteListFileDescriptor = fopen( whiteListFilePath, "rt" ) ) )
{
LOG( 8, "ERROR! Invalid file descriptor. whiteListFileDescriptor: %d, whiteListFilePath: %s", \
whiteListFileDescriptor, whiteListFilePath )
}
while( !feof( whiteListFileDescriptor ) )
{
fgets( whiteListFileDescriptor, currentLine, charsmax( currentLine ) );
trim( currentLine );
// skip commentaries while reading file
if( currentLine[ 0 ] == '^0'
|| currentLine[ 0 ] == ';'
|| ( currentLine[ 0 ] == '/'
&& currentLine[ 1 ] == '/' ) )
{
continue;
}
else
{
LOG( 8, "( loadWhiteListFileFromFile ) Adding the currentLine: %s", currentLine )
ArrayPushString( whitelistArray, currentLine );
loadedCount++;
}
}
fclose( whiteListFileDescriptor );
LOG( 1, "I AM EXITING loadWhiteListFileFromFile(2) whitelistArray: %d", whitelistArray )
LOG( 1, " ( loadWhiteListFileFromFile ) Returning loadedCount: %d", loadedCount )
return loadedCount;
}
stock processLoadedGroupMapFileFrom( Array:playerFillerMapsArray, Array:fillersFilePathsArray,
Trie:minPlayerFillerMapGroupTrie=Invalid_Trie, bool:isToClearTheTrie=true )
{
LOG( 128, "I AM ENTERING ON processLoadedGroupMapFileFrom(2) groupCount: %d", ArraySize( fillersFilePathsArray ) )
new loadedMapsTotal;
new fillerFilePath[ MAX_FILE_PATH_LENGHT ];
new Array:fillerMapsArray;
new groupCount = ArraySize( fillersFilePathsArray );
// fill remaining slots with random maps from each filler file, as much as possible
for( new groupIndex = 0; groupIndex < groupCount; ++groupIndex )
{
fillerMapsArray = ArrayCreate( MAX_MAPNAME_LENGHT );
ArrayGetString( fillersFilePathsArray, groupIndex, fillerFilePath, charsmax( fillerFilePath ) );
loadedMapsTotal += map_populateList( fillerMapsArray, fillerFilePath, minPlayerFillerMapGroupTrie, isToClearTheTrie );
ArrayPushCell( playerFillerMapsArray, fillerMapsArray );
LOG( 8, "[%i] groupCount: %i, filersMapCount: %i", groupIndex, groupCount, ArraySize( fillerMapsArray ) )
LOG( 8, " fillersFilePaths[%i]: %s", groupIndex, fillerFilePath )
}
return loadedMapsTotal;
}
/**
* To start loading the files.
*/
stock loadMapFiles( bool:readMapCycle = true )
{
LOG( 128, "I AM ENTERING ON loadMapFiles(1)" )
enum LoadMapFilesTypes
{
t_Whitelist,
t_MininumPlayers,
t_MiddlePlayers,
t_NormalPlayers
}
new loadedCount [ LoadMapFilesTypes ];
new mapFilerFilePath[ MAX_FILE_PATH_LENGHT ];
// The Whitelist list must to be loaded as the fist thing as the configureTheNextMapSetttings(1)
// need it to be loaded. And the configureTheNextMapSetttings(1) must to be the seconds thing to
// be loaded because the everything else depends on it being properly set up.
loadedCount[ t_Whitelist ] = configureTheWhiteListFeature( mapFilerFilePath );
if( readMapCycle ) configureTheNextMapSetttings( mapFilerFilePath );
loadedCount[ t_MininumPlayers ] = configureTheMinPlayersFeature( mapFilerFilePath );
loadedCount[ t_MiddlePlayers ] = configureTheMidPlayersFeature( mapFilerFilePath );
loadedCount[ t_NormalPlayers ] = configureTheNorPlayersFeature( mapFilerFilePath );
configureTheRTVFeature( mapFilerFilePath );
configureServerMapChange( mapFilerFilePath );
loadTheBanRecentMapsFeature( loadedCount[ t_NormalPlayers ] );
LOG( 4, "( loadMapFiles ) Maps Files Loaded." )
LOG( 4, "" )
LOG( 4, "" )
}
stock configureTheRTVFeature( mapFilerFilePath[] )
{
LOG( 128, "I AM ENTERING ON configureTheRTVFeature(1)" )
if( g_rtvCommands & RTV_CMD_STANDARD )
{
register_clcmd( "say rockthevote", "cmd_rockthevote", 0 );
}
if( get_pcvar_num( cvar_nomPlayerAllowance ) )
{
register_clcmd( "gal_votemap", "cmd_voteMap", ADMIN_MAP );
register_clcmd( "say nominations", "cmd_nominations", 0, "- displays current nominations for next map" );
register_concmd( "gal_listmaps", "map_listAll" );
if( get_pcvar_num( cvar_nomPrefixes ) )
{
map_loadPrefixList( mapFilerFilePath );
}
loadNominationList( mapFilerFilePath );
}
LOG( 4, "" )
LOG( 4, "" )
}
/**
* Setup the main task that schedules the end map voting and allow round finish feature.
*/
stock configureServerMapChange( emptyCycleFilePath[] )
{
LOG( 128, "I AM ENTERING ON configureServerMapChange(1)" )
if( IS_WHITELIST_BLOCKING( IS_WHITELIST_ENABLED(), g_nextMapName ) )
{
new currentNextMap[ MAX_MAPNAME_LENGHT ];
doAmxxLog( "( configureServerMapChange ) %s: %L", g_nextMapName, LANG_SERVER, "GAL_MATCH_WHITELIST" );
copy( currentNextMap, charsmax( currentNextMap ), g_nextMapName );
map_getNext( g_mapcycleFileListArray, currentNextMap, g_nextMapName, "mapcyclefile" );
// Need to be called to trigger special behaviors.
setNextMap( g_currentMapName, g_nextMapName );
}
if( get_pcvar_num( cvar_emptyServerWaitMinutes )
|| get_pcvar_num( cvar_isEmptyCycleByMapChange ) )
{
map_loadEmptyCycleList( emptyCycleFilePath );
if( get_pcvar_num( cvar_emptyServerWaitMinutes ) )
{
set_task( 60.0, "inicializeEmptyCycleFeature" );
}
}
}
stock configureTheNorPlayersFeature( mapFilerFilePath[] )
{
LOG( 128, "I AM ENTERING ON configureTheNorPlayersFeature(1)" )
new loadedCount;
get_pcvar_string( cvar_voteMapFilePath, mapFilerFilePath, MAX_FILE_PATH_LENGHT - 1 );
if( mapFilerFilePath[ 0 ] )
{
if( file_exists( mapFilerFilePath )
|| mapFilerFilePath[ 0 ] == MAP_CYCLE_LOAD_FLAG
|| mapFilerFilePath[ 0 ] == MAP_FOLDER_LOAD_FLAG )
{
LOG( 4, "" )
TRY_TO_CLEAN( clear_two_dimensional_array, g_norPlayerFillerMapGroupArrays, ArrayCreate() )
TRY_TO_CLEAN( ArrayClear, g_voteNorPlayerFillerPathsArray, ArrayCreate( MAX_MAPNAME_LENGHT ) )
TRY_TO_CLEAN( ArrayClear, g_norMaxMapsPerGroupToUseArray , ArrayCreate() )
loadMapGroupsFeatureFile( mapFilerFilePath, g_voteNorPlayerFillerPathsArray, g_norMaxMapsPerGroupToUseArray );
loadedCount = processLoadedGroupMapFileFrom( g_norPlayerFillerMapGroupArrays, g_voteNorPlayerFillerPathsArray );
LOG( 4, "", debugLoadedGroupMapFileFrom( g_norPlayerFillerMapGroupArrays, g_norMaxMapsPerGroupToUseArray ) )
}
else
{
doAmxxLog( "ERROR, configureTheNorPlayersFeature: Could not open the file ^"%s^"", mapFilerFilePath );
}
}
LOG( 1, " ( configureTheNorPlayersFeature ) Returning loadedCount: %d", loadedCount )
return loadedCount;
}
stock configureTheMidPlayersFeature( mapFilerFilePath[] )
{
LOG( 128, "I AM ENTERING ON configureTheMidPlayersFeature(1)" )
new loadedCount;
if( get_pcvar_num( cvar_voteMidPlayers ) > VOTE_MIDDLE_PLAYERS_REQUIRED )
{
get_pcvar_string( cvar_voteMidPlayersMapFilePath, mapFilerFilePath, MAX_FILE_PATH_LENGHT - 1 );
if( mapFilerFilePath[ 0 ] )
{
if( file_exists( mapFilerFilePath )
|| mapFilerFilePath[ 0 ] == MAP_CYCLE_LOAD_FLAG )
{
LOG( 4, "" )
TRY_TO_CLEAN( clear_two_dimensional_array, g_midPlayerFillerMapGroupArrays, ArrayCreate() )
TRY_TO_CLEAN( ArrayClear, g_voteMidPlayerFillerPathsArray, ArrayCreate( MAX_MAPNAME_LENGHT ) )
TRY_TO_CLEAN( ArrayClear, g_midMaxMapsPerGroupToUseArray , ArrayCreate() )
loadMapGroupsFeatureFile( mapFilerFilePath, g_voteMidPlayerFillerPathsArray, g_midMaxMapsPerGroupToUseArray );
loadedCount = processLoadedGroupMapFileFrom( g_midPlayerFillerMapGroupArrays, g_voteMidPlayerFillerPathsArray );
LOG( 4, "", debugLoadedGroupMapFileFrom( g_midPlayerFillerMapGroupArrays, g_midMaxMapsPerGroupToUseArray ) )
}
else
{
doAmxxLog( "ERROR, configureTheMidPlayersFeature: Could not open the file ^"%s^"", mapFilerFilePath );
}
}
}
LOG( 1, " ( configureTheMidPlayersFeature ) Returning loadedCount: %d", loadedCount )
return loadedCount;
}
stock configureTheMinPlayersFeature( mapFilerFilePath[] )
{
LOG( 128, "I AM ENTERING ON configureTheMinPlayersFeature(1)" )
new loadedCount;
if( get_pcvar_num( cvar_voteMinPlayers ) > VOTE_MININUM_PLAYERS_REQUIRED )
{
get_pcvar_string( cvar_voteMinPlayersMapFilePath, mapFilerFilePath, MAX_FILE_PATH_LENGHT - 1 );
if( mapFilerFilePath[ 0 ] )
{
if( file_exists( mapFilerFilePath )
|| mapFilerFilePath[ 0 ] == MAP_CYCLE_LOAD_FLAG )
{
LOG( 4, "" )
TRY_TO_CLEAN( clear_two_dimensional_array, g_minPlayerFillerMapGroupArrays, ArrayCreate() )
TRY_TO_CLEAN( TrieClear, g_minPlayerFillerMapGroupTrie , TrieCreate() )
TRY_TO_CLEAN( ArrayClear, g_voteMinPlayerFillerPathsArray, ArrayCreate( MAX_MAPNAME_LENGHT ) )
TRY_TO_CLEAN( ArrayClear, g_minMaxMapsPerGroupToUseArray , ArrayCreate() )
loadMapGroupsFeatureFile( mapFilerFilePath, g_voteMinPlayerFillerPathsArray, g_minMaxMapsPerGroupToUseArray );
loadedCount = processLoadedGroupMapFileFrom( g_minPlayerFillerMapGroupArrays,
g_voteMinPlayerFillerPathsArray, g_minPlayerFillerMapGroupTrie, false );
LOG( 4, "", debugLoadedGroupMapFileFrom( g_minPlayerFillerMapGroupArrays, g_minMaxMapsPerGroupToUseArray ) )
}
else
{
doAmxxLog( "ERROR, configureTheMinPlayersFeature: Could not open the file ^"%s^"", mapFilerFilePath );
}
}
}
LOG( 1, " ( configureTheMinPlayersFeature ) Returning loadedCount: %d", loadedCount )
return loadedCount;
}
stock configureTheWhiteListFeature( mapFilerFilePath[] )
{
LOG( 128, "I AM ENTERING ON configureTheWhiteListFeature(1)" )
new loadedCount;
if( IS_WHITELIST_ENABLED() )
{
LOG( 4, "" )
get_pcvar_string( cvar_voteWhiteListMapFilePath, mapFilerFilePath, MAX_FILE_PATH_LENGHT - 1 );
loadedCount = loadWhiteListFileFromFile( g_whitelistFileArray, mapFilerFilePath );
computeNextWhiteListLoadTime( 1, false );
loadTheWhiteListFeature();
}
LOG( 1, " ( configureTheWhiteListFeature ) Returning loadedCount: %d", loadedCount )
return loadedCount;
}
/**
* Create the recent maps cvar and load the banned file list form the file system.
*
* @param maximumLoadMapsCount how many maps are loaded from the main map file list.
*/
stock loadTheBanRecentMapsFeature( maximumLoadMapsCount )
{
LOG( 128, "I AM ENTERING ON loadTheBanRecentMapsFeature(1)" )
new loadedCount;
if( get_pcvar_num( cvar_recentMapsBannedNumber ) )
{
TRY_TO_CLEAN( TrieClear, g_recentMapsTrie, TrieCreate() )
TRY_TO_CLEAN( ArrayClear, g_recentListMapsArray, ArrayCreate( MAX_MAPNAME_LENGHT ) )
// If we are only banning the maps on the map cycle, we should consider its size instead of
// the voting filler's size.
if( get_pcvar_num( cvar_isOnlyRecentMapcycleMaps ) )
{
loadedCount = map_loadRecentBanList( ArraySize( g_mapcycleFileListArray ) );
}
else
{
loadedCount = map_loadRecentBanList( maximumLoadMapsCount );
}
register_clcmd( "say recentmaps", "cmd_listrecent", 0 );
// Do nothing if the map will be instantly changed
if( loadedCount
&& !( get_pcvar_num( cvar_isFirstServerStart ) == FIRST_SERVER_START
&& get_pcvar_num( cvar_serverStartAction ) ) )
{
writeRecentMapsBanList( loadedCount );
}
}
LOG( 1, " ( loadTheBanRecentMapsFeature ) Returning loadedCount: %d", loadedCount )
return loadedCount;
}
stock debugLoadedGroupMapFileFrom( &Array:playerFillerMapsArray, &Array:maxMapsPerGroupToUseArray )
{
LOG( 128, "I AM ENTERING ON debugLoadedGroupMapFileFrom(2) groupCount: %d", ArraySize( playerFillerMapsArray ) )
new arraySize;
new Array:fillerMapsArray;
new fillerMap[ MAX_MAPNAME_LENGHT ];
new groupCount = ArraySize( playerFillerMapsArray );
// fill remaining slots with random maps from each filler file, as much as possible
for( new groupIndex = 0; groupIndex < groupCount; ++groupIndex )
{
fillerMapsArray = ArrayGetCell( playerFillerMapsArray, groupIndex );
arraySize = ArraySize( fillerMapsArray );
LOG( 8, "[%i] maxMapsPerGroupToUse: %i, filersMapCount: %i", groupIndex, \
ArrayGetCell( maxMapsPerGroupToUseArray, groupIndex ), arraySize )
for( new mapIndex = 0; mapIndex < arraySize && mapIndex < 10; mapIndex++ )
{
GET_MAP_NAME( fillerMapsArray, mapIndex, fillerMap )
LOG( 8, " fillerMap[%i]: %s", mapIndex, fillerMap )
}
}
return 0;
}
stock loadMapGroupsFeatureFile( mapFilerFilePath[], &Array:mapFilersPathArray, &Array:maxMapsPerGroupToUse )
{
LOG( 128, "I AM ENTERING ON loadMapGroupsFeatureFile(3), mapFilerFilePath: %s", mapFilerFilePath )
// The mapFilerFilePaths '*' and '#' disables The Map Groups Feature.
if( mapFilerFilePath[ 0 ] != MAP_FOLDER_LOAD_FLAG
&& mapFilerFilePath[ 0 ] != MAP_CYCLE_LOAD_FLAG )
{
// determine what kind of file it's being used as
new mapFilerFile = fopen( mapFilerFilePath, "rt" );
if( mapFilerFile )
{
new currentReadedLine[ 16 ];
fgets( mapFilerFile, currentReadedLine, charsmax( currentReadedLine ) );
trim( currentReadedLine );
if( equali( currentReadedLine, "[groups]" ) )
{
new groupCount;
new fillerFilePath[ MAX_MAPNAME_LENGHT ];
LOG( 8, "" )
LOG( 8, "this is a [groups] mapFilerFile" )
// read the filler mapFilerFile to determine how many groups there are ( max of MAX_OPTIONS_IN_VOTE )
while( !feof( mapFilerFile ) )
{
fgets( mapFilerFile, currentReadedLine, charsmax( currentReadedLine ) );
trim( currentReadedLine );
LOG( 8, "currentReadedLine: %s isdigit: %i groupCount: %i ", \
currentReadedLine, isdigit( currentReadedLine[ 0 ] ), groupCount )
if( isdigit( currentReadedLine[ 0 ] ) )
{
if( groupCount < MAX_OPTIONS_IN_VOTE )
{
groupCount++;
ArrayPushCell( maxMapsPerGroupToUse, str_to_num( currentReadedLine ) );
formatex( fillerFilePath, charsmax( fillerFilePath ), "%s/%i.ini", g_configsDirPath, groupCount );
ArrayPushString( mapFilersPathArray, fillerFilePath );
LOG( 8, "fillersFilePaths: %s", fillerFilePath )
}
else
{
LOG( 1, "AMX_ERR_BOUNDS, %L %L", LANG_SERVER, "GAL_GRP_FAIL_TOOMANY", \
mapFilerFilePath, LANG_SERVER, "GAL_GRP_FAIL_TOOMANY_2" )
log_error( AMX_ERR_BOUNDS, "%L %L", LANG_SERVER, "GAL_GRP_FAIL_TOOMANY",
mapFilerFilePath, LANG_SERVER, "GAL_GRP_FAIL_TOOMANY_2" );
break;
}
}
}
if( groupCount == 0 )
{
LOG( 1, "AMX_ERR_GENERAL, %L", LANG_SERVER, "GAL_GRP_FAIL_NOCOUNTS", mapFilerFilePath )
log_error( AMX_ERR_GENERAL, "%L", LANG_SERVER, "GAL_GRP_FAIL_NOCOUNTS", mapFilerFilePath );
fclose( mapFilerFile );
goto loadTheDefaultMapFile;
}
}
// we presume it's a listing of maps, ala mapcycle.txt
else
{
fclose( mapFilerFile );
goto loadTheDefaultMapFile;
}
fclose( mapFilerFile );
}
else
{
LOG( 1, "AMX_ERR_NOTFOUND, %L", LANG_SERVER, "GAL_FILLER_NOTFOUND", mapFilerFilePath )
log_error( AMX_ERR_NOTFOUND, "%L", LANG_SERVER, "GAL_FILLER_NOTFOUND", mapFilerFilePath );
goto loadTheDefaultMapFile;
}
}
// we'll be loading all maps in the /maps folder or the current mapcycle file
else
{
loadTheDefaultMapFile:
// the options `*` and `#` will be handled by map_populateList(4) later.
ArrayPushString( mapFilersPathArray, mapFilerFilePath );
ArrayPushCell( maxMapsPerGroupToUse, MAX_OPTIONS_IN_VOTE );
}
LOG( 4, "( loadMapGroupsFeatureFile ) MapsGroups Loaded, mapFilerFilePath: %s", mapFilerFilePath )
LOG( 4, "" )
LOG( 4, "" )
}
/**
* This event is not registered by mp_fraglimitCvarSupport(0) if the game does not support the
* `mp_fraglimit` cvar natively or if the `cvar_fragLimitSupport` virtual support is not enabled.
*/
public client_death_event()
{
LOG( 256, "I AM ENTERING ON client_death_event(0)" )
new killerId = read_data( 1 );
if( killerId < MAX_PLAYERS_COUNT
&& killerId > 0 )
{
new frags;
if( ( frags = ++g_playersKills[ killerId ] ) > g_greatestKillerFrags )
{
g_greatestKillerFrags = frags;
if( g_fragLimitNumber )
{
new endOfMapVote = get_pcvar_num( cvar_endOfMapVote );
if( IS_TO_START_THE_VOTE_BY_FRAGS( g_fragLimitNumber - g_greatestKillerFrags )
&& isTimeToStartTheEndOfMapVoting( endOfMapVote ) )
{
start_voting_by_frags();
}
// This `? 2 : 1` condition allow Galileo to manage the round end, if it is enabled. Otherwise let
// the actual game mod to do it. If the support is not virtual but the `mp_fraglimitCvarSupport`,
// the try_to_manage_map_end(1) will to perform the map change.
if( g_greatestKillerFrags > g_fragLimitNumber - ( endOfMapVote ? 2 : 1 ) )
{
try_to_manage_map_end( true );
}
}
}
}
}
/**
* Switches between the `cvar_endOnRound` and `cvar_endOfMapVoteStart` options cases.
*
* case 1:
* `cvar_endOnRound`: When time runs out, end at the current round end.
* `cvar_endOfMapVoteStart`: To start the voting on the last round to be played.
*
* case 2:
* `cvar_endOnRound`: When time runs out, end at the next round end.
* `cvar_endOfMapVoteStart`: To start the voting on the round before the last.
*
* case 3:
* `cvar_endOnRound`: Do not applies.
* `cvar_endOfMapVoteStart`: To start the voting on the round before the last of the last.
*
* @param roundsRemaining how many rounds are remaining to the map end.
*/
stock chooseTheEndOfMapStartOption( roundsRemaining )
{
LOG( 128, "I AM ENTERING ON chooseTheEndOfMapStartOption(1) roundsRemaining: %d", roundsRemaining )
new endOfMapVoteStart = get_pcvar_num( cvar_endOfMapVoteStart );
switch( get_pcvar_num( cvar_endOnRound ) )
{
case END_AT_RIGHT_NOW:
{
// Sum +1 to start it earlier as the cannot block the map changing.
if( endOfMapVoteStart
&& roundsRemaining < endOfMapVoteStart + 1 )
{
LOG( 1, " ( chooseTheEndOfMapStartOption ) 1. Returning true." )
return true;
}
}
case END_AT_THE_CURRENT_ROUND_END:
{
if( ( g_isGameEndingTypeContextSaved ? g_isTheLastGameRoundContext : g_isTheLastGameRound )
|| ( endOfMapVoteStart
&& roundsRemaining < endOfMapVoteStart ) )
{
LOG( 1, " ( chooseTheEndOfMapStartOption ) 2. Returning true." )
return true;
}
}
case END_AT_THE_NEXT_ROUND_END:
{
switch( endOfMapVoteStart )
{
case 1:
{
if( ( g_isGameEndingTypeContextSaved ? g_isTheLastGameRoundContext : g_isTheLastGameRound )
|| ( endOfMapVoteStart
&& roundsRemaining < endOfMapVoteStart ) )
{
LOG( 1, " ( chooseTheEndOfMapStartOption ) 3. Returning true." )
return true;
}
}
case 2:
{
if( ( g_isGameEndingTypeContextSaved ? g_isThePenultGameRoundContext : g_isThePenultGameRound )
|| ( endOfMapVoteStart
&& roundsRemaining < endOfMapVoteStart ) )
{
LOG( 1, " ( chooseTheEndOfMapStartOption ) 4. Returning true." )
return true;
}
}
default:
{
if( endOfMapVoteStart
&& roundsRemaining < endOfMapVoteStart )
{
LOG( 1, " ( chooseTheEndOfMapStartOption ) 5. Returning true." )
return true;
}
}
}
}
}
LOG( 1, " ( chooseTheEndOfMapStartOption ) Returning false." )
return false;
}
/**
* Predict if this will be the last round and allow to start the voting. Give time range to try
* detecting the round start, to avoid the old buy weapons menu override. This is called every
* round start and determines whether this round should be used to perform the map voting.
*
* If this is called between the team_win_event(0) and the round_end_event(0)? This cannot to be
* called otherwise the seconds passed since the round started will be out of date.
*
* @param secondsRemaining how many seconds are remaining to the map end.
*/
stock isToStartTheVotingOnThisRound( secondsRemaining, GameEndingType:gameEndingType )
{
LOG( 128, "I AM ENTERING ON isToStartTheVotingOnThisRound(2) secondsRemaining: %d", secondsRemaining )
if( get_pcvar_num( cvar_endOfMapVote )
&& !task_exists( TASKID_START_VOTING_DELAYED ) )
{
new secondsPassed;
// Reduce the total time due the PERIODIC_CHECKING_INTERVAL error.
if( secondsRemaining > 0 )
{
if( ( secondsPassed = secondsRemaining - PERIODIC_CHECKING_INTERVAL ) < 1 )
{
secondsPassed = 1;
}
}
LOG( 0, "", debugIsTimeToStartTheEndOfMap( secondsRemaining, 256 ) )
new roundsRemaining = howManyRoundsAreRemaining( secondsPassed, gameEndingType );
return chooseTheEndOfMapStartOption( roundsRemaining );
}
LOG( 1, " ( isToStartTheVotingOnThisRound ) Just returning false." )
return false;
}
stock howManySecondsLastMapTheVoting( bool:isToIncludeRunoff = true )
{
LOG( 128, "I AM ENTERING ON howManySecondsLastMapTheVoting(0)" )
new temp;
new Float:voteTime;
// Until the pendingVoteCountdown(0) to finish takes getVoteAnnouncementTime() + VOTE_TIME_SEC + VOTE_TIME_SEC seconds.
voteTime = getVoteAnnouncementTime( get_pcvar_num( cvar_isToAskForEndOfTheMapVote ) ) + VOTE_TIME_SEC + VOTE_TIME_SEC;
// After, it takes more the `g_votingSecondsRemaining` until the the close vote function to be called.
SET_VOTING_TIME_TO( temp, cvar_voteDuration )
voteTime += temp;
// When the voting is closed on closeVoting(0), take more VOTE_TIME_COUNT seconds until the result to be counted.
voteTime += VOTE_TIME_COUNT;
// Let us assume the worst case, then always will be performed a runoff voting.
if( get_pcvar_num( cvar_runoffEnabled ) == RUNOFF_ENABLED
&& isToIncludeRunoff )
{
SET_VOTING_TIME_TO( temp, cvar_runoffDuration )
voteTime = voteTime + voteTime + temp + VOTE_TIME_RUNOFF;
}
LOG( 1, " ( howManySecondsLastMapTheVoting ) Returning the vote total time: %f", voteTime )
return floatround( voteTime, floatround_ceil );
}
/**
* This function choose which round round ending type it is and count how many rounds there are.
* The types are:
*
* 1) Per rounds.
* 2) Is by mp_winlimit expiration proximity?
* 3) Is by mp_maxrounds expiration proximity?
* 4) Per minutes.
*/
stock howManyRoundsAreRemaining( secondsRemaining, GameEndingType:whatGameEndingType )
{
LOG( 128, "I AM ENTERING ON howManyRoundsAreRemaining(2), g_roundAverageTime: %d", g_roundAverageTime )
new avoidExtraRound;
if( IS_THE_ROUND_AVERAGE_TIME_TOO_SHORT() )
{
avoidExtraRound = -2;
}
else if( IS_THE_ROUND_AVERAGE_TIME_SHORT() )
{
avoidExtraRound = -1;
}
switch( whatGameEndingType )
{
case GameEndingType_ByMaxRounds:
{
return ( g_isGameEndingTypeContextSaved ?
g_maxRoundsContextSaved : get_pcvar_num( cvar_mp_maxrounds ) ) - g_totalRoundsPlayed - 1 + avoidExtraRound;
}
case GameEndingType_ByWinLimit:
{
return ( g_isGameEndingTypeContextSaved ?
g_winLimitContextSaved :
get_pcvar_num( cvar_mp_winlimit ) ) - max( g_totalCtWins, g_totalTerroristsWins ) - 1 + avoidExtraRound;
}
case GameEndingType_ByFragLimit:
{
new roundsLeftBy_frags = ( g_isGameEndingTypeContextSaved ?
g_fragLimitContextSaved : get_pcvar_num( cvar_mp_fraglimit ) ) - g_greatestKillerFrags;
getRoundsRemainingBy( _, roundsLeftBy_frags );
return roundsLeftBy_frags + avoidExtraRound;
}
case GameEndingType_ByTimeLimit:
{
// The secondsRemaining is already correctly updated by the `g_isGameEndingTypeContextSaved`.
new roundsLeftBy_time = secondsRemaining;
getRoundsRemainingBy( roundsLeftBy_time );
return roundsLeftBy_time + avoidExtraRound;
}
}
LOG( 1, " ( howManyRoundsAreRemaining ) Returning MAX_INTEGER: %d", MAX_INTEGER )
return MAX_INTEGER;
}
stock getRoundsRemainingBy( &by_time = 0, &by_frags = 0 )
{
LOG( 128, "I AM ENTERING ON getRoundsRemainingBy(2), by_time: %d, by_frags: %d", by_time, by_frags )
if( by_time < 1 ) by_time = 1;
if( by_frags < 1 ) by_frags = 1;
// Make sure there are enough data to operate, otherwise set valid data.
if( g_totalRoundsSavedTimes > MIN_VOTE_START_ROUNDS_DELAY )
{
// Avoid zero division
if( g_roundAverageTime )
{
by_time = by_time / g_roundAverageTime - 1;
}
else
{
by_time = by_time / SECONDS_BY_ROUND_AVERAGE;
}
// Avoid zero division
if( g_greatestKillerFrags
&& g_totalRoundsPlayed )
{
new integerDivision = g_greatestKillerFrags / g_totalRoundsPlayed;
if( integerDivision )
{
by_frags = by_frags / integerDivision - 1;
}
else
{
by_frags = by_frags / FRAGS_BY_ROUND_AVERAGE;
}
}
else
{
by_frags = by_frags / FRAGS_BY_ROUND_AVERAGE;
}
}
else
{
by_time = by_time / SECONDS_BY_ROUND_AVERAGE;
by_frags = by_frags / FRAGS_BY_ROUND_AVERAGE;
}
}
stock debugWhatGameEndingTypeItIs( rounds_left_by_maxrounds, rounds_left_by_time, rounds_left_by_winlimit,
rounds_left_by_frags, debugLevel )
{
LOG( debugLevel, "I AM ENTERING ON debugWhatGameEndingTypeItIs(5)" )
LOG( debugLevel, "" )
LOG( debugLevel, "( whatGameEndingTypeItIs ) cv_winlimit: %2d", get_pcvar_num( cvar_mp_winlimit ) )
LOG( debugLevel, "( whatGameEndingTypeItIs ) cv_maxrounds: %0d", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( debugLevel, "( whatGameEndingTypeItIs ) cv_time: %6f", get_pcvar_float( cvar_mp_timelimit ) )
LOG( debugLevel, "( whatGameEndingTypeItIs ) cv_frags: %5d", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( debugLevel, "( whatGameEndingTypeItIs )" )
LOG( debugLevel, "( whatGameEndingTypeItIs ) rounds_left_by_winlimit: %2d", rounds_left_by_winlimit )
LOG( debugLevel, "( whatGameEndingTypeItIs ) rounds_left_by_maxrounds: %0d", rounds_left_by_maxrounds )
LOG( debugLevel, "( whatGameEndingTypeItIs ) rounds_left_by_time: %6d", rounds_left_by_time )
LOG( debugLevel, "( whatGameEndingTypeItIs ) rounds_left_by_frags: %5d", rounds_left_by_frags )
LOG( debugLevel, "( whatGameEndingTypeItIs )" )
LOG( debugLevel, "( whatGameEndingTypeItIs ) GameEndingType_ByWinLimit: %2d", GameEndingType_ByWinLimit )
LOG( debugLevel, "( whatGameEndingTypeItIs ) GameEndingType_ByMaxRounds: %d", GameEndingType_ByMaxRounds )
LOG( debugLevel, "( whatGameEndingTypeItIs ) GameEndingType_ByTimeLimit: %d", GameEndingType_ByTimeLimit )
LOG( debugLevel, "( whatGameEndingTypeItIs ) GameEndingType_ByFragLimit: %d", GameEndingType_ByFragLimit )
LOG( debugLevel, "" )
return 0;
}
/**
* Wrapper to call switchEndingGameType(10) without repeating the same `if` code everywhere.
*/
#define SWITCH_ENDING_GAME_TYPE_RETURN(%0,%1,%2,%3,%4,%5,%6,%7,%8,%9) \
{ \
gameType = switchEndingGameType( %0, %1, %2, %3, %4, %5, %6, %7, %8, %9 ); \
if( gameType != GameEndingType_ByNothing ) \
{ \
LOG( 1, " ( SWITCH_ENDING_GAME_TYPE_RETURN ) Returning GameEndingType: %d", gameType ) \
return gameType; \
} \
}
stock GameEndingType:whatGameEndingTypeItIs()
{
LOG( 128, "I AM ENTERING ON whatGameEndingTypeItIs(0)" )
new GameEndingType:gameType;
new by_time;
new by_frags;
new by_winlimit;
new by_maxrounds;
new cv_time;
new cv_frags;
new cv_maxrounds;
new cv_winlimit;
cv_winlimit = get_pcvar_num( cvar_mp_winlimit );
cv_maxrounds = get_pcvar_num( cvar_mp_maxrounds );
cv_time = get_pcvar_num( cvar_mp_timelimit );
cv_frags = get_pcvar_num( cvar_mp_fraglimit );
by_time = get_timeleft();
by_frags = cv_frags - g_greatestKillerFrags;
by_maxrounds = cv_maxrounds - g_totalRoundsPlayed;
by_winlimit = cv_winlimit - max( g_totalCtWins, g_totalTerroristsWins );
getRoundsRemainingBy( by_time, by_frags );
LOG( 0, "", debugWhatGameEndingTypeItIs( by_maxrounds, by_time, by_winlimit, by_frags, 256 ) )
// Check whether there is any allowed combination.
SWITCH_ENDING_GAME_TYPE_RETURN( by_winlimit , cv_winlimit , by_time , cv_time , \
by_maxrounds, cv_maxrounds, by_frags , cv_frags , GameEndingType_ByWinLimit , false )
SWITCH_ENDING_GAME_TYPE_RETURN( by_maxrounds , cv_maxrounds, by_time , cv_time , \
by_winlimit , cv_winlimit , by_frags , cv_frags , GameEndingType_ByMaxRounds, false )
SWITCH_ENDING_GAME_TYPE_RETURN( by_time , cv_time , by_winlimit, cv_winlimit , \
by_maxrounds, cv_maxrounds, by_frags , cv_frags , GameEndingType_ByTimeLimit, false )
SWITCH_ENDING_GAME_TYPE_RETURN( by_frags , cv_frags , by_time , cv_time , \
by_maxrounds, cv_maxrounds, by_winlimit, cv_winlimit , GameEndingType_ByFragLimit, false )
// Allow self return for the first matching slot, as there are not combinations at all.
SWITCH_ENDING_GAME_TYPE_RETURN( cv_winlimit , cv_winlimit , cv_time , cv_time , \
cv_maxrounds, cv_maxrounds, cv_frags , cv_frags , GameEndingType_ByWinLimit , true )
SWITCH_ENDING_GAME_TYPE_RETURN( cv_maxrounds , cv_maxrounds, cv_time , cv_time , \
cv_winlimit , cv_winlimit , cv_frags , cv_frags , GameEndingType_ByMaxRounds, true )
SWITCH_ENDING_GAME_TYPE_RETURN( cv_time , cv_time , cv_winlimit, cv_winlimit , \
cv_maxrounds, cv_maxrounds, cv_frags , cv_frags , GameEndingType_ByTimeLimit, true )
SWITCH_ENDING_GAME_TYPE_RETURN( cv_frags , cv_frags , cv_time , cv_time , \
cv_maxrounds, cv_maxrounds, cv_winlimit, cv_winlimit , GameEndingType_ByFragLimit, true )
LOG( 256, " ( whatGameEndingTypeItIs ) Returning: %d", GameEndingType_ByNothing )
return GameEndingType_ByNothing;
}
stock GameEndingType:switchEndingGameType( by_maxrounds, cv_maxrounds, by_time, cv_time, by_winlimit, cv_winlimit,
by_frags, cv_frags, GameEndingType:type, bool:allowSelfReturn )
{
LOG( 256, "I AM ENTERING ON switchEndingGameType(10) GameEndingType: %d", type )
// If the cvar original value is set to a zero value, the round will never ended by such cvar.
if( cv_maxrounds > 0 )
{
// If the `if-else` chain is not cut once the higher order is evaluated, it will be able to
// fall under the wrong entrance type, as lower the level, more the restrictions are lost.
if( cv_time > 0
&& cv_winlimit > 0
&& cv_frags > 0 )
{
if( by_time > by_maxrounds
&& by_winlimit > by_maxrounds
&& by_frags > by_maxrounds )
{
LOG( 256, " ( switchEndingGameType ) 1" )
return type;
}
}
else if( cv_time > 0
&& cv_winlimit > 0 )
{
if( by_time > by_maxrounds
&& by_winlimit > by_maxrounds )
{
LOG( 256, " ( switchEndingGameType ) 2" )
return type;
}
}
else if( cv_time > 0
&& cv_frags > 0 )
{
if( by_time > by_maxrounds
&& by_frags > by_maxrounds )
{
LOG( 256, " ( switchEndingGameType ) 3" )
return type;
}
}
else if( cv_winlimit > 0
&& cv_frags > 0 )
{
if( by_winlimit > by_maxrounds
&& by_frags > by_maxrounds )
{
LOG( 256, " ( switchEndingGameType ) 4" )
return type;
}
}
// If the `by_maxrounds` did not fall in any of these above traps to fall out the `if-else` chain,
// it is safe to let if free from this point towards.
else if( cv_time > 0
&& by_time > by_maxrounds )
{
LOG( 256, " ( switchEndingGameType ) 5" )
return type;
}
else if( cv_winlimit > 0
&& by_winlimit > by_maxrounds )
{
LOG( 256, " ( switchEndingGameType ) 6" )
return type;
}
else if( cv_frags > 0
&& by_frags > by_maxrounds )
{
LOG( 256, " ( switchEndingGameType ) 7" )
return type;
}
else if( allowSelfReturn )
{
LOG( 256, " ( switchEndingGameType ) 8" )
return type;
}
}
LOG( 256, " ( switchEndingGameType ) Returning GameEndingType_ByNothing: %d", GameEndingType_ByNothing )
return GameEndingType_ByNothing;
}
stock debugIsTimeToStartTheEndOfMap( secondsRemaining, debugLevel )
{
LOG( 128, "I AM ENTERING ON debugIsTimeToStartTheEndOfMap(2)" )
new taskExist = task_exists( TASKID_START_VOTING_DELAYED );
LOG( debugLevel, "" )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) task_exists TASKID_START_VOTING_DELAYED: %d", taskExist )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) g_isTheLastGameRound: %d", g_isTheLastGameRound )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) g_isThePenultGameRound: %d", g_isThePenultGameRound )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) debugLevel: %d", debugLevel )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) secondsRemaining: %d", secondsRemaining )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) cvar_endOnRound: %d", get_pcvar_num( cvar_endOnRound ) )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) cvar_endOfMapVote: %d", get_pcvar_num( cvar_endOfMapVote ) )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) cvar_endOfMapVoteStart: %d", get_pcvar_num( cvar_endOfMapVoteStart ) )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) START_VOTEMAP_MIN_TIME: %d", START_VOTEMAP_MIN_TIME )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) START_VOTEMAP_MAX_TIME: %d", START_VOTEMAP_MAX_TIME )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) g_totalRoundsSavedTimes: %d", g_totalRoundsSavedTimes )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) PERIODIC_CHECKING_INTERVAL: %d", PERIODIC_CHECKING_INTERVAL )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) MIN_VOTE_START_ROUNDS_DELAY: %d", MIN_VOTE_START_ROUNDS_DELAY )
LOG( debugLevel, "( isTimeToStartTheEndOfMapVoting ) IS_END_OF_MAP_VOTING_GOING_ON(): %d", IS_END_OF_MAP_VOTING_GOING_ON() )
LOG( debugLevel, "" )
return 0;
}
/**
* Determine whether the server is on an acceptable time to start a map voting on the middle of the
* round and to start the end map voting near the map time limit expiration.
*
* When other features as `cvar_endOfMapVoteStart` and `cvar_endOnRound` are not enabled, then
* we must to start the voting right away when the minimum necessary time comes.
*
* As we only periodically check to whether to start the map voting each 15 seconds, we must to set
* the minimum check as: g_votingSecondsRemaining + 15 seconds + 1
*/
stock isTimeToStartTheEndOfMapVoting( endOfMapVote )
{
LOG( 256, "I AM ENTERING ON isTimeToStartTheEndOfMapVoting(1) secondsRemaining: %d", get_timeleft() )
LOG( 0, "", debugIsTimeToStartTheEndOfMap( get_timeleft(), 32 ) )
if( !IS_END_OF_MAP_VOTING_GOING_ON()
&& !task_exists( TASKID_START_VOTING_DELAYED )
&& endOfMapVote )
{
// If the `cvar_endOfMapVoteStart` is not enabled, we must to start a map voting right now
// because the time is ending and the `cvar_endOfMapVoteStart` will not be able to start a map
// voting.
//
// If the `cvar_endOnRound` is not enabled we must to start the voting right now because
// there will be no round end waiting and once the `secondsRemaining` are finished, the map
// will change whether the voting is complete or not.
// This is not the case when the `cvar_endOnRound` is enabled. If the voting is not finish
// when the round is end, a new extra round will be played. If the map time is too big, makes
// no sense to wait and will probably not to start the voting.
//
// Let suppose the `cvar_endOnRound` is set to 1, and right now the the time left is 20, and
// there are remaining 30 seconds to finish the round. If this is called, it should do nothing
// because as the `cvar_endOnRound` is enabled, the voting will be scheduled by map_manageEnd(0).
//
if( !ARE_THERE_ENOUGH_PLAYERS_FOR_MANAGE_END()
|| !get_pcvar_num( cvar_endOfMapVoteStart )
|| !get_pcvar_num( cvar_endOnRound )
|| IS_THE_ROUND_TIME_TOO_BIG( get_pcvar_float( cvar_mp_roundtime ) )
|| IS_THE_ROUND_AVERAGE_TIME_TOO_SHORT() )
{
LOG( 256, " ( isTimeToStartTheEndOfMapVoting ) Just returning true." )
return true;
}
}
LOG( 256, " ( isTimeToStartTheEndOfMapVoting ) Just returning false." )
return false;
}
/**
* This only handles the voting starting by limit expiration.
*
* The map will not accept to change when the voting is running due the restriction on
* try_to_process_last_round(2). On the cases where that restriction does not have effect, the
* voting will already have been started by vote_manageEnd(0) when the maximum allowed time comes.
*/
stock tryToStartTheVotingOnThisRound( startDelay )
{
LOG( 128, "I AM ENTERING ON tryToStartTheVotingOnThisRound(1) startDelay: %d", startDelay )
new timeLeft;
new GameEndingType:gameEndingType;
// If the context was deleted by map_manageEnd(0) due the limit expiration, we need to used the saved
// context, before they being deleted to prevent the map from changing.
if( g_isGameEndingTypeContextSaved )
{
timeLeft = g_timeLeftContextSaved;
gameEndingType = g_gameEndingTypeContextSaved;
}
else
{
timeLeft = get_timeleft();
gameEndingType = whatGameEndingTypeItIs();
}
if( isToStartTheVotingOnThisRound( timeLeft, gameEndingType ) )
{
switch( gameEndingType )
{
case GameEndingType_ByWinLimit:
{
set_task( float( startDelay ), "start_voting_by_winlimit", TASKID_START_VOTING_DELAYED );
}
case GameEndingType_ByMaxRounds:
{
set_task( float( startDelay ), "start_voting_by_maxrounds", TASKID_START_VOTING_DELAYED );
}
case GameEndingType_ByFragLimit:
{
set_task( float( startDelay ), "start_voting_by_frags", TASKID_START_VOTING_DELAYED );
}
default:
{
set_task( float( startDelay ), "start_voting_by_timer", TASKID_START_VOTING_DELAYED );
}
}
}
}
stock debugTeamWinEvent( string_team_winner[], wins_CT_trigger, wins_Terrorist_trigger )
{
LOG( 32, "( team_win_event )" )
LOG( 32, "( team_win_event ) string_team_winner: %s", string_team_winner )
LOG( 32, "( team_win_event ) g_winLimitNumber: %d", g_winLimitNumber )
LOG( 32, "( team_win_event ) wins_CT_trigger: %d", wins_CT_trigger )
LOG( 32, "( team_win_event ) wins_Terrorist_trigger: %d", wins_Terrorist_trigger )
LOG( 32, "( team_win_event ) g_isGameEndingTypeContextSaved: %d", g_isGameEndingTypeContextSaved )
LOG( 32, "( team_win_event ) g_gameEndingTypeContextSaved: %d", g_gameEndingTypeContextSaved )
LOG( 32, "( team_win_event ) g_timeLeftContextSaved: %d", g_timeLeftContextSaved )
LOG( 32, "( team_win_event ) g_maxRoundsContextSaved: %d", g_maxRoundsContextSaved )
LOG( 32, "( team_win_event ) g_winLimitContextSaved: %d", g_winLimitContextSaved )
LOG( 32, "( team_win_event ) g_fragLimitContextSaved: %d", g_fragLimitContextSaved )
LOG( 32, "( team_win_event ) g_isTheLastGameRound: %d", g_isTheLastGameRound )
LOG( 32, "( team_win_event ) g_isTheLastGameRoundContext: %d", g_isTheLastGameRoundContext )
LOG( 32, "( team_win_event ) g_isThePenultGameRound: %d", g_isThePenultGameRound )
LOG( 32, "( team_win_event ) g_isThePenultGameRoundContext: %d", g_isThePenultGameRoundContext )
LOG( 32, "( team_win_event )" )
return 0;
}
public team_win_event()
{
LOG( 128, "" )
LOG( 128, "" )
LOG( 128, "I AM ENTERING ON team_win_event(0)" )
new wins_Terrorist_trigger;
new wins_CT_trigger;
new string_team_winner[ 16 ];
g_isTheRoundEnded = true;
read_logargv( 1, string_team_winner, charsmax( string_team_winner ) );
if( string_team_winner[ 0 ] == 'T' )
{
g_totalTerroristsWins++;
}
else if( string_team_winner[ 0 ] == 'C' )
{
g_totalCtWins++;
}
g_winLimitNumber = get_pcvar_num( cvar_mp_winlimit );
if( g_winLimitNumber )
{
wins_CT_trigger = g_totalCtWins + VOTE_START_ROUNDS;
wins_Terrorist_trigger = g_totalTerroristsWins + VOTE_START_ROUNDS;
if( ( wins_CT_trigger > g_winLimitNumber
|| wins_Terrorist_trigger > g_winLimitNumber )
&& isTimeToStartTheEndOfMapVoting( get_pcvar_num( cvar_endOfMapVote ) ) )
{
START_VOTING_BY_MIDDLE_ROUND_DELAY( "start_voting_by_winlimit" )
}
if( g_totalCtWins > g_winLimitNumber - 2
|| g_totalTerroristsWins > g_winLimitNumber - 2 )
{
try_to_manage_map_end();
}
}
if( g_voteStatus & IS_VOTE_IN_PROGRESS )
{
g_isTheRoundEndWhileVoting = true;
}
LOG( 0, "", debugTeamWinEvent( string_team_winner, wins_CT_trigger, wins_Terrorist_trigger ) )
}
public round_end_event()
{
LOG( 128, "I AM ENTERING ON round_end_event(0)" )
new current_rounds_trigger;
saveTheRoundTime();
g_isTheRoundEnded = true;
g_totalRoundsPlayed++;
// Get the updated value.
g_maxRoundsNumber = get_pcvar_num( cvar_mp_maxrounds );
// Also update their values when calling this function.
g_fragLimitNumber = get_pcvar_num( cvar_mp_fraglimit );
g_timeLimitNumber = get_pcvar_num( cvar_mp_timelimit );
if( g_maxRoundsNumber )
{
current_rounds_trigger = g_totalRoundsPlayed + VOTE_START_ROUNDS;
if( current_rounds_trigger > g_maxRoundsNumber
&& isTimeToStartTheEndOfMapVoting( get_pcvar_num( cvar_endOfMapVote ) ) )
{
START_VOTING_BY_MIDDLE_ROUND_DELAY( "start_voting_by_maxrounds" )
}
if( g_totalRoundsPlayed > g_maxRoundsNumber - 2 )
{
try_to_manage_map_end();
}
}
// If this is called when the voting is going on, it will cause the voting to be cut
// and will force the map to immediately change to the next map on the map cycle.
if( IS_ABLE_TO_PERFORM_A_MAP_CHANGE() )
{
endRoundWatchdog();
}
if( g_voteStatus & IS_VOTE_IN_PROGRESS )
{
g_isTheRoundEndWhileVoting = true;
}
LOG( 32, "( round_end_event ) g_maxRoundsNumber: %d", g_maxRoundsNumber )
LOG( 32, "( round_end_event ) g_totalRoundsPlayed: %d, current_rounds_trigger: %d", \
g_totalRoundsPlayed, current_rounds_trigger )
}
/**
* Called on the round_end_event(). This is the place to change the map when the variables
* `g_isThePenultGameRound` and `g_isTheLastGameRound` are set to true.
*/
stock endRoundWatchdog()
{
LOG( 128, "I AM ENTERING ON endRoundWatchdog(0)" )
new bool:endOfMapVoteExpiration = get_pcvar_num( cvar_endOfMapVoteExpiration ) != 0;
if( endOfMapVoteExpiration
&& g_voteStatus & IS_VOTE_OVER )
{
// Make the map to change immediately.
g_isTheLastGameRound = true;
g_isThePenultGameRound = false;
}
// This is what changes the map on the next round. The last round has ended and the map will change about now.
if( g_isTheLastGameRound )
{
// When time runs out, end map at the current round end.
if( endOfMapVoteExpiration
|| get_pcvar_num( cvar_endOnRoundChange ) == MAP_CHANGES_AT_THE_CURRENT_ROUND_END )
{
try_to_process_last_round();
}
}
else if( g_isThePenultGameRound )
{
// When time runs out, end map at the next round end.
g_isTheLastGameRound = true;
// Set it to false because later we first could try to check this before `g_isTheLastGameRound`
// resulting on an infinity loop.
g_isThePenultGameRound = false;
set_task( 5.0, "configure_last_round_HUD", TASKID_PROCESS_LAST_ROUND );
}
}
stock saveTheRoundTime()
{
LOG( 128, "I AM ENTERING ON saveTheRoundTime(0)" )
new roundTotalTime = floatround( get_gametime(), floatround_ceil ) - g_roundStartTime;
// Rounds taking less than 10 seconds does not seem to fit.
if( roundTotalTime > MIN_ROUND_TIME_DELAY )
{
static lastSavedRound;
static roundPlayedTimes[ MAX_SAVED_ROUNDS_FOR_AVERAGE ];
// To keep the latest round data up to date.
roundPlayedTimes[ lastSavedRound ] = roundTotalTime;
// Increments the counter until 20, and stops it as our array has only 20 slots.
if( g_totalRoundsSavedTimes < sizeof roundPlayedTimes )
{
g_totalRoundsSavedTimes++;
}
// Calculate the rounds average times.
g_roundAverageTime = roundPlayedTimes[ 0 ];
for( new index = 1; index < g_totalRoundsSavedTimes; index++ )
{
g_roundAverageTime = g_roundAverageTime + roundPlayedTimes[ index ];
}
LOG( 32, "( saveTheRoundTime ) Total Sum: %d", g_roundAverageTime )
g_roundAverageTime = ( g_roundAverageTime / g_totalRoundsSavedTimes ) + 1;
// Updates the next position to be inserted.
lastSavedRound = ( lastSavedRound + 1 ) % sizeof roundPlayedTimes;
LOG( 32, "( saveTheRoundTime ) lastSavedRound: %d", lastSavedRound )
LOG( 32, "( saveTheRoundTime ) g_roundAverageTime: %d", g_roundAverageTime )
LOG( 32, "( saveTheRoundTime ) g_totalRoundsSavedTimes: %d", g_totalRoundsSavedTimes )
}
LOG( 32, "( saveTheRoundTime ) roundTotalTime: %d", roundTotalTime )
}
/**
* Called before the freeze time to start counting. This event is not called for the first game round.
*/
public new_round_event()
{
LOG( 128, "I AM ENTERING ON new_round_event(0)" )
tryToStartTheVotingOnThisRound( ROUND_VOTING_START_SECONDS_DELAY() );
if( g_isTheLastGameRound
&& IS_ABLE_TO_PERFORM_A_MAP_CHANGE()
&& get_pcvar_num( cvar_endOnRoundChange ) == MAP_CHANGES_AT_THE_NEXT_ROUND_START )
{
try_to_process_last_round();
}
}
/**
* Called after the freeze time to stop counting.
*/
public round_start_event()
{
LOG( 128, "I AM ENTERING ON round_start_event(0)" )
// Provide the new_round_event(0) support for HLTV non supported game mods.
if( !IS_NEW_ROUND_EVENT_SUPPORTED() )
{
new_round_event();
}
g_isTheRoundEnded = false;
g_roundStartTime = floatround( get_gametime(), floatround_ceil );
if( g_isTimeToResetRounds )
{
g_isTimeToResetRounds = false;
set_task( 1.0, "resetRoundsScores" );
}
if( g_isTimeToResetGame )
{
g_isTimeToResetGame = false;
set_task( 1.0, "map_restoreEndGameCvars" );
}
// Lazy update the game ending context, after the round_start_event(0) to be completed.
g_isTheRoundEndWhileVoting = false;
if( g_isThePenultGameRoundContext && g_isThePenultGameRound )
{
g_isTheLastGameRoundContext = true;
g_isThePenultGameRoundContext = false;
}
}
stock try_to_manage_map_end( bool:isFragLimitEnd = false )
{
LOG( 128, "I AM ENTERING ON try_to_manage_map_end()" )
if( g_isOnMaintenanceMode )
{
prevent_map_change();
color_print( 0, "%L", LANG_PLAYER, "GAL_CHANGE_MAINTENANCE" );
}
else if( !( g_isTheLastGameRound
|| g_isThePenultGameRound ) )
{
// Do not invert the order of these conditional statements, otherwise it will break everything.
if( !ARE_THERE_ENOUGH_PLAYERS_FOR_MANAGE_END()
|| !map_manageEnd() )
{
try_to_process_last_round( isFragLimitEnd );
}
}
}
/**
* This is a fail safe to not allow map changes if must there be a map voting and it was not
* finished/performed yet.
*/
stock try_to_process_last_round( bool:isFragLimitEnd = false )
{
LOG( 128, "I AM ENTERING ON try_to_process_last_round(0)" )
new bool:allowMapChange;
if( g_voteStatus & IS_VOTE_OVER )
{
allowMapChange = true;
}
else
{
if( get_pcvar_num( cvar_endOfMapVote ) )
{
allowMapChange = false;
}
else
{
allowMapChange = true;
}
}
if( allowMapChange )
{
process_last_round( g_isTheLastGameRound || isFragLimitEnd );
}
}
/**
* This must to be called to perform the delayed map start vote, when the variables
* g_isTheLastGameRound/g_isThePenultGameRound are set to true. Therefore, it would set the task
* to start the map voting considering how many seconds from this round already have been played.
*
* Also, at the same time, the vote_manageEnd(0) must to attempt to start the voting in case
* the time to end the map or the round? Here we need to take care of:
*
* 1. If the time will end in 70 seconds, but the map will hold due `cvar_endOnRound`, we do not
* necessary need to start the map voting, as the map may last longer as 200 seconds.
* 2. Moreover, we just to need to take the `g_roundAverageTime` and the `g_roundStartTime`
* and calculate how much time is left on the round. Then pass it to:
* isTimeToStartTheEndOfMapVoting( roundSecondsLeft )
*
* Now, if the round end before the voting to complete, the endRoundWatchdog(0) will not be
* called and the a extra round will be played. But if we are not using the `cvar_endOnRound` feature?
*
* We do not wait to start the map voting, and to start the it right when the remaining time
* reaches the `g_votingSecondsRemaining` time. But as we only periodically check to whether to start
* the map voting each 15 seconds, we must to set the maximum check start as `g_votingSecondsRemaining`
* and the minimum start check as `g_votingSecondsRemaining + 15 seconds + 3`. These values are defined
* by the constants `START_VOTEMAP_MAX_TIME` and `START_VOTEMAP_MIN_TIME`, respectively.
*
* Now the the average round time is shorter than the total voting time, we must to
* start a map voting, otherwise we could get an extra round being played. This case also
* must to be handled by tryToStartTheVotingOnThisRound(1), to start the voting on round before
* the actual last round.
*/
public map_manageEnd()
{
LOG( 128, "I AM ENTERING ON map_manageEnd(0)" )
LOG( 2, "%32s mp_timelimit: %f, get_real_players_number: %d", "map_manageEnd(in)", \
get_pcvar_float( cvar_mp_timelimit ), get_real_players_number() )
switch( get_pcvar_num( cvar_endOnRound ) )
{
// when time runs out, end at the current round end
case END_AT_THE_CURRENT_ROUND_END:
{
g_isTheLastGameRound = true;
}
// when time runs out, end at the next round end
case END_AT_THE_NEXT_ROUND_END:
{
// This is to avoid have a extra round at special mods where time limit is equal the
// round timer.
if( get_pcvar_float( cvar_mp_roundtime ) > 8.0 )
{
g_isTheLastGameRound = true;
}
else
{
g_isThePenultGameRound = true;
}
}
default:
{
LOG( 1, " ( map_manageEnd ) Just returning and blocking the end management." )
return false;
}
}
// This could be or not the last round to be played. If this is the last round, it means the
// feature `cvar_endOnRound` is enabled. Otherwise the voting would have been started when the
// time left get on the minimum time set by `g_totalVoteTime`.
//
// We need to check it after to call prevent_map_change(0), otherwise we will not be able to
// calculate whether to start the voting on this round correctly. The point around here is,
//
// 1. If the voting was not started yet.
// 2. And the mapping is ending.
// 3. The `cvar_endOfMapVoteStart` predicted this is not the correct last round to start voting.
//
saveGameEndingTypeContext();
// These must to be called after saveGameEndingTypeContext(0), otherwise it will invalid its
// required data as `mp_timelimit`, `mp_fraglimit` and etc.
prevent_map_change();
configure_last_round_HUD();
LOG( 2, "%32s mp_timelimit: %f, get_real_players_number: %d", "map_manageEnd(out)", \
get_pcvar_float( cvar_mp_timelimit ), get_real_players_number() )
LOG( 1, " ( map_manageEnd ) Just returning and allowing the end management." )
return true;
}
stock saveGameEndingTypeContext()
{
LOG( 128, "I AM ENTERING ON saveGameEndingTypeContext(0)" )
g_isGameEndingTypeContextSaved = true;
// Save a round time which will force the voting to start immediately by howManyRoundsAreRemaining(2).
g_gameEndingTypeContextSaved = whatGameEndingTypeItIs();
g_timeLeftContextSaved = get_timeleft() ? MIN_ROUND_TIME_DELAY - 1 : 0;
g_timeLimitContextSaved = get_pcvar_float( cvar_mp_timelimit );
g_maxRoundsContextSaved = get_pcvar_num( cvar_mp_maxrounds );
g_winLimitContextSaved = get_pcvar_num( cvar_mp_winlimit );
g_fragLimitContextSaved = get_pcvar_num( cvar_mp_fraglimit );
// We need to save this variables because they are set at the round end before the new round start.
g_isTheLastGameRoundContext = g_isTheLastGameRound;
g_isThePenultGameRoundContext = g_isThePenultGameRound;
}
stock prevent_map_change()
{
LOG( 128, "I AM ENTERING ON prevent_map_change(0)" )
saveEndGameLimits();
// If somehow the cvar_mp_roundtime does not exist, it will point to a cvar within zero
new Float:roundTimeMinutes = get_pcvar_float( cvar_mp_roundtime );
// Prevent the map from ending automatically.
tryToSetGameModCvarFloat( cvar_mp_timelimit, 0.0 );
tryToSetGameModCvarNum( cvar_mp_maxrounds, 0 );
tryToSetGameModCvarNum( cvar_mp_winlimit, 0 );
tryToSetGameModCvarNum( cvar_mp_fraglimit, 0 );
LOG( 2, "( prevent_map_change ) IS CHANGING THE CVAR %-22s to '%f'.", "'mp_timelimit'", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, "( prevent_map_change ) IS CHANGING THE CVAR %-22s to '%d'.", "'mp_fraglimit'", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, "( prevent_map_change ) IS CHANGING THE CVAR %-22s to '%d'.", "'mp_maxrounds'", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, "( prevent_map_change ) IS CHANGING THE CVAR %-22s to '%d'.", "'mp_winlimit'", get_pcvar_num( cvar_mp_winlimit ) )
// Prevent the map from being played indefinitely. We do not need to check here for the
// `g_isThePenultGameRound` because it is being properly handled on endRoundWatchdog(0).
if( g_isTheLastGameRound
|| !( roundTimeMinutes > 0.1 ) )
{
roundTimeMinutes = 9.0;
}
else
{
roundTimeMinutes *= 3.0;
}
cacheCvarsValues();
set_task( roundTimeMinutes * 60, "map_restoreEndGameCvars", TASKID_PREVENT_INFITY_GAME );
}
/**
* To perform the switch between the straight intermission_processing(0) and the last_round_countdown(0).
*
* This is used to be called from the computeVotes(0) end voting function, there to call process_last_round(2)
* with the variable `g_isToChangeMapOnVotingEnd` properly set.
*/
stock process_last_round( bool:isToImmediatelyChangeLevel, bool:isCountDownAllowed = true )
{
LOG( 128, "I AM ENTERING ON process_last_round(2) isToImmediatelyChangeLevel: %d", isToImmediatelyChangeLevel )
// While the `IS_DISABLED_VOTEMAP_EXIT` bit flag is set, we cannot allow any decisions
if( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXIT )
{
// When this is called, there is not anyone else trying to show action menu, therefore
// invoke it before returning.
openTheVoteMapActionMenu();
LOG( 1, " ( process_last_round ) Just returning/blocking, g_voteMapStatus: %d", g_voteMapStatus )
return;
}
if( isToImmediatelyChangeLevel )
{
if( isCountDownAllowed
&& get_pcvar_num( cvar_isEndMapCountdown ) & IS_MAP_MAPCHANGE_COUNTDOWN )
{
// If somehow this is called twice, do nothing on the second time.
if( !task_exists( TASKID_PROCESS_LAST_ROUND_COUNT ) )
{
new totalTime;
new freezeTime;
new nextMapName[ MAX_MAPNAME_LENGHT ];
// IF there is a freeze time, and it is not too much big.
if( ( freezeTime = get_pcvar_num( cvar_mp_freezetime ) )
&& freezeTime < 15 )
{
// If true, this was trigged on the new round.
if( abs( floatround( get_gametime(), floatround_ceil ) - g_roundStartTime ) <= freezeTime + 3 )
{
totalTime = freezeTime;
}
else
{
// On Counter-Strike Condition Zero, 5 seems to be the time between the round end/start events.
totalTime = freezeTime + 5;
}
}
else
{
// If there is not freeze time, just do a countdown from 6.
totalTime = 6;
}
g_lastRoundCountdown = totalTime;
remove_task( TASKID_SHOW_LAST_ROUND_HUD );
set_task( 1.0, "last_round_countdown", TASKID_PROCESS_LAST_ROUND_COUNT, _, _, "a", totalTime );
get_pcvar_string( cvar_amx_nextmap, nextMapName, charsmax( nextMapName ) );
color_print( 0, "%L...", LANG_PLAYER, "DMAP_MAP_CHANGING_IN2", nextMapName, totalTime );
}
}
else
{
remove_task( TASKID_SHOW_LAST_ROUND_HUD );
intermission_processing( isCountDownAllowed );
}
}
else if( g_isTheLastGameRound
|| g_isThePenultGameRound )
{
// To restart the HUD counter to force it to show up, to announce the results.
new showDelay = 8;
g_showLastRoundHudCounter = LAST_ROUND_HUD_SHOW_INTERVAL - showDelay;
set_task( float( showDelay ) + 2, "show_last_round_message", TASKID_SHOW_LAST_ROUND_MESSAGE );
}
}
stock intermission_processing( bool:isCountDownAllowed = true )
{
LOG( 128, "I AM ENTERING ON intermission_processing(0)" )
new Float:mp_chattime = isCountDownAllowed ? get_intermission_chattime() : 0.1;
// Choose how to change the level.
if( g_isTimeToRestart )
{
set_task( mp_chattime, "map_change_stays", TASKID_MAP_CHANGE );
}
else
{
set_task( mp_chattime, "map_change", TASKID_MAP_CHANGE );
}
if( isCountDownAllowed ) show_intermission( mp_chattime );
}
stock Float:get_intermission_chattime()
{
LOG( 128, "I AM ENTERING ON get_intermission_chattime(0)" )
new Float:mp_chattime = get_pcvar_float( cvar_mp_chattime );
// Make sure the `mp_chattime` is long enough.
if( mp_chattime > 12 )
{
mp_chattime = 12.0;
}
else if( mp_chattime < 2.0 )
{
mp_chattime = 2.0;
}
return mp_chattime + 1.0;
}
/**
* Freeze the game and show the scoreboard.
*/
stock show_intermission( Float:mp_chattime )
{
LOG( 128, "I AM ENTERING ON show_intermission(1) mp_chattime: %f", mp_chattime )
new endGameType = get_pcvar_num( cvar_isEndMapCountdown );
if( endGameType )
{
set_task( mp_chattime - 0.5, "intermission_hold", TASKID_INTERMISSION_HOLD );
intermission_effects( endGameType, mp_chattime );
}
else
{
LOG( 4, " ( show_intermission ) Do nothing, just change the map." )
intermission_hold();
}
}
public intermission_hold()
{
LOG( 128, "I AM ENTERING ON intermission_hold(0)" )
message_begin( MSG_ALL, SVC_INTERMISSION );
message_end();
}
stock intermission_effects( endGameType, Float:mp_chattime )
{
LOG( 128, "I AM ENTERING ON intermission_effects(1) endGameType: %d", endGameType )
if( endGameType & IS_MAP_MAPCHANGE_FREEZE_PLAYERS )
{
g_original_sv_maxspeed = get_pcvar_float( cvar_sv_maxspeed );
tryToSetGameModCvarFloat( cvar_sv_maxspeed, 0.0 );
LOG( 2, "( intermission_effects ) IS CHANGING THE CVAR 'sv_maxspeed' to '%f'.", get_pcvar_float( cvar_sv_maxspeed ) )
}
if( cvar_mp_friendlyfire
&& endGameType & IS_MAP_MAPCHANGE_FRIENDLY_FIRE )
{
if( ( g_isToRestoreFriendlyFire = get_pcvar_num( cvar_mp_friendlyfire ) == 0 ) )
{
tryToSetGameModCvarNum( cvar_mp_friendlyfire, 1 );
}
LOG( 2, "( intermission_effects ) IS CHANGING THE CVAR 'mp_friendlyfire' to '%d'.", get_pcvar_num( cvar_mp_friendlyfire ) )
}
if( endGameType & ( IS_MAP_MAPCHANGE_DROP_WEAPONS | IS_MAP_MAPCHANGE_BUY_GRENADES ) )
{
new player_id;
new playersCount;
new players[32];
get_players( players, playersCount, "ah" );
for( --playersCount; playersCount > -1; playersCount-- )
{
player_id = players[ playersCount ];
if( endGameType & IS_MAP_MAPCHANGE_DROP_WEAPONS )
{
strip_user_weapons( player_id );
give_item( player_id, "weapon_knife" );
}
if( endGameType & IS_MAP_MAPCHANGE_BUY_GRENADES )
{
give_item( player_id, "weapon_smokegrenade" );
give_item( player_id, "weapon_flashbang" );
give_item( player_id, "weapon_hegrenade" );
}
}
}
client_cmd( 0, "+showscores" );
// Check also if there is not enough time to play it
if( !( get_pcvar_num( cvar_soundsMute ) & SOUND_MAPCHANGE )
&& mp_chattime > 3.0 )
{
client_cmd( 0, "speak ^"loading environment on to your computer^"" );
}
}
public last_round_countdown()
{
LOG( 128, "I AM ENTERING ON last_round_countdown(0) g_lastRoundCountdown: %d", g_lastRoundCountdown )
new real_number = g_lastRoundCountdown - 1;
if( real_number )
{
// visual countdown
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_CHANGELEVEL_COUNTDOWN ) )
{
new nextMapName[ MAX_MAPNAME_LENGHT ];
get_pcvar_string( cvar_amx_nextmap, nextMapName, charsmax( nextMapName ) );
set_hudmessage( 255, 10, 10, -1.0, 0.13, 0, 1.0, 0.94, 0.0, 0.0, -1 );
show_hudmessage( 0, "%L...", LANG_PLAYER, "DMAP_MAP_CHANGING_IN1", nextMapName, real_number );
}
// audio countdown
if( !( get_pcvar_num( cvar_soundsMute ) & SOUND_COUNTDOWN ) )
{
new word[ 6 ];
num_to_word( real_number, word, 5 );
client_cmd( 0, "spk ^"fvox/%s^"", word );
}
}
// decrement the countdown
g_lastRoundCountdown--;
if( g_lastRoundCountdown == 0 )
{
intermission_processing();
}
}
public configure_last_round_HUD()
{
LOG( 128, "I AM ENTERING ON configure_last_round_HUD(0)" )
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_CHANGELEVEL_ANNOUNCE ) )
{
remove_task( TASKID_SHOW_LAST_ROUND_HUD );
set_task( 7.0, "setup_last_round_HUD", TASKID_SHOW_LAST_ROUND_HUD );
}
show_last_round_message();
}
public setup_last_round_HUD()
{
LOG( 128, "I AM ENTERING ON setup_last_round_HUD(0) g_showLastRoundHudCounter: %d", g_showLastRoundHudCounter )
g_showLastRoundHudCounter = 0;
set_task( 1.0, "show_last_round_HUD", TASKID_SHOW_LAST_ROUND_HUD, _, _, "b" );
}
public show_last_round_message()
{
LOG( 128, "I AM ENTERING ON show_last_round_message(0)" )
new nextMapName[ MAX_MAPNAME_LENGHT ];
get_pcvar_string( cvar_amx_nextmap, nextMapName, charsmax( nextMapName ) );
if( g_voteStatus & IS_VOTE_OVER )
{
if( g_isTheLastGameRound
&& !g_isToChangeMapOnVotingEnd )
{
color_print( 0, "%L %L",
LANG_PLAYER, "GAL_CHANGE_NEXTROUND",
LANG_PLAYER, "GAL_NEXTMAP2", nextMapName );
}
else if( g_isThePenultGameRound )
{
color_print( 0, "%L %L", LANG_PLAYER, "GAL_CHANGE_TIMEEXPIRED2", LANG_PLAYER, "GAL_NEXTMAP2", nextMapName );
}
}
else if( g_isTheLastGameRound
|| g_isThePenultGameRound )
{
color_print( 0, "%L", LANG_PLAYER, "GAL_CHANGE_TIMEEXPIRED2" );
}
}
public show_last_round_HUD()
{
LOG( 256, "I AM ENTERING ON show_last_round_HUD(0)" )
if( ++g_showLastRoundHudCounter % LAST_ROUND_HUD_SHOW_INTERVAL > 6
|| g_isTheRoundEnded )
{
return;
}
// On the Amx Mod X 1.8.2 is not recognizing the player LANG_PLAYER when it is formatted before
// with formatex(...)
//
// formatex( last_round_message, charsmax( last_round_message ), "%L^n%L",
// player_id, "GAL_CHANGE_NEXTROUND", player_id, "GAL_NEXTMAP2", nextMapName );
// REMOVE_CODE_COLOR_TAGS( last_round_message )
// show_hudmessage( player_id, last_round_message );
//
set_hudmessage( 255, 255, 255, 0.15, 0.15, 0, 0.0, 1.0, 0.1, 0.1, 1 );
new nextMapName[ MAX_MAPNAME_LENGHT ];
get_pcvar_string( cvar_amx_nextmap, nextMapName, charsmax( nextMapName ) );
if( g_voteStatus & IS_VOTE_OVER )
{
if( g_isTheLastGameRound
&& !g_isToChangeMapOnVotingEnd )
{
show_hudmessage( 0, "%L^n%L", LANG_PLAYER, "GAL_CHANGE_NEXTROUND", LANG_PLAYER, "GAL_NEXTMAP1", nextMapName );
}
else if( g_isTheLastGameRound
|| g_isThePenultGameRound )
{
show_hudmessage( 0, "%L^n%L", LANG_PLAYER, "GAL_CHANGE_TIMEEXPIRED1", LANG_PLAYER, "GAL_NEXTMAP1", nextMapName );
}
}
else if( g_isTheLastGameRound
|| g_isThePenultGameRound )
{
show_hudmessage( 0, "%L", LANG_PLAYER, "GAL_CHANGE_TIMEEXPIRED1" );
}
}
/**
* Return whether the gaming is on going.
*/
stock is_there_game_commencing()
{
LOG( 128, "I AM ENTERING ON is_there_game_commencing(0)" )
new players[ 32 ];
new players_count;
get_players( players, players_count );
new CT_count = 0;
new TR_count = 0;
for( new player_index = 0; player_index < players_count; player_index++ )
{
switch( get_user_team( players[ player_index ] ) )
{
case 1:
{
TR_count++; // terror
}
case 2:
{
CT_count++; // ct
}
}
if( CT_count && TR_count )
{
LOG( 1, " ( is_there_game_commencing ) Returning true." )
return true;
}
}
LOG( 1, " ( is_there_game_commencing ) Returning false." )
return false;
}
/**
* Enable a new voting, as a new game is starting
*/
stock enableNewVoting()
{
LOG( 128, "I AM ENTERING ON enableNewVoting(0)" )
g_voteStatus &= ~IS_VOTE_OVER;
g_voteStatus &= ~IS_EARLY_VOTE;
g_voteStatus &= ~IS_VOTE_EXPIRED;
}
/**
* Reset rounds scores every game restart event. This relies on that the 'game_commencing_event()'
* is not triggered by the 'round_restart_event()'. This use 'is_there_game_commencing()' to determine
* if it must restore the time limit by calling 'game_commencing_event()', when there is none game
* on going, to avoid the infinity time limit due the allow last round finish feature.
*/
public round_restart_event()
{
LOG( 128, "I AM ENTERING ON round_restart_event(0)" )
enableNewVoting();
if( g_isEndGameLimitsChanged
&& is_there_game_commencing()
&& ( ( get_pcvar_num( cvar_mp_timelimit )
&& get_pcvar_num( cvar_serverTimeLimitRestart ) )
|| ( get_pcvar_num( cvar_mp_maxrounds )
&& get_pcvar_num( cvar_serverMaxroundsRestart ) )
|| ( get_pcvar_num( cvar_mp_winlimit )
&& get_pcvar_num( cvar_serverWinlimitRestart ) )
|| ( get_pcvar_num( cvar_mp_fraglimit )
&& get_pcvar_num( cvar_serverFraglimitRestart ) ) ) )
{
g_isTimeToResetRounds = true;
cancelVoting( true );
}
else
{
game_commencing_event();
}
}
/**
* Make sure the reset time is the original time limit.
*/
public game_commencing_event()
{
LOG( 128, "I AM ENTERING ON game_commencing_event(0)" )
#if DEBUG_LEVEL & ( DEBUG_LEVEL_UNIT_TEST_NORMAL | DEBUG_LEVEL_MANUAL_TEST_START | DEBUG_LEVEL_UNIT_TEST_DELAYED )
if( get_gametime() < 100
&& ( get_playersnum( 1 )
|| g_test_areTheUnitTestsRunning ) )
{
LOG( 1, "^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n" )
LOG( 1, "There are players on the server!" )
LOG( 1, "^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n^n" )
}
else
{
g_isTimeToResetGame = true;
g_isTimeToResetRounds = true;
}
#else
g_isTimeToResetGame = true;
g_isTimeToResetRounds = true;
#endif
enableNewVoting();
cancelVoting( true );
}
/**
* Reset the round ending, if it is in progress.
*/
stock resetRoundEnding()
{
LOG( 128, "I AM ENTERING ON resetRoundEnding(0)" )
// Each one of these entries must to be saved on saveRoundEnding(1) and restored at restoreRoundEnding(1).
g_isTheLastGameRound = false;
g_isTimeToRestart = false;
g_isThePenultGameRound = false;
g_isTheRoundEndWhileVoting = false;
g_isToChangeMapOnVotingEnd = false;
remove_task( TASKID_SHOW_LAST_ROUND_HUD );
client_cmd( 0, "-showscores" );
}
stock saveRoundEnding( bool:roundEndStatus[ SaveRoundEnding ] )
{
LOG( 128, "I AM ENTERING ON saveRoundEnding(1)" )
LOG( 32, "( saveRoundEnding ) roundEndStatus[0]: %d", roundEndStatus[ SaveRoundEnding_LastRound ] )
LOG( 32, "( saveRoundEnding ) roundEndStatus[1]: %d", roundEndStatus[ SaveRoundEnding_RestartTime ] )
LOG( 32, "( saveRoundEnding ) roundEndStatus[2]: %d", roundEndStatus[ SaveRoundEnding_PenultRound ] )
roundEndStatus[ SaveRoundEnding_LastRound ] = g_isTheLastGameRound;
roundEndStatus[ SaveRoundEnding_RestartTime ] = g_isTimeToRestart;
roundEndStatus[ SaveRoundEnding_PenultRound ] = g_isThePenultGameRound;
roundEndStatus[ SaveRoundEnding_VotingEnd ] = g_isToChangeMapOnVotingEnd;
roundEndStatus[ SaveRoundEnding_WhileVoting ] = g_isTheRoundEndWhileVoting;
}
stock restoreRoundEnding( bool:roundEndStatus[ SaveRoundEnding ] )
{
LOG( 128, "I AM ENTERING ON restoreRoundEnding(1)" )
LOG( 32, "( restoreRoundEnding ) roundEndStatus[0]: %d", roundEndStatus[ SaveRoundEnding_LastRound ] )
LOG( 32, "( restoreRoundEnding ) roundEndStatus[1]: %d", roundEndStatus[ SaveRoundEnding_RestartTime ] )
LOG( 32, "( restoreRoundEnding ) roundEndStatus[2]: %d", roundEndStatus[ SaveRoundEnding_PenultRound ] )
g_isTheLastGameRound = bool:roundEndStatus[ SaveRoundEnding_LastRound ];
g_isTimeToRestart = bool:roundEndStatus[ SaveRoundEnding_RestartTime ];
g_isThePenultGameRound = bool:roundEndStatus[ SaveRoundEnding_PenultRound ];
g_isToChangeMapOnVotingEnd = bool:roundEndStatus[ SaveRoundEnding_VotingEnd ];
g_isTheRoundEndWhileVoting = bool:roundEndStatus[ SaveRoundEnding_WhileVoting ];
}
/**
* Try to set the new game limits as specified by the cvars 'gal_srv_..._restart' feature. This
* macro requires the integer variable 'serverLimiterValue' defined before the use of this macro.
*
* @param limiterCvarPointer the 'gal_srv_..._restart' pointer
* @param serverCvarPointer the game cvar pointer as 'cvar_mp_timelimit'.
* @param limiterOffset the current game limit as an integer. Example: 'map_getMinutesElapsedInteger(0)'.
*/
#define CALCULATE_NEW_GAME_LIMIT(%1,%2,%3) \
{ \
serverLimiterValue = get_pcvar_num( %1 ); \
if( serverLimiterValue ) \
{ \
new serverCvarValue = get_pcvar_num( %2 ); \
if( serverCvarValue ) \
{ \
serverCvarValue = serverCvarValue - %3 + serverLimiterValue - 1; \
if( serverCvarValue > 0 ) \
{ \
saveEndGameLimits(); \
tryToSetGameModCvarNum( %2, serverCvarValue ); \
} \
} \
} \
}
public resetRoundsScores()
{
LOG( 128 + 2, "I AM ENTERING ON resetRoundsScores(0)" )
LOG( 2, "( resetRoundsScores ) TRYING to change the cvar %15s from '%f'.", "'mp_timelimit'", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, "( resetRoundsScores ) TRYING to change the cvar %15s from '%d'.", "'mp_fraglimit'", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, "( resetRoundsScores ) TRYING to change the cvar %15s from '%d'.", "'mp_maxrounds'", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, "( resetRoundsScores ) TRYING to change the cvar %15s from '%d'.", "'mp_winlimit'", get_pcvar_num( cvar_mp_winlimit ) )
new serverLimiterValue;
CALCULATE_NEW_GAME_LIMIT( cvar_serverTimeLimitRestart, cvar_mp_timelimit, map_getMinutesElapsedInteger() )
CALCULATE_NEW_GAME_LIMIT( cvar_serverWinlimitRestart , cvar_mp_winlimit , max( g_totalTerroristsWins, g_totalCtWins ) )
CALCULATE_NEW_GAME_LIMIT( cvar_serverMaxroundsRestart, cvar_mp_maxrounds, g_totalRoundsPlayed )
CALCULATE_NEW_GAME_LIMIT( cvar_serverFraglimitRestart, cvar_mp_fraglimit, g_greatestKillerFrags )
// Reset the plugin internal limiter counters.
g_totalTerroristsWins = 0;
g_totalCtWins = 0;
g_totalRoundsPlayed = -1;
g_greatestKillerFrags = 0;
LOG( 2, "( resetRoundsScores ) CHECKOUT the cvar %-25s is '%f'.", "'mp_timelimit'", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, "( resetRoundsScores ) CHECKOUT the cvar %-25s is '%d'.", "'mp_fraglimit'", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, "( resetRoundsScores ) CHECKOUT the cvar %-25s is '%d'.", "'mp_maxrounds'", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, "( resetRoundsScores ) CHECKOUT the cvar %-25s is '%d'.", "'mp_winlimit'", get_pcvar_num( cvar_mp_winlimit ) )
LOG( 1, " I AM EXITING ON resetRoundsScores(0)" )
}
stock map_populateList( Array:mapArray=Invalid_Array, mapFilePath[],
Trie:fillerMapTrie=Invalid_Trie, bool:isToClearTheTrie=true,
bool:isToLoadDuplicatedMaps=true )
{
LOG( 128, "I AM ENTERING ON map_populateList(5) mapFilePath: %s", mapFilePath )
// load the array with maps
new mapCount;
// If there is a map file to load
if( mapFilePath[ 0 ] )
{
if( file_exists( mapFilePath )
|| mapFilePath[ 0 ] == MAP_CYCLE_LOAD_FLAG
|| mapFilePath[ 0 ] == MAP_FOLDER_LOAD_FLAG )
{
new Trie:duplicatedMaps;
new bool:isMapFolderLoad;
if( !isToLoadDuplicatedMaps ) duplicatedMaps = TrieCreate();
isMapFolderLoad = ( mapFilePath[ 0 ] == MAP_FOLDER_LOAD_FLAG );
// clear the map array in case we're reusing it
TRY_TO_APPLY( ArrayClear, mapArray )
// Not always we want to discard the loaded maps
if( isToClearTheTrie ) TRY_TO_APPLY( TrieClear, fillerMapTrie )
if( !isMapFolderLoad
&& mapFilePath[ 0 ] != MAP_CYCLE_LOAD_FLAG )
{
LOG( 4, "" )
LOG( 4, " map_populateList(...) Loading the PASSED FILE! mapFilePath: %s", mapFilePath )
mapCount = loadMapFileList( mapArray, mapFilePath, fillerMapTrie, duplicatedMaps );
}
else if( isMapFolderLoad )
{
LOG( 4, "" )
LOG( 4, " map_populateList(...) Loading the MAP FOLDER! mapFilePath: %s", mapFilePath )
mapCount = loadMapsFolderDirectory( mapArray, fillerMapTrie );
}
else
{
get_cvar_string( "mapcyclefile", mapFilePath, MAX_FILE_PATH_LENGHT - 1 );
LOG( 4, "" )
LOG( 4, " map_populateList(...) Loading the MAPCYCLE! mapFilePath: %s", mapFilePath )
mapCount = loadMapFileList( mapArray, mapFilePath, fillerMapTrie, duplicatedMaps );
}
TRY_TO_APPLY( TrieDestroy, duplicatedMaps )
}
else
{
doAmxxLog( "ERROR, map_populateList: Could not open the file ^"%s^"", mapFilePath );
}
}
LOG( 1, " I AM EXITING map_populateList(4) mapCount: %d", mapCount )
return mapCount;
}
stock checkIfThereEnoughMapPopulated( mapCount, mapFileDescriptor, mapFilePath[] )
{
LOG( 128, "I AM ENTERING ON checkIfThereEnoughMapPopulated(2) mapCount: %d", mapCount )
if( mapCount < 2 )
{
new parsedLines;
new writePosition;
new readLines [ MAX_BIG_BOSS_STRING ];
new loadedMapName[ MAX_MAPNAME_LENGHT ];
fseek( mapFileDescriptor, SEEK_SET, 0 );
while( !feof( mapFileDescriptor )
&& parsedLines < 11 )
{
parsedLines++;
fgets( mapFileDescriptor, loadedMapName, charsmax( loadedMapName ) );
trim( loadedMapName );
if( writePosition < charsmax( readLines ) )
{
writePosition += copy( readLines[ writePosition ], charsmax( readLines ) - writePosition, loadedMapName );
}
}
LOG( 1, "( checkIfThereEnoughMapPopulated ) Not valid/enough(%d) maps found in: %s", mapCount, mapFilePath )
LOG( 1, "( checkIfThereEnoughMapPopulated ) ERROR %d, readLines: %s^n", AMX_ERR_NOTFOUND, readLines )
log_error( AMX_ERR_NOTFOUND, "Not valid/enough(%d) maps found in: %s", mapCount, mapFilePath );
log_error( AMX_ERR_NOTFOUND, "readLines: %s^n", readLines );
}
}
stock loadMapFileList( Array:mapArray, mapFilePath[], Trie:fillerMapTrie, Trie:duplicatedMaps )
{
LOG( 128, "I AM ENTERING ON loadMapFileList(4) mapFilePath: %s", mapFilePath )
new mapCount;
new mapFileDescriptor = fopen( mapFilePath, "rt" );
if( mapFileDescriptor )
{
// Removing the if's from the loop to improve speed
if( mapArray
&& fillerMapTrie )
{
mapCount = loadMapFileListComplete( mapFileDescriptor, mapArray, fillerMapTrie, duplicatedMaps );
}
else if( mapArray )
{
mapCount = loadMapFileListArray( mapFileDescriptor, mapArray, duplicatedMaps );
}
else if( fillerMapTrie )
{
mapCount = loadMapFileListTrie( mapFileDescriptor, fillerMapTrie, duplicatedMaps );
}
else
{
LOG( 1, "( loadMapFileList ) An invalid map descriptors %d/%d!^n", mapArray, fillerMapTrie )
log_error( AMX_ERR_PARAMS, "loadMapFileList: An invalid map descriptor %d/%d!^n", mapArray, fillerMapTrie );
}
checkIfThereEnoughMapPopulated( mapCount, mapFileDescriptor, mapFilePath );
fclose( mapFileDescriptor );
LOG( 4, "" )
}
else
{
LOG( 1, "( loadMapFileList ) ERROR %d, %L", AMX_ERR_NOTFOUND, LANG_SERVER, "GAL_MAPS_FILEMISSING", mapFilePath )
log_error( AMX_ERR_NOTFOUND, "%L", LANG_SERVER, "GAL_MAPS_FILEMISSING", mapFilePath );
}
return mapCount;
}
stock loadMapFileListComplete( mapFileDescriptor, Array:mapArray, Trie:fillerMapTrie, Trie:duplicatedMaps )
{
LOG( 128, "I AM ENTERING ON loadMapFileListComplete(2) mapFileDescriptor: %d", mapFileDescriptor )
new mapCount;
new loadedMapLine[ MAX_MAPNAME_LENGHT ];
new loadedMapName[ MAX_MAPNAME_LENGHT ];
if( duplicatedMaps )
{
while( !feof( mapFileDescriptor ) )
{
fgets( mapFileDescriptor, loadedMapLine, charsmax( loadedMapLine ) );
trim( loadedMapLine );
if( IS_IT_A_VALID_MAP_LINE( loadedMapLine ) )
{
GET_MAP_NAME_LEFT( loadedMapLine, loadedMapName )
if( IS_MAP_VALID( loadedMapName ) )
{
strtolower( loadedMapName );
if( !TrieKeyExists( duplicatedMaps, loadedMapName ) )
{
TrieSetCell( duplicatedMaps, loadedMapName, mapCount );
TrieSetCell( fillerMapTrie, loadedMapName, mapCount );
ArrayPushString( mapArray, loadedMapLine );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapLine ) )
++mapCount;
}
}
}
}
}
else
{
while( !feof( mapFileDescriptor ) )
{
fgets( mapFileDescriptor, loadedMapLine, charsmax( loadedMapLine ) );
trim( loadedMapLine );
if( IS_IT_A_VALID_MAP_LINE( loadedMapLine ) )
{
GET_MAP_NAME_LEFT( loadedMapLine, loadedMapName )
if( IS_MAP_VALID( loadedMapName ) )
{
strtolower( loadedMapName );
TrieSetCell( fillerMapTrie, loadedMapName, mapCount );
ArrayPushString( mapArray, loadedMapLine );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapLine ) )
++mapCount;
}
}
}
}
return mapCount;
}
stock loadMapFileListArray( mapFileDescriptor, Array:mapArray, Trie:duplicatedMaps )
{
LOG( 128, "I AM ENTERING ON loadMapFileListArray(2) mapFileDescriptor: %d", mapFileDescriptor )
new mapCount;
new loadedMapName[ MAX_MAPNAME_LENGHT ];
new loadedMapLine[ MAX_MAPNAME_LENGHT ];
if( duplicatedMaps )
{
while( !feof( mapFileDescriptor ) )
{
fgets( mapFileDescriptor, loadedMapLine, charsmax( loadedMapLine ) );
trim( loadedMapLine );
if( IS_IT_A_VALID_MAP_LINE( loadedMapLine ) )
{
GET_MAP_NAME_LEFT( loadedMapLine, loadedMapName )
if( IS_MAP_VALID( loadedMapName ) )
{
strtolower( loadedMapName );
if( !TrieKeyExists( duplicatedMaps, loadedMapName ) )
{
ArrayPushString( mapArray, loadedMapLine );
TrieSetCell( duplicatedMaps, loadedMapName, mapCount );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapLine ) )
++mapCount;
}
}
}
}
}
else
{
while( !feof( mapFileDescriptor ) )
{
fgets( mapFileDescriptor, loadedMapLine, charsmax( loadedMapLine ) );
trim( loadedMapLine );
if( IS_IT_A_VALID_MAP_LINE( loadedMapLine ) )
{
GET_MAP_NAME_LEFT( loadedMapLine, loadedMapName )
if( IS_MAP_VALID( loadedMapName ) )
{
ArrayPushString( mapArray, loadedMapLine );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapLine ) )
++mapCount;
}
}
}
}
return mapCount;
}
stock loadMapFileListTrie( mapFileDescriptor, Trie:fillerMapTrie, Trie:duplicatedMaps )
{
LOG( 128, "I AM ENTERING ON loadMapFileListTrie(2) mapFileDescriptor: %d", mapFileDescriptor )
new mapCount;
new loadedMapName[ MAX_MAPNAME_LENGHT ];
new loadedMapLine[ MAX_MAPNAME_LENGHT ];
if( duplicatedMaps )
{
while( !feof( mapFileDescriptor ) )
{
fgets( mapFileDescriptor, loadedMapLine, charsmax( loadedMapLine ) );
trim( loadedMapLine );
if( IS_IT_A_VALID_MAP_LINE( loadedMapLine ) )
{
GET_MAP_NAME_LEFT( loadedMapLine, loadedMapName )
if( IS_MAP_VALID( loadedMapName ) )
{
strtolower( loadedMapName );
if( !TrieKeyExists( duplicatedMaps, loadedMapName ) )
{
TrieSetCell( duplicatedMaps, loadedMapName, mapCount );
TrieSetCell( fillerMapTrie, loadedMapName, mapCount );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapLine ) )
++mapCount;
}
}
}
}
}
else
{
while( !feof( mapFileDescriptor ) )
{
fgets( mapFileDescriptor, loadedMapLine, charsmax( loadedMapLine ) );
trim( loadedMapLine );
if( IS_IT_A_VALID_MAP_LINE( loadedMapLine ) )
{
GET_MAP_NAME_LEFT( loadedMapLine, loadedMapName )
if( IS_MAP_VALID( loadedMapName ) )
{
strtolower( loadedMapName );
TrieSetCell( fillerMapTrie, loadedMapName, mapCount );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapLine ) )
++mapCount;
}
}
}
}
return mapCount;
}
stock loadMapsFolderDirectory( Array:mapArray, Trie:fillerMapTrie )
{
LOG( 128, "I AM ENTERING ON loadMapsFolderDirectory(2) Array:mapArray: %d", mapArray )
new mapCount;
new directoryDescriptor;
new parentDirectorUnused[ 5 ];
directoryDescriptor = open_dir( "maps", parentDirectorUnused, charsmax( parentDirectorUnused ) );
// Removing the if's from the loop to improve speed
if( directoryDescriptor )
{
if( mapArray
&& fillerMapTrie )
{
mapCount = loadMapsFolderDirectoryComplete( directoryDescriptor, mapArray, fillerMapTrie );
}
else if( mapArray )
{
mapCount = loadMapsFolderDirectoryArray( directoryDescriptor, mapArray );
}
else if( fillerMapTrie )
{
mapCount = loadMapsFolderDirectoryTrie( directoryDescriptor, fillerMapTrie );
}
else
{
LOG( 1, "( loadMapsFolderDirectory ) An invalid map descriptors %d/%d!^n", mapArray, fillerMapTrie )
log_error( AMX_ERR_PARAMS, "loadMapsFolderDirectory: An invalid map descriptor %d/%d!^n", mapArray, fillerMapTrie );
}
close_dir( directoryDescriptor );
}
else
{
// directory not found, wtf?
LOG( 1, "( loadMapsFolderDirectory ) ERROR %d, %L", AMX_ERR_NOTFOUND, LANG_SERVER, "GAL_MAPS_FOLDERMISSING" )
log_error( AMX_ERR_NOTFOUND, "%L", LANG_SERVER, "GAL_MAPS_FOLDERMISSING" );
}
return mapCount;
}
stock loadMapsFolderDirectoryComplete( directoryDescriptor, Array:mapArray, Trie:fillerMapTrie )
{
LOG( 128, "I AM ENTERING ON loadMapsFolderDirectoryComplete(3) directoryDescriptor: %d", directoryDescriptor )
new mapCount;
new mapNameLength;
new loadedMapName[ MAX_MAPNAME_LENGHT ];
while( next_file( directoryDescriptor, loadedMapName, charsmax( loadedMapName ) ) )
{
mapNameLength = strlen( loadedMapName );
if( mapNameLength > 4
&& equali( loadedMapName[ mapNameLength - 4 ], ".bsp", 4 ) )
{
loadedMapName[ mapNameLength - 4 ] = '^0';
if( IS_MAP_VALID( loadedMapName ) )
{
ArrayPushString( mapArray, loadedMapName );
strtolower( loadedMapName );
TrieSetCell( fillerMapTrie, loadedMapName, mapCount );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapName ) )
++mapCount;
}
}
}
return mapCount;
}
stock loadMapsFolderDirectoryArray( directoryDescriptor, Array:mapArray )
{
LOG( 128, "I AM ENTERING ON loadMapsFolderDirectoryArray(2) directoryDescriptor: %d", directoryDescriptor )
new mapCount;
new mapNameLength;
new loadedMapName[ MAX_MAPNAME_LENGHT ];
while( next_file( directoryDescriptor, loadedMapName, charsmax( loadedMapName ) ) )
{
mapNameLength = strlen( loadedMapName );
if( mapNameLength > 4
&& equali( loadedMapName[ mapNameLength - 4 ], ".bsp", 4 ) )
{
loadedMapName[ mapNameLength - 4 ] = '^0';
if( IS_MAP_VALID( loadedMapName ) )
{
ArrayPushString( mapArray, loadedMapName );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapName ) )
++mapCount;
}
}
}
return mapCount;
}
stock loadMapsFolderDirectoryTrie( directoryDescriptor, Trie:fillerMapTrie )
{
LOG( 128, "I AM ENTERING ON loadMapsFolderDirectoryTrie(2) directoryDescriptor: %d", directoryDescriptor )
new mapCount;
new mapNameLength;
new loadedMapName[ MAX_MAPNAME_LENGHT ];
while( next_file( directoryDescriptor, loadedMapName, charsmax( loadedMapName ) ) )
{
mapNameLength = strlen( loadedMapName );
if( mapNameLength > 4
&& equali( loadedMapName[ mapNameLength - 4 ], ".bsp", 4 ) )
{
loadedMapName[ mapNameLength - 4 ] = '^0';
if( IS_MAP_VALID( loadedMapName ) )
{
strtolower( loadedMapName );
TrieSetCell( fillerMapTrie, loadedMapName, mapCount );
LOG( 0, "", printUntilTheNthLoadedMap( mapCount, loadedMapName ) )
++mapCount;
}
}
}
return mapCount;
}
public loadNominationList( nomMapFilePath[] )
{
LOG( 128, "I AM ENTERING ON loadNominationList(1)" )
TRY_TO_CLEAN( ArrayClear, g_nominatedMapsArray , ArrayCreate() )
TRY_TO_CLEAN( ArrayClear, g_nominationLoadedMapsArray, ArrayCreate( MAX_MAPNAME_LENGHT ) )
TRY_TO_CLEAN( TrieClear, g_reverseSearchNominationsTrie, TrieCreate() )
TRY_TO_CLEAN( TrieClear, g_forwardSearchNominationsTrie, TrieCreate() )
TRY_TO_CLEAN( TrieClear, g_nominationLoadedMapsTrie , TrieCreate() )
get_pcvar_string( cvar_nomMapFilePath, nomMapFilePath, MAX_FILE_PATH_LENGHT - 1 );
LOG( 4, "( loadNominationList ) cvar_nomMapFilePath: %s", nomMapFilePath )
map_populateList( g_nominationLoadedMapsArray, nomMapFilePath, g_nominationLoadedMapsTrie );
LOG( 1, " ( loadNominationList ) loadedCount: %d", ArraySize( g_nominationLoadedMapsArray ) )
LOG( 1, " ( loadNominationList ) g_nominationLoadedMapsArray: %d", g_nominationLoadedMapsArray )
}
/**
* Currently the empty cycle behavior is based on the function map_getNext(4). Therefore this is a
* problem because it will create infinity empty cycle loops between duplicated maps on the empty
* map cycle.
*
* So here I have to options:
* a) change all the empty cycle algorithm
* b) do not load duplicated maps.
*
* As anyone until now complained about this behavior, I choose the former as easier to implement.
*/
stock map_loadEmptyCycleList( emptyCycleFilePath[] )
{
LOG( 128, "I AM ENTERING ON map_loadEmptyCycleList(1)" )
TRY_TO_CLEAN( ArrayClear, g_emptyCycleMapsArray, ArrayCreate( MAX_MAPNAME_LENGHT ) )
get_pcvar_string( cvar_emptyMapFilePath, emptyCycleFilePath, MAX_FILE_PATH_LENGHT - 1 );
map_populateList( g_emptyCycleMapsArray, emptyCycleFilePath, .isToLoadDuplicatedMaps=false );
LOG( 4, "( map_loadEmptyCycleList ) loadedCount: %d", ArraySize( g_emptyCycleMapsArray ) )
}
public map_loadPrefixList( prefixesFilePath[] )
{
LOG( 128, "I AM ENTERING ON map_loadPrefixList(1) g_mapPrefixCount: %d", g_mapPrefixCount )
new prefixesFile;
// To clear old values in case of reloading.
g_mapPrefixCount = 0;
formatex( prefixesFilePath, MAX_FILE_PATH_LENGHT - 1, "%s/prefixes.ini", g_configsDirPath );
prefixesFile = fopen( prefixesFilePath, "rt" );
if( prefixesFile )
{
new loadedMapPrefix[ MAX_PREFIX_SIZE ];
while( !feof( prefixesFile ) )
{
fgets( prefixesFile, loadedMapPrefix, charsmax( loadedMapPrefix ) );
trim( loadedMapPrefix );
if( loadedMapPrefix[ 0 ]
&& loadedMapPrefix[ 0 ] != ';'
&& !equal( loadedMapPrefix, "//", 2 ) )
{
if( g_mapPrefixCount < MAX_PREFIX_COUNT )
{
copy( g_mapPrefixes[ g_mapPrefixCount++ ], charsmax( loadedMapPrefix ), loadedMapPrefix );
}
else
{
LOG( 1, "AMX_ERR_BOUNDS, %L", LANG_SERVER, "GAL_PREFIXES_TOOMANY", MAX_PREFIX_COUNT, prefixesFilePath )
log_error( AMX_ERR_BOUNDS, "%L", LANG_SERVER, "GAL_PREFIXES_TOOMANY", MAX_PREFIX_COUNT, prefixesFilePath );
break;
}
}
}
fclose( prefixesFile );
}
else
{
LOG( 1, "AMX_ERR_NOTFOUND, %L", LANG_SERVER, "GAL_PREFIXES_NOTFOUND", prefixesFilePath )
log_error( AMX_ERR_NOTFOUND, "%L", LANG_SERVER, "GAL_PREFIXES_NOTFOUND", prefixesFilePath );
}
LOG( 1, " ( map_loadPrefixList ) g_mapPrefixCount: %d", g_mapPrefixCount )
}
/**
* By default, the Whitelist understand a period as 5-5 being 05:00:00 until 05:59:59, and a period
* as 5-6 being 05:00:00 until 06:59:59.
*
* 0 - To convert 5-5 to 05:00:00 until 05:59:59.
* 1 - I want to 5-5 to be all day long.
*
* To disable this feature, set this cvar to 0
*
* When the Whitelist ` gal_whitelist_hourly_set` says [0-0] it means it will allow them from 00:00:00 until 00:59:59
* When the Whitelist `!gal_whitelist_hourly_set` says [0-0] it means it will block them from 00:00:00 until 23:59:59
*
* As we may notice, the current whitelist feature is full featured and the `gal_whitelist_hourly_set` is obsolete.
* To force the Whitelist to block them from 00:00:00 until 23:59:59, we need to set it as {0-0}.
*
* For the `gal_whitelist_block_out` option 0 - Allow all maps outside the Whitelist rule.
*
* To block, we need to load them.
* To allow, we cannot to load them.
*
* How do I block a map?
* If I load the map from the file to the `Trie`, I will block it on that hour and allow all the others loaded.
*
* For the `gal_whitelist_block_out` option 1 - Block all maps outside the Whitelist rule.
*
* To block, we cannot to load them.
* To allow, we need to load them.
*
* How do I block a map?
* If I load the map from the file to the `Trie`, I will allow it on that hour and block all the others not loaded.
*
* @note the hours parameters `startHour` and `endHour` must to be already normalized by standardizeTheHoursForWhitelist(3).
*/
stock bool:isToLoadNextWhiteListGroupOpen( currentHour, startHour, endHour, bool:isBlackList = false )
{
LOG( 256, "I AM ENTERING ON isToLoadNextWhiteListGroupOpen(4) currentHour: %d", currentHour )
LOG( 256, "( isToLoadNextWhiteListGroupOpen ) startHour: %d, endHour: %d", startHour, endHour )
new bool:isToLoadTheseMaps;
// Here handle all the cases when the start hour is equals to the end hour.
if( startHour == endHour )
{
LOG( 8, "( isToLoadNextWhiteListGroupOpen ) startHour == endHour: %d", startHour )
// Manual fix needed to convert 5-5 to 05:00:00 until 05:59:59, instead of all day long.
if( endHour == currentHour )
{
isToLoadTheseMaps = isBlackList;
}
else
{
isToLoadTheseMaps = !isBlackList;
}
}
else
{
isToLoadTheseMaps = isToLoadNextWhiteListEndProcess( currentHour, startHour, endHour, isBlackList );
}
LOG( 0, "", debugIsToLoadNextWhiteListGroup( currentHour, startHour, endHour, isToLoadTheseMaps ) )
return isToLoadTheseMaps;
}
stock bool:isToLoadNextWhiteListGroupClose( currentHour, startHour, endHour, bool:isBlackList = false )
{
LOG( 256, "I AM ENTERING ON isToLoadNextWhiteListGroupClose(4) currentHour: %d", currentHour )
LOG( 256, "( isToLoadNextWhiteListGroupClose ) startHour: %d, endHour: %d", startHour, endHour )
new bool:isToLoadTheseMaps;
new bool:isDecreased = false;
if( startHour != endHour )
{
isDecreased = true;
if( endHour == 0 )
{
endHour = 23;
}
else
{
endHour -= 1;
}
}
LOG( 256, "( isToLoadNextWhiteListGroupClose ) startHour: %d, endHour: %d", startHour, endHour )
// Here handle all the cases when the start hour is equals to the end hour.
if( startHour == endHour )
{
LOG( 8, "( isToLoadNextWhiteListGroupClose ) startHour == endHour: %d", startHour )
// Manual set needed to convert 5-5 to always block a map. Now 0-23 is 00:00:00 until 22:59:59,
// and there is not way to allow a map from 00:00:00 until 23:59:59.
if( isDecreased
&& endHour == currentHour )
{
isToLoadTheseMaps = isBlackList;
}
else
{
isToLoadTheseMaps = !isBlackList;
}
}
else
{
isToLoadTheseMaps = isToLoadNextWhiteListEndProcess( currentHour, startHour, endHour, isBlackList );
}
LOG( 0, "", debugIsToLoadNextWhiteListGroup( currentHour, startHour, endHour, isToLoadTheseMaps ) )
return isToLoadTheseMaps;
}
stock bool:isToLoadNextWhiteListEndProcess( currentHour, startHour, endHour, bool:isBlackList )
{
LOG( 256, "I AM ENTERING ON isToLoadNextWhiteListEndProcess(4)" )
// 5 3
if( startHour > endHour )
{
// 5 > 3
// 23 > 1
// 22 > 12
// 24 > 23
//
// On this cases, we to go from one day to another, always. So we need to be able to
// calculate whether the current hour is between these. Doing ( 24 - startHour - endHour )
// here, we got how much hours there are between them.
//
// On 5-3, the possible value(s) for current hour: 4
//
// 4 5
if( currentHour < startHour
// 4 3
&& currentHour > endHour )
{
LOG( 256, "( isToLoadNextWhiteListEndProcess ) 1. Returning: %d", !isBlackList )
return !isBlackList;
}
// 6 5
else // if( currentHour > startHour )
{
LOG( 256, "( isToLoadNextWhiteListEndProcess ) 2. Returning: %d", isBlackList )
return isBlackList;
}
}
// 3 5
else // if( startHour < endHour )
{
// 3 < 5
// 1 < 23
// 12 < 22
// 23 < 24
//
// On this cases, we to go from the same day to the same day, always. So we need to be able
// to calculate whether the current hour is between these.
//
// On 3-5, the possible value(s) for current hour: 6, 7, ..., 1, 2
//
// 6 5
if( currentHour > endHour
// 2 3
|| currentHour < startHour )
{
LOG( 256, "( isToLoadNextWhiteListEndProcess ) 3. Returning: %d", !isBlackList )
return !isBlackList;
}
// 4 3
else // if( currentHour > startHour )
{
LOG( 256, "( isToLoadNextWhiteListEndProcess ) 4. Returning: %d", isBlackList )
return isBlackList;
}
}
// This is in fact unreachable code and the AMXX 183 know that, but he AMXX 182 don't.
#if AMXX_VERSION_NUM < 183
LOG( 256, " ( isToLoadNextWhiteListEndProcess ) Returning false." )
return false;
#endif
}
stock debugIsToLoadNextWhiteListGroup( currentHour, startHour, endHour, isToLoadTheseMaps )
{
LOG( 8, "( debugIsToLoadNextWhiteListGroup ) %2d > %2d : %2d", startHour, endHour, startHour > endHour )
LOG( 8, "( debugIsToLoadNextWhiteListGroup ) %2d >= %2d > %2d: %2d", \
startHour, currentHour, endHour, \
startHour >= currentHour && currentHour > endHour )
LOG( 8, "( debugIsToLoadNextWhiteListGroup ) %2d < %2d : %2d", startHour, endHour, startHour < endHour )
LOG( 8, "( debugIsToLoadNextWhiteListGroup ) %2d <= %2d < %2d: %2d, isToLoadTheseMaps: %d", \
startHour, currentHour, endHour, \
startHour <= currentHour && currentHour < endHour, isToLoadTheseMaps )
return 0;
}
/**
* Standardize the hours from 0 until 23.
*/
stock standardizeTheHoursForWhitelist( ¤tHour, &startHour, &endHour )
{
LOG( 256, "I AM ENTERING ON standardizeTheHoursForWhitelist(3) currentHour: %d, startHour: %d, endHour: %d", \
currentHour, startHour, endHour )
if( startHour > 23
|| startHour < 0 )
{
LOG( 8, "( standardizeTheHoursForWhitelist ) startHour: %d, will became 0.", startHour )
startHour = 0;
}
if( endHour > 23
|| endHour < 0 )
{
LOG( 8, "( standardizeTheHoursForWhitelist ) endHour: %d, will became 0.", endHour )
endHour = 0;
}
if( currentHour > 23
|| currentHour < 0 )
{
LOG( 8, "( standardizeTheHoursForWhitelist ) currentHour: %d, will became 0.", currentHour )
currentHour = 0;
}
}
/**
* Now [1-2] specifies the time you want to block them; from 1:00 (am) until 2:59 (am).
*
* This changes:
* From 1:00 until 2:59
* to
* From 3:00 until 0:59
*/
// 1 2
// 3 0
stock bool:convertWhitelistToBlacklist( &startHour, &endHour )
{
LOG( 256, "I AM ENTERING ON convertWhitelistToBlacklist(2)" )
new backup;
backup = ( endHour + 1 > 23 ? 0 : endHour + 1 );
endHour = ( startHour - 1 < 0 ? 23 : startHour - 1 );
startHour = backup;
LOG( 256, "( convertWhitelistToBlacklist ) startHour: %d, endHour: %d", startHour, endHour )
if( startHour == 0
&& endHour == 23 )
{
startHour = 0;
endHour = 0;
LOG( 256, " ( convertWhitelistToBlacklist ) Returning true." )
return true;
}
LOG( 256, " ( convertWhitelistToBlacklist ) Returning false." )
return false;
}
/**
* This must to be called always is needed to update the Whitelist loaded maps, or when it is the
* first time the Whitelist feature is loaded.
*
* @note It must to be protected by an 'IS_WHITELIST_ENABLED()' evaluation.
*/
stock loadTheWhiteListFeature()
{
LOG( 128, "I AM ENTERING ON loadTheWhiteListFeature(0)" )
new currentHour;
new currentHourString[ 8 ];
get_time( "%H", currentHourString, charsmax( currentHourString ) );
currentHour = str_to_num( currentHourString );
// When the `cvar_whitelistType` is set to true, the `cvar_isWhiteListBlockOut` must to loas as a Whitelist.
new bool:whitelistType = get_pcvar_num( cvar_whitelistType ) != 0;
if( get_pcvar_num( cvar_isWhiteListBlockOut ) )
{
loadWhiteListFile( currentHour, g_whitelistTrie, g_whitelistFileArray, !whitelistType, g_whitelistArray );
}
else if( whitelistType )
{
loadWhiteListFile( currentHour, g_blacklistTrie, g_whitelistFileArray, true );
}
else
{
loadWhiteListFile( currentHour, g_blacklistTrie, g_whitelistFileArray, false );
}
}
/**
* The parameters `listTrie` and `listArray` must to be passed by as reference because they are created
* internally by this function on setupLoadWhiteListParams(3).
*/
stock loadWhiteListFile( currentHour, &Trie:listTrie, Array:whitelistFileArray, bool:isBlackList, &Array:listArray = Invalid_Array )
{
LOG( 128, "I AM ENTERING ON loadWhiteListFile(5) currentHour: %d, listTrie: %d", currentHour, listTrie )
if( whitelistFileArray )
{
new startHour;
new endHour;
new linesCount;
new bool:isToLoadTheseMaps;
new bool:isWhiteListBlockOut;
new mapName [ MAX_MAPNAME_LENGHT ];
new currentLine [ MAX_MAPNAME_LENGHT ];
new startHourString[ MAX_MAPNAME_LENGHT / 2 ];
new endHourString [ MAX_MAPNAME_LENGHT / 2 ];
linesCount = ArraySize( whitelistFileArray );
isWhiteListBlockOut = get_pcvar_num( cvar_isWhiteListBlockOut ) != 0;
setupLoadWhiteListParams( isWhiteListBlockOut, listTrie, listArray );
for( new lineIndex = 0; lineIndex < linesCount; lineIndex++ )
{
ArrayGetString( whitelistFileArray, lineIndex, currentLine, charsmax( currentLine ) );
if( whiteListHourlySet( '[', currentLine, startHourString, endHourString, isBlackList, currentHour, startHour, endHour ) )
{
isToLoadTheseMaps = isToLoadNextWhiteListGroupOpen( currentHour, startHour, endHour, isBlackList );
continue;
}
else if( whiteListHourlySet( '{', currentLine, startHourString, endHourString, isBlackList, currentHour, startHour, endHour ) )
{
isToLoadTheseMaps = isToLoadNextWhiteListGroupClose( currentHour, startHour, endHour, isBlackList );
continue;
}
else if( !isToLoadTheseMaps )
{
continue;
}
else
{
LOG( 8, "( loadWhiteListFile ) Trying to add: %s", currentLine )
GET_MAP_NAME_LEFT( currentLine, mapName )
if( IS_MAP_VALID( mapName ) )
{
LOG( 8, "( loadWhiteListFile ) %d. OK! ", listTrie )
TrieSetCell( listTrie, mapName, lineIndex );
if( listArray )
{
ArrayPushString( listArray, currentLine );
}
}
}
}
}
else
{
LOG( 1, "( loadWhiteListFile ) An invalid map descriptors %d/%d!^n", whitelistFileArray, listTrie )
log_error( AMX_ERR_PARAMS, "loadWhiteListFile: An invalid map descriptor %d/%d!^n", whitelistFileArray, listTrie );
}
LOG( 1, " I AM EXITING loadWhiteListFile(5) listArray: %d, whitelistFileArray: %d", listArray, whitelistFileArray )
}
stock whiteListHourlySet( trigger, currentLine[], startHourString[], endHourString[], &isBlackList, ¤tHour, &startHour, &endHour )
{
LOG( 256, "I AM ENTERING ON whiteListHourlySet(4) trigger: %c", trigger )
if( currentLine[ 0 ] == trigger
&& isdigit( currentLine[ 1 ] ) )
{
// remove line delimiters [ and ]
replace_all( currentLine, MAX_MAPNAME_LENGHT - 1, "[", "" );
replace_all( currentLine, MAX_MAPNAME_LENGHT - 1, "{", "" );
replace_all( currentLine, MAX_MAPNAME_LENGHT - 1, "}", "" );
replace_all( currentLine, MAX_MAPNAME_LENGHT - 1, "]", "" );
LOG( 8, "( whiteListHourlySet ) " )
LOG( 8, "( whiteListHourlySet ) If we are %s these hours, we must load these maps:", ( isBlackList? "between" : "outside" ) )
LOG( 8, "( whiteListHourlySet ) currentLine: %s (currentHour: %d)", currentLine, currentHour )
// broke the current line
STR_TOKEN( currentLine ,
startHourString, MAX_MAPNAME_LENGHT / 2,
endHourString , MAX_MAPNAME_LENGHT / 2, '-', 0 );
startHour = str_to_num( startHourString );
endHour = str_to_num( endHourString );
standardizeTheHoursForWhitelist( currentHour, startHour, endHour );
LOG( 256, " ( whiteListHourlySet ) Returning true for: %s", currentLine )
return true;
}
LOG( 256, " ( whiteListHourlySet ) Returning false for: %s", currentLine )
return false;
}
stock setupLoadWhiteListParams( bool:isWhiteListBlockOut, &Trie:listTrie, &Array:listArray )
{
LOG( 128, "I AM ENTERING ON setupLoadWhiteListParams(3) isWhiteListBlockOut: %d", isWhiteListBlockOut )
LOG( 8, "( setupLoadWhiteListParams ) listTrie: %d, listArray: %d", listTrie, listArray )
if( listTrie )
{
TRY_TO_APPLY( TrieClear, listTrie )
}
else
{
listTrie = TrieCreate();
}
if( isWhiteListBlockOut )
{
// clear the map array in case we're reusing it
if( listArray )
{
TRY_TO_APPLY( ArrayClear, listArray )
}
else
{
listArray = ArrayCreate( MAX_MAPNAME_LENGHT );
}
}
}
stock FillersFilePathType:loadMapGroupsFeature()
{
LOG( 128, "I AM ENTERING ON loadMapGroupsFeature(0)" )
new realPlayersNumber = get_real_players_number();
if( realPlayersNumber > 0 )
{
new voteMininumPlayers = get_pcvar_num( cvar_voteMinPlayers );
new voteMiddlePlayers = get_pcvar_num( cvar_voteMidPlayers );
LOG( 4, "( processLoadedMapsFile ) realPlayersNumber: %s", realPlayersNumber )
LOG( 4, "( processLoadedMapsFile ) voteMininumPlayers: %s", voteMininumPlayers )
LOG( 4, "( processLoadedMapsFile ) voteMiddlePlayers: %s", voteMiddlePlayers )
if( realPlayersNumber < voteMininumPlayers
&& voteMininumPlayers > VOTE_MININUM_PLAYERS_REQUIRED )
{
return fillersFilePaths_MininumPlayers;
}
else if( voteMiddlePlayers > voteMininumPlayers
&& realPlayersNumber < voteMiddlePlayers
&& voteMiddlePlayers > VOTE_MIDDLE_PLAYERS_REQUIRED )
{
return fillersFilePaths_MiddlePlayers;
}
}
return fillersFilePaths_NormalPlayers;
}
stock processLoadedMapsFile( FillersFilePathType:fillersFilePathEnum, blockedMapsBuffer[], &announcementShowedTimes )
{
LOG( 128, "I AM ENTERING ON processLoadedMapsFile(3)" )
LOG( 4, "( processLoadedMapsFile ) fillersFilePathEnum: %d", fillersFilePathEnum )
LOG( 4, "( processLoadedMapsFile ) announcementShowedTimes: %d", announcementShowedTimes )
LOG( 4, "( processLoadedMapsFile ) blockedMapsBuffer: %s", blockedMapsBuffer )
new groupCount;
new choiceIndex;
new allowedFilersCount;
new maxMapsPerGroupToUse;
new Array:fillerMapsArray;
new Array:fillerMapGroupsArrays;
new Array:maxMapsPerGroupToUseArray;
new mapName[ MAX_MAPNAME_LENGHT ];
new mapInfo[ MAX_MAPNAME_LENGHT ];
switch( fillersFilePathEnum )
{
case fillersFilePaths_MininumPlayers:
{
fillerMapGroupsArrays = g_minPlayerFillerMapGroupArrays;
maxMapsPerGroupToUseArray = g_minMaxMapsPerGroupToUseArray;
}
case fillersFilePaths_MiddlePlayers:
{
fillerMapGroupsArrays = g_midPlayerFillerMapGroupArrays;
maxMapsPerGroupToUseArray = g_midMaxMapsPerGroupToUseArray;
}
default: // case fillersFilePaths_NormalPlayers:
{
fillerMapGroupsArrays = g_norPlayerFillerMapGroupArrays;
maxMapsPerGroupToUseArray = g_norMaxMapsPerGroupToUseArray;
}
}
new mapIndex;
new filersMapCount;
new unsuccessfulCount;
new currentBlockerStrategy;
new Array:randomGenaratorHolder = ArrayCreate();
new bool:isWhitelistEnabled = IS_WHITELIST_ENABLED();
new bool:useEqualiCurrentMap = true;
new bool:useWhitelistOutBlock = isWhitelistEnabled;
new bool:useIsPrefixInMenu = get_pcvar_num( cvar_voteUniquePrefixes ) != 0;
new bool:isWhiteListOutBlock = ( isWhitelistEnabled
&& get_pcvar_num( cvar_isWhiteListBlockOut ) != 0 );
new bool:useMapIsTooRecent = ( g_recentListMapsArray
&& ArraySize( g_recentListMapsArray )
&& get_pcvar_num( cvar_recentMapsBannedNumber ) != 0 );
/**
* This variable is to avoid double blocking which lead to the algorithm corruption and errors.
*/
new Trie:blockedFillersMapTrie;
if( useWhitelistOutBlock )
{
blockedFillersMapTrie = TrieCreate();
}
groupCount = ArraySize( fillerMapGroupsArrays );
LOG( 4, "( processLoadedMapsFile ) groupCount: %d, fillerMapGroupsArrays: %d", groupCount, fillerMapGroupsArrays )
// The Whitelist Out Block feature disables The Map Groups Feature.
if( isWhiteListOutBlock )
{
LOG( 4, "( processLoadedMapsFile ) Disabling the MapsGroups Feature due isWhiteListOutBlock" )
#if AMXX_VERSION_NUM < 183
groupCount = ArraySize( fillerMapGroupsArrays );
while( groupCount > 1 )
{
groupCount--;
ArrayDeleteItem( maxMapsPerGroupToUseArray, groupCount );
fillerMapsArray = ArrayGetCell( fillerMapGroupsArrays, groupCount );
ArrayDeleteItem( fillerMapGroupsArrays, groupCount );
TRY_TO_APPLY( ArrayDestroy, fillerMapsArray )
}
#else
groupCount = 1;
ArrayResize( fillerMapGroupsArrays , groupCount );
ArrayResize( maxMapsPerGroupToUseArray, groupCount );
#endif
}
new maxVotingChoices = MAX_VOTING_CHOICES();
// fill remaining slots with random maps from each filler file, as much as possible
for( new groupIndex = 0; groupIndex < groupCount; ++groupIndex )
{
if( isWhitelistEnabled )
{
// The Whitelist out block feature, disables The Map Groups Feature.
if( isWhiteListOutBlock )
{
LOG( 0, "", print_is_white_list_out_block() )
fillerMapsArray = g_whitelistArray;
useWhitelistOutBlock = false;
}
else
{
fillerMapsArray = ArrayGetCell( fillerMapGroupsArrays, groupIndex );
}
}
else
{
fillerMapsArray = ArrayGetCell( fillerMapGroupsArrays, groupIndex );
}
filersMapCount = ArraySize( fillerMapsArray );
LOG( 8, "" )
LOG( 8, "[%i] groupCount:%i, filersMapCount: %i, g_totalVoteOptions: %i, maxVotingChoices: %i", \
groupIndex, groupCount, filersMapCount, g_totalVoteOptions, maxVotingChoices )
if( filersMapCount
&& g_totalVoteOptions < maxVotingChoices )
{
maxMapsPerGroupToUse = ArrayGetCell( maxMapsPerGroupToUseArray, groupIndex );
allowedFilersCount = min( min(
maxMapsPerGroupToUse, maxVotingChoices - g_totalVoteOptions
), filersMapCount );
LOG( 8, "[%i] allowedFilersCount: %i maxMapsPerGroupToUse[%i]: %i", groupIndex, \
allowedFilersCount, groupIndex, maxMapsPerGroupToUse )
LOG( 8, "" )
LOG( 8, "" )
for( choiceIndex = 0; choiceIndex < allowedFilersCount; ++choiceIndex )
{
unsuccessfulCount = 0;
currentBlockerStrategy = -1;
keepSearching:
mapIndex = random_num( 0, filersMapCount - 1 );
GET_MAP_NAME( fillerMapsArray, mapIndex, mapName )
LOG( 8, "( in ) [%i] choiceIndex: %i, mapIndex: %i, mapName: %s, unsuccessfulCount: %i, g_totalVoteOptions: %i", \
groupIndex, choiceIndex, mapIndex, mapName, unsuccessfulCount, g_totalVoteOptions )
while( map_isInMenu( mapName )
|| (
useEqualiCurrentMap
&& equali( g_currentMapName, mapName )
)
|| (
useWhitelistOutBlock
&& TrieKeyExists( blockedFillersMapTrie, mapName )
)
|| (
useMapIsTooRecent
&& map_isTooRecent( mapName )
)
|| (
useIsPrefixInMenu
&& isPrefixInMenu( mapName )
)
)
{
// Some spacing
LOG( 8, "" )
LOG( 0, "", debug_vote_map_selection( choiceIndex, mapName, useWhitelistOutBlock, \
isWhiteListOutBlock, useEqualiCurrentMap, unsuccessfulCount, currentBlockerStrategy, \
useIsPrefixInMenu, useMapIsTooRecent, blockedFillersMapTrie ) )
// Heuristics to try to approximate the maximum menu map numbers, when there are just
// a few maps to fill the voting menu and there are a lot of filler restrictions.
if( unsuccessfulCount >= filersMapCount )
{
switch( ++currentBlockerStrategy )
{
case 0:
{
useIsPrefixInMenu = false;
}
case 1:
{
useMapIsTooRecent = false;
}
case 2:
{
if( isWhiteListOutBlock )
{
LOG( 8, "" )
LOG( 8, "" )
LOG( 8, "WARNING! This BlockerStrategy case is not used by the isWhiteListOutBlock." )
LOG( 8, "" )
LOG( 8, "" )
++currentBlockerStrategy;
goto isWhiteListOutBlockExitCase;
}
useWhitelistOutBlock = false;
}
case 3:
{
isWhiteListOutBlockExitCase:
useEqualiCurrentMap = false;
}
default:
{
LOG( 8, "" )
LOG( 8, "" )
LOG( 8, "WARNING! unsuccessfulCount: %i, filersMapCount: %i", unsuccessfulCount, filersMapCount )
LOG( 8, "" )
LOG( 8, "" )
goto exitSearch;
}
}
unsuccessfulCount = 0;
}
++mapIndex;
if( mapIndex >= filersMapCount )
{
mapIndex = 0;
}
unsuccessfulCount++;
GET_MAP_NAME( fillerMapsArray, mapIndex, mapName )
LOG( 0, "", debug_vote_map_selection( choiceIndex, mapName, useWhitelistOutBlock, \
isWhiteListOutBlock, useEqualiCurrentMap, unsuccessfulCount, currentBlockerStrategy, \
useIsPrefixInMenu, useMapIsTooRecent, blockedFillersMapTrie ) )
}
if( isWhitelistEnabled
&& !isWhiteListOutBlock
&& TrieKeyExists( g_blacklistTrie, mapName ) )
{
LOG( 8, " Trying to block: %s, by the whitelist map setting...", mapName )
if( !TrieKeyExists( blockedFillersMapTrie, mapName ) )
{
LOG( 8, " BLOCKED!" )
TrieSetCell( blockedFillersMapTrie, mapName, 0 );
announceVoteBlockedMap( mapName, blockedMapsBuffer, "GAL_FILLER_BLOCKED", announcementShowedTimes );
}
goto keepSearching;
}
GET_MAP_INFO( fillerMapsArray, mapIndex, mapInfo )
addMapToTheVotingMenu( mapName, mapInfo );
LOG( 8, "" )
LOG( 8, "( out ) [%i] choiceIndex: %i, mapIndex: %i, mapName: %s, unsuccessfulCount: %i, g_totalVoteOptions: %i", \
groupIndex, choiceIndex, mapIndex, mapName, unsuccessfulCount, g_totalVoteOptions )
LOG( 8, "" )
LOG( 8, "" )
}
if( g_dummy_value )
{
exitSearch:
LOG( 8, "" )
LOG( 8, "" )
LOG( 8, "WARNING! There aren't enough maps in this filler file to continue adding anymore." )
LOG( 8, "" )
LOG( 8, "" )
}
}
}
TRY_TO_APPLY( TrieDestroy, blockedFillersMapTrie )
TRY_TO_APPLY( ArrayDestroy, randomGenaratorHolder )
}
stock print_is_white_list_out_block()
{
LOG( 128, "I AM ENTERING ON print_is_white_list_out_block(2)" )
new mapName[ MAX_MAPNAME_LENGHT ];
new filersMapCount = ArraySize( g_whitelistArray );
LOG( 8, "" )
LOG( 8, "( print_is_white_list_out_block|FOR in)" )
for( new currentMapIndex = 0; currentMapIndex < filersMapCount; ++currentMapIndex )
{
ArrayGetString( g_whitelistArray, currentMapIndex, mapName, charsmax( mapName ) );
LOG( 8, "( print_is_white_list_out_block|FOR ) g_whitelistArray[%d]: %s", currentMapIndex, mapName )
}
LOG( 8, "( print_is_white_list_out_block|FOR out)" )
return 0;
}
stock debug_vote_map_selection( choiceIndex, mapName[], useWhitelistOutBlock, isWhiteListOutBlock,
useEqualiCurrentMap, unsuccessfulCount, currentBlockerStrategy,
useIsPrefixInMenu, useMapIsTooRecent, Trie:blockedFillersMapTrie )
{
LOG( 256, "I AM ENTERING ON debug_vote_map_selection(10) choiceIndex: %d", choiceIndex )
new type[5];
static isIncrementTime = 0;
static currentMapIndex = 0;
static lastchoiceIndex = 0;
// Reset the currentMapIndex map counting after a successful map addition.
if( choiceIndex != lastchoiceIndex )
{
currentMapIndex = 0;
lastchoiceIndex = choiceIndex;
}
// Alternate between `in` and `out` to get a debugging loop entrance and exit behavior.
if( isIncrementTime++ % 2 == 0 )
{
currentMapIndex++;
copy( type, charsmax( type ), "in" );
}
else
{
copy( type, charsmax( type ), "out" );
}
LOG( 8, "%d. ( debug_vote_map_selection|while_%s ) mapName: %s, map_isInMenu: %d, is_theCurrentMap: %d, \
map_isTooRecent: %d", currentMapIndex, type, mapName, map_isInMenu( mapName ), \
equali( g_currentMapName, mapName ), map_isTooRecent( mapName ) )
LOG( 8, " isPrefixInMenu: %d, TrieKeyExists( blockedFillersMapTrie ): %d, \
!TrieKeyExists( g_whitelistTrie ): %d", isPrefixInMenu( mapName ), \
( useWhitelistOutBlock? TrieKeyExists( blockedFillersMapTrie, mapName ) : false ), \
( isWhiteListOutBlock ? !TrieKeyExists( g_whitelistTrie , mapName ) : false ) )
LOG( 8, " useMapIsTooRecent: %d, useIsPrefixInMenu: %d, useEqualiCurrentMap: %d", \
useMapIsTooRecent, useIsPrefixInMenu, useEqualiCurrentMap )
LOG( 8, " currentBlockerStrategy: %d, unsuccessfulCount:%d, useWhitelistOutBlock: %d", \
currentBlockerStrategy, unsuccessfulCount, useWhitelistOutBlock )
return 0;
}
stock vote_addFillers( blockedMapsBuffer[], &announcementShowedTimes = 0 )
{
LOG( 128, "I AM ENTERING ON vote_addFillers(2) announcementShowedTimes: %d", announcementShowedTimes )
new maxVotingChoices = MAX_VOTING_CHOICES();
if( g_totalVoteOptions < maxVotingChoices )
{
new FillersFilePathType:fillersFilePathEnum = loadMapGroupsFeature();
processLoadedMapsFile( fillersFilePathEnum, blockedMapsBuffer, announcementShowedTimes );
}
else
{
LOG( 8, " ( vote_addFillers ) maxVotingChoices: %d", maxVotingChoices )
LOG( 8, " ( vote_addFillers ) g_maxVotingChoices: %d", g_maxVotingChoices )
LOG( 8, " ( vote_addFillers ) g_totalVoteOptions: %d", g_totalVoteOptions )
LOG( 1, " ( vote_addFillers ) Just Returning/blocking, the voting list is filled." )
}
}
stock vote_addNominations( blockedMapsBuffer[], &announcementShowedTimes = 0 )
{
LOG( 128, "I AM ENTERING ON vote_addNominations(2) announcementShowedTimes: %d", announcementShowedTimes )
new nominatedMapsCount;
// Try to add the nominations, if there are nominated maps.
if( g_nominatedMapsArray
&& ( nominatedMapsCount = ArraySize( g_nominatedMapsArray ) ) )
{
LOG( 128, "( vote_addNominations ) nominatedMapsCount: %d", nominatedMapsCount )
new Trie:whitelistMapTrie;
new mapIndex;
new mapName[ MAX_MAPNAME_LENGHT ];
new mapInfo[ MAX_MAPNAME_LENGHT ];
// Note: The Map Groups Feature will not work with the Minimum Players Feature when adding
// nominations, as we do not load the Map Groups Feature. But the Map Groups Feature will
// work fine with the Minimum Players Feature when filling the vote menu.
if( IS_NOMINATION_MININUM_PLAYERS_CONTROL_ENABLED() )
{
new mapFilerFilePath[ MAX_FILE_PATH_LENGHT ];
get_pcvar_string( cvar_voteMinPlayersMapFilePath, mapFilerFilePath, charsmax( mapFilerFilePath ) );
// '*' is invalid blacklist for voting, because it would block all server maps.
if( !mapFilerFilePath[ 0 ]
|| mapFilerFilePath[ 0 ] == MAP_FOLDER_LOAD_FLAG )
{
LOG( 1, "AMX_ERR_NOTFOUND, %L", LANG_SERVER, "GAL_MAPS_FILEMISSING", mapFilerFilePath )
log_error( AMX_ERR_NOTFOUND, "%L", LANG_SERVER, "GAL_MAPS_FILEMISSING", mapFilerFilePath );
}
else
{
whitelistMapTrie = g_minPlayerFillerMapGroupTrie;
}
}
new maxVotingChoices = MAX_VOTING_CHOICES();
// set how many total nominations we can use in this vote
new maxNominations = get_pcvar_num( cvar_nomQtyUsed );
new slotsAvailable = maxVotingChoices - g_totalVoteOptions;
new voteNominationMax = ( maxNominations ) ? min( maxNominations, slotsAvailable ) : slotsAvailable;
// print the players nominations for debug
LOG( 4, "( vote_addNominations ) nominatedMapsCount", nominatedMapsCount, show_all_players_nominations() )
// Add as many nominations as we can by FIFO
for( new nominationIndex = 0; nominationIndex < nominatedMapsCount; ++nominationIndex )
{
if( ( mapIndex = ArrayGetCell( g_nominatedMapsArray, nominationIndex ) ) < 0 )
{
continue;
}
else
{
GET_MAP_NAME( g_nominationLoadedMapsArray, mapIndex, mapName )
LOG( 4, "( vote_addNominations ) g_nominationLoadedMapsArray.mapIndex: %d, mapName: %s", mapIndex, mapName )
if( whitelistMapTrie
&& !TrieKeyExists( whitelistMapTrie, mapName ) )
{
LOG( 8, " The map: %s, was blocked by the minimum players map setting.", mapName )
announceVoteBlockedMap( mapName, blockedMapsBuffer, "GAL_FILLER_BLOCKED", announcementShowedTimes );
continue;
}
GET_MAP_INFO( g_nominationLoadedMapsArray, mapIndex, mapInfo )
addMapToTheVotingMenu( mapName, mapInfo );
if( g_totalVoteOptions == voteNominationMax )
{
break;
}
}
} // end nomination's players looking
} // end if nominations
LOG( 4, "" )
LOG( 4, "" )
}
stock show_all_players_nominations()
{
LOG( 128, "I AM ENTERING ON show_all_players_nominations(0)" )
new mapIndex;
new nominator_id;
new mapName [ MAX_MAPNAME_LENGHT ];
new playerName[ MAX_PLAYER_NAME_LENGHT ];
// set how many total nominations each player is allowed
new maxPlayerNominations = min( get_pcvar_num( cvar_nomPlayerAllowance ), MAX_OPTIONS_IN_VOTE );
for( new nominationIndex = 0; nominationIndex < maxPlayerNominations; ++nominationIndex )
{
LOG( 4, "" )
LOG( 4, "" )
LOG( 4, "( show_all_players_nominations ) nominationIndex: %d", nominationIndex )
LOG( 4, "( show_all_players_nominations ) maxPlayerNominations: %d", maxPlayerNominations )
LOG( 4, "( show_all_players_nominations ) g_nominationLoadedMapsArray: %d", g_nominationLoadedMapsArray )
LOG( 4, "( show_all_players_nominations ) ArraySize: %d", ArraySize( g_nominationLoadedMapsArray ) )
for( new player_id = 1; player_id < MAX_PLAYERS_COUNT; ++player_id )
{
if( ( mapIndex = getPlayerNominationMapIndex( player_id, nominationIndex ) ) < 0 )
{
continue;
}
else
{
ArrayGetString( g_nominationLoadedMapsArray, mapIndex, mapName, charsmax( mapName ) );
nominator_id = nomination_getPlayer( mapIndex );
GET_USER_NAME( nominator_id, playerName )
LOG( 4, " %-32s %s", mapName, playerName )
}
}
}
return 0;
}
stock loadOnlyNominationVoteChoices()
{
LOG( 128, "I AM ENTERING ON loadOnlyNominationVoteChoices(0)" )
if( IS_NOMINATION_MININUM_PLAYERS_CONTROL_ENABLED()
|| IS_WHITELIST_ENABLED() )
{
new announcementShowedTimes = 1;
new blockedMapsBuffer[ MAX_COLOR_MESSAGE ];
vote_addNominations( blockedMapsBuffer, announcementShowedTimes );
flushVoteBlockedMaps( blockedMapsBuffer, "GAL_FILLER_BLOCKED", announcementShowedTimes );
}
else
{
new dummyArray[] = 0;
vote_addNominations( dummyArray );
}
}
stock loadTheDefaultVotingChoices()
{
LOG( 128, "I AM ENTERING ON loadTheDefaultVotingChoices(0)" )
// To add the next map to the voting menu, if enabled.
if( get_pcvar_num( cvar_voteMapChoiceNext )
&& !equali( g_currentMapName, g_nextMapName )
&& !map_isTooRecent( g_nextMapName ) )
{
new mapIndex;
new mapInfo[ MAX_MAPNAME_LENGHT ];
if( TrieKeyExists( g_mapcycleFileListTrie, g_nextMapName ) )
{
TrieGetCell( g_mapcycleFileListTrie , g_nextMapName, mapIndex );
GET_MAP_INFO( g_mapcycleFileListArray, mapIndex , mapInfo )
}
addMapToTheVotingMenu( g_nextMapName, mapInfo );
}
if( IS_NOMINATION_MININUM_PLAYERS_CONTROL_ENABLED()
|| IS_WHITELIST_ENABLED() )
{
new announcementShowedTimes = 1;
new blockedMapsBuffer[ MAX_COLOR_MESSAGE ];
vote_addNominations( blockedMapsBuffer, announcementShowedTimes );
vote_addFillers( blockedMapsBuffer, announcementShowedTimes );
flushVoteBlockedMaps( blockedMapsBuffer, "GAL_FILLER_BLOCKED", announcementShowedTimes );
}
else
{
new dummyArray[] = 0;
vote_addNominations( dummyArray );
vote_addFillers( dummyArray );
}
SET_VOTING_TIME_TO( g_votingSecondsRemaining, cvar_voteDuration )
LOG( 4, " ( loadTheDefaultVotingChoices ) g_totalVoteOptions: %d", g_totalVoteOptions )
return g_totalVoteOptions;
}
/**
* Announce the Minplayers-Whitelist blocked maps.
*
* @param mapToAnnounce a map which was blocked.
* @param blockedMapsBuffer the output string to be printed.
*
* @note It does not immediately print the called map. The output occurs when the buffer is full.
*/
stock announceVoteBlockedMap( mapToAnnounce[], blockedMapsBuffer[], flushAnnouncement[], &announcementShowedTimes )
{
LOG( 128, "I AM ENTERING ON announceVoteBlockedMap(4) announcementShowedTimes: %d, \
mapToAnnounce: %s, ", announcementShowedTimes, mapToAnnounce )
if( announcementShowedTimes
&& announcementShowedTimes < 3 )
{
static copiedChars;
// Reset the characters counter for the output flush.
if( !blockedMapsBuffer[ 0 ] )
{
copiedChars = 0;
}
copiedChars += copy( blockedMapsBuffer[ copiedChars ], MAX_COLOR_MESSAGE - 1 - copiedChars, "^1, ^4" );
copiedChars += copy( blockedMapsBuffer[ copiedChars ], MAX_COLOR_MESSAGE - 1 - copiedChars, mapToAnnounce );
// Calculate whether to flush now or not.
if( copiedChars > MAX_COLOR_MESSAGE - MAX_MAPNAME_LENGHT )
{
flushVoteBlockedMaps( blockedMapsBuffer, flushAnnouncement, announcementShowedTimes );
}
}
}
/**
* Print the current blocked maps buffer, if there are any maps on it.
*
* @param blockedMapsBuffer the formatted maps list to be printed.
*/
stock flushVoteBlockedMaps( blockedMapsBuffer[], flushAnnouncement[], &announcementShowedTimes )
{
LOG( 128, "I AM ENTERING ON flushVoteBlockedMaps(3) announcementShowedTimes: %d, ", announcementShowedTimes )
LOG( 8, "blockedMapsBuffer: %s", blockedMapsBuffer )
if( blockedMapsBuffer[ 0 ] )
{
if( announcementShowedTimes == 1 )
{
color_print( 0, "%L", LANG_PLAYER, flushAnnouncement, 0, 0 );
}
if( !IS_COLORED_CHAT_ENABLED() ) REMOVE_CODE_COLOR_TAGS( blockedMapsBuffer )
color_print( 0, "%L", LANG_PLAYER, "GAL_MATCHING", blockedMapsBuffer[ 3 ] );
announcementShowedTimes++;
blockedMapsBuffer[ 0 ] = '^0';
}
}
stock computeNextWhiteListLoadTime( seconds, bool:isSecondsLeft = true )
{
LOG( 128, "I AM ENTERING ON computeNextWhiteListLoadTime(2) seconds: %d, isSecondsLeft: %d", seconds, isSecondsLeft )
new secondsForReload;
// This is tricky as 'seconds' could be 0, when there is no time-limit.
if( seconds )
{
new currentHour;
new currentMinute;
new currentSecond;
time( currentHour, currentMinute, currentSecond );
secondsForReload = ( 3600 - ( currentMinute * 60 + currentSecond ) );
if( isSecondsLeft )
{
// Here, when the 'secondsForReload' is greater than 'seconds', we will change map before
// the next reload, then when do not need to reload on this current server session.
if( seconds < secondsForReload )
{
g_whitelistNomBlockTime = 0;
}
else
{
g_whitelistNomBlockTime = seconds - secondsForReload + 1;
}
}
else
{
// Here on '!isSecondsLeft', we do not know when there will be a map change, then we
// just set the next time where the current hour will end.
g_whitelistNomBlockTime = secondsForReload + seconds;
}
}
else
{
g_whitelistNomBlockTime = 1000;
doAmxxLog( "ERROR, computeNextWhiteListLoadTime: The seconds parameter is zero!" );
}
LOG( 1, "I AM EXITING computeNextWhiteListLoadTime(2) g_whitelistNomBlockTime: %d, secondsForReload: %d", g_whitelistNomBlockTime, secondsForReload )
}
/**
* Action from handleServerStart to take when it is detected that the server has been
* restarted. 3 - start an early map vote after the first two minutes.
*/
stock vote_manageEarlyStart()
{
LOG( 128, "I AM ENTERING ON vote_manageEarlyStart(0) g_voteStatus: %d", g_voteStatus )
g_voteStatus |= IS_EARLY_VOTE;
set_task( 120.0, "startNonForcedVoting", TASKID_START_THE_VOTING );
}
public startNonForcedVoting()
{
LOG( 128, "I AM ENTERING ON startNonForcedVoting(0) g_endVotingType: %d", g_endVotingType )
startTheVoting( false );
}
public start_voting_by_winlimit()
{
LOG( 128, "I AM ENTERING ON start_voting_by_winlimit(0) g_endVotingType: %d", g_endVotingType)
LOG( 32, "( start_voting_by_winlimit ) get_pcvar_num( cvar_endOfMapVote ): %d", get_pcvar_num( cvar_endOfMapVote ) )
if( get_pcvar_num( cvar_endOfMapVote ) )
{
// This must to be called the first thing here.
resetVoteTypeGlobals();
g_endVotingType |= IS_BY_WINLIMIT;
startTheVoting( false );
}
}
public start_voting_by_maxrounds()
{
LOG( 128, "I AM ENTERING ON start_voting_by_maxrounds(0) g_endVotingType: %d", g_endVotingType)
LOG( 32, "( start_voting_by_maxrounds ) get_pcvar_num( cvar_endOfMapVote ): %d", get_pcvar_num( cvar_endOfMapVote ) )
if( get_pcvar_num( cvar_endOfMapVote ) )
{
// This must to be called the first thing here.
resetVoteTypeGlobals();
g_endVotingType |= IS_BY_ROUNDS;
startTheVoting( false );
}
}
public start_voting_by_frags()
{
LOG( 128, "I AM ENTERING ON start_voting_by_frags(0) g_endVotingType: %d", g_endVotingType)
LOG( 32, "( start_voting_by_frags ) get_pcvar_num( cvar_endOfMapVote ): %d", get_pcvar_num( cvar_endOfMapVote ) )
if( get_pcvar_num( cvar_endOfMapVote ) )
{
// This must to be called the first thing here.
resetVoteTypeGlobals();
g_endVotingType |= IS_BY_FRAGS;
startTheVoting( false );
}
}
public start_voting_by_timer()
{
LOG( 128, "I AM ENTERING ON start_voting_by_timer(0) g_endVotingType: %d", g_endVotingType)
LOG( 32, "( start_voting_by_timer ) get_pcvar_num( cvar_endOfMapVote ): %d", get_pcvar_num( cvar_endOfMapVote ) )
if( get_pcvar_num( cvar_endOfMapVote ) )
{
// This must to be called the first thing here.
resetVoteTypeGlobals();
g_endVotingType |= IS_BY_TIMER;
startTheVoting( false );
}
}
public startVotingByGameEngineCall()
{
LOG( 128, "I AM ENTERING ON startVotingByGameEngineCall(0) g_endVotingType: %d", g_endVotingType)
LOG( 32, "( startVotingByGameEngineCall ) get_pcvar_num( cvar_endOfMapVote ): %d", get_pcvar_num( cvar_endOfMapVote ) )
if( get_pcvar_num( cvar_endOfMapVote ) )
{
// This must to be called the first thing here.
resetVoteTypeGlobals();
g_isToChangeMapOnVotingEnd = true;
startTheVoting( false );
}
}
public vote_manageEnd()
{
LOG( 256, "I AM ENTERING ON vote_manageEnd(0) get_real_players_number: %d", get_real_players_number() )
new secondsLeft = get_timeleft();
if( secondsLeft )
{
// are we managing the end of the map?
if( secondsLeft < 30
&& secondsLeft > 0 )
{
// This cannot trigger the map change, otherwise it will change the map before the last
// seconds to be finished.
try_to_manage_map_end();
}
LOG( 256, "( vote_manageEnd ) START_VOTEMAP_MIN_TIME: %d", START_VOTEMAP_MIN_TIME )
LOG( 256, "( vote_manageEnd ) START_VOTEMAP_MAX_TIME: %d", START_VOTEMAP_MAX_TIME )
// Are we ready to start an "end of map" vote?
if( secondsLeft < START_VOTEMAP_MIN_TIME
&& secondsLeft > START_VOTEMAP_MAX_TIME )
{
new endOfMapVote = get_pcvar_num( cvar_endOfMapVote );
// Here we tread a special case. There were not enough rounds saved, but we already hit
// the vote_manageEnd(0) which is only called on the last seconds of the map. Then just
// to start the voting right now, to allow one round maps.
//
// Note: Only timed maps are susceptible to this problem, maps guided by fraglimit,
// maxrounds and winlimit are good to go.
if( isTimeToStartTheEndOfMapVoting( endOfMapVote )
|| ( endOfMapVote
&& !g_isTheRoundEnded
&& g_totalRoundsSavedTimes < MIN_VOTE_START_ROUNDS_DELAY + 1 ) )
{
start_voting_by_timer();
}
}
}
// Update the Whitelist maps when its the right time, configured by 'computeNextWhiteListLoadTime(2)'.
if( g_whitelistNomBlockTime )
{
new secondsElapsed;
secondsElapsed = floatround( get_gametime(), floatround_ceil );
if( g_whitelistNomBlockTime < secondsElapsed )
{
computeNextWhiteListLoadTime( secondsElapsed, false );
loadTheWhiteListFeature();
}
}
periodicTimeLeftHandleChecking( secondsLeft );
}
stock periodicTimeLeftHandleChecking( secondsLeft )
{
LOG( 256, "I AM ENTERING ON periodicTimeLeftHandleChecking(1) secondsLeft: %d", secondsLeft )
showRemainingTimeUntilVoting();
create_game_crash_recreation( secondsLeft );
}
stock showRemainingTimeUntilVoting()
{
LOG( 128, "I AM ENTERING ON showRemainingTimeUntilVoting(0)" )
static showCounter;
const trigger_count = 750 / PERIODIC_CHECKING_INTERVAL;
// PERIODIC_CHECKING_INTERVAL = 15 seconds, 15 * 50 = 750 = 12.5 minutes
if( ++showCounter % trigger_count < 1 )
{
new displayTime = 7;
set_hudmessage( 255, 255, 255, 0.15, 0.15, 0, 0.0, float( displayTime ), 0.1, 0.1, 1 );
switch( whatGameEndingTypeItIs() )
{
case GameEndingType_ByMaxRounds:
{
new roundsLeft = get_pcvar_num( cvar_mp_maxrounds ) - g_totalRoundsPlayed;
if( roundsLeft > 0 )
{
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_TIMELEFT_ANNOUNCE ) )
{
show_hudmessage( 0, "%L:^n%d %L", LANG_PLAYER, "TIME_LEFT", roundsLeft, LANG_PLAYER, "GAL_ROUNDS" );
}
color_print( 0, "%L %L...", LANG_PLAYER, "GAL_VOTE_COUNTDOWN", roundsLeft, LANG_PLAYER, "GAL_ROUNDS" );
}
}
case GameEndingType_ByWinLimit:
{
new winLeft = get_pcvar_num( cvar_mp_winlimit ) - max( g_totalCtWins, g_totalTerroristsWins );
if( winLeft > 0 )
{
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_TIMELEFT_ANNOUNCE ) )
{
show_hudmessage( 0, "%L:^n%d %L", LANG_PLAYER, "TIME_LEFT", winLeft, LANG_PLAYER, "GAL_ROUNDS" );
}
color_print( 0, "%L %L...", LANG_PLAYER, "GAL_VOTE_COUNTDOWN", winLeft, LANG_PLAYER, "GAL_ROUNDS" );
}
}
case GameEndingType_ByFragLimit:
{
new fragsLeft = get_pcvar_num( cvar_mp_fraglimit ) - g_greatestKillerFrags;
if( fragsLeft > 0 )
{
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_TIMELEFT_ANNOUNCE ) )
{
show_hudmessage( 0, "%L:^n%d %L", LANG_PLAYER, "TIME_LEFT", fragsLeft, LANG_PLAYER, "GAL_FRAGS" );
}
color_print( 0, "%L %L...", LANG_PLAYER, "GAL_VOTE_COUNTDOWN", fragsLeft, LANG_PLAYER, "GAL_FRAGS" );
}
}
case GameEndingType_ByTimeLimit:
{
new timeLeft = get_timeleft() - g_totalVoteTime;
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_TIMELEFT_ANNOUNCE ) )
{
set_task( 1.0, "displayRemainingTime", TASKID_DISPLAY_REMAINING_TIME, _, _, "a", displayTime );
}
if( timeLeft > 0 )
{
color_print( 0, "%L %L...", LANG_PLAYER, "GAL_VOTE_COUNTDOWN", ( timeLeft ) / 60, LANG_PLAYER, "GAL_MINUTES" );
}
}
default:
{
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_TIMELEFT_ANNOUNCE ) )
{
show_hudmessage( 0, "%L:^n%L", LANG_PLAYER, "TIME_LEFT", LANG_PLAYER, "NO_T_LIMIT" );
}
// When we put the color tags inside the plugin, we need to check whether the color chat is enabled.
if( IS_COLORED_CHAT_ENABLED() )
{
color_print( 0, "^4%L:^1 %L...", LANG_PLAYER, "TIME_LEFT", LANG_PLAYER, "NO_T_LIMIT" );
}
else
{
color_print( 0, "%L: %L...", LANG_PLAYER, "TIME_LEFT", LANG_PLAYER, "NO_T_LIMIT" );
}
}
}
}
}
public displayRemainingTime()
{
LOG( 128, "I AM ENTERING ON displayRemainingTime(0)" )
new timeLeft = get_timeleft();
new seconds = timeLeft % 60;
new minutes = floatround( ( timeLeft - seconds ) / 60.0 );
set_hudmessage( 255, 255, 255, 0.15, 0.15, 0, 0.0, 1.1, 0.1, 0.1, 1 );
show_hudmessage( 0, "%L:^n%d: %2d %L", LANG_PLAYER, "TIME_LEFT", minutes, seconds, LANG_PLAYER, "MINUTES" );
}
/**
* Handle the action to take immediately after half of the time-left or rounds-left passed
* when using the 'Game Server Crash Recreation' Feature.
*/
stock create_game_crash_recreation( secondsLeft )
{
LOG( 128, "I AM ENTERING ON create_game_crash_recreation(0)" )
if( g_isToCreateGameCrashFlag
&& ( g_timeLimitNumber / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR < g_timeLimitNumber - secondsLeft / 60
|| g_fragLimitNumber / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR < g_greatestKillerFrags
|| g_maxRoundsNumber / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR < g_totalRoundsPlayed + 1
|| g_winLimitNumber / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR < g_totalTerroristsWins + g_totalCtWins ) )
{
new gameCrashActionFilePath[ MAX_FILE_PATH_LENGHT ];
// stop creating this file unnecessarily
g_isToCreateGameCrashFlag = false;
LOG( 32, "( vote_manageEnd ) %d/%d < %d: %d", \
g_timeLimitNumber, SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR, g_timeLimitNumber - secondsLeft / 60, \
g_timeLimitNumber / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR < g_timeLimitNumber - secondsLeft / 60 )
LOG( 32, "( vote_manageEnd ) %d/%d < %d: %d", \
g_fragLimitNumber, SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR, g_greatestKillerFrags, \
g_fragLimitNumber / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR < g_greatestKillerFrags )
LOG( 32, "( vote_manageEnd ) %d/%d < %d: %d", \
g_maxRoundsNumber, SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR, g_totalRoundsPlayed + 1, \
g_maxRoundsNumber / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR < g_totalRoundsPlayed + 1 )
LOG( 32, "( vote_manageEnd ) %d/%d < %d: %d", \
g_winLimitNumber, SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR, g_totalTerroristsWins + g_totalCtWins, \
g_winLimitNumber / SERVER_GAME_CRASH_ACTION_RATIO_DIVISOR < g_totalTerroristsWins + g_totalCtWins )
generateGameCrashActionFilePath( gameCrashActionFilePath, charsmax( gameCrashActionFilePath ) );
write_file( gameCrashActionFilePath, "Game Crash Action Flag File^n^nSee the cvar \
'gal_game_crash_recreation'.^nDo not delete it." );
}
}
stock bool:approveTheVotingStart( bool:is_forced_voting )
{
LOG( 128, "I AM ENTERING ON approveTheVotingStart(1)" )
LOG( 4, "( approveTheVotingStart ) is_forced_voting: %d", is_forced_voting )
LOG( 4, "( approveTheVotingStart ) get_real_players_number: %d", get_real_players_number() )
LOG( 4, "( approveTheVotingStart ) cvar_nextMapChangeVotemap: %d", get_pcvar_num( cvar_nextMapChangeVotemap ) )
if( get_pcvar_num( cvar_nextMapChangeVotemap )
&& !is_forced_voting )
{
new nextMapFlag[ 128 ];
new nextMapName[ MAX_MAPNAME_LENGHT ];
formatex( nextMapFlag, charsmax( nextMapFlag ), "%L", LANG_SERVER, "GAL_NEXTMAP_UNKNOWN" );
REMOVE_CODE_COLOR_TAGS( nextMapFlag )
new bool:isNextMapChangeAnnounce = get_pcvar_num( cvar_nextMapChangeAnnounce ) != 0;
get_pcvar_string( cvar_amx_nextmap, nextMapName, charsmax( nextMapName ) );
LOG( 4, "( approveTheVotingStart ) nextMapFlag: %s", nextMapFlag )
LOG( 4, "( approveTheVotingStart ) nextMapName: %s", nextMapName )
LOG( 4, "( approveTheVotingStart ) g_nextMapName: %s", g_nextMapName )
LOG( 4, "( approveTheVotingStart ) isNextMapChangeAnnounce: %d", isNextMapChangeAnnounce )
if( isNextMapChangeAnnounce
&& !equali( nextMapFlag, nextMapName, strlen( nextMapName ) )
|| !isNextMapChangeAnnounce
&& !equali( g_nextMapName, nextMapName, strlen( nextMapName ) ) )
{
// The voting is over, i.e., must to be performed.
g_voteStatus |= IS_VOTE_OVER;
LOG( 1, " ( approveTheVotingStart ) Returning false due the `gal_nextmap_votemap` feature." )
return false;
}
}
// block the voting on some not allowed situations/cases
if( get_real_players_number() == 0
|| g_voteStatus & IS_VOTE_IN_PROGRESS
|| g_voteStatus & IS_RUNOFF_VOTE
|| ( !is_forced_voting
&& g_voteStatus & IS_VOTE_OVER ) )
{
LOG( 1, " ( approveTheVotingStart ) g_voteStatus: %d", g_voteStatus )
LOG( 1, " ( approveTheVotingStart ) g_voteStatus & IS_VOTE_OVER: %d", g_voteStatus & IS_VOTE_OVER != 0 )
#if DEBUG_LEVEL & ( DEBUG_LEVEL_UNIT_TEST_NORMAL | DEBUG_LEVEL_MANUAL_TEST_START | DEBUG_LEVEL_UNIT_TEST_DELAYED )
if( g_test_areTheUnitTestsRunning )
{
LOG( 1, " ( approveTheVotingStart ) Returning true on the if !g_test_areTheUnitTestsRunning" )
return true;
}
#endif
LOG( 1, "( approveTheVotingStart ) cvar_isEmptyCycleByMapChange: %d", get_pcvar_num( cvar_isEmptyCycleByMapChange ) )
// Start the empty cycle on the end of the map, if this feature is enabled
if( get_real_players_number() == 0 )
{
if( get_pcvar_num( cvar_isEmptyCycleByMapChange ) )
{
startEmptyCycleSystem();
}
// If somehow the voting is going on, disables it
if( g_voteStatus & IS_VOTE_IN_PROGRESS )
{
cancelVoting();
}
}
LOG( 1, " ( approveTheVotingStart ) Returning false on the big blocker." )
return false;
}
// allow a new forced voting while the map is ending
if( is_forced_voting
&& g_voteStatus & IS_VOTE_OVER )
{
new bool:roundEndStatus[ SaveRoundEnding ];
saveRoundEnding( roundEndStatus );
cancelVoting();
restoreRoundEnding( roundEndStatus );
restoreOriginalServerMaxSpeed();
}
// the rounds start delay task could be running
remove_task( TASKID_START_VOTING_DELAYED );
// If the voting menu deletion task is running, remove it then delete the menus right now.
if( remove_task( TASKID_DELETE_USERS_MENUS ) )
{
vote_resetStats();
}
if( g_isMapExtensionPeriodRunning
&& !is_forced_voting )
{
LOG( 1, " ( approveTheVotingStart ) Returning false, block the new voting after the map extension." )
return false;
}
LOG( 1, " ( approveTheVotingStart ) Returning true, due passed by all requirements." )
return true;
}
stock bool:approveTheRunoffVotingStart()
{
LOG( 128, "I AM ENTERING ON approveTheRunoffVotingStart(0)" )
LOG( 4, "( approveTheRunoffVotingStart ) get_real_players_number: %d", get_real_players_number() )
// block the voting on some not allowed situations/cases
if( get_real_players_number() == 0
|| ( g_voteStatus & IS_VOTE_OVER )
|| !( g_voteStatus & IS_RUNOFF_VOTE ) )
{
LOG( 1, " ( approveTheRunoffVotingStart ) g_voteStatus: %d", g_voteStatus )
LOG( 1, " ( approveTheRunoffVotingStart ) g_voteStatus & IS_VOTE_OVER: %d", g_voteStatus & IS_VOTE_OVER != 0 )
#if DEBUG_LEVEL & ( DEBUG_LEVEL_UNIT_TEST_NORMAL | DEBUG_LEVEL_MANUAL_TEST_START | DEBUG_LEVEL_UNIT_TEST_DELAYED )
if( g_test_areTheUnitTestsRunning )
{
LOG( 1, " ( approveTheRunoffVotingStart ) Returning true on the if !g_test_areTheUnitTestsRunning" )
LOG( 1, " ( approveTheRunoffVotingStart ) cvar_isEmptyCycleByMapChange: %d", get_pcvar_num( cvar_isEmptyCycleByMapChange ) )
return true;
}
#endif
LOG( 1, " ( approveTheRunoffVotingStart ) Returning false on the first blocker." )
return false;
}
LOG( 1, " ( approveTheRunoffVotingStart ) Returning true, due passed by all requirements." )
return true;
}
stock printVotingMaps( mapNames[][], mapInfos[][], votingMapsCount = MAX_OPTIONS_IN_VOTE )
{
LOG( 128, "I AM ENTERING ON printVotingMaps(3) votingMapsCount: %d", votingMapsCount )
for( new index = 0; index < votingMapsCount; index++ )
{
LOG( 16, "( printVotingMaps ) Voting map %d: %s %s", index, mapNames[ index ], mapInfos[ index ] )
}
LOG( 16, "" )
LOG( 16, "" )
// Removes the compiler warning `warning 203: symbol is never used` with some DEBUG levels.
if( mapNames[ 0 ][ 0 ] && mapInfos[ 0 ][ 0 ] ) { }
return 0;
}
stock loadRunOffVoteChoices()
{
LOG( 128, "I AM ENTERING ON loadRunOffVoteChoices(0)" )
new runoffChoiceName[ MAX_OPTIONS_IN_VOTE ][ MAX_MAPNAME_LENGHT ];
new runoffChoiceInfo[ MAX_OPTIONS_IN_VOTE ][ MAX_MAPNAME_LENGHT ];
// Create a clean copy to not copy overridden maps
for( new mapIndex = 0; mapIndex < g_totalVoteOptions; mapIndex++ )
{
copy( runoffChoiceName[ mapIndex ], charsmax( runoffChoiceName[] ), g_votingMapNames[ g_arrayOfRunOffChoices[ mapIndex ] ] );
copy( runoffChoiceInfo[ mapIndex ], charsmax( runoffChoiceInfo[] ), g_votingMapInfos[ g_arrayOfRunOffChoices[ mapIndex ] ] );
}
// Load runoff choices
for( new mapIndex = 0; mapIndex < g_totalVoteOptions; mapIndex++ )
{
copy( g_votingMapNames[ mapIndex ], charsmax( g_votingMapNames[] ), runoffChoiceName[ mapIndex ] );
copy( g_votingMapInfos[ mapIndex ], charsmax( g_votingMapInfos[] ), runoffChoiceInfo[ mapIndex ] );
}
SET_VOTING_TIME_TO( g_votingSecondsRemaining, cvar_runoffDuration )
LOG( 0, "", printVotingMaps( g_votingMapNames, g_votingMapInfos, g_totalVoteOptions ) )
LOG( 1, " ( loadRunOffVoteChoices ) g_totalVoteOptions: %d", g_totalVoteOptions )
return g_totalVoteOptions;
}
stock configureVotingStart( bool:is_forced_voting )
{
LOG( 128, "I AM ENTERING ON configureVotingStart(1) is_forced_voting: %d", is_forced_voting )
// update cached data for the new voting
cacheCvarsValues();
// make it known that a vote is in progress
g_voteStatus |= IS_VOTE_IN_PROGRESS;
// Make sure this is not pointing to anything before the voting.
g_invokerVoteMapNameToDecide[ 0 ] = '^0';
// Set the voting status to forced
if( is_forced_voting )
{
g_voteStatus |= IS_FORCED_VOTE;
}
configureTheExtensionOption( is_forced_voting );
LOG( 4, "( configureVotingStart ) g_voteStatus: %d, ", g_voteStatus )
LOG( 4, "( configureVotingStart ) g_voteMapStatus: %d, ", g_voteMapStatus )
// stop RTV reminders
remove_task( TASKID_RTV_REMINDER );
}
/**
* To allow show the extension option as `Stay Here` and `Extend` and to set the end voting type.
*/
stock configureTheExtensionOption( bool:is_forced_voting )
{
LOG( 128, "I AM ENTERING ON configureTheExtensionOption(1) is_forced_voting: %d", is_forced_voting )
new Float:cache;
// If we cannot find anything cancelling/blocking the map extension, allow it by the default.
if( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXTENSION )
{
LOG( 4, "( configureTheExtensionOption ) 1. " )
g_isMapExtensionAllowed = false;
}
else if( g_voteStatus & IS_RTV_VOTE
&& get_pcvar_num( cvar_rtvWaitAdmin ) & IS_TO_RTV_NOT_ALLOW_STAY )
{
LOG( 4, "( configureTheExtensionOption ) 2. " )
g_isMapExtensionAllowed = false;
}
else if( g_endVotingType & IS_BY_FRAGS
&& ( cache = Float:get_pcvar_num( cvar_maxMapExtendFrags ) ) )
{
LOG( 4, "( configureTheExtensionOption ) 3. " )
g_isMapExtensionAllowed =
GAME_ENDING_CONTEXT_SAVED( g_fragLimitContextSaved, get_pcvar_num( cvar_mp_fraglimit ) ) < cache;
}
else if( g_endVotingType & IS_BY_ROUNDS
&& ( cache = Float:get_pcvar_num( cvar_maxMapExtendRounds ) ) )
{
LOG( 4, "( configureTheExtensionOption ) 4. " )
g_isMapExtensionAllowed =
GAME_ENDING_CONTEXT_SAVED( g_maxRoundsContextSaved, get_pcvar_num( cvar_mp_maxrounds ) ) < cache;
}
else if( g_endVotingType & IS_BY_WINLIMIT
&& ( cache = get_pcvar_float( cvar_maxMapExtendRounds ) ) )
{
LOG( 4, "( configureTheExtensionOption ) 5. " )
g_isMapExtensionAllowed =
GAME_ENDING_CONTEXT_SAVED( g_winLimitContextSaved, get_pcvar_num( cvar_mp_winlimit ) ) < cache;
}
else if( g_endVotingType & IS_BY_TIMER
&& ( cache = get_pcvar_float( cvar_maxMapExtendTime ) ) )
{
LOG( 4, "( configureTheExtensionOption ) 6. " )
g_isMapExtensionAllowed =
GAME_ENDING_CONTEXT_SAVED( g_timeLimitContextSaved, get_pcvar_float( cvar_mp_timelimit ) ) < cache;
}
else
{
LOG( 4, "( configureTheExtensionOption ) 7. " )
g_isMapExtensionAllowed = true;
}
g_isGameFinalVoting = ( ( g_endVotingType & IS_BY_ROUNDS
|| g_endVotingType & IS_BY_WINLIMIT
|| g_endVotingType & IS_BY_TIMER
|| g_endVotingType & IS_BY_FRAGS )
&& !is_forced_voting );
LOG( 4, "( configureTheExtensionOption ) g_endVotingType: %d", g_endVotingType )
LOG( 4, "( configureTheExtensionOption ) is_forced_voting: %d", is_forced_voting )
LOG( 4, "( configureTheExtensionOption ) g_isGameFinalVoting: %d", g_isGameFinalVoting )
LOG( 4, "( configureTheExtensionOption ) g_isMapExtensionAllowed: %d", g_isMapExtensionAllowed )
}
/**
* Any voting not started by `cvar_endOfMapVoteStart`, `cvar_endOnRound` or ending limit expiration,
* is a forced voting.
*/
stock startTheVoting( bool:is_forced_voting )
{
LOG( 128, "I AM ENTERING ON startTheVoting(1) is_forced_voting: %d", is_forced_voting )
if( !approveTheVotingStart( is_forced_voting ) )
{
LOG( 1, " ( startTheVoting ) Just Returning/blocking, the voting was not approved." )
return;
}
// Clear the cmd_startVote(3) map settings just in case they where loaded.
// Clean it just to be sure as the voteMapMenuBuilder() could let it filled.
clearTheVotingMenu();
g_voteMapStatus = 0;
// to prepare the initial voting state
configureVotingStart( is_forced_voting );
// To load vote choices and show up the voting menu
if( loadTheDefaultVotingChoices() )
{
initializeTheVoteDisplay();
}
else
{
// Vote creation failed; no maps found.
color_print( 0, "%L", LANG_PLAYER, "GAL_VOTE_NOMAPS" );
finalizeVoting();
}
LOG( 4, "" )
LOG( 4, " ( startTheVoting|out ) g_isTheLastGameRound: %d", g_isTheLastGameRound )
LOG( 4, " ( startTheVoting|out ) g_isTimeToRestart: %d", g_isTimeToRestart )
LOG( 4, " ( startTheVoting|out ) g_voteStatus & IS_FORCED_VOTE: %d", g_voteStatus & IS_FORCED_VOTE != 0 )
}
/**
* Any voting not started by `cvar_endOfMapVoteStart`, `cvar_endOnRound` or ending limit expiration,
* is a forced voting.
*/
public startTheRunoffVoting()
{
LOG( 128, "I AM ENTERING ON startTheRunoffVoting(0)" )
if( !approveTheRunoffVotingStart() )
{
LOG( 1, " ( startTheRunoffVoting ) Just Returning/blocking, the voting was not approved." )
return;
}
// To load runoff vote choices and show up the voting menu
if( loadRunOffVoteChoices() )
{
initializeTheVoteDisplay();
}
else
{
// Vote creation failed; no maps found.
color_print( 0, "%L", LANG_PLAYER, "GAL_VOTE_NOMAPS" );
finalizeVoting();
}
LOG( 4, "" )
LOG( 4, " ( startTheRunoffVoting|out ) g_isTheLastGameRound: %d", g_isTheLastGameRound )
LOG( 4, " ( startTheRunoffVoting|out ) g_isTimeToRestart: %d", g_isTimeToRestart )
LOG( 4, " ( startTheRunoffVoting|out ) g_voteStatus & IS_FORCED_VOTE: %d", g_voteStatus & IS_FORCED_VOTE != 0 )
}
/**
* Sort static the static array `array[]` and keep the array `arraySync` updated with it.
*
* @param array a sorted two-dimensional static array
* @param arraySync a synced two-dimensional static array with the first parameter `array[][]`
* @param elementSize the size of the elements to be inserted
*/
stock SortCustomSynced2D( array[][], arraySync[][], arraySize )
{
LOG( 128, "I AM ENTERING ON SortCustomSynced2D(3) arraySize: %d", arraySize )
LOG( 0, "", printVotingMaps( array, arraySync ) )
new outerIndex;
new innerIndex;
new tempElement [ MAX_MAPNAME_LENGHT ];
new tempElementSync[ MAX_MAPNAME_LENGHT ];
for( outerIndex = 0; outerIndex < arraySize; outerIndex++ )
{
for( innerIndex = outerIndex + 1; innerIndex < arraySize; innerIndex++ )
{
if( strcmp( array[ outerIndex ], array[ innerIndex ] ) > 0 )
{
copy( tempElement , charsmax( tempElement ), array [ outerIndex ] );
copy( tempElementSync, charsmax( tempElementSync ), arraySync[ outerIndex ] );
copy( array [ outerIndex ], charsmax( tempElement ), array [ innerIndex ] );
copy( arraySync[ outerIndex ], charsmax( tempElement ), arraySync[ innerIndex ] );
copy( array [ innerIndex ], charsmax( tempElement ), tempElement );
copy( arraySync[ innerIndex ], charsmax( tempElementSync ), tempElementSync );
}
}
}
LOG( 0, "", printVotingMaps( array, arraySync ) )
}
stock initializeTheVoteDisplay()
{
LOG( 128, "I AM ENTERING ON initializeTheVoteDisplay(0)" )
new player_id;
new playersCount;
new players[ MAX_PLAYERS ];
new Float:handleChoicesDelay;
// Clear all nominations
nomination_clearAll();
// Alphabetize the maps
SortCustomSynced2D( g_votingMapNames, g_votingMapInfos, g_totalVoteOptions );
// Skip bots and hltv
get_players( players, playersCount, "ch" );
// Mark the players who are in this vote for use later
for( new playerIndex = 0; playerIndex < playersCount; ++playerIndex )
{
player_id = players[ playerIndex ];
if( g_isPlayerParticipating[ player_id ] )
{
g_isPlayerVoted[ player_id ] = false;
}
}
// Adjust the choices delay for the Unit Tests run or normal work flow
#if DEBUG_LEVEL & DEBUG_LEVEL_FAKE_VOTES
g_votingSecondsRemaining = 5;
handleChoicesDelay = 0.1;
#else
new isToAskForEndOfTheMapVote;
isToAskForEndOfTheMapVote = get_pcvar_num( cvar_isToAskForEndOfTheMapVote );
if( g_voteMapStatus & IS_DISABLED_VOTEMAP_INTRO
|| isToAskForEndOfTheMapVote & END_OF_MAP_VOTE_NO_ANNOUNCEMENT )
{
handleChoicesDelay = 0.1;
}
else
{
// Set_task 1.0 + pendingVoteCountdown 1.0
handleChoicesDelay = VOTE_TIME_SEC + VOTE_TIME_SEC + getVoteAnnouncementTime( isToAskForEndOfTheMapVote );
// Make perfunctory announcement: "get ready to choose a map"
if( !( get_pcvar_num( cvar_soundsMute ) & SOUND_GET_READY_TO_CHOOSE ) )
{
client_cmd( 0, "spk ^"get red( e80 ) ninety( s45 ) to check( e20 ) \
use bay( s18 ) mass( e42 ) cap( s50 )^"" );
}
// Announce the pending vote countdown from 7 | 5 to 1
if( isToAskForEndOfTheMapVote & END_OF_MAP_VOTE_ANNOUNCE1 )
{
if( isToAskForEndOfTheMapVote & END_OF_MAP_VOTE_ANNOUNCE2 )
{
set_task( VOTE_TIME_ANNOUNCE2, "announceThePendingVote", TASKID_PENDING_VOTE_COUNTDOWN );
announceThePendingVoteTime( VOTE_TIME_ANNOUNCE2 + VOTE_TIME_HUD2 );
}
else
{
set_task( VOTE_TIME_ANNOUNCE1, "announceThePendingVote", TASKID_PENDING_VOTE_COUNTDOWN );
announceThePendingVoteTime( VOTE_TIME_ANNOUNCE1 + VOTE_TIME_HUD1 );
}
}
else
{
// Visual countdown
announceThePendingVote();
announceThePendingVoteTime( VOTE_TIME_HUD1 );
}
}
#endif
// To create fake votes when needed
#if DEBUG_LEVEL & DEBUG_LEVEL_FAKE_VOTES
set_task( 2.0, "create_fakeVotes", TASKID_DBG_FAKEVOTES );
#endif
// Set debug options
LOG( 0, "", configureVoteDisplayDebugging() )
// Display the map choices, 1 second from now
set_task( handleChoicesDelay, "vote_handleDisplay", TASKID_VOTE_HANDLEDISPLAY );
}
stock announceThePendingVoteTime( Float:time )
{
LOG( 128, "I AM ENTERING ON announceThePendingVoteTime(1) time: %f", time )
new targetTime = floatround( time, floatround_floor );
color_print( 0, "%L", LANG_PLAYER, "DMAP_NEXTMAP_VOTE_REMAINING2", targetTime );
// If there is enough time
if( targetTime > 4
&& !( get_pcvar_num( cvar_hudsHide ) & HUD_VOTE_VISUAL_COUNTDOWN ) )
{
set_hudmessage( 0, 222, 50, -1.0, 0.13, 1, 1.0, 4.94, 0.0, 0.0, -1 );
show_hudmessage( 0, "%L", LANG_PLAYER, "DMAP_NEXTMAP_VOTE_REMAINING1", targetTime );
}
}
public announceThePendingVote()
{
LOG( 128, "I AM ENTERING ON announceThePendingVote(0)" )
if( get_pcvar_num( cvar_isToAskForEndOfTheMapVote ) & END_OF_MAP_VOTE_ANNOUNCE1 )
{
g_pendingVoteCountdown = floatround( VOTE_TIME_HUD2, floatround_floor ) + 1;
}
else
{
g_pendingVoteCountdown = floatround( VOTE_TIME_HUD1, floatround_floor ) + 1;
}
set_task( VOTE_TIME_SEC, "pendingVoteCountdown", TASKID_PENDING_VOTE_COUNTDOWN, _, _, "a", g_pendingVoteCountdown );
}
stock Float:getVoteAnnouncementTime( isToAskForEndOfTheMapVote )
{
LOG( 128, "I AM ENTERING ON getVoteAnnouncementTime(0)" )
if( isToAskForEndOfTheMapVote & END_OF_MAP_VOTE_NO_ANNOUNCEMENT )
{
return 1.0;
}
else
{
if( isToAskForEndOfTheMapVote & END_OF_MAP_VOTE_ANNOUNCE1 )
{
if( isToAskForEndOfTheMapVote & END_OF_MAP_VOTE_ANNOUNCE2 )
{
return VOTE_TIME_ANNOUNCE2 + VOTE_TIME_HUD2;
}
else
{
return VOTE_TIME_ANNOUNCE1 + VOTE_TIME_HUD2;
}
}
}
return VOTE_TIME_HUD1;
}
stock configureVoteDisplayDebugging()
{
// Print the voting map options
new voteOptions = ( g_totalVoteOptions == 1 ? 2 : g_totalVoteOptions );
LOG( 4, "" )
LOG( 4, "" )
LOG( 4, " [PLAYER CHOICES]" )
for( new dbgChoice = 0; dbgChoice < voteOptions; dbgChoice++ )
{
LOG( 4, " %i. %s %s", dbgChoice + 1, g_votingMapNames[ dbgChoice ], g_votingMapInfos[ dbgChoice ] )
}
return 0;
}
public pendingVoteCountdown()
{
LOG( 128, "I AM ENTERING ON pendingVoteCountdown(0) g_pendingVoteCountdown: %d", g_pendingVoteCountdown )
if( get_pcvar_num( cvar_isToAskForEndOfTheMapVote ) & END_OF_MAP_VOTE_ASK
&& !( g_voteStatus & IS_RUNOFF_VOTE ) )
{
displayEndOfTheMapVoteMenu( 0 );
}
// We increase it 1 more, and remove it later to allow the displayEndOfTheMapVoteMenu(1) to automatically
// select the Yes option when the counter hits 1.
if( g_pendingVoteCountdown > 1 )
{
// visual countdown
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_VOTE_VISUAL_COUNTDOWN ) )
{
set_hudmessage( 0, 222, 50, -1.0, 0.13, 0, 1.0, 0.94, 0.0, 0.0, -1 );
show_hudmessage( 0, "%L", LANG_PLAYER, "GAL_VOTE_COUNTDOWN", g_pendingVoteCountdown - 1 );
}
// audio countdown
if( !( get_pcvar_num( cvar_soundsMute ) & SOUND_COUNTDOWN ) )
{
new word[ 6 ];
num_to_word( g_pendingVoteCountdown - 1, word, 5 );
client_cmd( 0, "spk ^"fvox/%s^"", word );
}
}
// decrement the countdown
g_pendingVoteCountdown--;
}
public displayEndOfTheMapVoteMenu( player_id )
{
LOG( 128, "I AM ENTERING ON displayEndOfTheMapVoteMenu(1) player_id: %d", player_id )
static menu_body [ MAX_LONG_STRING ];
static menu_counter[ MAX_SHORT_STRING ];
new menu_id;
new menuKeys;
new menuKeysUnused;
new playersCount;
new players[ MAX_PLAYERS ];
new bool:isVoting;
new bool:playerAnswered;
if( player_id > 0 )
{
playersCount = 1;
players[ 0 ] = player_id;
}
else
{
get_players( players, playersCount, "ch" );
}
for( new playerIndex = 0; playerIndex < playersCount; playerIndex++ )
{
// If the player does not has completely closed the menu for good.
if( !g_isPlayerClosedTheVoteMenu[ ( player_id = players[ playerIndex ] ) ] )
{
isVoting = g_isPlayerParticipating[ player_id ];
playerAnswered = g_answeredForEndOfMapVote[ player_id ] || g_pendingVoteCountdown < 2;
menu_body [ 0 ] = '^0';
menu_counter[ 0 ] = '^0';
if( !playerAnswered )
{
menuKeys = MENU_KEY_0 | MENU_KEY_6;
formatex( menu_counter, charsmax( menu_counter ),
" %s(%s%d %L%s)",
COLOR_YELLOW, COLOR_GREY, g_pendingVoteCountdown - 1, LANG_PLAYER, "GAL_TIMELEFT", COLOR_YELLOW );
}
else
{
// The close for good option key
menuKeys = MENU_KEY_0;
}
formatex( menu_body, charsmax( menu_body ),
"%s%L^n^n\
%s6. %s%L %s^n\
%s0. %s%L",
COLOR_YELLOW, player_id, "GAL_CHOOSE_QUESTION",
COLOR_RED, ( playerAnswered ? ( isVoting ? COLOR_YELLOW : COLOR_GREY ) : COLOR_WHITE ),
player_id, "GAL_CHOOSE_QUESTION_YES", menu_counter,
COLOR_RED, ( playerAnswered ? ( !isVoting ? COLOR_YELLOW : COLOR_GREY ) : COLOR_WHITE ),
player_id, ( playerAnswered && !isVoting ? "GAL_OPTION_NONE_VOTE" : "GAL_CHOOSE_QUESTION_NO" ) );
get_user_menu( player_id, menu_id, menuKeysUnused );
if( menu_id == 0
|| menu_id == g_chooseMapQuestionMenuId )
{
show_menu( player_id, menuKeys, menu_body, ( g_pendingVoteCountdown == 1 ? 1 : 2 ), CHOOSE_MAP_MENU_QUESTION );
}
LOG( 4, " ( displayEndOfTheMapVoteMenu| for ) menu_body: %s", menu_body )
LOG( 4, " menu_id:%d, menuKeys: %d, isVoting: %d, playerAnswered:%d, \
player_id: %d, playerIndex: %d", menu_id, menuKeys, isVoting, playerAnswered, \
player_id, playerIndex )
LOG( 4, " playersCount: %d, g_pendingVoteCountdown: %d, menu_counter: %s", \
playersCount, g_pendingVoteCountdown, menu_counter )
}
}
LOG( 4, "%48s", " ( displayEndOfTheMapVoteMenu| out )" )
}
public handleEndOfTheMapVoteChoice( player_id, pressedKeyCode )
{
LOG( 128, "I AM ENTERING ON handleEndOfTheMapVoteChoice(2) player_id: %d, pressedKeyCode: %d", \
player_id, pressedKeyCode )
// pressedKeyCode 0 means the keyboard key 1
if( !g_answeredForEndOfMapVote[ player_id ]
&& pressedKeyCode == 9 )
{
announceRegistedVote( player_id, pressedKeyCode );
g_isPlayerVoted[ player_id ] = true;
g_isPlayerParticipating[ player_id ] = false;
}
else if( g_answeredForEndOfMapVote[ player_id ]
&& !g_isPlayerParticipating[ player_id ]
&& pressedKeyCode == 9 )
{
g_isPlayerClosedTheVoteMenu[ player_id ] = true;
LOG( 1, " ( handleEndOfTheMapVoteChoice ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
g_answeredForEndOfMapVote[ player_id ] = true;
// displayEndOfTheMapVoteMenu( player_id );
set_task( 0.1, "displayEndOfTheMapVoteMenu", player_id );
LOG( 1, " ( handleEndOfTheMapVoteChoice ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
public vote_handleDisplay()
{
LOG( 128, "I AM ENTERING ON vote_handleDisplay(0)" )
// announce: "time to choose"
if( !( get_pcvar_num( cvar_soundsMute ) & SOUND_TIME_TO_CHOOSE ) )
{
client_cmd( 0, "spk Gman/Gman_Choose%i", random_num( 1, 2 ) );
}
if( g_showVoteStatus
&& g_showVoteStatusType & STATUS_TYPE_PERCENTAGE )
{
copy( g_voteStatus_symbol, charsmax( g_voteStatus_symbol ), "%" );
}
// ensure the vote status doesn't indicate expired
g_voteStatus &= ~IS_VOTE_EXPIRED;
if( g_showVoteStatus == SHOW_STATUS_ALWAYS
|| g_showVoteStatus == SHOW_STATUS_AFTER_VOTE
|| g_showVoteStatus == SHOW_STATUS_ALWAYS_UNTIL_VOTE )
{
new argument[ 2 ] = { true, 0 };
set_task( 1.0, "vote_display", TASKID_VOTE_DISPLAY, argument, sizeof argument, "a", g_votingSecondsRemaining );
}
else // g_showVoteStatus == SHOW_STATUS_AT_END || g_showVoteStatus == SHOW_STATUS_NEVER
{
set_task( 1.0, "tryToShowTheVotingMenu", TASKID_VOTE_DISPLAY, _, _, "a", g_votingSecondsRemaining );
}
// display the vote outcome
set_task( float( g_votingSecondsRemaining ), "closeVoting", TASKID_VOTE_EXPIRE );
}
public tryToShowTheVotingMenu()
{
LOG( 128, "I AM ENTERING ON tryToShowTheVotingMenu(0)" )
new player_id;
new playersCount;
new players[ MAX_PLAYERS ];
new argument[ 2 ] = { false, 0 };
get_players( players, playersCount, "ch" ); // skip bots and hltv
for( new player_index; player_index < playersCount; ++player_index )
{
player_id = players[ player_index ];
if( !g_isPlayerSeeingTheVoteMenu[ player_id ]
&& isPlayerAbleToSeeTheVoteMenu( player_id ) )
{
argument[ 1 ] = player_id;
g_isPlayerSeeingTheVoteMenu[ player_id ] = true;
// Allow lazy players to see the menu when the `SHOW_STATUS_ALWAYS` is not set.
vote_display( argument );
}
}
}
public closeVoting()
{
LOG( 128, "I AM ENTERING ON closeVoting(0)" )
new argument[ 2 ] = { false, -1 };
// waits until the last voting second to finish
set_task( VOTE_TIME_SEC - 0.1, "voteExpire" );
set_task( VOTE_TIME_SEC, "vote_display", TASKID_VOTE_DISPLAY, argument, sizeof argument, "a", 3 );
// set_task( 1.5, "delete_users_menus_care", TASKID_DELETE_USERS_MENUS_CARE );
set_task( VOTE_TIME_COUNT, "computeVotes", TASKID_VOTE_EXPIRE );
}
public voteExpire()
{
LOG( 128, "I AM ENTERING ON voteExpire(0)" )
g_voteStatus |= IS_VOTE_EXPIRED;
// For the results to show up on the `Voting Results` menu.
if( !g_showVoteStatusType
&& g_showVoteStatus )
{
g_showVoteStatusType = STATUS_TYPE_PERCENTAGE;
}
// This is necessary because the SubVote Menu is not closing automatically when the voting finishes,
// then the voting results are being displayed forcing the SubMenu to looks like it is not closing
// never and ever, but actually it is just being displayed 3 times as it was the voting results.
arrayset( g_isPlayerSeeingTheSubMenu, false, sizeof g_isPlayerSeeingTheSubMenu );
}
/**
* This function is called with the correct player id only after the player voted or by the
* 'tryToShowTheVotingMenu(0)' function call.
*/
public vote_display( argument[ 2 ] )
{
LOG( 4, "I AM ENTERING ON vote_display(1)" )
new menuKeys;
static voteStatus [ MAX_BIG_BOSS_STRING - 100 ];
static mapVotingCount[ MAX_MAPNAME_LENGHT + 32 ];
new copiedChars = 0;
new player_id = argument[ 1 ];
new updateTimeRemaining = argument[ 0 ];
new bool:isVoteOver = g_voteStatus & IS_VOTE_EXPIRED != 0;
new bool:noneIsHidden = ( g_isToShowNoneOption
&& !g_voteShowNoneOptionType
&& !isVoteOver );
// Update time remaining
if( updateTimeRemaining ) g_votingSecondsRemaining--;
LOG( 4, " ( votedisplay ) player_id: %d", argument[ 1 ] )
LOG( 4, " ( votedisplay ) g_voteStatus: %d", g_voteStatus )
LOG( 4, " ( votedisplay ) updateTimeRemaining: %d", argument[ 0 ] )
LOG( 4, " ( votedisplay ) g_totalVoteOptions: %d", g_totalVoteOptions )
LOG( 4, " ( votedisplay ) g_votingSecondsRemaining: %d", g_votingSecondsRemaining )
LOG( 4, " ( votedisplay ) strlen( g_voteStatusClean ): %d", strlen( g_voteStatusClean ) )
// wipe the previous vote status
voteStatus[ 0 ] = '^0';
// register the 'None' option key
if( g_isToShowSubMenu || ( g_isToShowNoneOption && !isVoteOver ) ) menuKeys = MENU_KEY_0;
// add maps to the menu
for( new choiceIndex = 0; choiceIndex < g_totalVoteOptions; ++choiceIndex )
{
menuKeys |= ( 1 << choiceIndex );
computeMapVotingCount( mapVotingCount, charsmax( mapVotingCount ), choiceIndex );
copiedChars += formatex( voteStatus[ copiedChars ], charsmax( voteStatus ) - copiedChars,
"^n%s%d.%s %s\
%s\
%s%s",
COLOR_RED, choiceIndex + 1, COLOR_WHITE, g_votingMapNames[ choiceIndex ],
g_votingMapInfos[ choiceIndex ][ 0 ] ? " " : "",
g_votingMapInfos[ choiceIndex ], mapVotingCount );
}
// Make a copy of the virgin menu, using the first player's menu as base. To not make this
// causes all the subsequent clean menus being displayed on the first player language, we do not
// save it after adding the first LANG constant from the multilingual dictionary.
if( g_voteStatusClean[ 0 ] == '^0' )
{
copy( g_voteStatusClean, charsmax( g_voteStatusClean ), voteStatus );
}
// This is to optionally display to single player that just voted or never saw the menu.
// This function is called with the correct player id only after the player voted or by the
// 'tryToShowTheVotingMenu(0)' function call.
if( player_id > 0 )
{
if( g_isPlayerClosedTheVoteMenu[ player_id ] )
{
// Do nothing
}
else if( g_isToShowSubMenu
&& g_isPlayerSeeingTheSubMenu[ player_id ] )
{
dispaly_the_vote_sub_menu( player_id );
}
else if( g_showVoteStatus == SHOW_STATUS_ALWAYS
|| g_showVoteStatus == SHOW_STATUS_AFTER_VOTE )
{
menuKeys = addExtensionOption( player_id, copiedChars, voteStatus, charsmax( voteStatus ), menuKeys );
display_menu_dirt( player_id, menuKeys, isVoteOver, noneIsHidden, voteStatus );
}
else if( g_showVoteStatus != SHOW_STATUS_ALWAYS_UNTIL_VOTE )
{
// g_showVoteStatus == SHOW_STATUS_NEVER || g_showVoteStatus == SHOW_STATUS_AT_END
display_menu_clean( player_id, menuKeys );
}
}
else // just display to everyone
{
new playersCount;
new players[ MAX_PLAYERS ];
get_players( players, playersCount, "ch" ); // skip bots and hltv
for( new playerIndex = 0; playerIndex < playersCount; ++playerIndex )
{
player_id = players[ playerIndex ];
if( g_isPlayerClosedTheVoteMenu[ player_id ] )
{
continue;
}
else if( g_isToShowSubMenu
&& g_isPlayerSeeingTheSubMenu[ player_id ] )
{
dispaly_the_vote_sub_menu( player_id );
}
else
{
if( !g_isPlayerVoted[ player_id ]
&& !isVoteOver
&& g_showVoteStatus != SHOW_STATUS_ALWAYS
&& g_showVoteStatus != SHOW_STATUS_ALWAYS_UNTIL_VOTE )
{
display_menu_clean( player_id, menuKeys );
}
else if( g_showVoteStatus == SHOW_STATUS_ALWAYS
|| ( g_showVoteStatus == SHOW_STATUS_ALWAYS_UNTIL_VOTE
&& !g_isPlayerVoted[ player_id ] )
|| ( isVoteOver
&& g_showVoteStatus )
|| ( g_isPlayerVoted[ player_id ]
&& g_showVoteStatus == SHOW_STATUS_AFTER_VOTE ) )
{
menuKeys = addExtensionOption( player_id, copiedChars, voteStatus, charsmax( voteStatus ), menuKeys );
display_menu_dirt( player_id, menuKeys, isVoteOver, noneIsHidden, voteStatus );
}
}
}
}
}
stock dispaly_the_vote_sub_menu( player_id )
{
LOG( 128, "I AM ENTERING ON dispaly_the_vote_sub_menu(1) player_id: %d", player_id )
static menu_body[ MAX_LONG_STRING ];
new menuKeys = MENU_KEY_0 | MENU_KEY_5;
new bool:canVoteNone = !g_isPlayerVoted[ player_id ];
new bool:canCancel = !g_isPlayerCancelledVote[ player_id ] && g_isPlayerVoted[ player_id ];
menuKeys |= canVoteNone ? MENU_KEY_1 : 0;
menuKeys |= canCancel ? MENU_KEY_3 : 0;
menu_body[ 0 ] = '^0';
formatex( menu_body, charsmax( menu_body ),
"%s%L^n^n\
%s1.%s %L^n\
%s3.%s %L^n\
%s5.%s %L^n\
^n%s0.%s %L",
COLOR_YELLOW, player_id, "CMD_MENU",
COLOR_RED, canVoteNone ? COLOR_WHITE : COLOR_GREY, player_id, "GAL_OPTION_NONE_VOTE",
COLOR_RED, canCancel ? COLOR_WHITE : COLOR_GREY, player_id, "GAL_OPTION_CANCEL_VOTE",
COLOR_RED, COLOR_WHITE, player_id, "EXIT",
COLOR_RED, COLOR_WHITE, player_id, "BACK",
player_id );
display_vote_menu( true, player_id, menu_body, menuKeys );
LOG( 4, "%48s", " ( dispaly_the_vote_sub_menu| out )" )
}
stock processSubMenuKeyHit( player_id, key )
{
LOG( 4, "I AM ENTERING ON processSubMenuKeyHit(2) player_id: %d, key: %d", player_id, key )
switch( key )
{
case 0: // key 1
{
// None option
register_vote( player_id, 9 );
}
case 2: // key 3
{
// Cancel vote option
if( !g_isPlayerCancelledVote[ player_id ] )
{
cancel_player_vote( player_id );
}
}
case 4: // key 5
{
// Exit option
g_isPlayerClosedTheVoteMenu[ player_id ] = true;
return;
}
}
reshowTheVoteMenu( player_id );
}
stock addExtensionOption( player_id, copiedChars, voteStatus[], voteStatusLenght, menuKeys, bool:isToAddResults = true )
{
LOG( 4, "I AM ENTERING ON addExtensionOption(6) player_id: %d", player_id )
LOG( 4, "( addExtensionOption ) voteStatusLenght: %d, menuKeys: %d, copiedChars: %d", voteStatusLenght, menuKeys, copiedChars )
new bool:allowStay;
new bool:allowExtend;
allowExtend = ( g_isGameFinalVoting
&& !( g_voteStatus & IS_RUNOFF_VOTE ) );
allowStay = ( g_isExtendmapAllowStay
&& !g_isGameFinalVoting
&& !( g_voteStatus & IS_RUNOFF_VOTE ) );
if( g_isRunOffNeedingKeepCurrentMap )
{
// if it is a end map RunOff, then it is a extend button, not a keep current map button
if( g_isGameFinalVoting )
{
allowExtend = true;
allowStay = false;
}
else
{
allowExtend = false;
allowStay = true;
}
}
LOG( 4, " ( addExtensionOption ) Add optional menu item, allowStay: %d, allowExtend: %d, \
g_isExtendmapAllowStay: %d", allowStay, allowExtend, g_isExtendmapAllowStay )
// add optional menu item
if( IS_MAP_EXTENSION_ALLOWED()
&& ( allowExtend
|| allowStay ) )
{
new mapVotingCount[ MAX_MAPNAME_LENGHT ];
new bool:isToAddExtraLine = !( g_totalVoteOptions == 1
&& !g_isRunOffNeedingKeepCurrentMap );
// if it's not a runoff vote, add a space between the maps and the additional option
if( !( g_voteStatus & IS_RUNOFF_VOTE ) )
{
copiedChars += formatex( voteStatus[ copiedChars ], voteStatusLenght - copiedChars, "^n" );
}
computeMapVotingCount( mapVotingCount, charsmax( mapVotingCount ), g_totalVoteOptions, isToAddResults );
// The extension option has priority over the stay here option.
if( allowExtend )
{
new extend_step = 15;
new extend_option_type[ 32 ];
// add the "Extend Map" menu item.
if( g_endVotingType & ( IS_BY_ROUNDS | IS_BY_WINLIMIT ) )
{
extend_step = g_extendmapStepRounds;
copy( extend_option_type, charsmax( extend_option_type ), "GAL_OPTION_EXTEND_ROUND" );
}
else if( g_endVotingType & IS_BY_FRAGS )
{
extend_step = g_extendmapStepFrags;
copy( extend_option_type, charsmax( extend_option_type ), "GAL_OPTION_EXTEND_FRAGS" );
}
else
{
extend_step = g_extendmapStepMinutes;
copy( extend_option_type, charsmax( extend_option_type ), "GAL_OPTION_EXTEND" );
}
copiedChars += formatex( voteStatus[ copiedChars ], voteStatusLenght - copiedChars,
"%s%s%i. \
%s%L\
%s",
isToAddExtraLine ? "^n" : "", COLOR_RED, g_totalVoteOptions + 1,
COLOR_WHITE, player_id, extend_option_type, g_currentMapName, extend_step,
mapVotingCount );
}
else
{
// add the "Stay Here" menu item
if( g_extendmapAllowStayType )
{
copiedChars += formatex( voteStatus[ copiedChars ], voteStatusLenght - copiedChars,
"%s%s%i. \
%s%L\
%s",
isToAddExtraLine ? "^n" : "", COLOR_RED, g_totalVoteOptions + 1,
COLOR_WHITE, player_id, "GAL_OPTION_STAY_MAP", g_currentMapName,
mapVotingCount );
}
else
{
copiedChars += formatex( voteStatus[ copiedChars ], voteStatusLenght - copiedChars,
"%s%s%i. \
%s%L\
%s",
isToAddExtraLine ? "^n" : "", COLOR_RED, g_totalVoteOptions + 1,
COLOR_WHITE, player_id, "GAL_OPTION_STAY",
mapVotingCount );
}
}
// Added the extension/stay key option (1 << 2 = key 3, 1 << 3 = key 4, ...)
menuKeys |= ( 1 << g_totalVoteOptions );
}
LOG( 256, " ( addExtensionOption ) Returning menuKeys: %d", menuKeys )
return menuKeys;
}
stock display_menu_dirt( player_id, menuKeys, bool:isVoteOver, bool:noneIsHidden, voteStatus[] )
{
LOG( 256, "I AM ENTERING ON display_menu_dirt(5) player_id: %d", player_id )
LOG( 256, "( display_menu_dirt ) menuKeys: %d, noneIsHidden: %d, isVoteOver: %d", menuKeys, noneIsHidden, isVoteOver )
new bool:isToShowUndo;
new bool:isToAddExtraLine;
// menu showed after voted
static menuDirty[ MAX_BIG_BOSS_STRING ];
static voteFooter[ MAX_SHORT_STRING ];
static menuHeader[ MAX_SHORT_STRING / 2 ];
static noneOption[ MAX_SHORT_STRING / 2 ];
menuDirty [ 0 ] = '^0';
noneOption [ 0 ] = '^0';
isToAddExtraLine = ( g_voteStatus & IS_RUNOFF_VOTE
|| !IS_MAP_EXTENSION_ALLOWED()
|| g_totalVoteOptions == 1
&& !g_isRunOffNeedingKeepCurrentMap );
isToShowUndo = ( player_id > 0 \
&& g_voteShowNoneOptionType == CONVERT_NONE_OPTION_TO_CANCEL_LAST_VOTE \
&& g_isPlayerVoted[ player_id ] \
&& !g_isPlayerCancelledVote[ player_id ] );
computeVoteMenuFooter( player_id, voteFooter, charsmax( voteFooter ) );
// to append it here to always shows it AFTER voting.
if( isVoteOver )
{
// add the header
formatex( menuHeader, charsmax( menuHeader ), "%s%L",
COLOR_YELLOW, player_id, "GAL_RESULT" );
if( g_isToShowSubMenu )
{
formatex( menuDirty, charsmax( menuDirty ),
"%s^n%s^n\
%s%s0.%s %L^n^n\
%s%L",
menuHeader, voteStatus,
isToAddExtraLine ? "^n" : "", COLOR_RED, COLOR_WHITE, player_id, "CMD_MENU",
COLOR_YELLOW, player_id, "GAL_VOTE_ENDED" );
}
else if( g_isToShowNoneOption
&& g_voteShowNoneOptionType )
{
computeUndoButton( player_id, isToShowUndo, isVoteOver, noneOption, charsmax( noneOption ) );
formatex( menuDirty, charsmax( menuDirty ),
"%s^n%s^n\
%s^n^n\
%s%L",
menuHeader, voteStatus,
noneOption,
COLOR_YELLOW, player_id, "GAL_VOTE_ENDED" );
}
else
{
formatex( menuDirty, charsmax( menuDirty ),
"%s^n%s^n^n\
%s%L",
menuHeader, voteStatus,
COLOR_YELLOW, player_id, "GAL_VOTE_ENDED" );
}
}
else
{
// add the header
formatex( menuHeader, charsmax( menuHeader ), "%s%L",
COLOR_YELLOW, player_id, "GAL_CHOOSE" );
if( g_isToShowSubMenu )
{
formatex( menuDirty, charsmax( menuDirty ),
"%s^n%s^n\
%s%s0.%s %L\
%s",
menuHeader, voteStatus,
isToAddExtraLine ? "^n" : "", COLOR_RED, COLOR_WHITE, player_id, "CMD_MENU",
voteFooter );
}
else if( g_isToShowNoneOption )
{
computeUndoButton( player_id, isToShowUndo, isVoteOver, noneOption, charsmax( noneOption ) );
// remove the extra space between 'voteStatus' and 'voteFooter', after the 'None' option is hidden
if( noneIsHidden
&& g_isPlayerVoted[ player_id ] )
{
voteFooter[ 0 ] = ' ';
voteFooter[ 1 ] = ' ';
}
formatex( menuDirty, charsmax( menuDirty ),
"%s^n%s^n\
%s\
%s",
menuHeader, voteStatus,
noneOption,
voteFooter );
}
else
{
formatex( menuDirty, charsmax( menuDirty ),
"%s^n%s\
%s",
menuHeader, voteStatus,
voteFooter );
}
}
// Show the dirt menu to the player
display_vote_menu( false, player_id, menuDirty, menuKeys );
}
stock computeVoteMenuFooter( player_id, voteFooter[], voteFooterSize )
{
LOG( 256, "I AM ENTERING ON computeVoteMenuFooter(3) player_id: %d", player_id )
LOG( 256, "( computeVoteMenuFooter ) voteFooterSize: %d", voteFooterSize )
new copiedChars;
copiedChars = copy( voteFooter, voteFooterSize, "^n^n" );
if( g_isToShowExpCountdown )
{
if( ( g_votingSecondsRemaining < 10
|| g_isToShowVoteCounter )
&& ( g_showVoteStatus == SHOW_STATUS_AFTER_VOTE
|| g_showVoteStatus == SHOW_STATUS_ALWAYS
|| g_showVoteStatus == SHOW_STATUS_ALWAYS_UNTIL_VOTE ) )
{
if( g_votingSecondsRemaining >= 0 )
{
formatex( voteFooter[ copiedChars ], voteFooterSize - copiedChars, "%s%L: %s%i",
COLOR_WHITE, player_id, "GAL_TIMELEFT", COLOR_RED, g_votingSecondsRemaining + 1 );
}
else
{
formatex( voteFooter[ copiedChars ], voteFooterSize - copiedChars,
"%s%L", COLOR_YELLOW, player_id, "GAL_VOTE_ENDED" );
}
}
}
}
stock computeUndoButton( player_id, bool:isToShowUndo, bool:isVoteOver, noneOption[], noneOptionSize )
{
LOG( 256, "I AM ENTERING ON computeUndoButton(5) player_id: %d", player_id )
LOG( 256, "( computeUndoButton ) isToShowUndo: %d", isToShowUndo )
LOG( 256, "( computeUndoButton ) noneOption: %s, noneOptionSize: %d", noneOption, noneOptionSize )
new bool:isToAddExtraLine = ( g_voteStatus & IS_RUNOFF_VOTE
|| !IS_MAP_EXTENSION_ALLOWED()
|| g_totalVoteOptions == 1
&& !g_isRunOffNeedingKeepCurrentMap );
if( isToShowUndo )
{
formatex( noneOption, noneOptionSize,
"%s%s\
0. %s%L",
isToAddExtraLine ? "^n" : "" , COLOR_RED,
( isVoteOver ? COLOR_GREY : COLOR_WHITE ), player_id, "GAL_OPTION_CANCEL_VOTE" );
}
else
{
if( g_isPlayerCancelledVote[ player_id ] )
{
if( g_isPlayerVoted[ player_id ] )
{
formatex( noneOption, noneOptionSize,
"%s%s\
0. %s%L",
isToAddExtraLine ? "^n" : "", COLOR_RED,
COLOR_GREY, player_id, "GAL_OPTION_CANCEL_VOTE" );
}
else
{
formatex( noneOption, noneOptionSize,
"%s%s\
0. %s%L",
isToAddExtraLine ? "^n" : "" , COLOR_RED,
( isVoteOver ? COLOR_GREY : COLOR_WHITE ), player_id, "GAL_OPTION_NONE" );
}
}
else
{
switch( g_voteShowNoneOptionType )
{
case HIDE_AFTER_USER_VOTE_NONE_OPTION:
{
if( g_isPlayerVoted[ player_id ] )
{
noneOption[ 0 ] = '^0';
}
else
{
formatex( noneOption, noneOptionSize,
"%s%s\
0. %s%L",
isToAddExtraLine ? "^n" : "" , COLOR_RED,
( isVoteOver ? COLOR_GREY : COLOR_WHITE ), player_id, "GAL_OPTION_NONE" );
}
}
case ALWAYS_KEEP_SHOWING_NONE_OPTION, CONVERT_NONE_OPTION_TO_CANCEL_LAST_VOTE:
{
formatex( noneOption, noneOptionSize,
"%s%s\
0. %s%L",
isToAddExtraLine ? "^n" : "" , COLOR_RED,
( isVoteOver ? COLOR_GREY : COLOR_WHITE ), player_id, "GAL_OPTION_NONE" );
}
}
}
}
}
stock display_menu_clean( player_id, menuKeys )
{
LOG( 256, "I AM ENTERING ON display_menu_clean(2) player_id: %d", player_id )
LOG( 256, "( display_menu_clean ) menuKeys: %d", menuKeys )
new bool:isToShowUndo;
new bool:isToAddExtraLine;
// menu showed while voting
static menuClean[ MAX_BIG_BOSS_STRING ];
static voteFooter [ MAX_SHORT_STRING ];
static voteExtension[ MAX_SHORT_STRING ];
static menuHeader [ MAX_SHORT_STRING / 2 ];
static noneOption [ MAX_SHORT_STRING / 2 ];
menuClean [ 0 ] = '^0';
noneOption [ 0 ] = '^0';
isToAddExtraLine = ( g_voteStatus & IS_RUNOFF_VOTE
|| !IS_MAP_EXTENSION_ALLOWED()
|| g_totalVoteOptions == 1
&& !g_isRunOffNeedingKeepCurrentMap );
isToShowUndo = ( player_id > 0
&& g_voteShowNoneOptionType == CONVERT_NONE_OPTION_TO_CANCEL_LAST_VOTE
&& g_isPlayerVoted[ player_id ]
&& !g_isPlayerCancelledVote[ player_id ] );
computeVoteMenuFooter( player_id, voteFooter, charsmax( voteFooter ) );
menuKeys = addExtensionOption( player_id, 0, voteExtension, charsmax( voteExtension ), menuKeys, false );
// Add the header
formatex( menuHeader, charsmax( menuHeader ), "%s%L",
COLOR_YELLOW, player_id, "GAL_CHOOSE" );
// Append a "None" option on for people to choose if they don't like any other choice to append
// it here to always shows it WHILE voting.
if( g_isToShowSubMenu )
{
formatex( menuClean, charsmax( menuClean ),
"%s^n%s^n\
%s^n\
%s%s0.%s %L^n\
%s",
menuHeader, g_voteStatusClean,
voteExtension,
isToAddExtraLine ? "^n" : "", COLOR_RED, COLOR_WHITE, player_id, "CMD_MENU",
voteFooter );
}
else if( g_isToShowNoneOption )
{
if( isToShowUndo )
{
copy( noneOption, charsmax( noneOption ), "GAL_OPTION_CANCEL_VOTE" );
}
else
{
copy( noneOption, charsmax( noneOption ), "GAL_OPTION_NONE" );
}
formatex( menuClean, charsmax( menuClean ),
"%s^n%s^n\
%s^n\
%s%s0. %s%L\
%s",
menuHeader, g_voteStatusClean,
voteExtension,
isToAddExtraLine ? "^n" : "", COLOR_RED, COLOR_WHITE, player_id, noneOption,
voteFooter );
}
else
{
formatex( menuClean, charsmax( menuClean ),
"%s^n%s\
%s^n\
%s",
menuHeader, g_voteStatusClean,
voteExtension,
voteFooter );
}
// Show the dirt menu to the player
display_vote_menu( true, player_id, menuClean, menuKeys );
}
stock display_vote_menu( bool:menuType, player_id, menuBody[], menuKeys )
{
LOG( 128, "I AM ENTERING ON display_vote_menu(4) menuType: %d", menuType )
LOG( 4, "( display_vote_menu ) player_id: %d", player_id )
LOG( 4, "( display_vote_menu ) menuBody: %s, menuKeys: %d", menuBody, menuKeys )
if( isPlayerAbleToSeeTheVoteMenu( player_id ) )
{
show_menu( player_id, menuKeys, menuBody,
( menuType ? g_votingSecondsRemaining : max( 2, g_votingSecondsRemaining ) ),
CHOOSE_MAP_MENU_NAME );
}
}
stock isPlayerAbleToSeeTheVoteMenu( player_id )
{
LOG( 128, "I AM ENTERING ON isPlayerAbleToSeeTheVoteMenu(1) player_id: %d", player_id )
new menu_id;
new menuKeys_unused;
get_user_menu( player_id, menu_id, menuKeys_unused );
return ( menu_id == 0
|| menu_id == g_chooseMapMenuId
|| get_pcvar_num( cvar_isToReplaceByVoteMenu ) != 0 );
}
public vote_handleChoice( player_id, key )
{
LOG( 128, "I AM ENTERING ON vote_handleChoice(2) player_id: %d, key: %d", player_id, key )
if( g_voteStatus & IS_VOTE_EXPIRED )
{
client_cmd( player_id, "^"slot%i^"", key + 1 );
LOG( 1, " ( vote_handleChoice ) Just Returning/blocking, slot key pressed." )
return;
}
if( g_isToShowSubMenu
&& key == 9 )
{
if( g_isPlayerSeeingTheSubMenu[ player_id ] )
{
g_isPlayerSeeingTheSubMenu[ player_id ] = false;
}
else
{
g_isPlayerSeeingTheSubMenu[ player_id ] = true;
}
reshowTheVoteMenu( player_id );
return;
}
else if( g_isPlayerSeeingTheSubMenu[ player_id ] )
{
processSubMenuKeyHit( player_id, key );
return;
}
else if( !g_isPlayerVoted[ player_id ] )
{
register_vote( player_id, key );
}
else if( key == 9
&& !g_isPlayerCancelledVote[ player_id ]
&& g_voteShowNoneOptionType == CONVERT_NONE_OPTION_TO_CANCEL_LAST_VOTE
&& g_isToShowNoneOption )
{
cancel_player_vote( player_id );
}
else
{
client_cmd( player_id, "^"slot%i^"", key + 1 );
}
// display the vote again, with status
if( g_showVoteStatus == SHOW_STATUS_ALWAYS
|| g_showVoteStatus == SHOW_STATUS_AFTER_VOTE )
{
reshowTheVoteMenu( player_id );
}
else if( g_isPlayerVoted[ player_id ]
&& g_showVoteStatus == SHOW_STATUS_ALWAYS_UNTIL_VOTE
&& isPlayerAbleToSeeTheVoteMenu( player_id ) )
{
// Some times the menu does not exit after voting. So, override manually.
show_menu( player_id, 1, ".", 1, CHOOSE_MAP_MENU_NAME );
}
}
stock reshowTheVoteMenu( player_id )
{
new argument[ 2 ];
argument[ 0 ] = false;
argument[ 1 ] = player_id;
set_task( 0.1, "vote_display", TASKID_VOTE_DISPLAY, argument, sizeof argument );
}
stock cancel_player_vote( player_id )
{
LOG( 128, "I AM ENTERING ON cancel_player_vote(1) player_id: %d", player_id )
new voteWeight = g_playerVotedWeight[ player_id ];
g_isPlayerVoted[ player_id ] = false;
g_isPlayerParticipating[ player_id ] = true;
g_isPlayerCancelledVote[ player_id ] = true;
g_totalVotesCounted -= voteWeight;
g_arrayOfMapsWithVotesNumber[ g_playerVotedOption[ player_id ] ] -= voteWeight;
g_playerVotedOption[ player_id ] -= g_playerVotedOption[ player_id ];
g_playerVotedWeight[ player_id ] -= g_playerVotedWeight[ player_id ];
}
/**
* Register the player's choice giving extra weight to admin votes.
*/
stock register_vote( player_id, pressedKeyCode )
{
LOG( 128, "I AM ENTERING ON register_vote(2) player_id: %d, pressedKeyCode: %d", player_id, pressedKeyCode )
announceRegistedVote( player_id, pressedKeyCode );
g_isPlayerVoted[ player_id ] = true;
if( pressedKeyCode == 9 )
{
g_isPlayerParticipating[ player_id ] = false; // if is not interested now, at runoff wont also
g_playerVotedOption[ player_id ] = 0; // the None option does not integrate vote counting
g_playerVotedWeight[ player_id ] = 0; // the None option has no weight
}
else
{
g_isPlayerParticipating[ player_id ] = true;
g_playerVotedOption[ player_id ] = pressedKeyCode;
g_playerVotedWeight[ player_id ] = 1;
}
// pressedKeyCode 9 means the keyboard key 0 (the None option) and it does not integrate the vote
if( pressedKeyCode != 9 )
{
// increment votes cast count
g_totalVotesCounted++;
new voteWeight = get_pcvar_num( cvar_voteWeight );
if( voteWeight > 1
&& has_flag( player_id, g_voteWeightFlags ) )
{
g_playerVotedWeight[ player_id ] = voteWeight;
g_arrayOfMapsWithVotesNumber[ pressedKeyCode ] += voteWeight;
g_totalVotesCounted += ( voteWeight - 1 );
color_print( player_id, "%L", player_id, "GAL_VOTE_WEIGHTED", voteWeight );
}
else
{
g_arrayOfMapsWithVotesNumber[ pressedKeyCode ]++;
}
}
}
stock announceRegistedVote( player_id, pressedKeyCode )
{
LOG( 128, "I AM ENTERING ON announceRegistedVote(2) player_id: %d, pressedKeyCode: %d", player_id, pressedKeyCode )
new player_name[ MAX_PLAYER_NAME_LENGHT ];
new bool:isToAnnounceChoice = get_pcvar_num( cvar_voteAnnounceChoice ) != 0;
if( isToAnnounceChoice )
{
GET_USER_NAME( player_id, player_name )
}
// confirm the player's choice (pressedKeyCode = 9 means 0 on the keyboard, 8 is 7, etc)
if( pressedKeyCode == 9 )
{
LOG( 4, " %-32s ( none )", player_name )
if( isToAnnounceChoice )
{
color_print( 0, "%L", LANG_PLAYER, "GAL_CHOICE_NONE_ALL", player_name );
}
else
{
color_print( player_id, "%L", player_id, "GAL_CHOICE_NONE" );
}
}
else if( pressedKeyCode == g_totalVoteOptions )
{
// only display the "none" vote if we haven't already voted
// ( we can make it here from the vote status menu too )
if( !g_isPlayerVoted[ player_id ] )
{
LOG( 4, " %-32s ( extend )", player_name )
if( g_isGameFinalVoting )
{
if( isToAnnounceChoice )
{
color_print( 0, "%L", LANG_PLAYER, "GAL_CHOICE_EXTEND_ALL", player_name );
}
else
{
color_print( player_id, "%L", player_id, "GAL_CHOICE_EXTEND" );
}
}
else
{
if( isToAnnounceChoice )
{
color_print( 0, "%L", LANG_PLAYER, "GAL_CHOICE_STAY_ALL", player_name );
}
else
{
color_print( player_id, "%L", player_id, "GAL_CHOICE_STAY" );
}
}
}
}
else
{
LOG( 4, " %-32s %s", player_name, g_votingMapNames[ pressedKeyCode ] )
if( isToAnnounceChoice )
{
color_print( 0, "%L", LANG_PLAYER, "GAL_CHOICE_MAP_ALL",
player_name, g_votingMapNames[ pressedKeyCode ] );
}
else
{
color_print( player_id, "%L", player_id, "GAL_CHOICE_MAP",
g_votingMapNames[ pressedKeyCode ] );
}
}
}
stock computeMapVotingCount( mapVotingCount[], mapVotingCountLength, voteIndex, bool:isToAddResults = true )
{
LOG( 256, "I AM ENTERING ON computeMapVotingCount(3) mapVotingCount: %s, mapVotingCountLength: %d, \
voteIndex: %d", mapVotingCount, mapVotingCountLength, voteIndex )
new voteCountNumber = g_arrayOfMapsWithVotesNumber[ voteIndex ];
if( voteCountNumber
&& isToAddResults
&& g_showVoteStatus )
{
switch( g_showVoteStatusType )
{
case STATUS_TYPE_COUNT:
{
formatex( mapVotingCount, mapVotingCountLength, " %s(%s%i%s%s)",
COLOR_YELLOW, COLOR_GREY,
voteCountNumber, g_voteStatus_symbol,
COLOR_YELLOW );
}
case STATUS_TYPE_PERCENTAGE:
{
new votePercentNunber = percent( voteCountNumber, g_totalVotesCounted );
formatex( mapVotingCount, mapVotingCountLength, " %s(%s%i%s%s)",
COLOR_YELLOW, COLOR_GREY,
votePercentNunber, g_voteStatus_symbol,
COLOR_YELLOW );
}
case STATUS_TYPE_PERCENTAGE | STATUS_TYPE_COUNT:
{
new votePercentNunber = percent( voteCountNumber, g_totalVotesCounted );
formatex( mapVotingCount, mapVotingCountLength,
" %s(%s%i%s %s[%s%d%s]%s)",
COLOR_RED, COLOR_GREY,
votePercentNunber, g_voteStatus_symbol,
COLOR_YELLOW, COLOR_GREY,
voteCountNumber, COLOR_YELLOW,
COLOR_RED );
}
default:
{
mapVotingCount[ 0 ] = '^0';
}
}
}
else
{
mapVotingCount[ 0 ] = '^0';
}
LOG( 256, " ( computeMapVotingCount ) g_showVoteStatus: %d, g_showVoteStatusType: %d, voteCountNumber: %d", \
g_showVoteStatus, g_showVoteStatusType, voteCountNumber )
}
stock showPlayersVoteResult()
{
LOG( 128, "I AM ENTERING ON showPlayersVoteResult(0)" )
new mapVotingCount[ 32 ];
LOG( 4, "" )
LOG( 4, " [VOTE RESULT]" )
for( new playerVoteMapChoiceIndex = 0; playerVoteMapChoiceIndex <= g_totalVoteOptions;
++playerVoteMapChoiceIndex )
{
computeMapVotingCount( mapVotingCount, charsmax( mapVotingCount ), playerVoteMapChoiceIndex );
LOG( 4, " %2i/%-2i, %i. %s %s", \
g_arrayOfMapsWithVotesNumber[ playerVoteMapChoiceIndex ], g_totalVotesCounted, \
playerVoteMapChoiceIndex, g_votingMapNames[ playerVoteMapChoiceIndex ], \
g_votingMapInfos[ playerVoteMapChoiceIndex ] )
}
LOG( 4, "" )
return 0;
}
/**
* Get unique random numbers between a minimum until maximum. For now this function is used on the
* source code. It is here for historical purposes only. It was previously created for use but its
* used was deprecated. Now it can be used for implementation comparison between its sister function
* getUniqueRandomIntegerBasic(2) just bellow.
*
* If the `maximum`'s change between the function calls, the unique random number sequence will be
* restarted to this new maximum value. Also after the maximum value been reached, the random unique
* sequence will be restarted and a new unique random number sequence will be generated.
*
* If your `maximum` parameter value is not 0, you can to reset the sequence manually just to calling
* this function as `getUniqueRandomInteger( holder )` or using some the `maximum` parameter value.
* The random generated range is:
*
* minimum <= return value <= maximum
*
* 1. Do not forgot the call ArrayCreate() for the `holder` parameter before calling this function,
* and to call ArrayDestroy(1) for the `holder` parameter after you finished using this function.
* 2. Do not change the minimum value without changing at the same time the maximum value, otherwise
* you will still get number at the old minimum value starting range.
* 3. This algorithm complexity is linear `O( n )` for the first call and when the random generated
* sequence is restarted. The further function calls has constant `O( 1 )` complexity.
*
* @param holder an initially empty Dynamic Array used for internal purposes.
* @param minimum the inclusive lower bound limit, i.e., the minimum value to be sorted.
* @param maximum the inclusive upper bound limit, i.e., the maximum value to be sorted.
* @param restart if false, the sequence will not be automatically restarted and will to start
* returning the value -1;
*
* @return an unique random integer until the `maximum` parameter value.
*/
stock getUniqueRandomInteger( Array:holder, minimum = MIN_INTEGER, maximum = MIN_INTEGER, restart = true )
{
LOG( 128, "I AM ENTERING ON getUniqueRandomInteger(2) range: %d-%d", minimum, maximum )
static lastMaximum = MIN_INTEGER;
new randomIndex;
new returnValue;
new holderSize = ArraySize( holder );
if( lastMaximum != maximum
|| ( restart
&& holderSize < 1 ) )
{
LOG( 1, "( getUniqueRandomInteger ) Reseting the sequence, ArraySize: %d", holderSize )
lastMaximum = maximum;
TRY_TO_APPLY( ArrayClear, holder )
for( new index = minimum; index <= maximum; index++ )
{
ArrayPushCell( holder, index );
}
holderSize = ArraySize( holder );
}
else if( holderSize < 1 )
{
return -1;
}
LOG( 1, "( getUniqueRandomInteger ) ArraySize: %d", ArraySize( holder ) )
--holderSize;
// Get a unique random value
randomIndex = random_num( 0, holderSize );
returnValue = ArrayGetCell( holder, randomIndex );
// Swap the random value from the middle of the array to the last position, reduces the removal
// complexity from linear `O( n )` to constant `O( 1 )`.
ArraySwap( holder, randomIndex, holderSize );
ArrayDeleteItem( holder, holderSize );
LOG( 1, " ( getUniqueRandomInteger ) %d. Just Returning the random integer: %d", holderSize, returnValue )
return returnValue;
}
/**
* Get unique random positive numbers between 0 until 31. If the `maximum`'s parameter value
* provided is greater than 31, the generated numbers will not be unique. The range is:
*
* 0 <= return value <= maximum <= 31
*
* @param sequence a random positive number to reset the current unique return values.
* @param maximum the upper bound limit, i.e., the maximum value to be sorted.
*
* @return -1 when there are not new unique positive numbers to return.
*/
stock getUniqueRandomIntegerBasic( sequence, maximum )
{
LOG( 128, "I AM ENTERING ON getUniqueRandomIntegerBasic(2) maximum: %d", maximum )
static maximumBitField;
static lastSequence = -1;
static sortedIntegers = 0;
if( lastSequence != sequence )
{
lastSequence = sequence;
sortedIntegers = 0;
maximumBitField = 0;
for( new index = 0; index < maximum + 1; index++ )
{
maximumBitField |= ( 1 << index );
}
}
new randomInteger;
// Keep looping while there is numbers that haven't yet been selected.
while( sortedIntegers != maximumBitField )
{
randomInteger = random_num( 0, maximum );
// If the number has not yet been selected yet
if( !( sortedIntegers & ( 1 << randomInteger ) ) )
{
// Set bit on the sortedIntegers bit-field, so the integer will now be considered selected
sortedIntegers |= ( 1 << randomInteger );
LOG( 1, " ( getUniqueRandomIntegerBasic ) %d. Just Returning the random integer: %d", sequence, randomInteger )
return randomInteger;
}
}
LOG( 1, " ( getUniqueRandomIntegerBasic ) %d. Just Returning the random integer: %d", sequence, -1 )
return -1;
}
stock printIntegerArray( level, integerArray[], arrayName[], integerArraySize )
{
LOG( 128, "I AM ENTERING ON printIntegerArray(4) integerArraySize: %d", integerArraySize )
for( new index = 0; index < integerArraySize; index++ )
{
LOG( level, "( printIntegerArray ) %s: %d", arrayName, integerArray[ index ] )
}
return 0;
}
stock printRunOffMaps( runOffMapsCount )
{
LOG( 128, "I AM ENTERING ON printRunOffMaps(1) runOffMapsCount: %d", runOffMapsCount )
for( new index = 0; index < runOffMapsCount; index++ )
{
LOG( 16, "( printRunOffMaps ) RunOff map %d: %s", index, g_votingMapNames[ g_arrayOfRunOffChoices[ index ] ] )
}
return 0;
}
stock handleMoreThanTwoMapsAtFirst( firstPlaceChoices[], numberOfMapsAtFirstPosition )
{
LOG( 128, "I AM ENTERING ON handleMoreThanTwoMapsAtFirst(2)" )
LOG( 0, "", printIntegerArray( 16, firstPlaceChoices, "firstPlaceChoices", numberOfMapsAtFirstPosition ) )
new seedValue;
new randomInteger;
new maxVotingChoices;
new originalTotalVotingOptions;
// This only valid for the runoff voting. Do not use MAX_VOTING_CHOICES(0) here.
maxVotingChoices = min( MAX_OPTIONS_IN_VOTE, get_pcvar_num( cvar_runoffMapchoices ) );
maxVotingChoices = max( min( maxVotingChoices, numberOfMapsAtFirstPosition ), 2 );
originalTotalVotingOptions = g_totalVoteOptions;
g_totalVoteOptions = maxVotingChoices;
// Get an unique identification for the seed sequence value
seedValue = abs( get_systime() );
for( new voteOptionIndex = 0; voteOptionIndex < maxVotingChoices; voteOptionIndex++ )
{
randomInteger = getUniqueRandomIntegerBasic( seedValue, maxVotingChoices );
// If firstPlaceChoices[ numberOfMapsAtFirstPosition - 1 ] is equal to
// g_totalVoteOptions then it option is not a valid map, it is the keep current
// map option, and must be informed it to the vote_display function, to show the
// 1 map options and the keep current map.
if( firstPlaceChoices[ randomInteger ] == originalTotalVotingOptions )
{
g_totalVoteOptions--;
g_isRunOffNeedingKeepCurrentMap = true;
}
g_arrayOfRunOffChoices[ voteOptionIndex ] = firstPlaceChoices[ randomInteger ];
}
LOG( 16, "( handleMoreThanTwoMapsAtFirst ) Number of Maps at First Position > 2" )
LOG( 0, "", printRunOffMaps( g_totalVoteOptions ) )
color_print( 0, "%L", LANG_PLAYER, "GAL_RESULT_TIED1", numberOfMapsAtFirstPosition );
}
stock configureTheRunoffVoting( firstPlaceChoices[], secondPlaceChoices[], numberOfMapsAtFirstPosition,
numberOfMapsAtSecondPosition )
{
LOG( 128, "I AM ENTERING ON configureTheRunoffVoting(4)" )
new votePercent = floatround( 100 * get_pcvar_float( cvar_runoffRatio ), floatround_ceil );
// announce runoff voting requirement
color_print( 0, "%L", LANG_PLAYER, "GAL_RUNOFF_REQUIRED", votePercent );
if( !( get_pcvar_num( cvar_soundsMute ) & SOUND_RUNOFF_REQUIRED ) )
{
client_cmd( 0, "spk ^"run officer( e40 ) voltage( e30 ) accelerating( s70 ) \
is required^"" );
}
// let the server know the next vote will be a runoff
g_voteStatus |= IS_RUNOFF_VOTE;
if( numberOfMapsAtFirstPosition > 2 )
{
handleMoreThanTwoMapsAtFirst( firstPlaceChoices, numberOfMapsAtFirstPosition );
}
else if( numberOfMapsAtFirstPosition == 2 )
{
handleTwoMapsAtFirstPosition( firstPlaceChoices );
}
// If `numberOfMapsAtFirstPosition` is not greater than 2, neither equal to 2, therefore it is 1.
else if( numberOfMapsAtSecondPosition == 1 )
{
handleOneMapAtSecondPosition( firstPlaceChoices, secondPlaceChoices );
}
else // numberOfMapsAtFirstPosition == 1 && numberOfMapsAtSecondPosition > 1
{
handleOneMapAtFirstPosition( firstPlaceChoices, secondPlaceChoices, numberOfMapsAtSecondPosition );
}
// clear all the votes
vote_resetStats();
// start the runoff vote, startTheVoting
set_task( VOTE_TIME_RUNOFF, "startTheRunoffVoting", TASKID_START_THE_VOTING );
}
stock handleTwoMapsAtFirstPosition( firstPlaceChoices[] )
{
LOG( 128, "I AM ENTERING ON handleTwoMapsAtFirstPosition(1)" )
if( firstPlaceChoices[ 0 ] == g_totalVoteOptions )
{
g_isRunOffNeedingKeepCurrentMap = true;
g_arrayOfRunOffChoices[ 0 ] = firstPlaceChoices[ 1 ];
g_totalVoteOptions = 1;
}
else if( firstPlaceChoices[ 1 ] == g_totalVoteOptions )
{
g_isRunOffNeedingKeepCurrentMap = true;
g_arrayOfRunOffChoices[ 0 ] = firstPlaceChoices[ 0 ];
g_totalVoteOptions = 1;
}
else
{
g_totalVoteOptions = 2;
g_arrayOfRunOffChoices[ 0 ] = firstPlaceChoices[ 0 ];
g_arrayOfRunOffChoices[ 1 ] = firstPlaceChoices[ 1 ];
}
LOG( 16, "( handleTwoMapsAtFirstPosition ) Number of Maps at First Position == 2" )
LOG( 0, "", printRunOffMaps( g_totalVoteOptions ) )
}
stock handleOneMapAtSecondPosition( firstPlaceChoices[], secondPlaceChoices[] )
{
LOG( 128, "I AM ENTERING ON handleOneMapAtSecondPosition(2)" )
if( firstPlaceChoices[ 0 ] == g_totalVoteOptions )
{
g_isRunOffNeedingKeepCurrentMap = true;
g_arrayOfRunOffChoices[ 0 ] = secondPlaceChoices[ 0 ];
g_totalVoteOptions = 1;
}
else if( secondPlaceChoices[ 0 ] == g_totalVoteOptions )
{
g_isRunOffNeedingKeepCurrentMap = true;
g_arrayOfRunOffChoices[ 0 ] = firstPlaceChoices[ 0 ];
g_totalVoteOptions = 1;
}
else
{
g_totalVoteOptions = 2;
g_arrayOfRunOffChoices[ 0 ] = firstPlaceChoices[ 0 ];
g_arrayOfRunOffChoices[ 1 ] = secondPlaceChoices[ 0 ];
}
LOG( 16, "( handleOneMapAtSecondPosition ) Number of Maps at Second Position == 1" )
LOG( 0, "", printRunOffMaps( g_totalVoteOptions ) )
}
/**
* Do not implement the feature below here in:
*
* Another CVAR for the number of maps to be included in runoff voting.
* https://github.com/addonszz/Galileo/issues/33
*
* It is because it will cause complications as when there is only one player at the server. If the
* player vote for one map, it will cause this option to be triggered:
*
* numberOfMapsAtFirstPosition == 1 && numberOfMapsAtSecondPosition > 1
*
* And then a run off voting would be triggered when there is only one player at the server.
*/
stock handleOneMapAtFirstPosition( firstPlaceChoices[], secondPlaceChoices[], numberOfMapsAtSecondPosition )
{
LOG( 128, "I AM ENTERING ON handleOneMapAtFirstPosition(3)" )
new randonNumber = random_num( 0, numberOfMapsAtSecondPosition - 1 );
if( firstPlaceChoices[ 0 ] == g_totalVoteOptions )
{
g_isRunOffNeedingKeepCurrentMap = true;
g_arrayOfRunOffChoices[ 0 ] = secondPlaceChoices[ randonNumber ];
g_totalVoteOptions = 1;
}
else if( secondPlaceChoices[ randonNumber ] == g_totalVoteOptions )
{
g_isRunOffNeedingKeepCurrentMap = true;
g_arrayOfRunOffChoices[ 0 ] = firstPlaceChoices[ 0 ];
g_totalVoteOptions = 1;
}
else
{
g_totalVoteOptions = 2;
g_arrayOfRunOffChoices[ 0 ] = firstPlaceChoices[ 0 ];
g_arrayOfRunOffChoices[ 1 ] = secondPlaceChoices[ randonNumber ];
}
LOG( 16, "( handleOneMapAtFirstPosition ) Number of Maps at First Position == 1 && At Second Position > 1" )
LOG( 0, "", printRunOffMaps( g_totalVoteOptions ) )
color_print( 0, "%L", LANG_PLAYER, "GAL_RESULT_TIED2", numberOfMapsAtSecondPosition );
}
stock determineTheVotingFirstChoices( firstPlaceChoices[], secondPlaceChoices[],
&numberOfMapsAtFirstPosition, &numberOfMapsAtSecondPosition )
{
LOG( 128, "I AM ENTERING ON determineTheVotingFirstChoices(4)" )
new numberOfVotesAtFirstPlace;
new numberOfVotesAtSecondPlace;
new playerVoteMapChoiceIndex;
new bool:isRunoffVoting = ( g_voteStatus & IS_RUNOFF_VOTE ) != 0;
// Determine how much maps it should look up to, considering whether there is the option
// `Stay Here` or `Extend` displayed on the voting menu.
new maxVotingChoices = g_totalVoteOptions >= MAX_OPTIONS_IN_VOTE ? g_totalVoteOptions :
( IS_MAP_EXTENSION_ALLOWED() && !isRunoffVoting ? ( g_totalVoteOptions + 1 ) :
( g_isRunOffNeedingKeepCurrentMap ? ( g_totalVoteOptions + 1 ) : g_totalVoteOptions ) );
// determine the number of votes for 1st and 2nd places
for( playerVoteMapChoiceIndex = 0; playerVoteMapChoiceIndex < maxVotingChoices;
++playerVoteMapChoiceIndex )
{
if( numberOfVotesAtFirstPlace < g_arrayOfMapsWithVotesNumber[ playerVoteMapChoiceIndex ] )
{
numberOfVotesAtSecondPlace = numberOfVotesAtFirstPlace;
numberOfVotesAtFirstPlace = g_arrayOfMapsWithVotesNumber[ playerVoteMapChoiceIndex ];
}
else if( numberOfVotesAtSecondPlace < g_arrayOfMapsWithVotesNumber[ playerVoteMapChoiceIndex ] )
{
numberOfVotesAtSecondPlace = g_arrayOfMapsWithVotesNumber[ playerVoteMapChoiceIndex ];
}
}
// determine which maps are in 1st and 2nd places
for( playerVoteMapChoiceIndex = 0; playerVoteMapChoiceIndex < maxVotingChoices;
++playerVoteMapChoiceIndex )
{
LOG( 16, "Inside the for to determine which maps are in 1st and 2nd places, \
g_arrayOfMapsWithVotesNumber[%d] = %d ", playerVoteMapChoiceIndex, \
g_arrayOfMapsWithVotesNumber[ playerVoteMapChoiceIndex ] )
if( g_arrayOfMapsWithVotesNumber[ playerVoteMapChoiceIndex ] == numberOfVotesAtFirstPlace )
{
firstPlaceChoices[ numberOfMapsAtFirstPosition++ ] = playerVoteMapChoiceIndex;
}
else if( g_arrayOfMapsWithVotesNumber[ playerVoteMapChoiceIndex ] == numberOfVotesAtSecondPlace )
{
secondPlaceChoices[ numberOfMapsAtSecondPosition++ ] = playerVoteMapChoiceIndex;
}
}
// At for: g_totalVoteOptions: 5, numberOfMapsAtFirstPosition: 3, numberOfMapsAtSecondPosition: 0
LOG( 16, "After for to determine which maps are in 1st and 2nd places." )
LOG( 16, "" )
LOG( 16, "maxVotingChoices: %d, isRunoffVoting: %d", maxVotingChoices, isRunoffVoting )
LOG( 16, "g_totalVoteOptions: %d", g_totalVoteOptions )
LOG( 16, "" )
LOG( 16, "numberOfVotesAtFirstPlace: %d", numberOfVotesAtFirstPlace )
LOG( 16, "numberOfVotesAtSecondPlace: %d", numberOfVotesAtSecondPlace )
LOG( 16, "" )
LOG( 16, "numberOfMapsAtFirstPosition: %d", numberOfMapsAtFirstPosition )
LOG( 16, "numberOfMapsAtSecondPosition: %d", numberOfMapsAtSecondPosition )
LOG( 16, "" )
LOG( 1, "( determineTheVotingFirstChoices ) g_isTheLastGameRound: %d", g_isTheLastGameRound )
LOG( 1, "( determineTheVotingFirstChoices ) g_isTimeToRestart: %d", g_isTimeToRestart )
LOG( 1, "( determineTheVotingFirstChoices ) g_voteStatus & IS_FORCED_VOTE: %d", g_voteStatus & IS_FORCED_VOTE != 0 )
return numberOfVotesAtFirstPlace;
}
public computeVotes()
{
LOG( 128, "I AM ENTERING ON computeVotes(0)" )
LOG( 0, "", showPlayersVoteResult() )
new runoffEnabled;
new numberOfVotesAtFirstPlace;
// retain the number of draw maps at first and second positions
new numberOfMapsAtFirstPosition;
new numberOfMapsAtSecondPosition;
new firstPlaceChoices [ MAX_OPTIONS_IN_VOTE ];
new secondPlaceChoices[ MAX_OPTIONS_IN_VOTE ];
runoffEnabled = get_pcvar_num( cvar_runoffEnabled );
numberOfVotesAtFirstPlace = determineTheVotingFirstChoices( firstPlaceChoices,
secondPlaceChoices, numberOfMapsAtFirstPosition, numberOfMapsAtSecondPosition );
// announce the outcome
if( numberOfVotesAtFirstPlace )
{
LOG( 1, "( computeVotes ) On if(numberOfVotesAtFirstPlace)" )
// if the top vote getting map didn't receive over 50% of the votes cast, to start a runoff vote
if( numberOfVotesAtFirstPlace <= g_totalVotesCounted * get_pcvar_float( cvar_runoffRatio ) )
{
LOG( 1, "( computeVotes ) On cvar_runoffRatio" )
if( runoffEnabled == RUNOFF_ENABLED
&& !( g_voteStatus & IS_RUNOFF_VOTE )
&& !( g_voteMapStatus & IS_DISABLED_VOTEMAP_RUNOFF ) )
{
configureTheRunoffVoting( firstPlaceChoices, secondPlaceChoices, numberOfMapsAtFirstPosition,
numberOfMapsAtSecondPosition );
LOG( 1, " ( computeVotes ) Just Returning/blocking, its runoff starting." )
return;
}
else if( runoffEnabled == RUNOFF_EXTEND
&& IS_MAP_EXTENSION_ALLOWED() )
{
// Allow it only on a end map voting
if( g_isGameFinalVoting )
{
map_extend( "GAL_RUNOFF_REQUIRED_TOP" );
}
else
{
stayHereWon( "GAL_RUNOFF_REQUIRED_TOP" );
}
LOG( 1, "( computeVotes ) Its runoff extending." )
}
else
{
chooseTheVotingMapWinner( firstPlaceChoices, numberOfMapsAtFirstPosition );
}
}
else
{
chooseTheVotingMapWinner( firstPlaceChoices, numberOfMapsAtFirstPosition );
}
}
else // the execution flow gets here when anybody voted for next map
{
chooseRandomVotingWinner();
}
LOG( 1, " ( computeVotes|out ) g_isTheLastGameRound: %d", g_isTheLastGameRound )
LOG( 1, " ( computeVotes|out ) g_isTimeToRestart: %d, g_voteStatus & IS_FORCED_VOTE: %d", \
g_isTimeToRestart, g_voteStatus & IS_FORCED_VOTE != 0 )
finalizeVoting();
}
stock stayHereWon( const reason[] )
{
LOG( 128, "I AM ENTERING ON stayHereWon(0)" )
color_print( 0, "%L: %L", LANG_PLAYER, reason, LANG_PLAYER, "GAL_WINNER_STAY2" );
toShowTheMapStayHud( "GAL_VOTE_ENDED", reason, "GAL_WINNER_STAY1" );
// However here, none decisions are being made. Anyways, we cannot block the execution
// right here without executing the remaining code.
noLongerIsAnEarlyVoting();
}
stock chooseTheVotingMapWinner( firstPlaceChoices[], numberOfMapsAtFirstPosition )
{
LOG( 128, "I AM ENTERING ON chooseTheVotingMapWinner(2)" )
new winnerVoteMapIndex;
// If there is a tie for 1st, randomly select one as the winner
if( numberOfMapsAtFirstPosition > 1 )
{
// This message and others like it, does not need a HUD because they are not the last ones
// to be displayed, i.e., soon a new ending message within a HUD will be show.
winnerVoteMapIndex = firstPlaceChoices[ random_num( 0, numberOfMapsAtFirstPosition - 1 ) ];
color_print( 0, "%L", LANG_PLAYER, "GAL_WINNER_TIED", numberOfMapsAtFirstPosition );
}
else
{
winnerVoteMapIndex = firstPlaceChoices[ 0 ];
}
LOG( 1, " ( chooseTheVotingMapWinner ) g_isTheLastGameRound: %d ", g_isTheLastGameRound )
LOG( 1, " ( chooseTheVotingMapWinner ) g_isTimeToRestart: %d, g_voteStatus & IS_FORCED_VOTE: %d", \
g_isTimeToRestart, g_voteStatus & IS_FORCED_VOTE != 0 )
// winnerVoteMapIndex == g_totalVoteOptions, means the 'Stay Here' option.
// Then, here we keep the current map or extend current map.
if( winnerVoteMapIndex == g_totalVoteOptions )
{
if( !g_isGameFinalVoting // "stay here" won and the map mustn't be restarted.
&& !g_isTimeToRestart )
{
// While the `IS_DISABLED_VOTEMAP_EXIT` bit flag is set, we cannot allow any decisions
if( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXIT )
{
// When the stay here option is called, there is anyone else trying to show action menu,
// therefore invoke it before continuing.
openTheVoteMapActionMenu();
LOG( 1, " ( chooseTheVotingMapWinner ) Just opened the menu due g_voteMapStatus: %d", g_voteMapStatus )
}
stayHereWon( "DMAP_MAP_EXTENDED1" );
}
else if( !g_isGameFinalVoting // "stay here" won and the map must be restarted.
&& g_isTimeToRestart )
{
// This message does not need HUD's because immediately the map will be changed immediately.
color_print( 0, "%L", LANG_PLAYER, "GAL_WINNER_STAY2" );
noLongerIsAnEarlyVoting();
process_last_round( g_isToChangeMapOnVotingEnd, false );
}
else if( g_isGameFinalVoting ) // "extend map" won
{
map_extend( "GAL_VOTE_ENDED" );
}
}
else // The execution flow gets here when the winner option is not keep/extend map
{
setNextMap( g_currentMapName, g_votingMapNames[ winnerVoteMapIndex ] );
server_exec();
g_voteStatus |= IS_VOTE_OVER;
// When it is a `gal_votemap` we need to print its map winner, instead of the `g_nextMapName`.
if( g_invokerVoteMapNameToDecide[ 0 ] )
{
color_print( 0, "%L: %L", LANG_PLAYER, "DMAP_MAP_EXTENDED1", LANG_PLAYER, "GAL_NEXTMAP2", g_invokerVoteMapNameToDecide );
toShowTheMapNextHud( "GAL_VOTE_ENDED", "DMAP_MAP_EXTENDED1", "GAL_NEXTMAP1", g_invokerVoteMapNameToDecide );
}
else
{
color_print( 0, "%L: %L", LANG_PLAYER, "DMAP_MAP_EXTENDED1", LANG_PLAYER, "GAL_NEXTMAP2", g_nextMapName );
toShowTheMapNextHud( "GAL_VOTE_ENDED", "DMAP_MAP_EXTENDED1", "GAL_NEXTMAP1", g_nextMapName );
}
process_last_round( g_isToChangeMapOnVotingEnd );
}
}
stock noLongerIsAnEarlyVoting()
{
LOG( 128, "I AM ENTERING ON noLongerIsAnEarlyVoting(0)" )
// We are extending the map as result of the voting outcome, so reset the ending round variables.
resetRoundEnding();
// No longer is an early or forced voting
g_voteStatus &= ~IS_EARLY_VOTE;
g_voteStatus &= ~IS_FORCED_VOTE;
}
stock chooseRandomVotingWinner()
{
LOG( 128, "I AM ENTERING ON chooseRandomVotingWinner(1) isExtendmapOrderAllowed: %d", get_pcvar_num( cvar_isExtendmapOrderAllowed ) )
switch( get_pcvar_num( cvar_isExtendmapOrderAllowed ) )
{
// 1 - follow your current map-cycle order
case 1:
{
g_voteStatus |= IS_VOTE_OVER;
color_print( 0, "%L. %L", LANG_PLAYER, "GAL_WINNER_NO_ONE_VOTED", LANG_PLAYER, "GAL_WINNER_ORDERED2", g_nextMapName );
toShowTheMapNextHud( "GAL_WINNER_NO_ONE_VOTED", "DMAP_MAP_EXTENDED1", "GAL_WINNER_ORDERED1", g_nextMapName );
// Need to be called to trigger special behaviors.
setNextMap( g_currentMapName, g_nextMapName );
process_last_round( g_isToChangeMapOnVotingEnd );
}
// 2 - extend the current map
case 2:
{
// When called, to trigger the special behaviors.
map_extend( "GAL_WINNER_NO_ONE_VOTED" );
}
// 0 - choose a random map from the current voting map list, as next map
default:
{
g_voteStatus |= IS_VOTE_OVER;
new winnerVoteMapIndex = random_num( 0, g_totalVoteOptions - 1 );
setNextMap( g_currentMapName, g_votingMapNames[ winnerVoteMapIndex ] );
color_print( 0, "%L. %L", LANG_PLAYER, "GAL_WINNER_NO_ONE_VOTED", LANG_PLAYER, "GAL_WINNER_RANDOM2", g_nextMapName );
toShowTheMapNextHud( "GAL_WINNER_NO_ONE_VOTED", "DMAP_MAP_EXTENDED1", "GAL_WINNER_RANDOM1", g_nextMapName );
process_last_round( g_isToChangeMapOnVotingEnd );
}
}
}
stock resetVoteTypeGlobals()
{
LOG( 128, "I AM ENTERING ON resetVoteTypeGlobals(0)" )
g_endVotingType = 0;
g_isRunOffNeedingKeepCurrentMap = false;
}
/**
* Restore global variables to is default state. This is to be ready for a new voting.
*/
stock finalizeVoting()
{
LOG( 128, "I AM ENTERING ON finalizeVoting(0)" )
// As the voting has ended, reset the voting ending type.
resetVoteTypeGlobals();
// We cannot or need not the saved context anymore, as it is only used to start/setup the voting.
g_isGameEndingTypeContextSaved = false;
// vote is no longer in progress
g_voteStatus &= ~IS_VOTE_IN_PROGRESS;
// if we were in a runoff or RTV mode, get out of it
g_voteStatus &= ~IS_RUNOFF_VOTE;
g_voteStatus &= ~IS_RTV_VOTE;
// this must be called after 'g_voteStatus &= ~IS_RUNOFF_VOTE' above
vote_resetStats();
}
stock Float:map_getMinutesElapsed()
{
LOG( 128, "I AM ENTERING ON Float:map_getMinutesElapsed(0) mp_timelimit: %f", get_pcvar_float( cvar_mp_timelimit ) )
return get_pcvar_float( cvar_mp_timelimit ) - ( float( get_timeleft() ) / 60.0 );
}
stock map_getMinutesElapsedInteger()
{
LOG( 128, "I AM ENTERING ON Float:map_getMinutesElapsed(0) mp_timelimit: %f", get_pcvar_float( cvar_mp_timelimit ) )
// While the Unit Tests are running, to force a specific time.
#if DEBUG_LEVEL & ( DEBUG_LEVEL_UNIT_TEST_NORMAL | DEBUG_LEVEL_MANUAL_TEST_START | DEBUG_LEVEL_UNIT_TEST_DELAYED )
if( g_test_areTheUnitTestsRunning )
{
return g_test_gameElapsedTime;
}
#endif
return get_pcvar_num( cvar_mp_timelimit ) - ( get_timeleft() / 60 );
}
stock toAnnounceTheMapExtension( const lang[] )
{
LOG( 128, "I AM ENTERING ON toAnnounceTheMapExtension(1) lang: %s", lang )
if( g_endVotingType & ( IS_BY_ROUNDS | IS_BY_WINLIMIT ) )
{
color_print( 0, "%L %L", LANG_PLAYER, lang, LANG_PLAYER, "GAL_WINNER_EXTEND_ROUND2", g_extendmapStepRounds );
toShowTheMapExtensionHud( lang, "DMAP_MAP_EXTENDED1", "GAL_WINNER_EXTEND_ROUND1", g_extendmapStepRounds );
}
else if( g_endVotingType & IS_BY_FRAGS )
{
color_print( 0, "%L %L", LANG_PLAYER, lang, LANG_PLAYER, "GAL_WINNER_EXTEND_FRAGS2", g_extendmapStepFrags );
toShowTheMapExtensionHud( lang, "DMAP_MAP_EXTENDED1", "GAL_WINNER_EXTEND_FRAGS1", g_extendmapStepFrags );
}
else
{
color_print( 0, "%L %L", LANG_PLAYER, lang, LANG_PLAYER, "GAL_WINNER_EXTEND2", g_extendmapStepMinutes );
toShowTheMapExtensionHud( lang, "DMAP_MAP_EXTENDED1", "GAL_WINNER_EXTEND1", g_extendmapStepMinutes );
}
}
stock toShowTheMapExtensionHud( const lang1[], const lang2[], const lang3[], extend_step )
{
LOG( 128, "I AM ENTERING ON toShowTheMapExtensionHud(4) lang2: %s, lang3: %s, extend_step: %d", lang2, lang3, extend_step )
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_VOTE_RESULTS_ANNOUNCE ) )
{
set_hudmessage( 150, 120, 0, -1.0, 0.13, 0, 1.0, 6.94, 0.0, 0.0, -1 );
// If our lang is `GAL_RUNOFF_REQUIRED_TOP`, we cannot include the lang `DMAP_MAP_EXTENDED1`
// otherwise the message will be too big and will break a line which should not.
if( equali( lang1, "GAL_RUNOFF_REQUIRED_TOP" ) )
{
show_hudmessage( 0, "%L:^n%L", LANG_PLAYER, lang1, LANG_PLAYER, lang3, extend_step );
}
else
{
show_hudmessage( 0, "%L. %L:^n%L", LANG_PLAYER, lang1, LANG_PLAYER, lang2, LANG_PLAYER, lang3, extend_step );
}
}
}
stock toShowTheMapStayHud( const lang1[], const lang2[], const lang3[] )
{
LOG( 128, "I AM ENTERING ON toShowTheMapStayHud(3) lang1: %s, lang2: %s, lang3: %s", lang1, lang2, lang3 )
if( !( get_pcvar_num( cvar_hudsHide ) & HUD_VOTE_RESULTS_ANNOUNCE ) )
{
set_hudmessage( 150, 120, 0, -1.0, 0.13, 0, 1.0, 6.94, 0.0, 0.0, -1 );
// If our lang is `GAL_RUNOFF_REQUIRED_TOP`, we cannot include the lang `DMAP_MAP_EXTENDED1`
// otherwise the message will be too big and will break a line which should not.
if( equali( lang2, "GAL_RUNOFF_REQUIRED_TOP" ) )
{
show_hudmessage( 0, "%L:^n%L", LANG_PLAYER, lang2, LANG_PLAYER, lang3 );
}
else
{
show_hudmessage( 0, "%L. %L:^n%L", LANG_PLAYER, lang1, LANG_PLAYER, lang2, LANG_PLAYER, lang3 );
}
}
}
stock toShowTheMapNextHud( const lang1[], const lang2[], const lang3[], map[] )
{
LOG( 128, "I AM ENTERING ON toShowTheMapNextHud(4) lang1: %s, lang2: %s, lang3: %s", lang1, lang2, lang3 )
// The end of map count countdown will immediately start, so there is not point int showing any messages.
if( !g_isToChangeMapOnVotingEnd
&& !( get_pcvar_num( cvar_hudsHide ) & HUD_VOTE_RESULTS_ANNOUNCE ) )
{
set_hudmessage( 150, 120, 0, -1.0, 0.13, 0, 1.0, 6.94, 0.0, 0.0, -1 );
show_hudmessage( 0, "%L. %L:^n%L", LANG_PLAYER, lang1, LANG_PLAYER, lang2, LANG_PLAYER, lang3, map );
}
}
stock map_extend( const lang[] )
{
LOG( 128, "I AM ENTERING ON map_extend(1)" )
LOG( 2, "%32s g_rtvWaitMinutes: %f, g_extendmapStepMinutes: %d", "map_extend( in )", g_rtvWaitMinutes, g_extendmapStepMinutes )
// While the `IS_DISABLED_VOTEMAP_EXIT` bit flag is set, we cannot allow any decisions.
if( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXIT )
{
color_print( 0, "%L: %L", LANG_PLAYER, "DMAP_MAP_EXTENDED1", LANG_PLAYER, "GAL_WINNER_STAY2" );
toShowTheMapExtensionHud( "GAL_VOTE_ENDED", "DMAP_MAP_EXTENDED1", "GAL_WINNER_STAY1", 0 );
// When the map extension is called, there is anyone else trying to show action menu,
// therefore invoke it before returning.
openTheVoteMapActionMenu();
LOG( 1, " ( map_extend ) Just returning/blocking, g_voteMapStatus: %d", g_voteMapStatus )
return;
}
LOG( 2, "( map_extend ) TRYING to change the cvar %15s from '%f'.", "'mp_timelimit'", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, "( map_extend ) TRYING to change the cvar %15s from '%d'.", "'mp_fraglimit'", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, "( map_extend ) TRYING to change the cvar %15s from '%d'.", "'mp_maxrounds'", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, "( map_extend ) TRYING to change the cvar %15s from '%d'.", "'mp_winlimit'", get_pcvar_num( cvar_mp_winlimit ) )
saveEndGameLimits();
resetTheRtvWaitTime();
doTheActualMapExtension();
// Remove the fail safe, as we are extending the map. The fail safe could be running if the
// `cvar_endOfMapVoteStart` failed to predict the correct last round to start voting, the voting
// could have been started on the map_manageEnd(0) function. Then the fail safe will be running,
// but if the map extension was the voting winner option, then we must to disable the fail safe
// as we do not need it anymore.
remove_task( TASKID_PREVENT_INFITY_GAME );
remove_task( TASKID_SHOW_LAST_ROUND_HUD );
blockNewVotingToStart();
toAnnounceTheMapExtension( lang );
noLongerIsAnEarlyVoting();
LOG( 2, " ( map_extend ) CHECKOUT the cvar %19s is '%f'.", "'mp_timelimit'", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, " ( map_extend ) CHECKOUT the cvar %19s is '%d'.", "'mp_fraglimit'", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, " ( map_extend ) CHECKOUT the cvar %19s is '%d'.", "'mp_maxrounds'", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, " ( map_extend ) CHECKOUT the cvar %19s is '%d'.", "'mp_winlimit'", get_pcvar_num( cvar_mp_winlimit ) )
LOG( 2, "%32s g_rtvWaitMinutes: %f, g_extendmapStepMinutes: %d", "map_extend( out )", g_rtvWaitMinutes, g_extendmapStepMinutes )
}
/**
* There are several folks trying to start the voting, but they are blocked when the voting status is
* set as over, however when the extension option wins, the vote status is not set as over to allow a
* new voting when the extension time to expires.
*
* To fix the voting starting right again after the map extension, blocking it by 2 minutes should be
* big enough to not block the new voting after the map extension time expires.
*/
stock blockNewVotingToStart()
{
LOG( 128, "I AM ENTERING ON blockNewVotingToStart(0)" )
g_isMapExtensionPeriodRunning = true;
set_task( 120.0, "unblockNewVotingToStart", TASKID_BLOCK_NEW_VOTING_START );
}
public unblockNewVotingToStart()
{
LOG( 128, "I AM ENTERING ON unblockNewVotingToStart(0)" )
g_isMapExtensionPeriodRunning = false;
}
/**
* Reset the "rtv wait" time, taking into consideration the map extension.
*
* This must to be called before the doTheActualMapExtension(0)!
*/
stock resetTheRtvWaitTime()
{
LOG( 128, "I AM ENTERING ON resetTheRtvWaitTime(0)" )
LOG( 2, "( resetTheRtvWaitTime ) g_rtvWaitFrags: %d", g_rtvWaitFrags )
LOG( 2, "( resetTheRtvWaitTime ) g_rtvWaitRounds: %d", g_rtvWaitRounds )
LOG( 2, "( resetTheRtvWaitTime ) g_rtvWaitMinutes: %d", g_rtvWaitMinutes )
if( !( g_rtvCommands & RTV_CMD_EXTENSION_WAIT_DISABLE ) )
{
if( g_rtvWaitMinutes )
{
g_rtvWaitMinutes += GAME_ENDING_CONTEXT_SAVED( g_timeLimitContextSaved, get_pcvar_float( cvar_mp_timelimit ) );
}
if( g_rtvWaitRounds )
{
new cache = GAME_ENDING_CONTEXT_SAVED( g_maxRoundsContextSaved, get_pcvar_num( cvar_mp_maxrounds ) );
if( cache )
{
g_rtvWaitRounds += cache;
}
else if( ( cache = GAME_ENDING_CONTEXT_SAVED( g_winLimitContextSaved, get_pcvar_num( cvar_mp_winlimit ) ) ) )
{
g_rtvWaitRounds += cache;
}
}
if( g_rtvWaitFrags )
{
g_rtvWaitFrags += GAME_ENDING_CONTEXT_SAVED( g_fragLimitContextSaved, get_pcvar_num( cvar_mp_fraglimit ) );
}
}
LOG( 2, "( resetTheRtvWaitTime ) g_rtvWaitFrags: %d", g_rtvWaitFrags )
LOG( 2, "( resetTheRtvWaitTime ) g_rtvWaitRounds: %d", g_rtvWaitRounds )
LOG( 2, "( resetTheRtvWaitTime ) g_rtvWaitMinutes: %d", g_rtvWaitMinutes )
}
stock doTheActualMapExtension()
{
LOG( 128, "I AM ENTERING ON doTheActualMapExtension(0)" )
// Stop the map changing on a forced voting.
g_isToChangeMapOnVotingEnd = false;
if( g_endVotingType & IS_BY_ROUNDS )
{
new total = GAME_ENDING_CONTEXT_SAVED( g_maxRoundsContextSaved, get_pcvar_num( cvar_mp_maxrounds ) );
tryToSetGameModCvarNum( cvar_mp_maxrounds, total + g_extendmapStepRounds );
tryToSetGameModCvarNum( cvar_mp_winlimit , 0 );
tryToSetGameModCvarNum( cvar_mp_fraglimit, 0 );
tryToSetGameModCvarFloat( cvar_mp_timelimit, 0.0 );
}
else if( g_endVotingType & IS_BY_WINLIMIT )
{
new total = GAME_ENDING_CONTEXT_SAVED( g_winLimitContextSaved, get_pcvar_num( cvar_mp_winlimit ) );
tryToSetGameModCvarNum( cvar_mp_maxrounds, 0 );
tryToSetGameModCvarNum( cvar_mp_winlimit , total + g_extendmapStepRounds );
tryToSetGameModCvarNum( cvar_mp_fraglimit, 0 );
tryToSetGameModCvarFloat( cvar_mp_timelimit, 0.0 );
}
else if( g_endVotingType & IS_BY_FRAGS )
{
new total = GAME_ENDING_CONTEXT_SAVED( g_fragLimitContextSaved, get_pcvar_num( cvar_mp_fraglimit ) );
tryToSetGameModCvarNum( cvar_mp_maxrounds, 0 );
tryToSetGameModCvarNum( cvar_mp_winlimit , 0 );
tryToSetGameModCvarNum( cvar_mp_fraglimit, total + g_extendmapStepFrags );
tryToSetGameModCvarFloat( cvar_mp_timelimit, 0.0 );
}
else
{
new Float:total = GAME_ENDING_CONTEXT_SAVED( g_timeLimitContextSaved, get_pcvar_float( cvar_mp_timelimit ) );
tryToSetGameModCvarNum( cvar_mp_maxrounds, 0 );
tryToSetGameModCvarNum( cvar_mp_winlimit , 0 );
tryToSetGameModCvarNum( cvar_mp_fraglimit, 0 );
tryToSetGameModCvarFloat( cvar_mp_timelimit, total + g_extendmapStepMinutes );
}
}
stock saveEndGameLimits()
{
LOG( 128, "I AM ENTERING ON saveEndGameLimits(0)" )
if( !g_isEndGameLimitsChanged )
{
g_isEndGameLimitsChanged = true;
g_originalTimelimit = get_pcvar_float( cvar_mp_timelimit );
g_originalMaxRounds = get_pcvar_num( cvar_mp_maxrounds );
g_originalWinLimit = get_pcvar_num( cvar_mp_winlimit );
g_originalFragLimit = get_pcvar_num( cvar_mp_fraglimit );
LOG( 2, "( saveEndGameLimits ) SAVING the cvar %15s to '%f'.", "'mp_timelimit'", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, "( saveEndGameLimits ) SAVING the cvar %15s to '%d'.", "'mp_fraglimit'", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, "( saveEndGameLimits ) SAVING the cvar %15s to '%d'.", "'mp_maxrounds'", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, "( saveEndGameLimits ) SAVING the cvar %15s to '%d'.", "'mp_winlimit'", get_pcvar_num( cvar_mp_winlimit ) )
}
}
public map_restoreEndGameCvars()
{
LOG( 128 + 2, "I AM ENTERING ON map_restoreEndGameCvars(0)" )
restoreTheChatTime();
restoreOriginalServerMaxSpeed();
LOG( 2, "( map_restoreEndGameCvars ) TRYING to change the cvar %15s from '%f'.", "'mp_timelimit'", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, "( map_restoreEndGameCvars ) TRYING to change the cvar %15s from '%d'.", "'mp_fraglimit'", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, "( map_restoreEndGameCvars ) TRYING to change the cvar %15s from '%d'.", "'mp_maxrounds'", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, "( map_restoreEndGameCvars ) TRYING to change the cvar %15s from '%d'.", "'mp_winlimit'", get_pcvar_num( cvar_mp_winlimit ) )
if( g_isEndGameLimitsChanged )
{
g_isEndGameLimitsChanged = false;
tryToSetGameModCvarFloat( cvar_mp_timelimit, g_originalTimelimit );
tryToSetGameModCvarNum( cvar_mp_maxrounds, g_originalMaxRounds );
tryToSetGameModCvarNum( cvar_mp_winlimit , g_originalWinLimit );
tryToSetGameModCvarNum( cvar_mp_fraglimit, g_originalFragLimit );
LOG( 2, "( map_restoreEndGameCvars ) RESTORING the cvar %-24s to '%f'.", "'mp_timelimit'", get_pcvar_float( cvar_mp_timelimit ) )
LOG( 2, "( map_restoreEndGameCvars ) RESTORING the cvar %-24s to '%d'.", "'mp_fraglimit'", get_pcvar_num( cvar_mp_fraglimit ) )
LOG( 2, "( map_restoreEndGameCvars ) RESTORING the cvar %-24s to '%d'.", "'mp_maxrounds'", get_pcvar_num( cvar_mp_maxrounds ) )
LOG( 2, "( map_restoreEndGameCvars ) RESTORING the cvar %-24s to '%d'.", "'mp_winlimit'", get_pcvar_num( cvar_mp_winlimit ) )
// restore to the original/right values
g_rtvWaitMinutes = get_pcvar_float( cvar_rtvWaitMinutes );
g_rtvWaitRounds = get_pcvar_num( cvar_rtvWaitRounds );
g_rtvWaitFrags = get_pcvar_num( cvar_rtvWaitFrags );
}
cacheCvarsValues();
LOG( 1, " I AM EXITING ON map_restoreEndGameCvars(0)" )
}
stock restoreOriginalServerMaxSpeed()
{
LOG( 128, "I AM ENTERING ON restoreOriginalServerMaxSpeed(0) g_original_sv_maxspeed: %f", g_original_sv_maxspeed )
if( floatround( g_original_sv_maxspeed, floatround_floor ) )
{
tryToSetGameModCvarFloat( cvar_sv_maxspeed, g_original_sv_maxspeed );
LOG( 2, "( restoreOriginalServerMaxSpeed ) IS CHANGING THE CVAR 'sv_maxspeed' to '%f'.", g_original_sv_maxspeed )
g_original_sv_maxspeed = 0.0;
}
if( cvar_mp_friendlyfire
&& g_isToRestoreFriendlyFire )
{
tryToSetGameModCvarNum( cvar_mp_friendlyfire, 0 );
LOG( 2, "( restoreOriginalServerMaxSpeed ) IS CHANGING THE CVAR 'mp_friendlyfire' to '%d'.", get_pcvar_num( cvar_mp_friendlyfire ) )
g_isToRestoreFriendlyFire = false;
}
}
stock map_isInMenu( map[] )
{
LOG( 256, "I AM ENTERING ON map_isInMenu(1) map: %s", map )
for( new playerVoteMapChoiceIndex = 0;
playerVoteMapChoiceIndex < g_totalVoteOptions; ++playerVoteMapChoiceIndex )
{
if( equali( map, g_votingMapNames[ playerVoteMapChoiceIndex ] ) )
{
LOG( 256, " ( map_isInMenu ) Returning true." )
return true;
}
}
LOG( 256, " ( map_isInMenu ) Returning false." )
return false;
}
stock removeMapFromTheVotingMenu( mapName[] )
{
LOG( 1, "I AM ENTERING ON removeMapFromTheVotingMenu(1) map: %s", mapName )
new index;
for( ; index < g_totalVoteOptions; index++ )
{
if( equali( mapName, g_votingMapNames[ index ] ) )
{
LOG( 4, "( removeMapFromTheVotingMenu ) Removing map: %s", mapName )
g_votingMapNames[ index ][ 0 ] = '^0';
g_votingMapInfos[ index ][ 0 ] = '^0';
g_totalVoteOptions--;
break;
}
}
// Shift the entries to not mess with everything depending on the `g_totalVoteOptions` size.
for( ; index < g_totalVoteOptions; index++ )
{
copy( g_votingMapNames[ index ], charsmax( g_votingMapNames[] ), g_votingMapNames[ index + 1 ] );
copy( g_votingMapInfos[ index ], charsmax( g_votingMapInfos[] ), g_votingMapInfos[ index + 1 ] );
}
}
stock addMapToTheVotingMenu( mapName[], mapInfo[] )
{
LOG( 1, "I AM ENTERING ON addMapToTheVotingMenu(1) map: %s, mapInfo: %s", mapName, mapInfo )
if( !map_isInMenu( mapName ) )
{
copy( g_votingMapNames[ g_totalVoteOptions ], charsmax( g_votingMapNames[] ), mapName );
copy( g_votingMapInfos[ g_totalVoteOptions ], charsmax( g_votingMapInfos[] ), mapInfo );
g_totalVoteOptions++;
}
}
stock isPrefixInMenu( map[] )
{
LOG( 256, "I AM ENTERING ON isPrefixInMenu(1) map: %s", map )
new junk[ 8 ];
new possiblePrefix[ 8 ];
new existingPrefix[ 8 ];
STR_TOKEN( map, possiblePrefix, charsmax( possiblePrefix ), junk, charsmax( junk ), '_', 1 );
for( new playerVoteMapChoiceIndex = 0;
playerVoteMapChoiceIndex < g_totalVoteOptions; ++playerVoteMapChoiceIndex )
{
STR_TOKEN( g_votingMapNames[ playerVoteMapChoiceIndex ],
existingPrefix, charsmax( existingPrefix ),
junk , charsmax( junk ) , '_', 1 );
if( equali( possiblePrefix, existingPrefix ) )
{
LOG( 256, " ( isPrefixInMenu ) Returning true." )
return true;
}
}
LOG( 256, " ( isPrefixInMenu ) Returning false." )
return false;
}
stock map_isTooRecent( map[] )
{
LOG( 256, "I AM ENTERING ON map_isTooRecent(1) map: %s", map )
if( g_recentMapsTrie )
{
LOG( 256, " ( map_isTooRecent ) Returning TrieKeyExists: %d", TrieKeyExists( g_recentMapsTrie, map ) )
return TrieKeyExists( g_recentMapsTrie, map );
}
return false;
}
stock announcerockFailToosoon( player_id, Float:minutesElapsed )
{
LOG( 128, "I AM ENTERING ON announcerockFailToosoon(1) minutesElapsed: %d", minutesElapsed )
new remaining_time;
// It will be 2 minutes because there is not point to calculate whether it will be 1 or 2 minutes.
if( g_isMapExtensionPeriodRunning )
{
remaining_time = 2;
}
else
{
remaining_time = floatround( g_rtvWaitMinutes - minutesElapsed, floatround_ceil );
}
color_print( player_id, "%L", player_id, "GAL_ROCK_FAIL_TOOSOON", remaining_time );
LOG( 1, " ( announcerockFailToosoon ) Just Returning/blocking, too soon to rock by minutes." )
}
stock debugRtvVote()
{
LOG( 4, "( is_to_block_RTV ) g_voteStatus: %d", g_voteStatus )
LOG( 4, "( is_to_block_RTV ) g_rtvCommands: %d", g_rtvCommands )
LOG( 4, "( is_to_block_RTV ) g_timeLimitNumber: %d", g_timeLimitNumber )
LOG( 4, "( is_to_block_RTV ) g_fragLimitNumber: %d", g_fragLimitNumber )
LOG( 4, "( is_to_block_RTV ) g_rtvWaitMinutes: %d", g_rtvWaitMinutes )
LOG( 4, "( is_to_block_RTV ) g_maxRoundsNumber: %d", g_maxRoundsNumber )
LOG( 4, "( is_to_block_RTV ) g_winLimitNumber: %d", g_winLimitNumber )
LOG( 4, "( is_to_block_RTV ) g_rtvWaitRounds: %d", g_rtvWaitRounds )
LOG( 4, "( is_to_block_RTV ) g_totalRoundsPlayed: %d", g_totalRoundsPlayed )
LOG( 4, "( is_to_block_RTV ) g_rtvWaitFrags: %d", g_rtvWaitFrags )
LOG( 4, "( is_to_block_RTV ) g_greatestKillerFrags %d", g_greatestKillerFrags )
LOG( 4, "( is_to_block_RTV ) g_rtvWaitAdminNumber: %d", g_rtvWaitAdminNumber )
LOG( 4, "( is_to_block_RTV ) cvar_rtvWaitAdmin: %d", get_pcvar_num( cvar_rtvWaitAdmin ) )
LOG( 4, "( is_to_block_RTV ) get_real_players_number: %d", get_real_players_number() )
return 0;
}
stock is_to_block_RTV( player_id )
{
LOG( 128, "I AM ENTERING ON is_to_block_RTV(1) player_id: %d", player_id )
LOG( 0, "", debugRtvVote() )
// If time-limit is 0, minutesElapsed will always be 0.
new Float:minutesElapsed;
// If an early vote is pending, don't allow any rocks
if( g_voteStatus & IS_EARLY_VOTE )
{
color_print( player_id, "%L", player_id, "GAL_ROCK_FAIL_PENDINGVOTE" );
LOG( 1, " ( is_to_block_RTV ) Just Returning/blocking, the early voting is pending." )
}
// Rocks can only be made if a vote isn't already in progress
else if( g_voteStatus & IS_VOTE_IN_PROGRESS )
{
color_print( player_id, "%L", player_id, "GAL_ROCK_FAIL_INPROGRESS" );
LOG( 1, " ( is_to_block_RTV ) Just Returning/blocking, the voting is in progress." )
}
// If the outcome of the vote hasn't already been determined
else if( g_voteStatus & IS_VOTE_OVER )
{
color_print( player_id, "%L", player_id, "GAL_ROCK_FAIL_VOTEOVER" );
LOG( 1, " ( is_to_block_RTV ) Just Returning/blocking, the voting is over." )
}
// Cannot rock when admins are online
else if( get_pcvar_num( cvar_rtvWaitAdmin ) & IS_TO_RTV_WAIT_ADMIN
&& g_rtvWaitAdminNumber > 0 )
{
color_print( player_id, "%L", player_id, "GAL_ROCK_WAIT_ADMIN" );
LOG( 1, " ( is_to_block_RTV ) Just Returning/blocking, cannot rock when admins are online." )
}
// If the player is the only one on the server, bring up the vote immediately
else if( get_real_players_number() == 1
&& !( g_rtvCommands & RTV_CMD_SINGLE_PLAYER_DISABLE ) )
{
start_rtvVote();
LOG( 1, " ( is_to_block_RTV ) Just Returning/blocking, the voting started." )
}
// Make sure enough time has gone by on the current map
else if( ( g_timeLimitNumber
|| g_fragLimitNumber )
&& g_rtvWaitMinutes
&& ( minutesElapsed = map_getMinutesElapsed() )
&& minutesElapsed < g_rtvWaitMinutes )
{
announcerockFailToosoon( player_id, minutesElapsed );
}
// Make sure enough rounds has gone by on the current map
else if( ( g_maxRoundsNumber
|| g_winLimitNumber )
&& g_rtvWaitRounds
&& g_totalRoundsPlayed < g_rtvWaitRounds )
{
new remaining_rounds = g_rtvWaitRounds - g_totalRoundsPlayed;
color_print( player_id, "%L", player_id, "GAL_ROCK_FAIL_TOOSOON_ROUNDS", remaining_rounds );
LOG( 1, " ( is_to_block_RTV ) Just Returning/blocking, too soon to rock by rounds." )
}
// Make sure enough frags has gone by on the current map
else if( g_rtvWaitFrags
&& g_greatestKillerFrags < g_rtvWaitFrags )
{
new remaining_frags = g_rtvWaitFrags - g_greatestKillerFrags;
color_print( player_id, "%L", player_id, "GAL_ROCK_FAIL_TOOSOON_FRAGS", remaining_frags );
LOG( 1, " ( is_to_block_RTV ) Just Returning/blocking, too soon to rock by frags." )
}
else
{
LOG( 1, " ( is_to_block_RTV ) Just Returning/allowing, the RTV." )
return false;
}
return true;
}
public vote_rock( player_id )
{
LOG( 128, "I AM ENTERING ON vote_rock(1) player_id: %d", player_id )
new rocksNeeded;
if( !is_to_block_RTV( player_id )
&& compute_the_RTV_vote( player_id, ( rocksNeeded = vote_getRocksNeeded() ) ) )
{
try_to_start_the_RTV( rocksNeeded );
}
}
/**
* Allow the player to rock the vote.
*/
stock compute_the_RTV_vote( player_id, rocksNeeded )
{
LOG( 128, "I AM ENTERING ON compute_the_RTV_vote(2)" )
LOG( 4, "( compute_the_RTV_vote ) player_id: %d", player_id )
LOG( 4, "( compute_the_RTV_vote ) rocksNeeded: %d", rocksNeeded )
// make sure player hasn't already rocked the vote
if( g_rockedVote[ player_id ] )
{
color_print( player_id, "%L", player_id, "GAL_ROCK_FAIL_ALREADY", rocksNeeded - g_rockedVoteCount );
rtv_remind( TASKID_RTV_REMINDER + player_id );
LOG( 1, " ( compute_the_RTV_vote ) Just Returning/blocking, already rocked the vote." )
return false;
}
g_rockedVoteCount++;
g_rockedVote[ player_id ] = true;
color_print( player_id, "%L", player_id, "GAL_ROCK_SUCCESS" );
LOG( 1, " ( compute_the_RTV_vote ) Just Returning/blocking, accepting rock the vote." )
return true;
}
/**
* Determine if there have been enough rocks for a vote yet.
*
* @param rocksNeeded how many RTVs are necessary/required to start the voting
* @param silent whether or not to announce by chat how many RTVs are remaining to start the voting
*/
stock try_to_start_the_RTV( rocksNeeded, bool:silent=false )
{
LOG( 128, "I AM ENTERING ON try_to_start_the_RTV(2)" )
LOG( 4, "( try_to_start_the_RTV ) rocksNeeded: %d", rocksNeeded )
LOG( 4, "( try_to_start_the_RTV ) g_rockedVoteCount: %d", g_rockedVoteCount )
// make sure the rtv reminder timer has stopped
if( task_exists( TASKID_RTV_REMINDER ) )
{
remove_task( TASKID_RTV_REMINDER );
}
if( g_rockedVoteCount >= rocksNeeded )
{
// announce that the vote has been rocked
color_print( 0, "%L", LANG_PLAYER, "GAL_ROCK_ENOUGH" );
// start up the vote director
start_rtvVote();
}
else if( !silent )
{
// let the players know how many more rocks are needed
rtv_remind( TASKID_RTV_REMINDER );
if( get_pcvar_num( cvar_rtvReminder ) )
{
// initialize the rtv reminder timer to repeat how many
// rocks are still needed, at regular intervals
set_task( get_pcvar_float( cvar_rtvReminder ) * 60.0, "rtv_remind", TASKID_RTV_REMINDER, _, _, "b" );
}
}
}
/**
* Indicates when a map should end after the RTV voting is finished.
* If selected a value higher than 0, cvar_endOnRoundRtv indicates also the players
* minimum number necessary to allow the last round to be finished when
* the time runs out.
* For example, if cvar_endOnRoundRtv value is set to 2, and there are only 1 player
* on the server, the round will end immediately.
*/
stock start_rtvVote()
{
LOG( 128, "I AM ENTERING ON start_rtvVote(0)" )
new endOnRoundRtv = get_pcvar_num( cvar_endOnRoundRtv );
// Just to be sure. And this must to be called the first thing here.
resetVoteTypeGlobals();
if( endOnRoundRtv
&& get_real_players_number() >= endOnRoundRtv )
{
g_isTheLastGameRound = true;
}
else
{
g_isToChangeMapOnVotingEnd = true;
}
// Set the RTV voting status and remember, the RTV voting does not need to set the `g_endVotingType`
// because there is not map extension option, only `Stay Here` for forced voting as RTV.
g_voteStatus |= IS_RTV_VOTE;
// Any voting not started by `cvar_endOfMapVoteStart` or ending limit expiration, is a forced voting.
startTheVoting( true );
}
stock vote_unrockTheVote( player_id )
{
LOG( 128, "I AM ENTERING ON vote_unrockTheVote(1) player_id: %d", player_id )
if( g_rockedVote[ player_id ] )
{
g_rockedVoteCount--;
g_rockedVote[ player_id ] = false;
}
try_to_start_the_RTV( vote_getRocksNeeded(), true );
}
/**
* It does not consider how may RTV there are done, just how many are needed in total.
*
* @return how many RTVs there necessary to start the voting
*/
stock vote_getRocksNeeded()
{
LOG( 128, "I AM ENTERING ON vote_getRocksNeeded(0)" )
new rocks = floatround( get_pcvar_float( cvar_rtvRatio ) * float( get_real_players_number() ), floatround_floor );
LOG( 4, "( vote_getRocksNeeded ) cvar_rtvRatio: %f", get_pcvar_float( cvar_rtvRatio ) )
LOG( 4, "( vote_getRocksNeeded ) get_real_players_number: %d", get_real_players_number() )
if( rocks > 0 )
{
LOG( 4, " ( vote_getRocksNeeded ) rocks: %d", rocks )
return rocks;
}
LOG( 4, " ( vote_getRocksNeeded ) There are %d rocks! Returning: 1", rocks )
return 1;
}
public rtv_remind( param )
{
LOG( 128, "I AM ENTERING ON rtv_remind(1) param: %d", param )
new player_id = param - TASKID_RTV_REMINDER;
// let the players know how many more rocks are needed
color_print( player_id, "%L", LANG_PLAYER, "GAL_ROCK_NEEDMORE", vote_getRocksNeeded() - g_rockedVoteCount );
}
// change to the map
public map_change()
{
LOG( 128, "I AM ENTERING ON map_change(0)" )
// grab the name of the map we're changing to
new map[ MAX_MAPNAME_LENGHT ];
get_cvar_string( "amx_nextmap", map, charsmax( map ) );
resetRoundEnding();
// verify we're changing to a valid map
if( !IS_MAP_VALID( map ) )
{
// probably admin did something dumb like changed the map time limit below
// the time remaining in the map, thus making the map over immediately.
// since the next map is unknown, just restart the current map.
copy( map, charsmax( map ), g_currentMapName );
}
serverChangeLevel( map );
}
public map_change_stays()
{
LOG( 128, "I AM ENTERING ON map_change_stays(0)" )
resetRoundEnding();
LOG( 4, "( map_change_stays ) g_currentMapName: %s", g_currentMapName )
serverChangeLevel( g_currentMapName );
}
public serverChangeLevel( mapName[] )
{
LOG( 128, "I AM ENTERING ON serverChangeLevel(1)" )
LOG( 4, "( serverChangeLevel ) mapName: %s", mapName )
LOG( 4, "( serverChangeLevel ) AMXX_VERSION_NUM: %d", AMXX_VERSION_NUM )
LOG( 4, "( serverChangeLevel ) IS_TO_ENABLE_RE_HLDS_RE_AMXMODX_SUPPORT: %d", IS_TO_ENABLE_RE_HLDS_RE_AMXMODX_SUPPORT )
#if DEBUG_LEVEL & ( DEBUG_LEVEL_UNIT_TEST_NORMAL | DEBUG_LEVEL_MANUAL_TEST_START | DEBUG_LEVEL_UNIT_TEST_DELAYED )
if( g_test_areTheUnitTestsRunning )
{
if( IS_MAP_VALID( mapName ) )
{
copy( g_currentMapName, charsmax( g_currentMapName ), mapName );
}
else
{
new length = strlen( mapName );
copy( g_currentMapName[ length ], charsmax( g_currentMapName ) - length, "_invalid_map" );
}
LOG( 1, " I AM EXITING serverChangeLevel(1)... g_currentMapName: %s", g_currentMapName )
LOG( 1, "" )
return;
}
#endif
#if AMXX_VERSION_NUM < 183 || IS_TO_ENABLE_RE_HLDS_RE_AMXMODX_SUPPORT > 0
server_cmd( "changelevel %s", mapName );
#else
engine_changelevel( mapName );
#endif
LOG( 1, " I AM EXITING serverChangeLevel(1)..." )
LOG( 1, "" )
}
public cmd_HL1_votemap( player_id )
{
LOG( 128, "I AM ENTERING ON cmd_HL1_votemap(1) player_id: %d", player_id )
if( get_pcvar_num( cvar_cmdVotemap ) == 0 )
{
console_print( player_id, "%L", player_id, "GAL_DISABLED" );
LOG( 1, " ( cmd_HL1_votemap ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
LOG( 1, " ( cmd_HL1_votemap ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
public cmd_HL1_listmaps( player_id )
{
LOG( 128, "I AM ENTERING ON cmd_HL1_listmaps(1) player_id: %d", player_id )
switch( get_pcvar_num( cvar_cmdListmaps ) )
{
case 0:
{
console_print( player_id, "%L", player_id, "GAL_DISABLED" );
}
case 2:
{
map_listAll( player_id );
}
default:
{
LOG( 1, " ( cmd_HL1_listmaps ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
}
LOG( 1, " ( cmd_HL1_listmaps ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
public map_listAll( player_id )
{
LOG( 128, "I AM ENTERING ON map_listAll(1) player_id: %d", player_id )
static lastMapDisplayed[ MAX_MAPNAME_LENGHT ][ 2 ];
new start;
new userid;
new mapPerPage;
new command [ 32 ];
new parameter[ 8 ];
// determine if the player has requested a listing before
userid = get_user_userid( player_id );
if( userid != lastMapDisplayed[ player_id ][ LISTMAPS_USERID ] )
{
lastMapDisplayed[ player_id ][ LISTMAPS_USERID ] = 0;
}
read_argv( 0, command, charsmax( command ) );
mapPerPage = get_pcvar_num( cvar_listmapsPaginate );
if( mapPerPage )
{
if( read_argv( 1, parameter, charsmax( parameter ) ) )
{
if( parameter[ 0 ] == '*' )
{
// if the last map previously displayed belongs to the current user,
// start them off there, otherwise, start them at 1
if( lastMapDisplayed[ player_id ][ LISTMAPS_USERID ] )
{
start = lastMapDisplayed[ player_id ][ LISTMAPS_LAST_MAP ] + 1;
}
else
{
start = 1;
}
}
else
{
start = str_to_num( parameter );
}
}
else
{
start = 1;
}
if( player_id == 0
&& read_argc() == 3
&& read_argv( 2, parameter, charsmax( parameter ) ) )
{
mapPerPage = str_to_num( parameter );
}
}
if( start < 1 )
{
start = 1;
}
new nominationsMapsCount = ArraySize( g_nominationLoadedMapsArray );
if( start >= nominationsMapsCount )
{
start = nominationsMapsCount - 1;
}
new end = mapPerPage ? start + mapPerPage - 1 : nominationsMapsCount;
if( end > nominationsMapsCount )
{
end = nominationsMapsCount;
}
// this enables us to use 'command *' to get the next group of maps, when paginated
lastMapDisplayed[ player_id ][ LISTMAPS_LAST_MAP ] = end - 1;
lastMapDisplayed[ player_id ][ LISTMAPS_USERID ] = userid;
console_print( player_id, "^n----- %L -----", player_id, "GAL_LISTMAPS_TITLE", nominationsMapsCount );
// Second part start
new mapIndex;
new nominator_id;
new mapName [ MAX_MAPNAME_LENGHT ];
new nominated [ MAX_PLAYER_NAME_LENGHT + 32 ];
new player_name[ MAX_PLAYER_NAME_LENGHT ];
for( mapIndex = start - 1; mapIndex < end; mapIndex++ )
{
nominator_id = nomination_getPlayer( mapIndex );
if( nominator_id )
{
GET_USER_NAME( nominator_id, player_name )
formatex( nominated, charsmax( nominated ), "%L", player_id, "GAL_NOMINATEDBY", player_name );
}
else
{
nominated[ 0 ] = '^0';
}
GET_MAP_NAME( g_nominationLoadedMapsArray, mapIndex, mapName )
console_print( player_id, "%3i: %s %s", mapIndex + 1, mapName, nominated );
}
if( mapPerPage
&& mapPerPage < nominationsMapsCount )
{
console_print( player_id, "----- %L -----", player_id, "GAL_LISTMAPS_SHOWING",
start, mapIndex, nominationsMapsCount );
if( end < nominationsMapsCount )
{
console_print( player_id, "----- %L -----", player_id, "GAL_LISTMAPS_MORE",
command, end + 1, command );
}
}
}
/**
* Remove the color tags form the message before print it to the given player console.
*
* @param player_id the player id.
* @param message[] the text formatting rules to display.
* @param any the variable number of formatting parameters.
*/
stock no_color_print( const player_id, const message[], any:... )
{
LOG( 128, "I AM ENTERING ON color_console_print(...) player_id: %d, message: %s...", player_id, message )
new formatted_message[ MAX_COLOR_MESSAGE ];
vformat( formatted_message, charsmax( formatted_message ), message, 3 );
REMOVE_CODE_COLOR_TAGS( formatted_message )
console_print( player_id, formatted_message );
}
stock restartEmptyCycle()
{
LOG( 128, "I AM ENTERING ON restartEmptyCycle(0)" )
set_pcvar_num( cvar_isToStopEmptyCycle, 0 );
LOG( 2, "( restartEmptyCycle ) IS CHANGING THE CVAR 'gal_in_empty_cycle' to '%d'.", 0 )
remove_task( TASKID_EMPTYSERVER );
}
/**
* The reamxmodx is requiring more parameters to allow a call to `client_authorized()` from the
* Unit Test. So, the stock client_authorized_stock(1) is just a shadow of the client_authorized(1)
* just to allow to perform the Unit tests.
*/
#define CLIENT_AUTHORIZED_MACRO(%1) \
{ \
LOG( 128, "I AM ENTERING ON client_authorized(1) player_id: %d", %1 ) \
restartEmptyCycle(); \
if( get_user_flags( %1 ) & ADMIN_MAP ) \
{ \
g_rtvWaitAdminNumber++; \
} \
}
stock client_authorized_stock( player_id )
{
CLIENT_AUTHORIZED_MACRO( player_id )
}
public client_authorized( player_id )
{
CLIENT_AUTHORIZED_MACRO( player_id )
}
/**
* I do not know whether client_disconnected(1) will present the same problem as CLIENT_AUTHORIZED_MACRO(1)
* macro just above fixes, so I put it on a stock just for precaution.
*/
stock clientDisconnected( player_id )
{
LOG( 128, "I AM ENTERING ON clientDisconnected(1) player_id: %d [%d|%d]", player_id, get_playersnum(), get_real_players_number() )
if( is_user_bot( player_id ) ) return;
if( get_user_flags( player_id ) & ADMIN_MAP )
{
g_rtvWaitAdminNumber--;
}
// Always unrock the vote, otherwise the server may start a new map vote and for the map to
// change immediately when the `approveTheVotingStart(1)` reach on the `gal_nextmap_votemap`
// feature, which triggers the `startEmptyCycleSystem(0)`.
vote_unrockTheVote( player_id );
if( get_pcvar_num( cvar_unnominateDisconnected ) )
{
unnominatedDisconnectedPlayer( player_id );
}
isToHandleRecentlyEmptyServer();
}
#if AMXX_VERSION_NUM < 183
public client_disconnect( player_id )
#else
public client_disconnected( player_id )
#endif
{
LOG( 128, "I AM ENTERING ON client_disconnected(1) player_id: %d", player_id )
clientDisconnected( player_id );
}
stock unnominatedDisconnectedPlayer( player_id )
{
LOG( 128, "I AM ENTERING ON unnominatedDisconnectedPlayer(1) player_id: %d", player_id )
new mapIndex;
new maxPlayerNominations;
new announcementShowedTimes;
new mapName [ MAX_MAPNAME_LENGHT ];
new blockedMapsBuffer[ MAX_COLOR_MESSAGE ];
// cancel player's nominations and print what was cancelled.
maxPlayerNominations = min( get_pcvar_num( cvar_nomPlayerAllowance ), MAX_OPTIONS_IN_VOTE );
announcementShowedTimes = 1;
for( new nominationIndex = 0; nominationIndex < maxPlayerNominations; ++nominationIndex )
{
if( ( mapIndex = getPlayerNominationMapIndex( player_id, nominationIndex ) ) < 0 )
{
continue;
}
else
{
setPlayerNominationMapIndex( player_id, nominationIndex, -1 );
GET_MAP_NAME( g_nominationLoadedMapsArray, mapIndex, mapName )
announceVoteBlockedMap( mapName, blockedMapsBuffer, "GAL_FILLER_BLOCKED", announcementShowedTimes );
}
}
flushVoteBlockedMaps( blockedMapsBuffer, "GAL_CANCEL_SUCCESS", announcementShowedTimes );
}
/**
* If the empty cycle feature was initialized by 'inicializeEmptyCycleFeature()' function, this
* function to start the empty cycle map change system, when the last server player disconnect.
*/
stock isToHandleRecentlyEmptyServer()
{
LOG( 128, "I AM ENTERING ON isToHandleRecentlyEmptyServer(0)" )
new playersCount = get_real_players_number();
LOG( 2, "( isToHandleRecentlyEmptyServer ) mp_timelimit: %f, g_originalTimelimit: %f, playersCount: %d", \
get_pcvar_float( cvar_mp_timelimit ), g_originalTimelimit, playersCount )
if( playersCount == 0 )
{
if( g_originalTimelimit != get_pcvar_float( cvar_mp_timelimit ) )
{
// It's possible that the map has been extended at least once. That
// means that if someone comes into the server, the time limit will
// be the extended time limit rather than the normal time limit, bad.
// Reset the original time limit
map_restoreEndGameCvars();
}
// If it is utilizing "empty server" feature, to start it.
if( g_isUsingEmptyCycle
&& g_emptyCycleMapsArray
&& ArraySize( g_emptyCycleMapsArray ) )
{
startEmptyCycleCountdown();
}
}
LOG( 2, "I AM EXITING ON isToHandleRecentlyEmptyServer(0) g_isUsingEmptyCycle = %d, \
g_emptyCycleMapsArray: %d", g_isUsingEmptyCycle, g_emptyCycleMapsArray )
}
/**
* Inicializes the empty cycle server feature at map starting.
*/
public inicializeEmptyCycleFeature()
{
LOG( 128, "I AM ENTERING ON inicializeEmptyCycleFeature(0)" )
if( get_real_players_number() == 0 )
{
if( get_pcvar_num( cvar_isToStopEmptyCycle ) )
{
configureNextEmptyCycleMap();
}
else
{
startEmptyCycleCountdown();
}
}
g_isUsingEmptyCycle = true;
}
stock startEmptyCycleCountdown()
{
LOG( 128, "I AM ENTERING ON startEmptyCycleCountdown(0)" )
new waitMinutes = get_pcvar_num( cvar_emptyServerWaitMinutes );
if( waitMinutes )
{
set_task( float( waitMinutes * 60 ), "startEmptyCycleSystem", TASKID_EMPTYSERVER );
}
}
/**
* Set the next map from the empty cycle list, if and only if, it is not already configured.
*
* @return -1 if the current map is not on the empty cycle list. Otherwise anything else.
*/
stock configureNextEmptyCycleMap()
{
LOG( 128, "I AM ENTERING ON configureNextEmptyCycleMap(0)" )
new mapIndex;
new nextMapName [ MAX_MAPNAME_LENGHT ];
new lastEmptyCycleMap[ MAX_MAPNAME_LENGHT ];
mapIndex = map_getNext( g_emptyCycleMapsArray, g_currentMapName, nextMapName, "empty_cycle_maps" );
if( !g_isEmptyCycleMapConfigured )
{
g_isEmptyCycleMapConfigured = true;
getLastEmptyCycleMap( lastEmptyCycleMap );
map_getNext( g_emptyCycleMapsArray, lastEmptyCycleMap, nextMapName, "empty_cycle_maps" );
setLastEmptyCycleMap( nextMapName );
setNextMap( g_currentMapName, nextMapName );
}
LOG( 128, " ( configureNextEmptyCycleMap ) mapIndex: %d", mapIndex )
return mapIndex;
}
stock getLastEmptyCycleMap( lastEmptyCycleMap[ MAX_MAPNAME_LENGHT ] )
{
LOG( 128, "I AM ENTERING ON getLastEmptyCycleMap(1) lastEmptyCycleMap: %s", lastEmptyCycleMap )
new lastEmptyCycleMapFile;
new lastEmptyCycleMapFilePath[ MAX_FILE_PATH_LENGHT ];
formatex( lastEmptyCycleMapFilePath, charsmax( lastEmptyCycleMapFilePath ), "%s/%s", g_dataDirPath, LAST_EMPTY_CYCLE_FILE_NAME );
lastEmptyCycleMapFile = fopen( lastEmptyCycleMapFilePath, "rt" );
if( lastEmptyCycleMapFile )
{
fgets( lastEmptyCycleMapFile, lastEmptyCycleMap, charsmax( lastEmptyCycleMap ) );
}
}
stock setLastEmptyCycleMap( lastEmptyCycleMap[ MAX_MAPNAME_LENGHT ] )
{
LOG( 128, "I AM ENTERING ON setLastEmptyCycleMap(1) lastEmptyCycleMap: %s", lastEmptyCycleMap )
new lastEmptyCycleMapFile;
new lastEmptyCycleMapFilePath[ MAX_FILE_PATH_LENGHT ];
formatex( lastEmptyCycleMapFilePath, charsmax( lastEmptyCycleMapFilePath ), "%s/%s", g_dataDirPath, LAST_EMPTY_CYCLE_FILE_NAME );
lastEmptyCycleMapFile = fopen( lastEmptyCycleMapFilePath, "wt" );
if( lastEmptyCycleMapFile )
{
fprintf( lastEmptyCycleMapFile, "%s", lastEmptyCycleMap );
fclose( lastEmptyCycleMapFile );
}
}
public startEmptyCycleSystem()
{
LOG( 128, "I AM ENTERING ON startEmptyCycleSystem(0)" )
// stop this system at the next map, due we already be at a popular map
set_pcvar_num( cvar_isToStopEmptyCycle, 1 );
LOG( 2, "( startEmptyCycleSystem ) IS CHANGING THE CVAR 'gal_in_empty_cycle' to '%d'.", 1 )
// if the current map isn't part of the empty cycle,
// immediately change to next map that is
if( configureNextEmptyCycleMap() == -1 )
{
map_change();
}
}
/**
* Given a mapArray list and the currentMap, calculates the next map after the currentMap provided at
* the mapArray. The map list to start on 0 as the first map.
*
* If there is not found a next map, the current map name on `nextMapName` will to be set as the
* first map cycle map name.
*
* @param mapArray the dynamic array with the map list to search
* @param currentMap the string printer to the current map name
* @param nextMapName the string pointer which will receive the next map
*
* @return mapIndex the nextMapName index in the mapArray. -1 if not found a nextMapName.
*/
stock map_getNext( Array:mapArray, const currentMap[], nextMapName[], const caller[] )
{
LOG( 128, "I AM ENTERING ON map_getNext(4) currentMap: %s", currentMap )
new bool:isWhitelistBlocking;
new mapCount;
new nextmapIndex;
new returnValue = -1;
new thisMap[ MAX_MAPNAME_LENGHT ];
if( mapArray ) mapCount = ArraySize( mapArray );
new bool:isWhitelistEnabled = IS_WHITELIST_ENABLED();
for( new currentMapIndex = 0; currentMapIndex < mapCount; currentMapIndex++ )
{
GET_MAP_NAME( mapArray, currentMapIndex, thisMap )
if( isWhitelistBlocking
|| equali( currentMap, thisMap ) )
{
// When the current map is the last one, the next map is the first maps on the map cycle.
if( currentMapIndex == mapCount - 1 )
{
nextmapIndex = 0;
}
else
{
nextmapIndex = currentMapIndex + 1;
}
GET_MAP_NAME( mapArray, nextmapIndex, nextMapName )
if( IS_WHITELIST_BLOCKING( isWhitelistEnabled, nextMapName ) )
{
doAmxxLog( "WARNING, map_getNext: The Whitelist feature is blocking the map ^"%s^"", nextMapName );
isWhitelistBlocking = true;
continue;
}
returnValue = nextmapIndex;
break;
}
}
if( isWhitelistBlocking )
{
if( mapCount > 0
&& returnValue > -1 )
{
GET_MAP_NAME( mapArray, nextmapIndex, nextMapName )
}
else
{
doAmxxLog( "WARNING, map_getNext: Your ^"%s^" server variable does not contain valid maps by the Whitelist feature!", caller );
copy( nextMapName, MAX_MAPNAME_LENGHT - 1, g_currentMapName );
}
}
else
{
if( mapCount > 0 )
{
GET_MAP_NAME( mapArray, nextmapIndex, nextMapName )
}
else
{
doAmxxLog( "WARNING, map_getNext: Your ^"%s^" server variable map file does not contain valid maps!", caller );
copy( nextMapName, MAX_MAPNAME_LENGHT - 1, g_currentMapName );
}
}
LOG( 1, " ( map_getNext ) Returning mapIndex: %d, nextMapName: %s", returnValue, nextMapName )
return returnValue;
}
public client_putinserver( player_id )
{
LOG( 128, "I AM ENTERING ON client_putinserver(1) player_id: %d", player_id )
if( ( g_voteStatus & IS_EARLY_VOTE )
&& !is_user_bot( player_id )
&& !is_user_hltv( player_id ) )
{
set_task( 20.0, "srv_announceEarlyVote", player_id );
}
}
public srv_announceEarlyVote( player_id )
{
LOG( 128, "I AM ENTERING ON srv_announceEarlyVote(1) player_id: %d", player_id )
if( is_user_connected( player_id ) )
{
color_print( player_id, "%L", player_id, "GAL_VOTE_EARLY" );
}
}
stock nomination_announceCancellation( nominations[] )
{
LOG( 128, "I AM ENTERING ON nomination_announceCancellation(1) nominations: %s", nominations )
color_print( 0, "%L", LANG_PLAYER, "GAL_CANCEL_SUCCESS", nominations );
}
stock nomination_clearAll()
{
LOG( 128, "I AM ENTERING ON nomination_clearAll(0)" )
if( get_pcvar_num( cvar_nomCleaning ) )
{
TRY_TO_APPLY( TrieClear, g_reverseSearchNominationsTrie )
TRY_TO_APPLY( TrieClear, g_forwardSearchNominationsTrie )
TRY_TO_APPLY( ArrayClear, g_nominatedMapsArray )
}
}
stock map_announceNomination( player_id, map[] )
{
LOG( 128, "I AM ENTERING ON map_announceNomination(2) player_id: %d, map: %s", player_id, map )
new player_name[ MAX_PLAYER_NAME_LENGHT ];
GET_USER_NAME( player_id, player_name )
color_print( 0, "%L", LANG_PLAYER, "GAL_NOM_SUCCESS", player_name, map );
}
public cmd_rockthevote( player_id )
{
LOG( 128, "I AM ENTERING ON cmd_rockthevote(1) player_id: %d", player_id )
color_print( player_id, "%L", player_id, "GAL_CMD_RTV" );
vote_rock( player_id );
LOG( 1, " ( cmd_rockthevote ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
public cmd_nominations( player_id )
{
LOG( 128, "I AM ENTERING ON cmd_nominations(1) player_id: %d", player_id )
color_print( player_id, "%L", player_id, "GAL_CMD_NOMS" );
nomination_list();
LOG( 1, " ( cmd_nominations ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
public cmd_listrecent( player_id )
{
LOG( 128, "I AM ENTERING ON cmd_listrecent(1) player_id: %d", player_id )
new recentMapCount;
if( g_recentListMapsArray
&& ( recentMapCount = ArraySize( g_recentListMapsArray ) ) > 0 )
{
switch( get_pcvar_num( cvar_banRecentStyle ) )
{
case 1:
{
new copiedChars;
new recentMapName [ MAX_MAPNAME_LENGHT ];
new recentMapsMessage[ MAX_COLOR_MESSAGE ];
for( new mapIndex = 0; mapIndex < recentMapCount; ++mapIndex )
{
ArrayGetString( g_recentListMapsArray, mapIndex, recentMapName, charsmax( recentMapName ) );
if( copiedChars < charsmax( recentMapsMessage ) )
{
copiedChars += formatex( recentMapsMessage[ copiedChars ],
charsmax( recentMapsMessage ) - copiedChars, ", %s", recentMapName );
}
else
{
break;
}
}
color_print( 0, "%L: %s", LANG_PLAYER, "GAL_MAP_RECENTMAPS", recentMapsMessage[ 2 ] );
}
case 2:
{
new recentMapName[ MAX_MAPNAME_LENGHT ];
for( new mapIndex = 0; mapIndex < recentMapCount && mapIndex < 5; ++mapIndex )
{
ArrayGetString( g_recentListMapsArray, mapIndex, recentMapName, charsmax( recentMapName ) );
color_print( 0, "%L ( %i ): %s",
LANG_PLAYER, "GAL_MAP_RECENTMAP", mapIndex + 1, recentMapName );
}
}
case 3:
{
showRecentMapsListMenu( player_id );
}
}
}
LOG( 1, " ( cmd_listrecent ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
public showRecentMapsListMenu( player_id )
{
LOG( 128, "I AM ENTERING ON showRecentMapsListMenu(1) player_id: %d", player_id )
new mapIndex;
new itemsCount;
new recentMapName[ MAX_MAPNAME_LENGHT ];
new menuOptionString[ 64 ];
// Calculate how much pages there are available.
new recentMapCount = ArraySize( g_recentListMapsArray );
new currentPageNumber = g_recentMapsMenuPages[ player_id ];
new lastPageNumber = GET_LAST_PAGE_NUMBER( recentMapCount, MAX_MENU_ITEMS_PER_PAGE )
// To create the menu
formatex( menuOptionString, charsmax( menuOptionString ),
IS_COLORED_CHAT_ENABLED() ? "%L\R%d /%d" : "%L %d /%d",
player_id, "GAL_MAP_RECENTMAPS", currentPageNumber + 1, lastPageNumber );
new menu = menu_create( menuOptionString, "cmd_listrecent_handler" );
// Disables the menu paging.
menu_setprop( menu, MPROP_PERPAGE, 0 );
// Configure the menu buttons.
// SET_MENU_LANG_STRING_PROPERTY( MPROP_EXITNAME, menu, "EXIT" )
// SET_MENU_LANG_STRING_PROPERTY( MPROP_NEXTNAME, menu, "MORE" )
// SET_MENU_LANG_STRING_PROPERTY( MPROP_BACKNAME, menu, "BACK" )
if( ( mapIndex = currentPageNumber * MAX_MENU_ITEMS_PER_PAGE ) )
{
mapIndex = mapIndex - 1;
}
// Add the menu items.
for( ; mapIndex < recentMapCount && itemsCount < MAX_MENU_ITEMS_PER_PAGE; ++mapIndex, ++itemsCount )
{
LOG( 4, "( showRecentMapsListMenu ) mapIndex: %d", mapIndex )
ArrayGetString( g_recentListMapsArray, mapIndex, recentMapName, charsmax( recentMapName ) );
menu_additem( menu, recentMapName );
LOG( 4, "( showRecentMapsListMenu ) recentMapName: %s", recentMapName )
}
LOG( 4, "( showRecentMapsListMenu ) itemsCount: %d, mapIndex: %d", itemsCount, mapIndex )
addMenuMoreBackOptions( menu, player_id, menuOptionString, mapIndex < recentMapCount, currentPageNumber > 0, itemsCount );
// To display the menu.
menu_display( player_id, menu );
}
stock addMenuMoreBackOptions( menu, player_id, menuOptionString[], bool:isToEnableMoreButton, bool:isToEnableBackButton, itemsCount )
{
LOG( 128, "I AM ENTERING ON addMenuMoreBackOptions(5) isToEnableMoreButton: %d, \
isToEnableBackButton: %d", isToEnableMoreButton, isToEnableBackButton )
// Force the menu control options to be present on the keys 8 (more), 9 (back) and 0 (exit).
while( itemsCount < MAX_MENU_ITEMS_PER_PAGE )
{
itemsCount++;
formatex( menuOptionString, MAX_SHORT_STRING - 1, "%L", player_id, "OFF" );
menu_additem( menu, menuOptionString, _, 1 << 26 );
// When using slot=1 this might break your menu. To achieve this functionality
// menu_addblank2() should be used (AMXX 183 only).
// menu_addblank( menu, 1 );
}
// Add some space from the control options and format the more button within the LANG file.
menu_addblank( menu, 0 );
formatex( menuOptionString, MAX_SHORT_STRING - 1, "%L", player_id, "MORE" );
// If there are more maps, add the more option
if( isToEnableMoreButton )
{
menu_additem( menu, menuOptionString, _, 0 );
}
else
{
menu_additem( menu, menuOptionString, _, 1 << 26 );
}
// If we are on the first page, disable the back option and to add the exit button.
if( isToEnableBackButton )
{
formatex( menuOptionString, MAX_SHORT_STRING - 1, "%L", player_id, "BACK" );
menu_additem( menu, menuOptionString, _, 0 );
}
else
{
// To add the exit button
formatex( menuOptionString, MAX_SHORT_STRING - 1, "%L", player_id, "EXIT" );
menu_additem( menu, menuOptionString, _, 0 );
}
}
/**
* This menu handler uses the convert_numeric_base(3) instead of menu_item_getinfo() to allow easy
* conversion to the olde menu style, and also because it is working fine as it is.
*/
public cmd_listrecent_handler( player_id, menu, item )
{
LOG( 128, "I AM ENTERING ON cmd_listrecent_handler(3) player_id: %d, menu: %d, item: %d", player_id, menu, item )
// Let go to destroy the menu and clean some memory. As the menu is not paginated, the item 9
// is the key 0 on the keyboard. Also, the item 8 is the key 9; 7, 8; 6, 7; 5, 6; 4, 5; etc.
if( item < 0
|| ( item == 9
&& g_recentMapsMenuPages[ player_id ] == 0 ) )
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
LOG( 1, " ( cmd_listrecent_handler ) Just Returning PLUGIN_HANDLED, as menu is destroyed." )
return PLUGIN_HANDLED;
}
// If the 0 button item is hit, and we are not on the first page, we must to perform the back option.
if( item == 9
&& g_recentMapsMenuPages[ player_id ] > 0 )
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
g_recentMapsMenuPages[ player_id ] ? g_recentMapsMenuPages[ player_id ]-- : 0;
// Try to block/difficult players from performing the Denial Of Server attack.
// set_task( 0.1, "showRecentMapsListMenu", player_id );
showRecentMapsListMenu( player_id );
LOG( 1, " ( cmd_listrecent_handler ) Just Returning PLUGIN_HANDLED, doing the back button." )
return PLUGIN_HANDLED;
}
// If the 9 button item is hit, and we are on some page not the last one, we must to perform the more option.
if( item == 8 )
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
g_recentMapsMenuPages[ player_id ]++;
// Try to block/difficult players from performing the Denial Of Server attack.
// set_task( 0.1, "showRecentMapsListMenu", player_id );
showRecentMapsListMenu( player_id );
LOG( 1, " ( cmd_listrecent_handler ) Just Returning PLUGIN_HANDLED, doing the more button." )
return PLUGIN_HANDLED;
}
// Just keep showing the menu until the exit button is pressed.
menu_display( player_id, menu );
LOG( 1, " ( cmd_listrecent_handler ) Just Returning PLUGIN_HANDLED." )
return PLUGIN_HANDLED;
}
public cmd_changeLevel( player_id, level, cid )
{
LOG( 128, "I AM ENTERING ON cmd_changeLevel(3) player_id: %d, level: %d, cid: %d", player_id, level, cid )
if( !cmd_access( player_id, level, cid, 1 ) )
{
LOG( 1, " ( cmd_changeLevel ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
new argumentsCount = read_argc();
if( argumentsCount > 1 )
{
new arguments[ MAX_BIG_BOSS_STRING ];
read_args( arguments, charsmax( arguments ) );
remove_quotes( arguments );
LOG( 8, "( cmd_changeLevel ) " )
LOG( 8, "( cmd_changeLevel ) argumentsCount: %d, arguments: %s", argumentsCount, arguments )
if( containi( arguments, "now" ) > -1 )
{
process_last_round( true, false );
}
else
{
process_last_round( true );
}
}
else
{
process_last_round( true );
}
LOG( 1, " ( cmd_changeLevel ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
public cmd_cancelVote( player_id, level, cid )
{
LOG( 128, "I AM ENTERING ON cmd_cancelVote(3) player_id: %d, level: %d, cid: %d", player_id, level, cid )
if( !cmd_access( player_id, level, cid, 1 ) )
{
LOG( 1, " ( cmd_cancelVote ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
// If the are on debug mode, just to erase everything, as may be there something overlapping.
#if defined DEBUG
cancelVoting( true );
// To avoid the warning unreachable code.
if( !g_dummy_value )
{
LOG( 1, " ( cmd_cancelVote ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
#endif
if( g_voteStatus & IS_VOTE_IN_PROGRESS )
{
color_print( 0, "%L", LANG_SERVER, "VOT_CANC" );
cancelVoting( true );
}
else
{
color_print( 0, "%L", LANG_SERVER, "NO_VOTE_CANC" );
}
LOG( 1, " ( cmd_cancelVote ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
stock bool:approveTheVotingStartLight()
{
LOG( 128, "I AM ENTERING ON approveTheVotingStartLight(1) get_real_players_number: %d", \
get_real_players_number() )
// block the voting on some not allowed situations/cases
if( get_real_players_number() == 0)
{
LOG( 1, " ( approveTheVotingStartLight ) Returning false 0 players on the server." )
return false;
}
// the rounds start delay task could be running
remove_task( TASKID_START_VOTING_DELAYED );
// If the voting menu deletion task is running, remove it then delete the menus right now.
if( remove_task( TASKID_DELETE_USERS_MENUS ) )
{
vote_resetStats();
}
LOG( 1, " ( approveTheVotingStart ) Returning true, due passed by all requirements." )
return true;
}
/**
* It will receive a list of maps and will to perform a map voting as if it was an automatic or
* forced one. The only difference would be the maps it will use. Instead of random, they will
* the the maps passed to the command `gal_votemap map1 map2 map3 ... map9`.
*
* Issue: Add the command `gal_votemap` https://github.com/addonszz/Galileo/issues/48
*/
public cmd_voteMap( player_id, level, cid )
{
LOG( 128, "I AM ENTERING ON cmd_voteMap(3) player_id: %d, level: %d, cid: %d", player_id, level, cid )
if( !cmd_access( player_id, level, cid, 1 ) )
{
LOG( 1, " ( cmd_voteMap ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
// There is a real strange `Run time error 5: memory access` bug around these declarations,
// if you use the approveTheVotingStart(1) instead of the approveTheVotingStartLight(1)!
if( g_voteStatus & IS_VOTE_IN_PROGRESS )
{
color_print( player_id, "%L", player_id, "GAL_VOTE_INPROGRESS" );
}
else if( approveTheVotingStartLight() )
{
new argumentsCount;
new arguments[ MAX_BIG_BOSS_STRING ];
read_args( arguments, charsmax( arguments ) );
remove_quotes( arguments );
argumentsCount = read_argc();
log_amx( "%L: %s", LANG_SERVER, "GAL_VOTE_START", arguments );
LOG( 8, "( cmd_voteMap ) " )
LOG( 8, "( cmd_voteMap ) arguments: %s", arguments )
if( argumentsCount > 1
&& g_isExtendmapAllowStay
|| argumentsCount > 2 )
{
new argument[ MAX_MAPNAME_LENGHT ];
new bool:isWhitelistEnabled = IS_WHITELIST_ENABLED();
// If the voteMapMenuBuilder(1) added some maps, they will be around here, but we do not
// want to them be here as this is a full spec command.
clearTheVotingMenu();
// The initial settings setup
g_voteMapStatus = IS_DISABLED_VOTEMAP_EXIT;
// To start from 1 because the first argument 0, is the command line name `gal_startvote`.
for( new index = 1; index < argumentsCount; index++ )
{
new cache;
read_argv( index, argument, charsmax( argument ) );
LOG( 8, "( cmd_voteMap ) argument[%d]: %s", index, argument )
if( IS_WHITELIST_BLOCKING( isWhitelistEnabled, argument ) )
{
console_print( player_id, "%s: %L", argument, player_id, "GAL_MATCH_WHITELIST" );
LOG( 8, " ( cmd_voteMap ) %s: %L", argument, player_id, "GAL_MATCH_WHITELIST" )
// We do not need to print help, as the Whilelist message is clear.
goto invalid_map_provited;
}
else if( IS_MAP_VALID( argument ) )
{
LOG( 8, " ( cmd_voteMap ) argument is a valid map." )
addMapToTheVotingMenu( argument, "" );
}
else if( -1 < ( cache = containi( argument, "nointro" ) )
&& cache < 2 )
{
LOG( 8, " ( cmd_voteMap ) Entering on argument `nointro`" )
g_voteMapStatus |= IS_DISABLED_VOTEMAP_INTRO;
}
else if( -1 < ( cache = containi( argument, "norunoff" ) )
&& cache < 2 )
{
LOG( 8, " ( cmd_voteMap ) Entering on argument `norunoff`" )
g_voteMapStatus |= IS_DISABLED_VOTEMAP_RUNOFF;
}
else if( -1 < ( cache = containi( argument, "noextension" ) )
&& cache < 2 )
{
LOG( 8, " ( cmd_voteMap ) Entering on argument `noextension`" )
g_voteMapStatus |= IS_DISABLED_VOTEMAP_EXTENSION;
}
else if( -1 < ( cache = containi( argument, "loadnominations" ) )
&& cache < 2 )
{
LOG( 8, " ( cmd_voteMap ) Entering on argument `loadnominations`" )
// Load on the nominations maps.
loadOnlyNominationVoteChoices();
}
else
{
showGalVoteMapHelp( player_id, index, argument );
invalid_map_provited:
// If this was just called but not within the sufficient maps, the menu will contain
// invalid maps, therefore clean it just to be sure.
clearTheVotingMenu();
// As should not be any invalid arguments, we do allow to keep going on, otherwise
// we would have to use a buffers loader as on announceVoteBlockedMap(4) and
// flushVoteBlockedMaps(3) to avoid the client overflow when too many how arguments
// are passed by.
LOG( 1, " ( cmd_voteMap ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
}
LOG( 8, " ( cmd_voteMap ) g_voteMapStatus: %d", g_voteMapStatus )
startVoteMapVoting( player_id );
}
else
{
showGalVoteMapHelp( player_id );
}
}
LOG( 1, " ( cmd_voteMap ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
stock startVoteMapVoting( player_id )
{
LOG( 128, "I AM ENTERING ON startVoteMapVoting(1) player_id: %s", player_id )
if( g_totalVoteOptions > 0
&& !( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXTENSION )
&& g_isExtendmapAllowStay
|| g_totalVoteOptions > 1 )
{
// Load the voting time
SET_VOTING_TIME_TO( g_votingSecondsRemaining, cvar_voteDuration )
// Save the invoker id to use it later when we get the outcome result
g_voteMapInvokerPlayerId = player_id;
// to prepare the initial voting state, forcing the start up.
configureVotingStart( true );
// Show up the voting menu
initializeTheVoteDisplay();
}
else
{
// Vote creation failed; no maps found.
color_print( 0, "%L", LANG_PLAYER, "GAL_VOTE_NOMAPS" );
finalizeVoting();
showGalVoteMapHelp( player_id );
}
}
/**
* This is the `gal_votemap` admin's command line help displayer.
*/
stock showGalVoteMapHelp( player_id, index = 0, argument[] = {0} )
{
LOG( 128, "I AM ENTERING ON showGalVoteMapHelp(1) argument: %s", argument )
if( argument[ 0 ] )
{
console_print( player_id,
"^nThe argument `%d=%s` could not be recognized as a valid map or option.", index, argument );
}
// It was necessary to split the message up to 190 characters due the output print being cut.
console_print( player_id,
"Examples:\
^ngal_votemap map1 map2 map3 map4 ... map9 -nointro -noextension -norunoff" );
console_print( player_id,
"gal_votemap map1 map2 map3 map4 ... map9\
^ngal_votemap map1 map2 map3 -nointro -noextension" );
console_print( player_id,
"gal_votemap map1 map2 -nointro\
^ngal_votemap map1 map2 -loadnominations\
^ngal_votemap map1 map2" );
}
/**
* This is the main `say galmenu` builder called from the cmd_say(1) handler.
*/
stock voteMapMenuBuilder( player_id )
{
LOG( 128, "I AM ENTERING ON voteMapMenuBuilder(0) player_id: %d", player_id )
// The initial settings setup
g_voteMapStatus = IS_DISABLED_VOTEMAP_EXIT;
displayVoteMapMenuHook( player_id );
}
/**
* Due there are several first menu options, take `VOTEMAP_FIRST_PAGE_ITEMS_COUNTING` items less.
*/
#define VOTEMAP_FIRST_PAGE_ITEMS_COUNTING 4
/**
* Used to allow the menu displayVoteMapMenu(1) to have parameters within a default value.
* It is because public functions are not allow to have a default value and we need this function
* be public to allow it to be called from a set_task().
*/
public displayVoteMapMenuHook( player_id )
{
LOG( 128, "I AM ENTERING ON displayVoteMapMenuHook(1) currentPage: %d", g_voteMapMenuPages[ player_id ] )
displayVoteMapMenu( player_id );
}
/**
* This is the main menu `say galmenu` builder.
*/
stock displayVoteMapMenu( player_id )
{
LOG( 128, "I AM ENTERING ON displayVoteMapMenu(1) player_id: %d", player_id )
new mapIndex;
new itemsCount;
new nominationsMapsCount;
new choice [ MAX_MAPNAME_LENGHT + 32 ];
new nominationMap [ MAX_MAPNAME_LENGHT ];
new selectedMap [ MAX_SHORT_STRING ];
new disabledReason[ MAX_SHORT_STRING ];
nominationsMapsCount = ArraySize( g_nominationLoadedMapsArray );
// Calculate how much pages there are available.
new currentPageNumber = g_voteMapMenuPages[ player_id ];
new lastPageNumber = ( ( ( nominationsMapsCount + 1 ) / MAX_NOM_MENU_ITEMS_PER_PAGE )
+ ( ( ( ( nominationsMapsCount + 1 ) % MAX_NOM_MENU_ITEMS_PER_PAGE ) > 0 ) ? 1 : 0 ) );
// To create the menu
formatex( disabledReason, charsmax( disabledReason ),
IS_COLORED_CHAT_ENABLED() ? "%L\R%d /%d" : "%L %d /%d",
player_id, "GAL_LISTMAPS_TITLE", currentPageNumber + 1, lastPageNumber );
new menu = menu_create( disabledReason, "handleDisplayVoteMap" );
// The first page contains by default options, then the first page will get one less items due
// the extra options.
if( currentPageNumber < 1 )
{
new bool:isOn;
formatex( selectedMap, charsmax( selectedMap ), "%s%L", COLOR_YELLOW, player_id, "GAL_CHOICE_MAP", 0, 0 );
mapIndex = 0;
itemsCount = 4;
isOn = g_voteMapStatus & IS_DISABLED_VOTEMAP_INTRO != 0;
formatex( disabledReason, charsmax( disabledReason ), "-nointro %s", isOn ? selectedMap : {0} );
menu_additem( menu, disabledReason, _, 0 );
isOn = g_voteMapStatus & IS_DISABLED_VOTEMAP_RUNOFF != 0;
formatex( disabledReason, charsmax( disabledReason ), "-norunoff %s", isOn ? selectedMap : {0} );
menu_additem( menu, disabledReason, _, 0 );
isOn = g_voteMapStatus & IS_DISABLED_VOTEMAP_EXTENSION != 0;
formatex( disabledReason, charsmax( disabledReason ), "-noextension %s", isOn ? selectedMap : {0} );
menu_additem( menu, disabledReason, _, 0 );
isOn = g_voteMapStatus & IS_ENABLED_VOTEMAP_NOMINATIONS != 0;
formatex( disabledReason, charsmax( disabledReason ), "-loadnominations %s", isOn ? selectedMap : {0} );
menu_additem( menu, disabledReason, _, isOn ? ( 1 << 26 ) : 0 );
// Add some space from the last option.
// menu_addblank( menu, 0 );
}
else
{
// Due there are several first menu options, take `VOTEMAP_FIRST_PAGE_ITEMS_COUNTING` items less.
mapIndex = currentPageNumber * MAX_NOM_MENU_ITEMS_PER_PAGE - VOTEMAP_FIRST_PAGE_ITEMS_COUNTING;
itemsCount = 0;
}
// Disables the menu paging.
menu_setprop( menu, MPROP_PERPAGE, 0 );
// Configure the menu buttons.
// SET_MENU_LANG_STRING_PROPERTY( MPROP_EXITNAME, menu, "EXIT" )
// SET_MENU_LANG_STRING_PROPERTY( MPROP_NEXTNAME, menu, "MORE" )
// SET_MENU_LANG_STRING_PROPERTY( MPROP_BACKNAME, menu, "BACK" )
new bool:isWhitelistEnabled = IS_WHITELIST_ENABLED();
for( ; mapIndex < nominationsMapsCount && itemsCount < MAX_NOM_MENU_ITEMS_PER_PAGE; mapIndex++ )
{
GET_MAP_NAME( g_nominationLoadedMapsArray, mapIndex, nominationMap )
itemsCount++;
// Start the menu entry item calculation
{
// in most cases, the map will be available for selection, so assume that's the case here
selectedMap [ 0 ] = '^0';
disabledReason[ 0 ] = '^0';
// disable if the map has already been nominated
if( map_isInMenu( nominationMap ) )
{
formatex( selectedMap, charsmax( selectedMap ), "%s%L", COLOR_YELLOW, player_id, "GAL_MATCH_NOMINATED" );
}
else if( g_totalVoteOptions > 8 )
{
formatex( disabledReason, charsmax( disabledReason ), "%L", player_id, "GAL_GRP_FAIL_TOOMANY_2" );
}
else if( equali( g_currentMapName, nominationMap ) )
{
formatex( disabledReason, charsmax( disabledReason ), "%L", player_id, "GAL_MATCH_CURRENTMAP" );
}
else if( IS_WHITELIST_BLOCKING( isWhitelistEnabled, nominationMap ) )
{
formatex( disabledReason, charsmax( disabledReason ), "%L", player_id, "GAL_MATCH_WHITELIST" );
}
formatex( choice, charsmax( choice ), "%s %s %s", nominationMap, selectedMap, disabledReason );
LOG( 4, "( displayVoteMapMenu ) choice: %s", choice )
menu_additem( menu, choice, _, ( disabledReason[ 0 ] == '^0' ? 0 : ( 1 << 26 ) ) );
} // end the menu entry item calculation.
} // end for 'mapIndex'.
LOG( 4, "( displayVoteMapMenu ) itemsCount: %d, mapIndex: %d", itemsCount, mapIndex )
addMenuMoreBackStartOptions( menu, player_id, disabledReason, mapIndex < nominationsMapsCount, currentPageNumber > 0, itemsCount );
menu_display( player_id, menu );
}
stock addMenuMoreBackStartOptions( menu, player_id, disabledReason[], bool:isToEnableMoreButton, bool:isToEnableBackButton, itemsCount )
{
LOG( 128, "I AM ENTERING ON addMenuMoreBackStartOptions(5) isToEnableMoreButton: %d", isToEnableMoreButton )
addMenuMoreBackButtons( menu, player_id, disabledReason, isToEnableMoreButton, isToEnableBackButton, itemsCount );
// To add the exit button
if( g_totalVoteOptions > 0
&& !( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXTENSION )
&& g_isExtendmapAllowStay
|| g_totalVoteOptions > 1 )
{
formatex( disabledReason, MAX_SHORT_STRING - 1, "%L%s (%d)", player_id, "CMD_MENU", COLOR_YELLOW, g_totalVoteOptions );
menu_additem( menu, disabledReason, _, 0 );
}
else
{
formatex( disabledReason, MAX_SHORT_STRING - 1, "%L%s (%d)", player_id, "EXIT", COLOR_GREY, g_totalVoteOptions );
menu_additem( menu, disabledReason, _, 0 );
}
}
/**
* This is the `say galmenu` main menu handler.
*
* This menu handler uses the convert_numeric_base(3) instead of menu_item_getinfo() to allow easy
* conversion to the olde menu style, and also because it is working fine as it is.
*/
public handleDisplayVoteMap( player_id, menu, item )
{
LOG( 128, "I AM ENTERING ON handleDisplayVoteMap(3) player_id: %d, menu: %d, item: %d", player_id, menu, item )
// Let go to destroy the menu and clean some memory. As the menu is not paginated, the item 9
// is the key 0 on the keyboard. Also, the item 8 is the key 9; 7, 8; 6, 7; 5, 6; 4, 5; etc.
if( item < 0
|| ( item == 9
&& !( g_totalVoteOptions > 0
&& !( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXTENSION )
&& g_isExtendmapAllowStay
|| g_totalVoteOptions > 1 ) ) )
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
LOG( 1, " ( handleDisplayVoteMap ) Just Returning PLUGIN_HANDLED, the menu is destroyed." )
return PLUGIN_HANDLED;
}
// To start the voting
if( item == 9
&& ( g_totalVoteOptions > 0
&& !( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXTENSION )
&& g_isExtendmapAllowStay
|| g_totalVoteOptions > 1 ) )
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
displayVoteMapMenuCommands( player_id );
LOG( 1, " ( handleDisplayVoteMap ) Just Returning PLUGIN_HANDLED, starting the voting." )
return PLUGIN_HANDLED;
}
switch( item )
{
// If the 8 button item is hit, and we are not on the first page, we must to perform the back option.
case 7:
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
g_voteMapMenuPages[ player_id ] ? g_voteMapMenuPages[ player_id ]-- : 0;
// Try to block/difficult players from performing the Denial Of Server attack.
// set_task( 0.1, "displayVoteMapMenuHook", player_id );
displayVoteMapMenuHook( player_id );
LOG( 1, " ( handleDisplayVoteMap ) Just Returning PLUGIN_HANDLED, doing the back button." )
return PLUGIN_HANDLED;
}
// If the 9 button item is hit, and we are on some page not the last one, we must to perform the more option.
case 8:
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
g_voteMapMenuPages[ player_id ]++;
// Try to block/difficult players from performing the Denial Of Server attack.
// set_task( 0.1, "displayVoteMapMenuHook", player_id );
displayVoteMapMenuHook( player_id );
LOG( 1, " ( handleDisplayVoteMap ) Just Returning PLUGIN_HANDLED, doing the more button." )
return PLUGIN_HANDLED;
}
}
// Due the firsts items to be specials, intercept them, but if and only if we are on the menu's first page.
if( g_voteMapMenuPages[ player_id ] == 0
&& item < 4 )
{
switch( item )
{
// pressedKeyCode 0 means the keyboard key 1
case 0:
{
LOG( 8, " ( cmd_voteMap ) Entering on argument `nointro`" )
TOGGLE_BIT_FLAG_ON_OFF( g_voteMapStatus, IS_DISABLED_VOTEMAP_INTRO )
}
case 1:
{
LOG( 8, " ( cmd_voteMap ) Entering on argument `norunoff`" )
TOGGLE_BIT_FLAG_ON_OFF( g_voteMapStatus, IS_DISABLED_VOTEMAP_RUNOFF )
}
case 2:
{
LOG( 8, " ( cmd_voteMap ) Entering on argument `noextension`" )
TOGGLE_BIT_FLAG_ON_OFF( g_voteMapStatus, IS_DISABLED_VOTEMAP_EXTENSION )
}
case 3:
{
// Load on the nominations maps.
loadOnlyNominationVoteChoices();
// This option cannot be undone, to reduce the code complexity.
g_voteMapStatus |= IS_ENABLED_VOTEMAP_NOMINATIONS;
LOG( 8, " ( cmd_voteMap ) Entering on argument `loadnominations`" )
}
}
}
else
{
new mapName[ MAX_MAPNAME_LENGHT ];
new mapInfo[ MAX_MAPNAME_LENGHT ];
new pageSeptalNumber = convert_numeric_base( g_voteMapMenuPages[ player_id ], 10, MAX_NOM_MENU_ITEMS_PER_PAGE );
// Due there are several first menu options, take `VOTEMAP_FIRST_PAGE_ITEMS_COUNTING` items less.
item = convert_numeric_base( pageSeptalNumber * 10, MAX_NOM_MENU_ITEMS_PER_PAGE, 10 ) + item - VOTEMAP_FIRST_PAGE_ITEMS_COUNTING;
GET_MAP_NAME( g_nominationLoadedMapsArray, item, mapName )
GET_MAP_INFO( g_nominationLoadedMapsArray, item, mapInfo )
// Toggle it if enabled
map_isInMenu( mapName ) ? removeMapFromTheVotingMenu( mapName ) : addMapToTheVotingMenu( mapName, mapInfo );
}
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
// displayVoteMapMenuHook( player_id );
set_task( 0.1, "displayVoteMapMenuHook", player_id );
LOG( 1, " ( handleDisplayVoteMap ) Just Returning PLUGIN_HANDLED, successful nomination." )
return PLUGIN_HANDLED;
}
/**
* Used to select indexes values at the array `g_votingMapNames` instead of the usual array, when we're
* are on the submenu `Commands Menu`.
*/
#define VOTEMAP_VOTING_MAP_NAMES_INDEX_FLAG -2
/**
* This is the secondary `say galmenu` builder. It is used to choose to cancel the personal voting,
* start it or see the added maps.
*/
public displayVoteMapMenuCommands( player_id )
{
LOG( 128, "I AM ENTERING ON displayVoteMapMenuCommands(1) player_id: %d", player_id )
new mapIndex;
new info[ 1 ];
new choice [ MAX_SHORT_STRING ];
new menuOptionString[ MAX_SHORT_STRING ];
// To create the menu
formatex( choice, charsmax( choice ), "%L", player_id, "CMD_MENU" );
new menu = menu_create( choice, "handleDisplayVoteMapCommands" );
// The first menus items
formatex( choice, charsmax( choice ), "%L%s (%d)", player_id, "GAL_VOTE_START", COLOR_YELLOW, g_totalVoteOptions );
menu_additem( menu, choice, { -1 }, g_totalVoteOptions > 0
&& g_isExtendmapAllowStay
&& !( g_voteMapStatus & IS_DISABLED_VOTEMAP_EXTENSION )
|| g_totalVoteOptions > 1 ? 0 : ( 1 << 26 ) );
formatex( choice, charsmax( choice ), "%L", player_id, "EXIT" );
menu_additem( menu, choice, { -1 }, 0 );
formatex( choice, charsmax( choice ), "%L", player_id, "CANC_VOTE" );
menu_additem( menu, choice, { -1 }, g_totalVoteOptions > 0 ? 0 : ( 1 << 26 ) );
formatex( choice, charsmax( choice ), "%L", player_id, "GAL_VOTE_GO_TO_PAGE" );
menu_additem( menu, choice, { -1 }, 0 );
// Add some space from the first menu options.
// menu_addblank( menu, 0 );
// Configure the menu buttons.
SET_MENU_LANG_STRING_PROPERTY( MPROP_EXITNAME, menu, "GAL_LISTMAPS_TITLE" )
SET_MENU_LANG_STRING_PROPERTY( MPROP_NEXTNAME, menu, "MORE" )
SET_MENU_LANG_STRING_PROPERTY( MPROP_BACKNAME, menu, "BACK" )
for( mapIndex = 0; mapIndex < g_totalVoteOptions; mapIndex++ )
{
if( g_votingMapNames[ mapIndex ][ 0 ] )
{
info[ 0 ] = VOTEMAP_VOTING_MAP_NAMES_INDEX_FLAG - mapIndex;
formatex( choice, charsmax( choice ), "%s%s %L", g_votingMapNames[ mapIndex ], COLOR_YELLOW, player_id, "GAL_MATCH_NOMINATED" );
LOG( 4, "( displayVoteMapMenuCommands ) choice: %s, info[0]: %d", choice, info[ 0 ] )
menu_additem( menu, choice, info, 0 );
}
}
// The exit option is not showing up at the button 0, but on 9! This forces it to.
while( mapIndex + 3 < MAX_NOM_MENU_ITEMS_PER_PAGE )
{
mapIndex++;
formatex( menuOptionString, MAX_SHORT_STRING - 1, "%L", player_id, "OFF" );
menu_additem( menu, menuOptionString, _, 1 << 26 );
// When using slot=1 this might break your menu. To achieve this functionality
// menu_addblank2() should be used (AMXX 183 only).
// menu_addblank( menu, 1 );
}
menu_display( player_id, menu );
}
/**
* This is the secondary `say galmenu` handler.
*
* This menu handler uses the menu_item_getinfo() instead of convert_numeric_base(3) because it was
* recently written and there is not need to use the old menu's style with specific handler.
*/
public handleDisplayVoteMapCommands( player_id, menu, item )
{
LOG( 128, "I AM ENTERING ON handleDisplayVoteMapCommands(3) player_id: %d, menu: %d, item: %d", player_id, menu, item )
if( item == MENU_EXIT )
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
displayVoteMapMenu( player_id );
LOG( 1, " ( handleDisplayVoteMapCommands ) Just Returning PLUGIN_HANDLED, returning to the main menu." )
return PLUGIN_HANDLED;
}
if( item < 0 )
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
LOG( 1, " ( handleDisplayVoteMapCommands ) Just Returning PLUGIN_HANDLED, the menu is destroyed." )
return PLUGIN_HANDLED;
}
switch( item )
{
case 0:
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
startVoteMapVoting( player_id );
LOG( 1, " ( handleDisplayVoteMapCommands ) Just Returning PLUGIN_HANDLED, starting the voting." )
return PLUGIN_HANDLED;
}
case 1:
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
LOG( 1, " ( handleDisplayVoteMapCommands ) Just Returning PLUGIN_HANDLED, exit the menu." )
return PLUGIN_HANDLED;
}
case 2:
{
clearTheVotingMenu();
g_voteMapMenuPages[ player_id ] = 0;
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
LOG( 1, " ( handleDisplayVoteMapCommands ) Just Returning PLUGIN_HANDLED, cleaning the voting." )
return PLUGIN_HANDLED;
}
case 3:
{
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
client_cmd( player_id, "messagemode ^"say %s^"", GAL_VOTEMAP_MENU_COMMAND );
LOG( 1, " ( handleDisplayVoteMapCommands ) Just Returning PLUGIN_HANDLED, opening go to page." )
return PLUGIN_HANDLED;
}
}
// debugging menu info tracker
LOG( 4, "", debug_nomination_match_choice( player_id, menu, item ) )
new access;
new callback;
new info[ 1 ];
new mapName[ MAX_MAPNAME_LENGHT ];
new mapInfo[ MAX_MAPNAME_LENGHT ];
menu_item_getinfo( menu, item, access, info, sizeof info, _, _, callback );
if( info[ 0 ] > -1 )
{
GET_MAP_NAME( g_nominationLoadedMapsArray, info[0], mapName )
GET_MAP_INFO( g_nominationLoadedMapsArray, info[0], mapInfo )
// Toggle it if enabled
map_isInMenu( mapName ) ? removeMapFromTheVotingMenu( mapName ) : addMapToTheVotingMenu( mapName, mapInfo );
}
else
{
new mapIndex = abs( info[ 0 ] ) + VOTEMAP_VOTING_MAP_NAMES_INDEX_FLAG;
if( g_votingMapNames[ mapIndex ][ 0 ] )
{
removeMapFromTheVotingMenu( g_votingMapNames[ mapIndex ] );
}
}
// Before re-creating the menu within the updated data, we need to wait for it be destroyed.
DESTROY_PLAYER_NEW_MENU_TYPE( menu )
// Try to block/difficult players from performing the Denial Of Server attack.
// set_task( 0.1, "displayVoteMapMenuCommands", player_id );
displayVoteMapMenuCommands( player_id );
LOG( 1, " ( handleDisplayVoteMapCommands ) Just Returning PLUGIN_HANDLED, the menu is showed again." )
return PLUGIN_HANDLED;
}
stock debug_nomination_match_choice( player_id, menu, item )
{
LOG( 128, "I AM ENTERING ON debug_nomination_match_choice(3) player_id: %d, menu: %d, item: %d", player_id, menu, item )
new access;
new callback;
new info[ 1 ];
LOG( 4, "( debug_nomination_match_choice ) item: %d, player_id: %d, menu: %d, \
g_menuMapIndexForPlayerArrays[player_id]: %d", \
item, player_id, menu, g_menuMapIndexForPlayerArrays[ player_id ] )
// Get item info
menu_item_getinfo( menu, item, access, info, sizeof info, _, _, callback );
LOG( 4, "( debug_nomination_match_choice ) info[0]: %d, access: %d, menu%d", info[ 0 ], access, menu )
return 0;
}
/**
* This set up the `say galmenu` final admin's choice builder.
*/
stock openTheVoteMapActionMenu()
{
LOG( 128, "I AM ENTERING ON openTheVoteMapActionMenu(0) player_id: %d", g_voteMapInvokerPlayerId )
g_pendingMapVoteCountdown = get_pcvar_num( cvar_voteDuration ) + 120;
set_task( 1.0, "displayTheVoteMapActionMenu", TASKID_PENDING_VOTE_COUNTDOWN, _, _, "a", g_pendingMapVoteCountdown );
}
/**
* This is the `say galmenu` final admin's choice builder.
*/
public displayTheVoteMapActionMenu()
{
LOG( 128, "I AM ENTERING ON displayTheVoteMapActionMenu(0) player_id: %d", g_voteMapInvokerPlayerId )
new player_id = g_voteMapInvokerPlayerId;
if( is_user_connected( player_id )
&& --g_pendingMapVoteCountdown > 0 )
{
new winnerMap [ MAX_MAPNAME_LENGHT ];
new menu_body [ MAX_LONG_STRING ];
new menu_counter[ MAX_SHORT_STRING ];
new menu_id;
new menuKeys;
new menuKeysUnused;
new bool:allowChange = g_invokerVoteMapNameToDecide[ 0 ] != 0;
// To change the keys, go also to configureTheVotingMenus(0)
menuKeys = MENU_KEY_1;
// If the g_invokerVoteMapNameToDecide is empty, then the winner map is the stay here option.
if( allowChange )
{
menuKeys |= MENU_KEY_3 | MENU_KEY_5;
formatex( winnerMap, charsmax( winnerMap ), "%s", g_invokerVoteMapNameToDecide );
}
else
{
formatex( winnerMap, charsmax( winnerMap ), "%L", player_id, "GAL_OPTION_STAY" );
}
formatex( menu_counter, charsmax( menu_counter ),
" %s(%s%d %L%s)",
COLOR_YELLOW, COLOR_GREY, g_pendingMapVoteCountdown, player_id, "GAL_TIMELEFT", COLOR_YELLOW );
formatex( menu_body, charsmax( menu_body ),
"\
%L%s: %s%s^n\
%s%L^n\
^n%s1.%s %L %s\
^n%s3.%s %L %s\
^n%s5.%s %L\
",
player_id, "THE_RESULT", COLOR_RED, COLOR_WHITE, winnerMap,
COLOR_YELLOW, player_id, "WANT_CONTINUE",
COLOR_RED, COLOR_WHITE, player_id, "CANC_VOTE", menu_counter,
COLOR_RED, allowChange ? COLOR_WHITE : COLOR_GREY, player_id, "CHANGE_MAP_TO" , winnerMap,
COLOR_RED, allowChange ? COLOR_WHITE : COLOR_GREY, player_id, "GAL_OPTION_CANCEL_PARTIALLY", winnerMap,
0 );
get_user_menu( player_id, menu_id, menuKeysUnused );
if( menu_id == 0
|| menu_id == g_chooseVoteMapQuestionMenuId )
{
show_menu( player_id, menuKeys, menu_body, ( g_pendingMapVoteCountdown == 1 ? 1 : 2 ),
CHOOSE_VOTEMAP_MENU_QUESTION );
}
LOG( 4, "( displayTheVoteMapActionMenu ) menu_body: %s", menu_body )
LOG( 4, " menu_id: %d, menuKeys: %d, ", menu_id, menuKeys )
LOG( 4, " g_pendingMapVoteCountdown: %d", g_pendingMapVoteCountdown )
}
else
{
// To perform the default action automatically, nothing is answered.
handleVoteMapActionMenu( player_id, 0 );
}
LOG( 4, "%48s", " ( displayTheVoteMapActionMenu| out )" )
}
/**
* This is the `say galmenu` final admin's choice handler.
*/
public handleVoteMapActionMenu( player_id, pressedKeyCode )
{
LOG( 128, "I AM ENTERING ON handleVoteMapActionMenu(2) player_id: %d, pressedKeyCode: %d", \
player_id, pressedKeyCode )
// Allow the result outcome to be processed
g_voteMapStatus = 0;
// Stop the menu from showing up again
remove_task( TASKID_PENDING_VOTE_COUNTDOWN );
switch( pressedKeyCode )
{
// pressedKeyCode 0 means the keyboard key 1
case 0:
{
// If we are rejecting the results, allow a new map end voting to start
g_voteStatus &= ~IS_VOTE_OVER;
// Then this is empty, the winner was stay here, and this result has already been announced.
if( g_invokerVoteMapNameToDecide[ 0 ] )
{
color_print( 0, "%L. %L: %s", LANG_PLAYER, "RESULT_REF", LANG_PLAYER, "VOT_CANC", g_invokerVoteMapNameToDecide );
toShowTheMapNextHud( "RESULT_REF", "VOT_CANC", "GAL_OPTION_STAY_MAP", g_currentMapName );
}
}
case 2:
{
if( g_invokerVoteMapNameToDecide[ 0 ] )
{
// The end of map count countdown will immediately start, so there is not point int showing any messages.
setNextMap( g_currentMapName, g_invokerVoteMapNameToDecide );
process_last_round( true );
}
}
case 4:
{
// Only set the next map
if( g_invokerVoteMapNameToDecide[ 0 ] )
{
color_print( 0, "%L. %L: %s", LANG_PLAYER, "RESULT_ACC", LANG_PLAYER, "VOTE_SUCCESS", g_invokerVoteMapNameToDecide );
toShowTheMapNextHud( "RESULT_ACC", "DMAP_MAP_EXTENDED1", "GAL_WINNER_ORDERED1", g_invokerVoteMapNameToDecide );
setNextMap( g_currentMapName, g_invokerVoteMapNameToDecide );
}
}
}
LOG( 1, " ( handleEndOfTheMapVoteChoice ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
/**
* See setTheCurrentAndNextMapSettings(0).
*/
public cmd_quit2()
{
LOG( 128, "I AM ENTERING ON cmd_quit2(0)" )
g_isServerShuttingDown = true;
server_cmd( "quit" );
return PLUGIN_CONTINUE;
}
/**
* Called when need to start a vote map, where the command line first argument could be:
* -nochange: extend the current map, aka, Keep Current Map, will to do the real extend.
* -restart: extend the current map, aka, Keep Current Map restart the server at the current map.
*/
public cmd_startVote( player_id, level, cid )
{
LOG( 128, "I AM ENTERING ON cmd_startVote(3) player_id: %d, level: %d, cid: %d", player_id, level, cid )
if( !cmd_access( player_id, level, cid, 1 ) )
{
LOG( 1, " ( cmd_startVote ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
if( g_voteStatus & IS_VOTE_IN_PROGRESS )
{
color_print( player_id, "%L", player_id, "GAL_VOTE_INPROGRESS" );
}
else
{
new waitTime;
g_isToChangeMapOnVotingEnd = true;
if( read_argc() == 2 )
{
new argument[ 32 ];
read_args( argument, charsmax( argument ) );
remove_quotes( argument );
if( equali( argument, "-nochange" ) )
{
g_isToChangeMapOnVotingEnd = false;
}
else if( equali( argument, "-restart", 4 ) )
{
g_isTimeToRestart = true;
}
LOG( 8, "( cmd_startVote ) equali( %s, '-restart', 4 )? %d", argument, equali( argument, "-restart", 4 ) )
}
LOG( 8, "( cmd_startVote ) g_isTimeToRestart? %d, g_isToChangeMapOnVotingEnd? %d, \
g_voteStatus & IS_FORCED_VOTE: %d", g_isTimeToRestart, g_isToChangeMapOnVotingEnd, \
g_voteStatus & IS_FORCED_VOTE != 0 )
waitTime = floatround( getVoteAnnouncementTime( get_pcvar_num( cvar_isToAskForEndOfTheMapVote ) ), floatround_ceil );
console_print( player_id, "%L", player_id, "GAL_VOTE_COUNTDOWN", waitTime );
startTheVoting( true );
}
LOG( 1, " ( cmd_startVote ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
public cmd_createMapFile( player_id, level, cid )
{
LOG( 128, "I AM ENTERING ON cmd_createMapFile(3) player_id: %d, level: %d, cid: %d", player_id, level, cid )
if( !cmd_access( player_id, level, cid, 1 ) )
{
LOG( 1, " ( cmd_createMapFile ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
new argumentsNumber = read_argc() - 1;
switch( argumentsNumber )
{
case 1:
{
new mapFileName[ MAX_MAPNAME_LENGHT ];
new mapFilePath[ MAX_FILE_PATH_LENGHT ];
read_argv( 1, mapFileName, charsmax( mapFileName ) );
remove_quotes( mapFileName );
formatex( mapFilePath, charsmax( mapFilePath ), "%s/%s", g_configsDirPath, mapFileName );
createMapFileFromAllServerMaps( player_id, mapFilePath );
}
default:
{
// inform of correct usage
console_print( player_id, "%L", player_id, "GAL_CMD_CREATEFILE_USAGE1" );
console_print( player_id, "%L", player_id, "GAL_CMD_CREATEFILE_USAGE2" );
}
}
LOG( 1, " ( cmd_createMapFile ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
stock createMapFileFromAllServerMaps( player_id, mapFilePath[] )
{
LOG( 128, "I AM ENTERING ON createMapFileFromAllServerMaps(2) player_id: %d, mapFilePath: %s", player_id, mapFilePath )
// map name is MAX_MAPNAME_LENGHT, .bsp: 4 + string terminator: 1 = 5
new loadedMapName[ MAX_MAPNAME_LENGHT + 5 ];
new directoryDescriptor = open_dir( "maps", loadedMapName, charsmax( loadedMapName ) );
if( directoryDescriptor )
{
new mapFileDescriptor = fopen( mapFilePath, "wt" );
if( mapFileDescriptor )
{
new mapCount;
new mapNameLength;
new Array:allMapsArray = ArrayCreate( MAX_MAPNAME_LENGHT );
while( next_file( directoryDescriptor, loadedMapName, charsmax( loadedMapName ) ) )
{
mapNameLength = strlen( loadedMapName );
if( mapNameLength > 4
&& equali( loadedMapName[ mapNameLength - 4 ], ".bsp", 4 ) )
{
loadedMapName[ mapNameLength - 4 ] = '^0';
if( IS_MAP_VALID( loadedMapName ) )
{
ArrayPushString( allMapsArray, loadedMapName );
}
}
}
mapCount = ArraySize( allMapsArray );
ArraySort( allMapsArray, "sort_stringsi" );
for( new index = 0; index < mapCount; index++ )
{
ArrayGetString( allMapsArray, index, loadedMapName, charsmax( loadedMapName) );
fprintf( mapFileDescriptor, "%s^n", loadedMapName );
}
fclose( mapFileDescriptor );
ArrayDestroy( allMapsArray );
console_print( player_id, "%L", player_id, "GAL_CREATIONSUCCESS", mapFilePath, mapCount );
}
else
{
console_print( player_id, "%L", player_id, "GAL_CREATIONFAILED", mapFilePath );
LOG( 1, "ERROR: %L", LANG_SERVER, "GAL_CREATIONFAILED", mapFilePath )
}
close_dir( directoryDescriptor );
}
else
{
// directory not found, wtf?
console_print( player_id, "%L", player_id, "GAL_MAPSFOLDERMISSING" );
LOG( 1, "ERROR: %L", LANG_SERVER, "GAL_MAPSFOLDERMISSING" )
}
}
public sort_stringsi( Array:array, elem1, elem2, data[], data_size )
{
LOG( 256, "I AM ENTERING ON sort_stringsi(5) array: %d, elem1: %d, elem2: %d", array, elem1, elem2 )
new map1[ MAX_MAPNAME_LENGHT ];
new map2[ MAX_MAPNAME_LENGHT ];
ArrayGetString( array, elem1, map1, charsmax( map1 ) );
ArrayGetString( array, elem2, map2, charsmax( map2 ) );
LOG( 256, " ( sort_stringsi ) Returning %s > %s: %d", map1, map2, strcmp( map1, map2, 1 ) )
return strcmp( map1, map2, 1 );
}
public cmd_maintenanceMode( player_id, level, cid )
{
LOG( 128, "I AM ENTERING ON cmd_maintenanceMode(3) player_id: %d, level: %d, cid: %d", player_id, level, cid )
if( !cmd_access( player_id, level, cid, 1 ) )
{
LOG( 1, " ( cmd_maintenanceMode ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
// Always print to the console for logging, because it is a important event.
if( g_isOnMaintenanceMode )
{
g_isOnMaintenanceMode = false;
map_restoreEndGameCvars();
color_print( 0, "%L", LANG_PLAYER, "GAL_CHANGE_MAINTENANCE_STATE", LANG_PLAYER, "GAL_CHANGE_MAINTENANCE_OFF" );
no_color_print( player_id, "%L", player_id, "GAL_CHANGE_MAINTENANCE_STATE", player_id, "GAL_CHANGE_MAINTENANCE_OFF" );
}
else
{
g_isOnMaintenanceMode = true;
color_print( 0, "%L", LANG_PLAYER, "GAL_CHANGE_MAINTENANCE_STATE", LANG_PLAYER, "GAL_CHANGE_MAINTENANCE_ON" );
no_color_print( player_id, "%L", player_id, "GAL_CHANGE_MAINTENANCE_STATE", player_id, "GAL_CHANGE_MAINTENANCE_ON" );
}
LOG( 1, " ( cmd_maintenanceMode ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
public cmd_lookingForCrashes( player_id, level, cid )
{
LOG( 128, "I AM ENTERING ON cmd_lookingForCrashes(3) player_id: %d, level: %d, cid: %d", player_id, level, cid )
if( !cmd_access( player_id, level, cid, 1 ) )
{
LOG( 1, " ( cmd_lookingForCrashes ) Returning PLUGIN_CONTINUE" )
return PLUGIN_CONTINUE;
}
new crashedMapsFile;
new modeFlagFilePath [ MAX_FILE_PATH_LENGHT ];
new crashedMapsFilePath[ MAX_FILE_PATH_LENGHT ];
formatex( modeFlagFilePath, charsmax( modeFlagFilePath ), "%s/%s", g_dataDirPath, TO_STOP_THE_CRASH_SEARCH );
formatex( crashedMapsFilePath, charsmax( crashedMapsFilePath ), "%s/%s", g_dataDirPath, MAPS_WHERE_THE_SERVER_CRASHED );
if( file_exists( modeFlagFilePath ) )
{
delete_file( modeFlagFilePath );
if( ( crashedMapsFile = fopen( crashedMapsFilePath, "rt" ) ) )
{
new mapLoaded[ MAX_MAPNAME_LENGHT ];
doAmxxLog( "Stopping the server crash change...^nContents of the file: ^n^"%s^"^n", crashedMapsFilePath);
console_print( player_id, "Stopping the server crash change...^n\
See your server console or the server file:^n%s^n", crashedMapsFilePath);
while( !feof( crashedMapsFile ) )
{
fgets( crashedMapsFile, mapLoaded, charsmax( mapLoaded ) );
trim( mapLoaded );
server_print( "%s^n", mapLoaded );
}
fclose( crashedMapsFile );
}
else
{
doAmxxLog( "ERROR, cmd_lookingForCrashes: Couldn't open the file ^"%s^"", crashedMapsFilePath );
}
}
else
{
new message[ MAX_LONG_STRING ];
formatex( message, charsmax( message ), "^nStarting the crash maps search. This will check all your server maps." );
doAmxxLog( message );
console_print( player_id, message );
console_print( player_id, "To stop the search, run this command again or delete the file" );
if( ( crashedMapsFile = fopen( crashedMapsFilePath, "a+" ) ) )
{
new currentDate[ MAX_SHORT_STRING ];
get_time( "%m/%d/%Y - %H:%M:%S", currentDate, charsmax( currentDate ) );
fprintf( crashedMapsFile, "^n^n%s^n", currentDate );
fclose( crashedMapsFile );
createMapFileFromAllServerMaps( player_id, modeFlagFilePath );
runTheServerMapCrashSearch();
}
else
{
doAmxxLog( "ERROR, cmd_lookingForCrashes: Couldn't create the file ^"%s^"", crashedMapsFilePath );
}
}
LOG( 1, " ( cmd_lookingForCrashes ) Returning PLUGIN_HANDLED" )
return PLUGIN_HANDLED;
}
/**
* Generic say handler to determine if we need to act on what was said.
*
* This need to be registered only if the RTV and Nominations are enabled.
*/
public cmd_say( player_id )
{
LOG( 128, "I AM ENTERING ON cmd_say(1) player_id: %s", player_id )
new bool:status;
new thirdWord[ 2 ];
static sentence [ 70 ];
static firstWord [ 32 ];
static secondWord[ 32 ];
sentence [ 0 ] = '^0';
firstWord [ 0 ] = '^0';
secondWord[ 0 ] = '^0';
read_args( sentence, charsmax( sentence ) );
remove_quotes( sentence );
parse( sentence ,
firstWord , charsmax( firstWord ),
secondWord, charsmax( secondWord ),
thirdWord , charsmax( thirdWord ) );
LOG( 4, "( cmd_say ) sentence: %s, firstWord: %s, secondWord: %s, thirdWord: %s", \
sentence, firstWord, secondWord, thirdWord )
// if the chat line has more than 2 words, we're not interested at all
if( thirdWord[ 0 ] == '^0' )
{
new userFlags = get_user_flags( player_id );
new nomPlayerAllowance = get_pcvar_num( cvar_nomPlayerAllowance );
LOG( 4, "( cmd_say ) the thirdWord is empty, userFlags: %d", userFlags )
strtolower( firstWord );
strtolower( secondWord );
// if the chat line contains 1 word, it could be a map or a one-word command as
// "say [rtv|rockthevote]"
if( secondWord[ 0 ] == '^0' )
{
LOG( 4, "( cmd_say ) the secondWord is empty. Handling: '%s'.", firstWord )
if( nomPlayerAllowance
&& userFlags & ADMIN_MAP
&& containi( firstWord, GAL_VOTEMAP_MENU_COMMAND ) > -1 )
{
// Calculate how much pages there are available.
new nominationsMapsCount = ArraySize( g_nominationLoadedMapsArray );
new lastPageNumber = GET_LAST_PAGE_NUMBER( nominationsMapsCount, MAX_NOM_MENU_ITEMS_PER_PAGE )
setCorrectMenuPage( player_id, firstWord, g_voteMapMenuPages, lastPageNumber );
voteMapMenuBuilder( player_id );
status = true;
}
else if( ( g_rtvCommands & RTV_CMD_SHORTHAND
&& equali( firstWord, "rtv" ) )
|| ( g_rtvCommands & RTV_CMD_DYNAMIC
&& equali( firstWord, "rockthe", 7 )
&& equali( firstWord[ strlen( firstWord ) - 4 ], "vote" )
&& !( g_rtvCommands & RTV_CMD_STANDARD ) ) )
{
vote_rock( player_id );
status = true;
}
else if( nomPlayerAllowance )
{
status = sayHandlerForOneNomWords( player_id, firstWord );
}
}
else if( nomPlayerAllowance
&& userFlags & ADMIN_MAP
&& equali( firstWord, GAL_VOTEMAP_MENU_COMMAND ) )
{
// Calculate how much pages there are available.
new nominationsMapsCount = ArraySize( g_nominationLoadedMapsArray );
new lastPageNumber = GET_LAST_PAGE_NUMBER( nominationsMapsCount, MAX_NOM_MENU_ITEMS_PER_PAGE )
setCorrectMenuPage( player_id, secondWord, g_voteMapMenuPages, lastPageNumber );
voteMapMenuBuilder( player_id );
status = true;
}
else if( nomPlayerAllowance ) // "say