· 9 years ago · Oct 03, 2016, 01:02 PM
1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Ultimate Mapchooser - Core *
3 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
4
5#pragma semicolon 1
6
7#define RUNTESTS 0
8
9//Dependencies
10#include <umc-core>
11#include <umc_utils>
12#include <sourcemod>
13#include <sdktools_sound>
14
15#include <emitsoundany>
16
17#undef REQUIRE_PLUGIN
18
19//Auto update
20#include <updater>
21#if AUTOUPDATE_DEV
22 #define UPDATE_URL "http://www.ccs.neu.edu/home/steell/sourcemod/ultimate-mapchooser/dev/updateinfo-umc-core.txt"
23#else
24 #define UPDATE_URL "http://www.ccs.neu.edu/home/steell/sourcemod/ultimate-mapchooser/updateinfo-umc-core.txt"
25#endif
26
27//Some definitions
28#define NOTHING_OPTION "?nothing?"
29#define WEIGHT_KEY "___calculated-weight"
30
31//Plugin Information
32public Plugin:myinfo =
33{
34 name = "[UMC] Ultimate Mapchooser Core",
35 author = "Steell",
36 description = "Core component for [UMC]",
37 version = PL_VERSION,
38 url = "http://forums.alliedmods.net/showthread.php?t=134190"
39};
40
41//Changelog:
42/*
43 3.4.5 (10/7/2010)
44 Added initial support for CS:GO.
45 Improved error handling with overlapping votes.
46 Group votes with only one group available will now trigger a map vote, containing maps from the group.
47 Fixed bug with selective runoff votes trying to send the vote menu to invalid clients.
48 Fixed bug where Random Mapcycle would select a map after the map has been selected and ended due to instant mapchange from a vote.
49 Fixed memory leak in certain error cases.
50 Improved error handling in situations with an invalid mapcycle handle.
51
52 3.4.4 (9/1/2012)
53 Fixed issue where End of Map Votes determined by mp_maxrounds would not start.
54
55 3.4.3 (8/31/2012)
56 Fixed issue where map changes were happening too early.
57 Added support for CS:GO mp_match_can_clinch convar.
58 Improved German language translation. (Thanks elninjo!)
59
60 3.4.2 (8/26/2012)
61 Fixed issue where all RTV votes would count as bonus votes if no bonus flags were set.
62 Added support for changes in GoldenEye: Source
63 Immediate map changes will now attempt to end the round immediately instead of just changing the map.
64 Cleaned up some log statements.
65 Fixed potential crashing issue.
66 When the next map is set, the "nextmap" cvar is now also updated.
67
68 3.4.1 (8/4/2012)
69 Added ability for admins to count extra towards entering an RTV.
70 -New cvars "sm_umc_rtv_enteradminflags_bonusflags" and "sm_umc_rtv_enteradminflags_bonusamt" to control this feature
71 Updated End of Map Vote so that changes in mp_timelimit after a vote has failed will restart the timer.
72 Added group-wide exclusion to Post-Played Exclusion
73 -New "group_allow_every" option for Map Groups specifies how long before the group can be played again.
74 -"default_allow_every" still assigns "allow_every" for each map
75 -Maps can override a group's "group_allow_every" setting, which will affect the entire group
76 -If a map has an "allow_every" setting, it overrides the "group_allow_every" setting, but just for the map.
77 Fixed issue with second stage of multistage votes sometimes not working.
78 Fixed odd crashing issue (I think).
79
80 3.4 (7/22/2012)
81 Modified Map Commands:
82 -"pre_command" now fires at Map End
83 -"postvote_command" fires when map is set as next map by UMC
84 -"command" has not changed and still fires at the start of the map
85 Added ability to specify different map change times for End of Map Votes
86 -New cvar "sm_umc_endvote_changetime" to control this ability.
87 Added advanced template system for map names.
88 -New templates: {NOMINATED}, {MIN_PLAYERS}, {MAX_PLAYERS}, {MIN_TIME}, {MAX_TIME}, {RATING}
89 -New cvar "sm_umc_nomination_display" in ultimate-mapchooser.cfg
90 Added Map Start indicator to UMC logs.
91 Fixed RTV entrance threshold so that admin flags are taken into account.
92 Fixed Non-Selective runoff votes so that admin flags are taken into account.
93 Fixed issue where Extend Map and Don't Change options would not appear in Group votes.
94 Fixed issue where nominating a map via chat with an argument would display incorrect information.
95 Fixed rare issue where clients connecting could get recently-disconnected clients' votes.
96 Fixed memory leak with nominations
97 Moved source code repository to GitHub.
98
99 3.3.2 (3/4/2012)
100 Updated UMC Logging functionality
101 -All UMC logging is placed in it's own log files (prepended with UMC)
102 -Verbose logs now also log:
103 --The layout of the vote menu
104 --All clients in the vote
105 --What each client voted for, at the time they voted
106 Added ability for Random Mapcycle to select the next map at the start of the game.
107 -New cvar "sm_umc_randcycle_start" to control this ability
108 Added ability to view the current mapcycle of all modules
109 -New admin command "sm_umc_displaymaplists" to control this ability
110 Added ability to make the first slot in the vote a "No Vote" button. (thanks Azelphur!)
111 -New cvar sm_umc_votemanager_core_novote to control this feature.
112 Fixed issue where extend map wasn't working with BuiltinVotes
113 Fixed issue where bots were being included in votes.
114 Various other small fixes.
115
116 3.3.1 (12/13/2011)
117 Updated sm_umc_rtv_postvoteaction cvar to allow for normal RTV votes after a vote has taken place.
118 Fixed issue where errors were being logged accidentally.
119 Fixed issue where cancelling a vote could cause errors (and in some cases cause voting to stop working).
120 Fixed issue where Selective Runoff was always enabled.
121 Fixed issue where channging the map when the round ends could cause it to change instantly.
122
123 3.3 (11/17/11)
124 Slot blocking functionality has been changed. Modules now cannot specify how many slots they want blocked. All slot blocking is not controlled by core.
125 -New cvar "votemanager_core_blockslots" has been added to control this feature in Core.
126 -All "blockslots" cvars in various UMC modules have been removed.
127 Vote Manager support placeholder has been added. Next version will allow modules to specify their own VM IDs.
128 New cvar "sm_umc_votemanager_core_blockslots" has been added. With this update, the ability to specify how many slots to be blocked on a per-module basis has been removed. This cvar will be used for all cases where block slots are added to votes. (This means that the blockslots cvars in the various module .cfgs are now meaningless, and can be removed if you so desire).
129 Removed "runoff_blockslots" cvar. Runoff votes will always use the same BlockSlot setting as is used normally.
130 Added new Extend Command cvar, used to specify a command to be executed when a map is extended.
131 -New cvar "extend_command" in ultimate-mapchooser.cfg to control this feature.
132 Fixed issue with Random Mapcycle module which caused "next_mapgroup" option to be ignored.
133 Fixed rare memory leaks.
134
135 3.2.4 (10/12/11)
136 Added Auto Updating support.
137 Added new feature to the End of Map Vote module that delays a vote for a certain amount of time after it is triggered due to a round ending.
138 -New cvar "roundend_delaystart" to control this feature.
139 Fixed bug in Player Count Monitoring where an incorrect translation phrase was breaking Yes/No votes.
140
141 3.2.3 (9/17/11)
142 Updated Map Rate Map-Reweighting module to conform with the new Map Rate RELOADED cvars.
143 Fixed issue in TF2 where maps ending due to the mp_winlimit cvar would not trigger Random Mapcycle map selection.
144 Fixed issue with Tiered Votes where "Extend" and "Don't Change" options were not properly handled.
145
146 3.2.2 (8/11/11)
147 Added new Post-Played Exclusion module, giving the ability to specify a period of time after a map is played that it should be excluded. (Thanks Sazpaimon!)
148 Fixed bug which caused runoff votes to fail immediately when maximum amount of runoffs is set to 0 (infinite).
149 Fixed issue with invalid translation phrase in the ADMINMENU module.
150 Fixed issue with portugese translation.
151 Fixed issue where the option to change the map immediately was not working in Player Count Monitor.
152 Fixed issue with translation phrase which caused max player threshold to not trigger. PLAYERCOUNTMONITOR
153
154 3.2.1 (7/4/11)
155 Fixed issue where previously played groups were not being removed from the mapcycle.
156 Optimized vote menu population.
157
158 3.2 (7/2/11)
159 Modified previous exclusion so that it is performed at the start of the map.
160 Fixed issue where tiered votes would not work correctly when started via the admin menu and exlusion is ignored.
161 Changed previous-map exclusion so it doesn't automatically exclude the current map. If cvar is set to 0, the current map will not be excluded.
162
163 3.1.2 (6/24/11)
164 Disabled Prefix Exclusion by default.
165 Fixed issue with Map Votes not starting when there are no nominations.
166 Fixed issues where cancelled votes could cause memory leaks.
167
168 3.1.1 (6/23/11)
169 Fixed translation typo in Admin Menu
170 Fixed translation bug in Admin Menu
171 Fixed issue where admin flags would sometimes cause votes not to appear.
172 Fixed bug in the Admin Menu where Stopping an active vote would do nothing.
173
174 3.1 (6/22/11)
175 Added new map option to associate a nomination with a different group.
176 -New "nominate_group" option at the map-level of the mapcycle definition.
177 Added admin flag for ability to see vote menu.
178 -New "adminflags" cvar in ADMINMENU, ENDVOTE, PLAYERCOUNTMONITOR, ROCKTHEVOTE, and VOTECOMMAND.
179 Added admin flag for ability to enter rtv. [RTV]
180 -New "enteradminflags" cvar in RTV.
181 Added ability to specify flags for maps, limiting which players can nominate them.
182 -New group and map option in mapcycle "nominate_flags" to specify flags.
183 -New cvar "adminflags" in NOMINATE to set default.
184 Added ability to specify flags for maps, limiting which admins can select them in the admin menu.
185 -New group and map option in mapcycle "adminmenu_flags" to specify flags.
186 Added admin flags for admins who can ignore map exclusion in the admin menu.
187 -New cvar "sm_umc_am_adminflags_exclude" to control this feature.
188 Added admin flags for admins who can override default settings in the admin menu.
189 -New cvar "sm_umc_am_adminflags_defaults" to control this feature.
190 Added mp_winlimit-based vote warnings. [ENDVOTE-WARNINGS]
191 Turned basic time-based vote warnings on by default.
192 Added ability for sound to be played during countdown between Runoff/Tiered votes. [CORE]
193 -New "sm_umc_countdown_sound" cvar to specify the sound.
194 Added ability to specify a default weight for maps that do not have enough Map Rate ratings. [MAPRATE-REWEIGHT]
195 -New "sm_umc_maprate_default" cvar to control this feature.
196 Fixed bug with nominations not using group exclusion settings.
197 Fixed bug with nominations not passing their own mapcycle to forwards.
198 Fixed bug with tiered nomination menu excluding groups unnecessarily.
199 Fixed memory leak when parsing nominations for map group vote menus.
200 Fixed bug which could cause groups with no valid maps to be added to group votes.
201 Fixed bug in Prefix Exclusion which caused prefixes to be excluded even when the memory cvar was set to 0.
202
203 3.0.8 (6/11/11)
204 Fixed bug where sometimes only the first map group would be processed for map exclusions.
205
206 3.0.7 (6/10/11)
207 Changed default value for all threshold cvars to 0.
208 Added standard mapcycle feature; if given a special header, regular one-map-per-line mapcycles can be used with UMC.
209 Fixed bug that was causing "handle is invalid" error messages when runoff votes fail and thair failaction is set to consider the vote a success.
210 Fixed issues with Polish translation.
211 Modified exclusion algorithm when generating vote menus, should allow for nominations to be added to empty groups.
212 Heavily modified internal nomination system.
213 Fixed memory leak when mapcycle files can not be found.
214
215 3.0.6 (6/7/11)
216 Changed minimum value of the sm_umc_maprate_expscale cvar to 0. [MAPRATE-REWEIGHT]
217 Fixed bug in Player Count Monitoring where it couldn't auto-detect the current group. [PLAYERCOUNTMONITOR]
218 Fixed bug where errors could be caused by group exclusion code.
219 Heavily optimized debugging system, should result in execution speedup.
220 Optimized umc-maprate-reweight to fetch map weights in O(1) as opposed to O(n)
221 Added more debug messages.
222
223 3.0.5 (5/29/11)
224 Fixed bug where individual clients cancelling a vote menu could break tiered and group votes.
225
226 3.0.4 (5/28/11)
227 Added experimental admin menu module. [ADMINMENU]
228 Fixed rare bug with tiered nomination menus displaying groups with no maps in it. [NOMINATIONS]
229 Fixed bug where error log would be spammed with KillTimer errors (finally). [ENDVOTE]
230 Fixed bug where endvotes would not work in games without certain cvars. [ENDVOTE]
231 Made map weights of 0 automatically exclude maps from selection. [WEIGHT]
232 Updated various documentation.
233 Minor Optimizations.
234
235 3.0.3 (5/23/11)
236 Added ability to specify amount of times a prefix can be in the previously played prefixes before it is excluded.
237 -New cvar "sm_umc_prefixexclude_amount" in umc-prefixexclude to control this feature.
238 Fixed bug where endvotes would not appear after a map was extended. [ENDVOTE]
239
240 3.0.2 (5/22/11)
241 Fixed bug with previously played map exclusion in second stage tiered vote.
242 Fixed bug where nominated maps were not being excluded from votes properly.
243 Fixed bug where group votes would pick a random map group to be the next map.
244 Optimized map weight system so each map is only weighted once.
245 Made modules with previous map exclusions search for the current group if core reports it as INVALID_GROUP.
246 Added ability to specify a scale for umc-maprate-reweight
247 -New cvar "sm_umc_maprate_expscale" in umc-maprate-reweight to control this feature.
248 Added ability to make all votes use valve-syle menus (users press ESC to vote).
249 -New cvar "sm_umc_menu_esc" in umc-core to control this feature.
250 Made center message in umc-echonextmap last for at least 3 seconds.
251 Sequences of warnings can now be defined using a dash (-) as well as an elipses (...).
252 Added new Map Prefix Exclusion module (umc-prefixexclude).
253
254 3.0.1 (5/18/11)
255 Added extra argument to sm_setnextmap that specifies when the map will be changed.
256 Added response to the reload mapcycles command.
257 Fixed bug with map exclusion in tiered votes.
258
259 3.0 (5/16/11)
260 Near-complete rewrite of UMC. Divided up plugin into separate modules which operate independently. These modules are linked together through UMC's Core (this file).
261 Fixed many, many bugs with the rewrite. I will only be listing the ones I know have been fixed.
262 Fixed bug with Tiered Votes sometimes not displaying the second vote due to an invalid mapcycle.
263 Fixed bug with Map Group Exclusion not working correctly.
264 Fixed bug with Map Exclusion sometimes not working correctly.
265 Implemented support for "mp_winlimit" triggered end of map votes.
266 When UMC sets the next map, it can now be displayed in Center and Hint messages.
267 -New "sm_umc_echonextmap_hint" and "sm_umc_echonextmap_center" cvars to control this ability.
268 Added "next_mapgroup" to maps in the definitions, not just the groups anymore.
269 Added ability to delay end of map votes so they will only appear at the end of rounds.
270 -New "sm_umc_endvote_delayroundend" cvar to control this ability.
271 Added command to display how maprate-reweight will be reweighting maps.
272 Implemented developer's API, but it is not yet fully supported (in case I decide I need to make changes).
273 Probably more, I really should have kept track.
274
275 2.5.1 (5/5/11)
276 Added color to the [UMC] prefix in say messages.
277 Added German translation (thanks Leitwolf!)
278 Added Polish translation (thanks Arcy!)
279 Fixed minor issue with Random Selection of the Next Map not excluding maps correctly.
280 Fixed minor issue with Player Limits that was generating errors.
281 Fixed issue with "next_mapgroup" where it didn't properly check for excluded maps.
282 Fixed bug with max runoff votes not working as intended.
283 Implemented pre-command.
284
285 2.5 (4/3/11)
286 Added new feature: you can specify a maximum number of maps to appear in runoff votes.
287 -New "sm_umc_runoff_max" cvar to control this ability.
288 Players are now allowed to change their nomination.
289 Fixed bug where the map group configuration is not set properly.
290 Fixed tiered nomination menu where groups with no maps were still displayed.
291 Fixed bug where the sm_umc_mapvote command would change to an invalid map if nobody voted.
292 Added prevention code for various errors, not sure if it will fix them since I can't reproduce them.
293 Optimized memory usage in adt_arrays with Strings.
294 Added placeholders for "pre-command" support.
295
296 2.4.6 (3/25/11)
297 Fixed bug where having the same map in one group could cause a crash.
298 Fixed errors where delays between votes (runoff and tiered) could cause errors if the map ends during them.
299 Added dynamic reweight system, allowing other plugins to affect the weight of maps in UMC.
300
301 2.4.5 (3/20/11)
302 Disabled exit button on runoff votes.
303 Made runoff votes use proper pagination (< 9 options = no pagination [Radio Style Menus only])
304
305 2.4.4 (3/18/11)
306 Fixed issue where an extension could cause multiple votes.
307
308 2.4.3 (3/16/11)
309 Fixed issue with second stage tiered votes not limiting maps to the winning map group.
310
311 2.4.2 (3/9/11)
312 Modified sm_umc_mapvote command to take an argument specifying when to change the map after the vote.
313 Fixed issue with excluding previously played maps.
314 Fixed issue with random selection of the next map excluding previously played maps.
315
316 2.4.1 (3/7/11)
317 Fixed bug that caused runoff votes to never have a maximum.
318 Fixed bug that prevented some runoff votes from working correctly.
319
320 2.4 (3/7/11)
321 Made delay between votes (tiered and runoff) countdown to zero as opposed to one.
322 Fixed bug that disabled RTVs and Random Selection of the Next Map if a vote occurs and there are no votes.
323 Fixed bug with auto-pagination that broke votes with 8 or 9 items in HL2DM (and other mods that don't support Radio menus).
324
325 2.3.4-beta3 (3/6/11)
326 Made nomination menu not display maps from a map group if the strict cvar is on and enough maps have already been nominated to satisfy the maps_invote setting.
327 Fixed selective runoff votes so they take into account previous votes (from before the runoff).
328
329 2.3.4-beta2 (3/3/11)
330 Added ability for map votes to allow duplicate maps. This is useful for people running votes where the same map may appear for different mods.
331 -New sm_umc_vote_allowduplicates cvar to control this ability
332 Added ability to filter the nomination menu based off of what maps should be excluded at the time it's displayed.
333 -Removed sm_umc_nominate_timelimits cvar
334 -New sm_umc_nominate_displaylimits cvar to control this ability
335 Fixed cases where ignorelimits was not working.
336 Code refactoring
337
338 2.3.4-beta1 (3/2/11)
339 Fixed bug with Yes/No Playerlimit vote where a tie would result in garbage display.
340 Fixed bug where two RTVs could happen, one right after another, if enough people enter RTV during an RTV.
341 Made nomination menu not close automatically.
342 Fixed behavior of ignorelimits cvars and nominations.
343
344 2.3 (2/24/11)
345 Added client-side translations to all menus.
346 Added optional "display" option to map definitions, and "display-template" option to map group definitions.
347 Added ability to display nomination menu in tiers -- first select a group, then select a map.
348 -New cvar to control this feature.
349 Added ability to disable Map Exclusion for end-of-map votes, RTVs, random next map, and nominations.
350 -Four new cvars to control this feature.
351 Made all votes attempt to retry to initiate in the event they are blocked by a vote that is already running.
352 Added call to OnNominationRemoved forward in all appropriate places.
353 Modified nomination code in map group votes to not exclude the group if there were nominations for maps in it.
354 Fixed odd bug where a vote would display garbage if there was a map group with a cumulative weight of 0.
355
356 2.2.4 (2/21/11)
357 Fixed bug with center message warnings.
358 Fixed bug with runoff votes being populated with the wrong maps.
359 Fixed bug with runoff votes not paying attention to the threshold cvar.
360 Fixed bug with end of map vote timer that caused errors when the timelimit was changed.
361
362 2.2.3 (2/19/11)
363 Fixed bug that completely screwed up vote warnings.
364
365 2.2.2 (2/19/11)
366 Fixed bug that caused all time-based vote warnings to appear at 0 seconds.
367
368 2.2.1 (2/18/11)
369 Fixed problem with default min and max players for groups not being read correctly.
370
371 2.2 (2/18/11)
372 Added vote warnings support for frag and round limits.
373 -Removed sm_umc_endvote_warnings cvar
374 -Added three new cvars to control the feature: 1 each for time, frag, and round warnings.
375 Added mapchooser's "sm_setnextmap" command.
376 Changed required admin flag for commands from ADMFLAG_RCON to ADMFLAG_CHANGEMAP.
377 Added prevention measures from starting RTVs during delay between runoff and tiered votes.
378 Changed event hooks to act more like original mapchooser (may fix weird round_end bugs).
379 Fixed "nextmap" chat command functionality.
380 Fixed bug with end of map vote that prevented frag limit from triggering it.
381 Fixed bug that required mapchooser to be enabled.
382 Fixed rare memory leak with map exclusion algorithm for map groups with no maps defined.
383
384 2.1 (2/17/11)
385 Added support for mapchooser's natives.
386 Added customization for how runoff and tiered vote messages are displayed.
387 Fixed memory leak with nominations that are not added to votes.
388 Fixed memory leak with map exclusion algorithm.
389
390 2.0.2 (2/15/11)
391 Fixed (another) obscure bug with Runoff Votes, this time preventing map change.
392
393 2.0.1 (2/14/11)
394 Fixed obscure bug with Runoff Votes that prevents a vote from starting.
395
396 2.0 (2/13/11)
397 Added new "command" option to map groups and maps defined in the mapcycle. The strings supplied will be executed at the start of the map.
398 Added some code optimizations.
399 Improved logging.
400 Added ability for plugin to search for a map's player limits if the map wasn't changed by the plugin.
401 Fixed bug with Tiered Votes where votes would fail if the winning group had only one available map.
402 Fixed bug with data not being cleared when a vote menu fails to be created.
403 Fixed bug with "rockthevote" chat triggers not working.
404 Fixed bug with vote warnings where mp_timelimit was 0.
405 Fixed bug where strict nominations would sometimes cause duplicate vote entries.
406 Fixed bug where player limits would stop working.
407 Fixed bug with group voting where if a nominated map won the winning map wasn't set correctly.
408 Fixed memory leak with checking for maps with proper time and player counts.
409 Organized code
410
411 2.0-beta (2/5/11)
412 Added Runoff Vote feature. If a vote end and the winning option is less than a specified threshold, another vote will be run with losing options eliminated.
413 -Added cvar to control max amount runoffs to run (> 0 enables the feature)
414 -Added cvar to control the threshold required to prevent a runoff
415 -Added cvar to control which sound is played at the start of a runoff
416 -Added cvar to specify whether runoffs are shown to everyone or just players who need to (re)vote.
417 Added Tiered Votes. If enabled, first players will vote for a category, and then they will vote for a map from that category.
418 -Modified type cvars to allow for this kind of vote.
419 -Added cvar to control how many maps are displayed in the vote (after a category is already selected).
420 Added ability to exclude previously played categories.
421 -Three new cvars to control this ability.
422 Added ability to specify how many slots to block (up to 5)
423 -Modified blocking cvar to allow for this customization.
424 Internationalisation - Translations are now supported.
425 Votes will now only paginate when there are more than 9 slots required in the menu.
426 Added optional auto-updating.
427 Fixed memory leak with Random Selection of the Next Map.
428 Fixed memory leak with Vote Warnings.
429 Fixed bug with Vote Warnings not working after a map change.
430
431 1.5.1 (1/26/11)
432 Added shortcut to vote warnings that allows users to specify a sequence of warnings with one definition.
433 Fixed issue with time restrictions that prevented max_time from being larger than min_time (necessary for, as an example, 11:00PM - 6:00AM, which would be min_time: 2300 max_time: 0600)
434 Changed name of plugin from Improved Map Randomizer (IMR) to Ultimate Mapchooser (UMC)
435
436 1.5 (1/24/11)
437 Added vote warning feature. You can now specify warnings which appear to players before an end-of-map vote. Warnings are fully customizeable.
438 -New cvar to enable/disable this feature.
439 Added vote sounds. Cvars specify sounds to be played when a vote starts and completed.
440 -Four new cvars, 2 for end-of-map votes and 2 for RTVs
441 Added time-based map selection. New "min_time" and "max_time" options added to maps in "random_mapcycle.txt".
442 Made votes with less than 10 items not have a paginated menu.
443 Added more chat triggers for RTV. Now accepts: rtv, !rtv, rockthevote, and !rockthevote.
444 Fixed memory bug with tracking min/max players for a map.
445 Fixed bug with nominations where some categories stopped appearing in the menu.
446 Fixed bug with strict nomination cvar and populating votes.
447 Fixed bug with random group selections where groups with all their maps having been played recently and still excluded would still appear in votes.
448 Literally commented the entire plugin source code thoroughly. Happy reading!
449
450 1.4.1 (8/27/10)
451 Fixed bug in some mods where random selection of the next map was not being triggered.
452
453 1.4 (8/13/10)
454 Added separate options for when the max player limit and min player limit of the current map is broken.
455 -Two new cvars to control this feature.
456 -Old cvar removed.
457 Added a delay before the plugin checks to see if the current map has a valid number of players.
458 -New cvar to control this feature.
459 Added the ability to limit the number of nominations appearing in a vote to the number specified by that group's "maps_invote" setting.
460 -New cvar to control this feature.
461
462 1.3 (8/10/10)
463 Added vote slot blocking feature. When enabled, the first four vote slots are disabled to prevent accidental votes.
464 -New cvar to control this feature.
465 Fixed issues with displaying certain text to clients.
466 Improved error handling when rotation file is invalid.
467
468 1.2 (8/8/10)
469 Fixed bug where "next_mapgroup" was not working properly with votes.
470 Fixed bug where current map was appearing in nomination menu.
471 Added feature where if the current players on the server is not within the range defined by the current map's "min_players" and "max_players", the map can be changed.
472 -Two new cvars to control this feature.
473 Nominations now work with group votes.
474 -When a group wins the vote, it selects a random map from the nominations for that group, taking into account the weights of the maps.
475
476 1.1.3 (8/5/10)
477 Fixed memory bug with nominations.
478 Fixed (another) bug where random selection of the next map would not work properly.
479 Added ability to include a "Don't Change" option in RTVs.
480 -New cvar to enable/disable this ability.
481 -New cvar to control the delay between an RTV where "Don't Change" wins and the ability for players to RTV again.
482
483 1.1.2 (8/4/10)
484 Fixed bug in DOD:S where plugin could not start due to missing "mp_maxrounds" cvar.
485
486 1.1.1 (8/3/10)
487 Fixed bug where rounds ending would trigger end of map votes even after one was already triggered.
488 Fixed bug where frags would trigger end of map votes even after one was already triggered.
489 Fixed bug where current map would appear in votes.
490 Fixed bug where random selection of the next map would not work properly.
491
492 1.1 (8/2/10)
493 Added public cvar for tracking.
494 Modified nominations
495 -Nominations menu now contains all maps in rotation. Nominated maps will now be rejected when considered for inclusion in the vote. This way, players can nominate maps which may be valid when it's time to vote, even if they aren't valid at time of nomination.
496
497 1.0 (8/1/10)
498 Initial Release
499*/
500
501//TODO / IDEAS:
502/*
503 Keep track of clients between votes by storing them as userids OR steamids.
504 -Possibility of SteamIDs due to how visual vote operates.
505
506 Natives take an array of clients rather than a string of flags.
507
508 New "next_map" map command, works with "next_mapgroup".
509 -If next_map is set but next_mapgroup isn't, the current group is assumed.
510 -If next_map is not set but next_mapgroup is, then a map is selected at random from the group.
511 -If neither are set, a random map from a random group is selected.
512
513 Need to find a cleaner/clearer way to handle case where nominations are only used in certain modules.
514 -Solution 1: new "display-group" option that mimicks the "display" option for maps (but this is for groups).
515 -Solution 2: implement mapcycle-level options (to complement group and map options).
516
517 Take nominations into account when selecting a random map.
518 Add cvar to control where nominations are placed in the vote (on top vs. scrambled)
519 Possible Bug: map change (sm_map or changelevel) after a vote completes can set the wrong
520 current_cat. I'm not exactly sure how to fix this.
521 PERHAPS: store the next map, when the map changes compare the current map to the one we have
522 stored. If they are different, set the current_cat to INVALID_GROUP.
523 New mapexclude_strict cvar that doesn't take map group into account when excluding previously played maps.
524 In situations where we're filtering a list of map tries (map/group tries) for a specific
525 group, it may be easier to store it instead as a trie of groups, where each group points
526 to a list of maps.
527 */
528
529//BUGS:
530
531//************************************************************************************************//
532// GLOBAL VARIABLES //
533//************************************************************************************************//
534
535 ////----CONVARS-----/////
536new Handle:cvar_runoff_display = INVALID_HANDLE;
537new Handle:cvar_runoff_selective = INVALID_HANDLE;
538new Handle:cvar_vote_tieramount = INVALID_HANDLE;
539new Handle:cvar_vote_tierdisplay = INVALID_HANDLE;
540new Handle:cvar_logging = INVALID_HANDLE;
541new Handle:cvar_extend_display = INVALID_HANDLE;
542new Handle:cvar_dontchange_display = INVALID_HANDLE;
543new Handle:cvar_valvemenu = INVALID_HANDLE;
544new Handle:cvar_version = INVALID_HANDLE;
545new Handle:cvar_count_sound = INVALID_HANDLE;
546new Handle:cvar_extend_command = INVALID_HANDLE;
547new Handle:cvar_default_vm = INVALID_HANDLE;
548new Handle:cvar_block_slots = INVALID_HANDLE;
549new Handle:cvar_novote = INVALID_HANDLE;
550new Handle:cvar_nomdisp = INVALID_HANDLE;
551
552//Stores the current category.
553new String:current_cat[MAP_LENGTH];
554
555//Stores the category of the next map.
556new String:next_cat[MAP_LENGTH];
557
558//Array of nomination tries.
559new Handle:nominations_arr = INVALID_HANDLE;
560
561//Forward for when a nomination is removed.
562new Handle:nomination_reset_forward = INVALID_HANDLE;
563
564//
565new String:countdown_sound[PLATFORM_MAX_PATH];
566
567/* Reweight System */
568new Handle:reweight_forward = INVALID_HANDLE;
569new Handle:reweight_group_forward = INVALID_HANDLE;
570new bool:reweight_active = false;
571new Float:current_weight;
572
573/* Exclusion System */
574new Handle:exclude_forward = INVALID_HANDLE;
575
576/* Reload System */
577new Handle:reload_forward = INVALID_HANDLE;
578
579/* Extend System */
580new Handle:extend_forward = INVALID_HANDLE;
581
582/* Nextmap System */
583new Handle:nextmap_forward = INVALID_HANDLE;
584
585/* Failure System */
586new Handle:failure_forward = INVALID_HANDLE;
587
588/* Vote Notification System */
589new Handle:vote_start_forward = INVALID_HANDLE;
590new Handle:vote_end_forward = INVALID_HANDLE;
591new Handle:client_voted_forward = INVALID_HANDLE;
592
593/* Vote Management System */
594new Handle:vote_managers = INVALID_HANDLE;
595new Handle:vote_manager_ids = INVALID_HANDLE;
596
597/* Maplist Display */
598new Handle:maplistdisplay_forward = INVALID_HANDLE;
599
600/* Template System */
601new Handle:template_forward = INVALID_HANDLE;
602
603//Flags
604new bool:change_map_round; //Change map when the round ends?
605
606//Misc ConVars
607new Handle:cvar_maxrounds = INVALID_HANDLE;
608new Handle:cvar_fraglimit = INVALID_HANDLE;
609new Handle:cvar_winlimit = INVALID_HANDLE;
610//new Handle:cvar_nextmap = INVALID_HANDLE;
611new Handle:cvar_nextlevel = INVALID_HANDLE; //GE:S
612
613
614#if RUNTESTS
615RunTests()
616{
617 LogUMCMessage("TEST: Running UMC tests.");
618
619 LogUMCMessage("TEST: Finished running UMC tests.");
620}
621#endif
622
623
624//************************************************************************************************//
625// SOURCEMOD EVENTS //
626//************************************************************************************************//
627
628//Called before the plugin loads, sets up our natives.
629public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
630{
631 CreateNative("UMC_AddWeightModifier", Native_UMCAddWeightModifier);
632 CreateNative("UMC_StartVote", Native_UMCStartVote);
633 CreateNative("UMC_GetCurrentMapGroup", Native_UMCGetCurrentGroup);
634 CreateNative("UMC_GetRandomMap", Native_UMCGetRandomMap);
635 CreateNative("UMC_SetNextMap", Native_UMCSetNextMap);
636 CreateNative("UMC_IsMapNominated", Native_UMCIsMapNominated);
637 CreateNative("UMC_NominateMap", Native_UMCNominateMap);
638 CreateNative("UMC_CreateValidMapArray", Native_UMCCreateMapArray);
639 CreateNative("UMC_CreateValidMapGroupArray", Native_UMCCreateGroupArray);
640 CreateNative("UMC_IsMapValid", Native_UMCIsMapValid);
641 //CreateNative("UMC_IsGroupValid", Native_UMCIsGroupValid);
642 CreateNative("UMC_FilterMapcycle", Native_UMCFilterMapcycle);
643 CreateNative("UMC_IsVoteInProgress", Native_UMCIsVoteInProgress);
644 CreateNative("UMC_StopVote", Native_UMCStopVote);
645 CreateNative("UMC_RegisterVoteManager", Native_UMCRegVoteManager);
646 CreateNative("UMC_UnregisterVoteManager", Native_UMCUnregVoteManager);
647 CreateNative("UMC_VoteManagerVoteCompleted", Native_UMCVoteManagerComplete);
648 CreateNative("UMC_VoteManagerVoteCancelled", Native_UMCVoteManagerCancel);
649 CreateNative("UMC_VoteManagerClientVoted", Native_UMCVoteManagerVoted);
650 CreateNative("UMC_FormatDisplayString", Native_UMCFormatDisplay);
651 CreateNative("UMC_IsNewVoteAllowed", Native_UMCIsNewVoteAllowed);
652
653 RegPluginLibrary("umccore");
654
655 return APLRes_Success;
656}
657
658
659//Called when the plugin is finished loading.
660public OnPluginStart()
661{
662 cvar_nomdisp = CreateConVar(
663 "sm_umc_nomination_display",
664 "^",
665 "String to replace the {NOMINATED} map display-template string with."
666 );
667
668 cvar_novote = CreateConVar(
669 "sm_umc_votemanager_core_novote",
670 "0",
671 "Enable No Vote option at the top of vote menus. Requires SourceMod >= 1.4",
672 0, true, 0.0, true, 1.0
673 );
674
675 cvar_block_slots = CreateConVar(
676 "sm_umc_votemanager_core_blockslots",
677 "0",
678 "Specifies how many slots in a vote are disabled to prevent accidental voting.",
679 0, true, 0.0, true, 5.0
680 );
681
682 cvar_default_vm = CreateConVar(
683 "sm_umc_votemanager_default",
684 "core",
685 "Specifies the default UMC Vote Manager to be used for voting. The default value of \"core\" means that Sourcemod's built-in voting will be used."
686 );
687
688 cvar_extend_command = CreateConVar(
689 "sm_umc_extend_command",
690 "",
691 "Specifies a server command to be executed when the map is extended by UMC."
692 );
693
694 cvar_count_sound = CreateConVar(
695 "sm_umc_countdown_sound",
696 "",
697 "Specifies a sound to be played each second during the countdown time between runoff and tiered votes. (Sound will be precached and added to the download table.)"
698 );
699
700 cvar_valvemenu = CreateConVar(
701 "sm_umc_votemanager_core_menu_esc",
702 "0",
703 "If enabled, votes will use Valve-Stlye menus (players will be required to press ESC in order to vote). NOTE: this may not work in TF2!",
704 0, true, 0.0, true, 1.0
705 );
706
707 cvar_extend_display = CreateConVar(
708 "sm_umc_extend_display",
709 "0",
710 "Determines where in votes the \"Extend Map\" option will be displayed.\n 0 - Bottom,\n 1 - Top",
711 0, true, 0.0, true, 1.0
712 );
713
714 cvar_dontchange_display = CreateConVar(
715 "sm_umc_dontchange_display",
716 "0",
717 "Determines where in votes the \"Don't Change\" option will be displayed.\n 0 - Bottom,\n 1 - Top",
718 0, true, 0.0, true, 1.0
719 );
720
721 cvar_logging = CreateConVar(
722 "sm_umc_logging_verbose",
723 "1",
724 "Enables in-depth logging. Use this to have the plugin log how votes are being populated.",
725 0, true, 0.0, true, 1.0
726 );
727
728 cvar_runoff_selective = CreateConVar(
729 "sm_umc_runoff_selective",
730 "0",
731 "Specifies whether runoff votes are only displayed to players whose votes were eliminated in the runoff and players who did not vote.",
732 0, true, 0.0, true, 1.0
733 );
734
735 cvar_vote_tieramount = CreateConVar(
736 "sm_umc_vote_tieramount",
737 "6",
738 "Specifies the maximum number of maps to appear in the second part of a tiered vote.",
739 0, true, 2.0
740 );
741
742 cvar_runoff_display = CreateConVar(
743 "sm_umc_runoff_display",
744 "C",
745 "Determines where the Runoff Vote Message is displayed on the screen.\n C - Center Message\n S - Chat Message\n T - Top Message\n H - Hint Message"
746 );
747
748 cvar_vote_tierdisplay = CreateConVar(
749 "sm_umc_vote_tierdisplay",
750 "C",
751 "Determines where the Tiered Vote Message is displayed on the screen.\n C - Center Message\n S - Chat Message\n T - Top Message\n H - Hint Message"
752 );
753
754 //Version
755 cvar_version = CreateConVar(
756 "improved_map_randomizer_version", PL_VERSION, "Ultimate Mapchooser's version",
757 FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY|FCVAR_REPLICATED
758 );
759
760 //Create the config if it doesn't exist, and then execute it.
761 AutoExecConfig(true, "ultimate-mapchooser");
762
763 //Admin command to set the next map
764 RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>");
765
766 //Admin command to reload the mapcycle.
767 RegAdminCmd(
768 "sm_umc_reload_mapcycles", Command_Reload, ADMFLAG_RCON, "Reloads the mapcycle file."
769 );
770
771 //Admin command to stop votes in progress.
772 RegAdminCmd(
773 "sm_umc_stopvote", Command_StopVote, ADMFLAG_CHANGEMAP,
774 "Stops all UMC votes that are in progress."
775 );
776
777 RegAdminCmd(
778 "sm_umc_maphistory", Command_MapHistory, ADMFLAG_CHANGEMAP,
779 "Shows the most recent maps played"
780 );
781
782 RegAdminCmd(
783 "sm_umc_displaymaplists", Command_DisplayMapLists, ADMFLAG_CHANGEMAP,
784 "Displays the current maplist for all UMC modules."
785 );
786
787 //Hook round end events
788 HookEvent("round_end", Event_RoundEnd); //Generic
789 HookEventEx("game_round_end", Event_RoundEnd); //Hidden: Source, Neotokyo
790 HookEventEx("teamplay_win_panel", Event_RoundEnd); //TF2
791 HookEventEx("arena_win_panel", Event_RoundEnd); //TF2
792 HookEventEx("round_win", Event_RoundEnd); //Nuclear Dawn
793
794 //Initialize our vote arrays
795 nominations_arr = CreateArray();
796
797 //Make listeners for player chat. Needed to recognize chat commands ("rtv", etc.)
798 AddCommandListener(OnPlayerChat, "say");
799 AddCommandListener(OnPlayerChat, "say2"); //Insurgency Only
800 AddCommandListener(OnPlayerChat, "say_team");
801
802 //Fetch Cvars
803 cvar_maxrounds = FindConVar("mp_maxrounds");
804 cvar_fraglimit = FindConVar("mp_fraglimit");
805 cvar_winlimit = FindConVar("mp_winlimit");
806 //cvar_nextmap = FindConVar("nextmap");
807
808 //GE:S Fix
809 new String:game[20];
810 GetGameFolderName(game, sizeof(game));
811 if (StrEqual(game, "gesource", false))
812 cvar_nextlevel = FindConVar("nextlevel");
813
814 //Load the translations file
815 LoadTranslations("ultimate-mapchooser.phrases");
816
817 //Setup our forward for when a nomination is removed
818 nomination_reset_forward = CreateGlobalForward(
819 "OnNominationRemoved", ET_Ignore, Param_String, Param_Cell
820 );
821
822 reweight_forward = CreateGlobalForward(
823 "UMC_OnReweightMap", ET_Ignore, Param_Cell, Param_String, Param_String
824 );
825
826 reweight_group_forward = CreateGlobalForward(
827 "UMC_OnReweightGroup", ET_Ignore, Param_Cell, Param_String
828 );
829
830 exclude_forward = CreateGlobalForward(
831 "UMC_OnDetermineMapExclude",
832 ET_Hook, Param_Cell, Param_String, Param_String, Param_Cell, Param_Cell
833 );
834
835 reload_forward = CreateGlobalForward("UMC_RequestReloadMapcycle", ET_Ignore);
836
837 extend_forward = CreateGlobalForward("UMC_OnMapExtended", ET_Ignore);
838
839 nextmap_forward = CreateGlobalForward(
840 "UMC_OnNextmapSet", ET_Ignore, Param_Cell, Param_String, Param_String, Param_String
841 );
842
843 failure_forward = CreateGlobalForward("UMC_OnVoteFailed", ET_Ignore);
844
845 maplistdisplay_forward = CreateGlobalForward(
846 "UMC_DisplayMapCycle", ET_Ignore, Param_Cell, Param_Cell
847 );
848
849 vote_start_forward = CreateGlobalForward(
850 "UMC_VoteStarted", ET_Ignore, Param_String, Param_Array, Param_Cell, Param_Cell
851 );
852
853 vote_end_forward = CreateGlobalForward(
854 "UMC_VoteEnded", ET_Ignore, Param_String, Param_Cell
855 );
856
857 client_voted_forward = CreateGlobalForward(
858 "UMC_ClientVoted", ET_Ignore, Param_String, Param_Cell, Param_Cell
859 );
860
861 template_forward = CreateGlobalForward(
862 "UMC_OnFormatTemplateString",
863 ET_Ignore,
864 Param_String, Param_Cell, Param_Cell, Param_String, Param_String
865 );
866
867 vote_managers = CreateTrie();
868 vote_manager_ids = CreateArray(ByteCountToCells(64));
869
870 UMC_RegisterVoteManager("core", VM_MapVote, VM_GroupVote, VM_CancelVote, VM_IsVoteInProgress);
871
872#if AUTOUPDATE_ENABLE
873 if (LibraryExists("updater"))
874 {
875 Updater_AddPlugin(UPDATE_URL);
876 }
877#endif
878
879#if RUNTESTS
880 RunTests();
881#endif
882}
883
884
885#if AUTOUPDATE_ENABLE
886//Called when a new API library is loaded. Used to register UMC auto-updating.
887public OnLibraryAdded(const String:name[])
888{
889 if (StrEqual(name, "updater"))
890 {
891 Updater_AddPlugin(UPDATE_URL);
892 }
893}
894#endif
895
896
897//************************************************************************************************//
898// GAME EVENTS //
899//************************************************************************************************//
900
901//Called before any configs are executed.
902public OnMapStart()
903{
904 DEBUG_MESSAGE("Executing Core OnMapStart")
905 decl String:map[MAP_LENGTH];
906 GetCurrentMap(map, sizeof(map));
907
908 LogUMCMessage("---------------------MAP CHANGE: %s---------------------", map);
909
910 //Update the current category.
911 strcopy(current_cat, sizeof(current_cat), next_cat);
912 strcopy(next_cat, sizeof(next_cat), INVALID_GROUP);
913
914 CreateTimer(5.0, UpdateTrackingCvar);
915}
916
917
918//
919public Action:UpdateTrackingCvar(Handle:timer)
920{
921 SetConVarString(cvar_version, PL_VERSION, false, false);
922}
923
924
925//Called after all config files were executed.
926public OnConfigsExecuted()
927{
928 DEBUG_MESSAGE("Executing Core OnConfigsExecuted")
929 //Have all plugins reload their mapcycles.
930 //Call_StartForward(reload_forward);
931 //Call_Finish();
932
933 //Turn off the reweight system in the event it was left active (map change?)
934 reweight_active = false;
935
936 //No votes have been completed.
937 //vote_completed = false;
938
939 change_map_round = false;
940
941 //TODO: Turn off all votes?
942 //vote_inprogress = false;
943 //vote_active = false; //Assuming the case where this is still active was caught by VoteComplete()
944
945 GetConVarString(cvar_count_sound, countdown_sound, sizeof(countdown_sound));
946 CacheSound(countdown_sound);
947}
948
949
950//Called when a player types in chat.
951//Required to handle user commands.
952public Action:OnPlayerChat(client, const String:command[], argc)
953{
954 //Return immediately if...
955 // ...nothing was typed.
956 if (argc == 0) return Plugin_Continue;
957
958 //Get what was typed.
959 decl String:text[13];
960 GetCmdArg(1, text, sizeof(text));
961
962 if (StrEqual(text, "umc", false) || StrEqual(text, "!umc", false)
963 || StrEqual(text, "/umc", false))
964 PrintToChatAll("[SM] Ultimate Mapchooser v%s by Steell", PL_VERSION);
965
966 return Plugin_Continue;
967}
968
969
970//Called when a client has left the server. Needed to update nominations.
971public OnClientDisconnect(client)
972{
973 //Find this client in the array of clients who have entered RTV.
974 new index = FindClientNomination(client);
975
976 //Remove the client from the nomination pool if...
977 // ...the client is in the pool to begin with.
978 if (index != -1)
979 {
980 new Handle:nomination = GetArrayCell(nominations_arr, index);
981
982 decl String:oldMap[MAP_LENGTH];
983 GetTrieString(nomination, MAP_TRIE_MAP_KEY, oldMap, sizeof(oldMap));
984 new owner;
985 GetTrieValue(nomination, "client", owner);
986 Call_StartForward(nomination_reset_forward);
987 Call_PushString(oldMap);
988 Call_PushCell(owner);
989 Call_Finish();
990
991 new Handle:nomKV;
992 GetTrieValue(nomination, "mapcycle", nomKV);
993 CloseHandle(nomKV);
994 CloseHandle(nomination);
995 RemoveFromArray(nominations_arr, index);
996 }
997}
998
999
1000//Called when a round ends.
1001public Event_RoundEnd(Handle:evnt, const String:name[], bool:dontBroadcast)
1002{
1003 if (change_map_round)
1004 {
1005 change_map_round = false;
1006
1007 // //Routine by Tsunami to end the map
1008 // new iGameEnd = FindEntityByClassname(-1, "game_end");
1009 // if (iGameEnd == -1 && (iGameEnd = CreateEntityByName("game_end")) == -1)
1010 // {
1011 decl String:map[MAP_LENGTH];
1012 GetNextMap(map, sizeof(map));
1013 ForceChangeInFive(map, "CORE");
1014 // }
1015 // else
1016 // {
1017 // AcceptEntityInput(iGameEnd, "EndGame");
1018 // }
1019 }
1020}
1021
1022
1023//Called at the end of a map.
1024public OnMapEnd()
1025{
1026 DEBUG_MESSAGE("Executing Core OnMapEnd")
1027
1028 //Empty array of nominations (and close all handles).
1029 ClearNominations();
1030
1031 //End all votes currently in progress.
1032 new size = GetArraySize(vote_manager_ids);
1033 new Handle:vM;
1034 new bool:inProgress;
1035 decl String:id[64];
1036 for (new i = 0; i < size; i++)
1037 {
1038 GetArrayString(vote_manager_ids, i, id, sizeof(id));
1039 GetTrieValue(vote_managers, id, vM);
1040 GetTrieValue(vM, "in_progress", inProgress);
1041 if (inProgress)
1042 {
1043 DEBUG_MESSAGE("Ending vote in progress: %s", id)
1044 VoteCancelled(vM);
1045 }
1046 }
1047}
1048
1049
1050//************************************************************************************************//
1051// NATIVES //
1052//************************************************************************************************//
1053
1054public Native_UMCIsNewVoteAllowed(Handle:plugin, numParams)
1055{
1056 //Retrieve the vote manager.
1057 new len;
1058 GetNativeStringLength(1, len);
1059 new String:voteManagerID[len+1];
1060 if (len > 0)
1061 GetNativeString(1, voteManagerID, len+1);
1062
1063 if (strlen(voteManagerID) == 0)
1064 GetConVarString(cvar_default_vm, voteManagerID, len+1);
1065
1066 new Handle:voteManager = INVALID_HANDLE;
1067 if (!GetTrieValue(vote_managers, voteManagerID, voteManager))
1068 {
1069 if (StrEqual(voteManagerID, "core"))
1070 {
1071 LogError("FATAL: Could not find core vote manager. Aborting vote.");
1072 return _:false;
1073 }
1074 LogError("Could not find a vote manager matching ID \"%s\". Using \"core\" instead.");
1075 if (!GetTrieValue(vote_managers, "core", voteManager))
1076 {
1077 LogError("FATAL: Could not find core vote manager. Aborting vote.");
1078 return _:false;
1079 }
1080 strcopy(voteManagerID, len+1, "core");
1081 }
1082
1083 new bool:vote_inprogress;
1084 GetTrieValue(voteManager, "in_progress", vote_inprogress);
1085
1086 if (vote_inprogress)
1087 {
1088 return _:false;
1089 }
1090
1091 return _:!IsVMVoteInProgress(voteManager);
1092}
1093
1094public Native_UMCFormatDisplay(Handle:plugin, numParams)
1095{
1096 new maxlen = GetNativeCell(2);
1097 new Handle:kv = CreateKeyValues("umc_mapcycle");
1098 KvCopySubkeys(Handle:GetNativeCell(3), kv);
1099
1100 new len;
1101 GetNativeStringLength(4, len);
1102 new String:map[len+1];
1103 if (len > 0)
1104 GetNativeString(4, map, len+1);
1105 GetNativeStringLength(5, len);
1106 new String:group[len+1];
1107 if (len > 0)
1108 GetNativeString(5, group, len+1);
1109
1110 decl String:display[MAP_LENGTH], String:gDisp[MAP_LENGTH];
1111 KvJumpToKey(kv, group);
1112 KvGetString(kv, "display-template", gDisp, sizeof(gDisp), "{MAP}");
1113 KvGoBack(kv);
1114
1115 GetMapDisplayString(kv, group, map, gDisp, display, sizeof(display));
1116 CloseHandle(kv);
1117
1118 SetNativeString(1, display, maxlen);
1119}
1120
1121
1122//
1123public Native_UMCVoteManagerVoted(Handle:plugin, numParams)
1124{
1125 new len;
1126 GetNativeStringLength(1, len);
1127 new String:id[len+1];
1128 if (len > 0)
1129 GetNativeString(1, id, len+1);
1130
1131 new client = GetNativeCell(2);
1132
1133 new Handle:option = Handle:GetNativeCell(3);
1134
1135 Call_StartForward(client_voted_forward);
1136 Call_PushString(id);
1137 Call_PushCell(client);
1138 Call_PushCell(option);
1139 Call_Finish();
1140}
1141
1142
1143//
1144public Native_UMCFilterMapcycle(Handle:plugin, numParams)
1145{
1146 new Handle:kv = CreateKeyValues("umc_rotation");
1147 new Handle:arg = Handle:GetNativeCell(1);
1148 KvCopySubkeys(arg, kv);
1149
1150 new Handle:mapcycle = Handle:GetNativeCell(2);
1151
1152 new bool:isNom = bool:GetNativeCell(3);
1153 new bool:forMapChange = bool:GetNativeCell(4);
1154
1155 FilterMapcycle(kv, mapcycle, isNom, forMapChange);
1156
1157 return _:CloseAndClone(kv, plugin);
1158}
1159
1160
1161//
1162public Native_UMCVoteManagerCancel(Handle:plugin, numParams)
1163{
1164 DEBUG_MESSAGE("*UMC_VoteManagerCancel*")
1165 new len;
1166 GetNativeStringLength(1, len);
1167 new String:id[len+1];
1168 if (len > 0)
1169 GetNativeString(1, id, len+1);
1170
1171 new Handle:voteManager;
1172 if (!GetTrieValue(vote_managers, id, voteManager))
1173 {
1174 ThrowNativeError(SP_ERROR_PARAM, "A Vote Manager with the ID \"%s\" does not exist!", id);
1175 }
1176
1177 DEBUG_MESSAGE("Vote Manager found, calling VoteCancelled")
1178
1179 VoteCancelled(voteManager);
1180}
1181
1182
1183//
1184public Native_UMCRegVoteManager(Handle:plugin, numParams)
1185{
1186 new Handle:voteManager;
1187
1188 new len;
1189 GetNativeStringLength(1, len);
1190 new String:id[len+1];
1191 if (len > 0)
1192 GetNativeString(1, id, len+1);
1193
1194 if (GetTrieValue(vote_managers, id, voteManager))
1195 {
1196 UMC_UnregisterVoteManager(id);
1197 }
1198
1199 voteManager = CreateTrie();
1200
1201 SetTrieValue(vote_managers, id, voteManager);
1202
1203 new Handle:mapCallback = CreateForward(ET_Single, Param_Cell, Param_Cell, Param_Array, Param_Cell, Param_String);
1204 AddToForward(mapCallback, plugin, GetNativeFunction(2));
1205
1206 new Handle:groupCallback = CreateForward(ET_Single, Param_Cell, Param_Cell, Param_Array, Param_Cell, Param_String);
1207 AddToForward(groupCallback, plugin, GetNativeFunction(3));
1208
1209 new Handle:cancelCallback = CreateForward(ET_Ignore);
1210 AddToForward(cancelCallback, plugin, GetNativeFunction(4));
1211
1212 new Handle:progressCallback = CreateForward(ET_Single);
1213 new Function:progressFunction = GetNativeFunction(5);
1214 if (progressFunction != INVALID_FUNCTION)
1215 {
1216 AddToForward(progressCallback, plugin, progressFunction);
1217 }
1218 SetTrieValue(voteManager, "plugin", plugin);
1219 SetTrieValue(voteManager, "map", mapCallback);
1220 SetTrieValue(voteManager, "group", groupCallback);
1221 SetTrieValue(voteManager, "cancel", cancelCallback);
1222 SetTrieValue(voteManager, "checkprogress", progressCallback);
1223 SetTrieValue(voteManager, "vote_storage", CreateArray());
1224 SetTrieValue(voteManager, "in_progress", false);
1225 SetTrieValue(voteManager, "active", false);
1226 SetTrieValue(voteManager, "total_votes", 0);
1227 SetTrieValue(voteManager, "prev_vote_count", 0);
1228 SetTrieValue(voteManager, "map_vote", CreateArray());
1229
1230 PushArrayString(vote_manager_ids, id);
1231}
1232
1233
1234//
1235public Native_UMCUnregVoteManager(Handle:plugin, numParams)
1236{
1237 new len;
1238 GetNativeStringLength(1, len);
1239 new String:id[len+1];
1240 if (len > 0)
1241 GetNativeString(1, id, len+1);
1242
1243 new Handle:vM;
1244
1245 if (!GetTrieValue(vote_managers, id, vM))
1246 {
1247 ThrowNativeError(SP_ERROR_PARAM, "A Vote Manager with the ID \"%s\" does not exist!", id);
1248 }
1249
1250 if (UMC_IsVoteInProgress(id))
1251 {
1252 UMC_StopVote(id);
1253 }
1254
1255 new Handle:hndl;
1256 GetTrieValue(vM, "vote_storage", hndl);
1257 CloseHandle(hndl);
1258 GetTrieValue(vM, "map_vote", hndl);
1259 CloseHandle(hndl);
1260 GetTrieValue(vM, "map", hndl);
1261 CloseHandle(hndl);
1262 GetTrieValue(vM, "group", hndl);
1263 CloseHandle(hndl);
1264 GetTrieValue(vM, "cancel", hndl);
1265 CloseHandle(hndl);
1266 GetTrieValue(vM, "checkprogress", hndl);
1267 CloseHandle(hndl);
1268
1269
1270 CloseHandle(vM);
1271
1272 RemoveFromTrie(vote_managers, id);
1273
1274 new index = FindStringInArray(vote_manager_ids, id);
1275 if (index != -1)
1276 {
1277 RemoveFromArray(vote_manager_ids, index);
1278 }
1279
1280 if (StrEqual(id, "core", false))
1281 {
1282 UMC_RegisterVoteManager("core", VM_MapVote, VM_GroupVote, VM_CancelVote, VM_IsVoteInProgress);
1283 }
1284}
1285
1286
1287//
1288public Native_UMCVoteManagerComplete(Handle:plugin, numParams)
1289{
1290 DEBUG_MESSAGE("*VoteManagerVoteCompleted*")
1291
1292 new len;
1293 GetNativeStringLength(1, len);
1294 new String:id[len+1];
1295 if (len > 0)
1296 GetNativeString(1, id, len+1);
1297
1298 new Handle:voteOptions = Handle:GetNativeCell(2);
1299
1300 new Handle:vM;
1301 GetTrieValue(vote_managers, id, vM);
1302
1303 new Handle:response = ProcessVoteResults(vM, voteOptions);
1304
1305 new UMC_VoteResponseHandler:handler = UMC_VoteResponseHandler:GetNativeFunction(3);
1306
1307 new UMC_VoteResponse:result;
1308 new String:param[MAP_LENGTH];
1309 GetTrieValue(response, "response", result);
1310 GetTrieString(response, "param", param, sizeof(param));
1311
1312 Call_StartFunction(plugin, handler);
1313 Call_PushCell(result);
1314 Call_PushString(param);
1315 Call_Finish();
1316
1317 Call_StartForward(vote_end_forward);
1318 Call_PushString(id);
1319 Call_PushCell(result);
1320 Call_Finish();
1321
1322 CloseHandle(response);
1323}
1324
1325
1326//native Handle:UMC_CreateValidMapArray(Handle:kv, const String:group[], bool:isNom,
1327// bool:forMapChange);
1328public Native_UMCCreateMapArray(Handle:plugin, numParams)
1329{
1330 new Handle:kv = CreateKeyValues("umc_rotation");
1331 new Handle:arg = Handle:GetNativeCell(1);
1332 KvCopySubkeys(arg, kv);
1333
1334 new Handle:mapcycle = Handle:GetNativeCell(2);
1335
1336 new len;
1337 GetNativeStringLength(3, len);
1338 new String:group[len+1];
1339 if (len > 0)
1340 GetNativeString(3, group, len+1);
1341
1342 new bool:isNom = bool:GetNativeCell(4);
1343 new bool:forMapChange = bool:GetNativeCell(5);
1344
1345 new Handle:result = CreateMapArray(kv, mapcycle, group, isNom, forMapChange);
1346
1347 CloseHandle(kv);
1348
1349 if (result == INVALID_HANDLE)
1350 {
1351 ThrowNativeError(SP_ERROR_PARAM,
1352 "Could not generate valid map array from provided mapcycle.");
1353 }
1354
1355 DEBUG_MESSAGE("*MEMLEAKTEST* Closing and cloning map trie made at {2}")
1356
1357 //Clone all of the handles in the array to prevent memory leaks.
1358 new Handle:cloned = CreateArray();
1359
1360 new size = GetArraySize(result);
1361 new Handle:map;
1362 for (new i = 0; i < size; i++)
1363 {
1364 map = GetArrayCell(result, i);
1365 PushArrayCell(cloned, CloseAndClone(map, plugin));
1366 }
1367
1368 CloseHandle(result);
1369
1370 return _:CloseAndClone(cloned, plugin);
1371}
1372
1373
1374//Create an array of valid maps from the given mapcycle and group.
1375Handle:CreateMapArray(Handle:kv, Handle:mapcycle, const String:group[], bool:isNom,
1376 bool:forMapChange)
1377{
1378 if (kv == INVALID_HANDLE)
1379 {
1380 LogError("NATIVE: Cannot build map array, mapcycle is invalid.");
1381 return INVALID_HANDLE;
1382 }
1383
1384 new bool:oneSection = false;
1385 if (StrEqual(group, INVALID_GROUP))
1386 {
1387 if (!KvGotoFirstSubKey(kv))
1388 {
1389 LogError("NATIVE: Cannot build map array, mapcycle has no groups.");
1390 return INVALID_HANDLE;
1391 }
1392 }
1393 else
1394 {
1395 if (!KvJumpToKey(kv, group))
1396 {
1397 LogError("NATIVE: Cannot build map array, mapcycle has no group '%s'", group);
1398 return INVALID_HANDLE;
1399 }
1400
1401 oneSection = true;
1402 }
1403
1404 new Handle:result = CreateArray();
1405 decl String:mapName[MAP_LENGTH], String:groupName[MAP_LENGTH];
1406 do
1407 {
1408 KvGetSectionName(kv, groupName, sizeof(groupName));
1409
1410 if (!KvGotoFirstSubKey(kv))
1411 {
1412 if (!oneSection)
1413 continue;
1414 else
1415 break;
1416 }
1417
1418 do
1419 {
1420 if (IsValidMap(kv, mapcycle, groupName, isNom, forMapChange))
1421 {
1422 KvGetSectionName(kv, mapName, sizeof(mapName));
1423 DEBUG_MESSAGE("*MEMLEAKTEST* Creating map trie (CreateMapArray) [2]")
1424 PushArrayCell(result, CreateMapTrie(mapName, groupName));
1425 }
1426 }
1427 while (KvGotoNextKey(kv));
1428
1429 KvGoBack(kv);
1430
1431 if (oneSection) break;
1432 }
1433 while (KvGotoNextKey(kv));
1434
1435 KvGoBack(kv);
1436
1437 return result;
1438}
1439
1440
1441//native Handle:UMC_CreateValidMapGroupArray(Handle:kv, bool:isNom, bool:forMapChange);
1442public Native_UMCCreateGroupArray(Handle:plugin, numParams)
1443{
1444 new Handle:arg = Handle:GetNativeCell(1);
1445 new Handle:kv = CreateKeyValues("umc_rotation");
1446 KvCopySubkeys(arg, kv);
1447 new Handle:mapcycle = Handle:GetNativeCell(2);
1448 new bool:isNom = bool:GetNativeCell(3);
1449 new bool:forMapChange = bool:GetNativeCell(4);
1450
1451 new Handle:result = CreateMapGroupArray(kv, mapcycle, isNom, forMapChange);
1452
1453 CloseHandle(kv);
1454
1455 return _:CloseAndClone(result, plugin);
1456}
1457
1458
1459//Create an array of valid maps from the given mapcycle and group.
1460Handle:CreateMapGroupArray(Handle:kv, Handle:mapcycle, bool:isNom, bool:forMapChange)
1461{
1462 if (!KvGotoFirstSubKey(kv))
1463 {
1464 LogError("NATIVE: Cannot build map array, mapcycle has no groups.");
1465 return INVALID_HANDLE;
1466 }
1467
1468 new Handle:result = CreateArray(ByteCountToCells(MAP_LENGTH));
1469 decl String:groupName[MAP_LENGTH];
1470 do
1471 {
1472 if (IsValidCat(kv, mapcycle, isNom, forMapChange))
1473 {
1474 KvGetSectionName(kv, groupName, sizeof(groupName));
1475 PushArrayString(result, groupName);
1476 }
1477 }
1478 while (KvGotoNextKey(kv));
1479
1480 KvGoBack(kv);
1481
1482 return result;
1483}
1484
1485
1486//native bool:UMC_IsMapNominated(const String:map[], const String:group[]);
1487public Native_UMCIsMapNominated(Handle:plugin, numParams)
1488{
1489 new len;
1490 GetNativeStringLength(1, len);
1491 new String:map[len+1];
1492 if (len > 0)
1493 GetNativeString(1, map, len+1);
1494
1495 GetNativeStringLength(2, len);
1496 new String:group[len+1];
1497 if (len > 0)
1498 GetNativeString(2, group, len+1);
1499
1500 return _:(FindNominationIndex(map, group) != -1);
1501}
1502
1503
1504//native bool:UMC_NominateMap(const String:map[], const String:group[]);
1505public Native_UMCNominateMap(Handle:plugin, numParams)
1506{
1507 new Handle:kv = CreateKeyValues("umc_rotation");
1508 KvCopySubkeys(Handle:GetNativeCell(1), kv);
1509
1510#if UMC_DEBUG
1511 LogKv(kv);
1512#endif
1513
1514 new len;
1515 GetNativeStringLength(2, len);
1516 new String:map[len+1];
1517 if (len > 0)
1518 GetNativeString(2, map, len+1);
1519
1520 GetNativeStringLength(3, len);
1521 new String:group[len+1];
1522 if (len > 0)
1523 GetNativeString(3, group, len+1);
1524
1525 new String:nomGroup[MAP_LENGTH];
1526 if (numParams > 4)
1527 {
1528 GetNativeStringLength(5, len);
1529 if (len > 0)
1530 GetNativeString(5, nomGroup, sizeof(nomGroup));
1531 }
1532 else
1533 {
1534 strcopy(nomGroup, sizeof(nomGroup), INVALID_GROUP);
1535 }
1536
1537 return _:InternalNominateMap(kv, map, group, GetNativeCell(4), nomGroup);
1538}
1539
1540
1541//native AddWeightModifier(MapWeightModifier:func);
1542public Native_UMCAddWeightModifier(Handle:plugin, numParams)
1543{
1544 if (reweight_active)
1545 {
1546 current_weight *= Float:GetNativeCell(1);
1547 DEBUG_MESSAGE("New map weight: %f", current_weight)
1548 }
1549 else
1550 LogError("REWEIGHT: Attempted to add weight modifier outside of UMC_OnReweightMap forward.");
1551}
1552
1553
1554//native bool:UMC_StartVote( ...20+ params... );
1555public Native_UMCStartVote(Handle:plugin, numParams)
1556{
1557 //Retrieve the many, many parameters.
1558 new len;
1559 GetNativeStringLength(1, len);
1560 new String:voteManagerID[len+1];
1561 if (len > 0)
1562 GetNativeString(1, voteManagerID, len+1);
1563
1564 if (strlen(voteManagerID) == 0)
1565 GetConVarString(cvar_default_vm, voteManagerID, len+1);
1566
1567 new Handle:voteManager = INVALID_HANDLE;
1568 if (!GetTrieValue(vote_managers, voteManagerID, voteManager))
1569 {
1570 if (StrEqual(voteManagerID, "core"))
1571 {
1572 LogError("FATAL: Could not find core vote manager. Aborting vote.");
1573 return _:false;
1574 }
1575 LogError("Could not find a vote manager matching ID \"%s\". Using \"core\" instead.");
1576 if (!GetTrieValue(vote_managers, "core", voteManager))
1577 {
1578 LogError("FATAL: Could not find core vote manager. Aborting vote.");
1579 return _:false;
1580 }
1581 strcopy(voteManagerID, len+1, "core");
1582 }
1583
1584 new bool:vote_inprogress;
1585 GetTrieValue(voteManager, "in_progress", vote_inprogress);
1586
1587 if (vote_inprogress)
1588 {
1589 LogError("Cannot start a vote, vote manager \"%s\" already has a vote in progress.", voteManagerID);
1590 return _:false;
1591 }
1592
1593 //Get the name of the calling plugin.
1594 decl String:stored_reason[PLATFORM_MAX_PATH];
1595 GetPluginFilename(plugin, stored_reason, sizeof(stored_reason));
1596 SetTrieString(voteManager, "stored_reason", stored_reason);
1597
1598 //vote_completed = false;
1599
1600 new Handle:kv = Handle:GetNativeCell(2);
1601 new Handle:mapcycle = Handle:GetNativeCell(3);
1602 new UMC_VoteType:type = UMC_VoteType:GetNativeCell(4);
1603 new time = GetNativeCell(5);
1604 new bool:scramble = bool:GetNativeCell(6);
1605
1606
1607 GetNativeStringLength(7, len);
1608 new String:startSound[len+1];
1609 if (len > 0)
1610 GetNativeString(7, startSound, len+1);
1611 GetNativeStringLength(8, len);
1612 new String:endSound[len+1];
1613 if (len > 0)
1614 GetNativeString(8, endSound, len+1);
1615
1616 new bool:extend = bool:GetNativeCell(9);
1617 new Float:timestep = Float:GetNativeCell(10);
1618 new roundstep = GetNativeCell(11);
1619 new fragstep = GetNativeCell(12);
1620 new bool:dontChange = bool:GetNativeCell(13);
1621 new Float:threshold = Float:GetNativeCell(14);
1622 new UMC_ChangeMapTime:successAction = UMC_ChangeMapTime:GetNativeCell(15);
1623 new UMC_VoteFailAction:failAction = UMC_VoteFailAction:GetNativeCell(16);
1624 new maxRunoffs = GetNativeCell(17);
1625 new maxRunoffMaps = GetNativeCell(18);
1626 new UMC_RunoffFailAction:runoffFailAction = UMC_RunoffFailAction:GetNativeCell(19);
1627
1628 GetNativeStringLength(20, len);
1629 new String:runoffSound[len+1];
1630 if (len > 0)
1631 GetNativeString(20, runoffSound, len+1);
1632
1633 new bool:nominationStrictness = bool:GetNativeCell(21);
1634 new bool:allowDuplicates = bool:GetNativeCell(22);
1635
1636 new voteClients[MAXPLAYERS+1];
1637 GetNativeArray(23, voteClients, sizeof(voteClients));
1638 new numClients = GetNativeCell(24);
1639
1640 new bool:runExclusionCheck = (numParams >= 25) ? (bool:GetNativeCell(25)) : true;
1641
1642 //OK now that that's done, let's save 'em.
1643 SetTrieValue(voteManager, "stored_type", type);
1644 SetTrieValue(voteManager, "stored_scramble", scramble);
1645 SetTrieValue(voteManager, "stored_ignoredupes", allowDuplicates);
1646 SetTrieValue(voteManager, "stored_strictnoms", nominationStrictness);
1647
1648 if (failAction == VoteFailAction_Nothing)
1649 {
1650 SetTrieValue(voteManager, "stored_fail_action", RunoffFailAction_Nothing);
1651 SetTrieValue(voteManager, "remaining_runoffs", 0);
1652 }
1653 else if (failAction == VoteFailAction_Runoff)
1654 {
1655 SetTrieValue(voteManager, "stored_fail_action", runoffFailAction);
1656 SetTrieValue(voteManager, "remaining_runoffs", (maxRunoffs == 0) ? -1 : maxRunoffs);
1657 }
1658
1659 SetTrieValue(voteManager, "extend_timestep", timestep);
1660 SetTrieValue(voteManager, "extend_roundstep", roundstep);
1661 SetTrieValue(voteManager, "extend_fragstep", fragstep);
1662 SetTrieValue(voteManager, "stored_threshold", threshold);
1663 SetTrieValue(voteManager, "stored_runoffmaps_max", maxRunoffMaps);
1664 SetTrieValue(voteManager, "stored_votetime", time);
1665
1666 SetTrieValue(voteManager, "change_map_when", successAction);
1667
1668 new Handle:stored_kv = CreateKeyValues("umc_rotation");
1669 KvCopySubkeys(kv, stored_kv);
1670 SetTrieValue(voteManager, "stored_kv", stored_kv);
1671
1672 new Handle:stored_mapcycle = CreateKeyValues("umc_rotation");
1673 KvCopySubkeys(mapcycle, stored_mapcycle);
1674 SetTrieValue(voteManager, "stored_mapcycle", stored_mapcycle);
1675
1676 SetTrieString(voteManager, "stored_start_sound", startSound);
1677 SetTrieString(voteManager, "stored_end_sound", endSound);
1678 SetTrieString(voteManager, "stored_runoff_sound", (strlen(runoffSound) > 0) ? runoffSound : startSound);
1679
1680 new users[MAXPLAYERS+1];
1681 ConvertClientsToUserIDs(voteClients, users, numClients);
1682 SetTrieArray(voteManager, "stored_users", users, numClients);
1683
1684 SetTrieValue(voteManager, "stored_exclude", runExclusionCheck);
1685
1686 //Make the vote menu.
1687 new Handle:options = BuildVoteItems(voteManager, stored_kv, stored_mapcycle, type, scramble,
1688 allowDuplicates, nominationStrictness, runExclusionCheck,
1689 extend, dontChange);
1690
1691 //Run the vote if...
1692 // ...the menu was created successfully.
1693 if (options != INVALID_HANDLE)
1694 {
1695 new bool:vote_active = PerformVote(voteManager, type, options, time, voteClients,
1696 numClients, startSound);
1697 if (vote_active)
1698 {
1699 Call_StartForward(vote_start_forward);
1700 Call_PushString(voteManagerID);
1701 Call_PushArray(voteClients, numClients);
1702 Call_PushCell(numClients);
1703 Call_PushCell(options);
1704 Call_Finish();
1705 }
1706 else
1707 {
1708 DeleteVoteParams(voteManager);
1709 ClearVoteArrays(voteManager);
1710 //VoteFailed(voteManager);
1711 }
1712
1713 FreeOptions(options);
1714 return _:vote_active;
1715 }
1716 else
1717 {
1718 DeleteVoteParams(voteManager);
1719 return _:false;
1720 }
1721}
1722
1723
1724//native bool:UMC_GetRandomMap(Handle:kv, const String:group=INVALID_GROUP, String:buffer[], size,
1725// Handle:excludedMaps, Handle:excludedGroups, bool:forceGroup);
1726public Native_UMCGetRandomMap(Handle:plugin, numParams)
1727{
1728 new Handle:kv = Handle:GetNativeCell(1);
1729 new Handle:filtered = CreateKeyValues("umc_rotation");
1730 KvCopySubkeys(kv, filtered);
1731
1732 new Handle:mapcycle = Handle:GetNativeCell(2);
1733 new len;
1734 GetNativeStringLength(3, len);
1735 new String:group[len+1];
1736 if (len > 0)
1737 GetNativeString(3, group, len+1);
1738
1739 DEBUG_MESSAGE("Looking for random map in group \"%s\".", group)
1740
1741 new bool:isNom = bool:GetNativeCell(8);
1742 new bool:forMapChange = bool:GetNativeCell(9);
1743
1744 FilterMapcycle(filtered, mapcycle, isNom, forMapChange);
1745 WeightMapcycle(filtered, mapcycle);
1746
1747#if UMC_DEBUG
1748 LogKv(filtered);
1749#endif
1750
1751 decl String:map[MAP_LENGTH], String:groupResult[MAP_LENGTH];
1752 new bool:result = GetRandomMapFromCycle(filtered, group, map, sizeof(map), groupResult,
1753 sizeof(groupResult));
1754
1755 CloseHandle(filtered);
1756
1757 if (result)
1758 {
1759 SetNativeString(4, map, GetNativeCell(5), false);
1760 SetNativeString(6, groupResult, GetNativeCell(7), false);
1761 return true;
1762 }
1763 return false;
1764}
1765
1766
1767//native bool:UMC_SetNextMap(Handle:kv, const String:map[], const String:group[]);
1768public Native_UMCSetNextMap(Handle:plugin, numParams)
1769{
1770 new Handle:kv = Handle:GetNativeCell(1);
1771
1772 new len;
1773 GetNativeStringLength(2, len);
1774 new String:map[len+1];
1775 if (len > 0)
1776 GetNativeString(2, map, len+1);
1777 GetNativeStringLength(3, len);
1778 new String:group[len+1];
1779 if (len > 0)
1780 GetNativeString(3, group, len+1);
1781
1782 if (!IsMapValid(map))
1783 {
1784 LogError("SETMAP: Map %s is invalid!", map);
1785 return;
1786 }
1787
1788 new UMC_ChangeMapTime:when = UMC_ChangeMapTime:GetNativeCell(4);
1789
1790 decl String:reason[PLATFORM_MAX_PATH];
1791 GetPluginFilename(plugin, reason, sizeof(reason));
1792
1793 DoMapChange(when, kv, map, group, reason, map);
1794}
1795
1796
1797//
1798public Native_UMCIsVoteInProgress(Handle:plugin, numParams)
1799{
1800 if (numParams > 0)
1801 {
1802 new len;
1803 GetNativeStringLength(1, len);
1804 new String:voteManagerID[len+1];
1805 if (len > 0)
1806 GetNativeString(1, voteManagerID, len+1);
1807
1808 if (strlen(voteManagerID) > 0)
1809 {
1810 new bool:inProgress;
1811 new Handle:vM;
1812 if (!GetTrieValue(vote_managers, voteManagerID, vM))
1813 {
1814 ThrowNativeError(SP_ERROR_PARAM,
1815 "A Vote Manager with the ID \"%s\" does not exist!", voteManagerID);
1816 }
1817 GetTrieValue(vM, "in_progress", inProgress);
1818 return inProgress;
1819 }
1820 }
1821 decl String:buffer[64];
1822 new size = GetArraySize(vote_manager_ids);
1823 new Handle:vM;
1824 new bool:inProgress;
1825 for (new i = 0; i < size; i++)
1826 {
1827 GetArrayString(vote_manager_ids, i, buffer, sizeof(buffer));
1828 GetTrieValue(vote_managers, buffer, vM);
1829 GetTrieValue(vM, "in_progress", inProgress);
1830 if (inProgress)
1831 return _:true;
1832 }
1833 return _:false;
1834}
1835
1836
1837//
1838//"sm_umc_stopvote"
1839public Native_UMCStopVote(Handle:plugin, numParams)
1840{
1841 Native_UMCVoteManagerCancel(plugin, numParams);
1842}
1843
1844
1845//
1846public Native_UMCIsMapValid(Handle:plugin, numParams)
1847{
1848 new Handle:arg = Handle:GetNativeCell(1);
1849 new Handle:kv = CreateKeyValues("umc_rotation");
1850 KvCopySubkeys(arg, kv);
1851
1852#if UMC_DEBUG
1853 DEBUG_MESSAGE("UMC_IsValidMap passed mapcycle:")
1854 LogKv(arg);
1855#endif
1856
1857 new len;
1858 GetNativeStringLength(2, len);
1859 new String:map[len+1];
1860 if (len > 0)
1861 GetNativeString(2, map, len+1);
1862 GetNativeStringLength(3, len);
1863 new String:group[len+1];
1864 if (len > 0)
1865 GetNativeString(3, group, len+1);
1866
1867 new bool:isNom = bool:GetNativeCell(4);
1868 new bool:forMapChange = bool:GetNativeCell(5);
1869
1870 if (!KvJumpToKey(kv, group))
1871 {
1872 LogError("NATIVE: No group '%s' in mapcycle.", group);
1873 return _:false;
1874 }
1875 if (!KvJumpToKey(kv, map))
1876 {
1877 LogError("NATIVE: No map %s found in group '%s'", map, group);
1878 return _:false;
1879 }
1880
1881 return _:IsValidMap(kv, arg, group, isNom, forMapChange);
1882}
1883
1884
1885//
1886public Native_UMCGetCurrentGroup(Handle:plugin, numParams)
1887{
1888 SetNativeString(1, current_cat, GetNativeCell(2), false);
1889}
1890
1891
1892//************************************************************************************************//
1893// COMMANDS //
1894//************************************************************************************************//
1895
1896//
1897public Action:Command_DisplayMapLists(client, args)
1898{
1899 new bool:filtered;
1900 if (args < 1)
1901 filtered = false;
1902 else
1903 {
1904 decl String:arg[5];
1905 GetCmdArg(1, arg, sizeof(arg));
1906 filtered = StringToInt(arg) > 0;
1907 }
1908
1909 PrintToConsole(client, "UMC Maplists:");
1910
1911 Call_StartForward(maplistdisplay_forward);
1912 Call_PushCell(client);
1913 Call_PushCell(filtered);
1914 Call_Finish();
1915
1916 return Plugin_Handled;
1917}
1918
1919
1920//
1921public Action:Command_MapHistory(client, args)
1922{
1923 PrintToConsole(client, "Map History:");
1924
1925 new size = GetMapHistorySize();
1926 decl String:map[MAP_LENGTH], String:reason[100], String:timeString[100];
1927 new time;
1928 for (new i = 0; i < size; i++)
1929 {
1930 GetMapHistory(i, map, sizeof(map), reason, sizeof(reason), time);
1931 FormatTime(timeString, sizeof(timeString), NULL_STRING, time);
1932 ReplyToCommand(client, "%02i. %s : %s : %s", i+1, map, reason, timeString);
1933 }
1934 return Plugin_Handled;
1935}
1936
1937
1938//Called when the command to set the nextmap is called.
1939public Action:Command_SetNextmap(client, args)
1940{
1941 if (args < 1)
1942 {
1943 ReplyToCommand(
1944 client,
1945 "\x03[UMC]\x01 Usage: sm_setnextmap <map> <0|1|2>\n 0 - Change Now\n 1 - Change at end of round\n 2 - Change at end of map."
1946 );
1947 return Plugin_Handled;
1948 }
1949
1950 decl String:map[MAP_LENGTH];
1951 GetCmdArg(1, map, sizeof(map));
1952
1953 if (!IsMapValid(map))
1954 {
1955 ReplyToCommand(client, "\x03[UMC]\x01 Map '%s' was not found.", map);
1956 return Plugin_Handled;
1957 }
1958
1959 new UMC_ChangeMapTime:when = ChangeMapTime_MapEnd;
1960 if (args > 1)
1961 {
1962 decl String:whenArg[2];
1963 GetCmdArg(2, whenArg, sizeof(whenArg));
1964 when = UMC_ChangeMapTime:StringToInt(whenArg);
1965 }
1966
1967 //DisableVoteInProgress(id);
1968 DoMapChange(when, INVALID_HANDLE, map, INVALID_GROUP, "sm_setnextmap", map);
1969
1970 //TODO: Make this a translation
1971 ShowActivity(client, "Changed nextmap to \"%s\".", map);
1972 LogUMCMessage("%L changed nextmap to \"%s\"", client, map);
1973
1974 //vote_completed = true;
1975
1976 return Plugin_Handled;
1977}
1978
1979
1980//Called when the command to reload the mapcycle has been triggered.
1981public Action:Command_Reload(client, args)
1982{
1983 //Call the reload forward.
1984 Call_StartForward(reload_forward);
1985 Call_Finish();
1986
1987 ReplyToCommand(client, "\x03[UMC]\x01 UMC Mapcycles Reloaded.");
1988
1989 //Return success
1990 return Plugin_Handled;
1991}
1992
1993
1994//"sm_umc_stopvote"
1995public Action:Command_StopVote(client, args)
1996{
1997 //End all votes currently in progress.
1998 new size = GetArraySize(vote_manager_ids);
1999 new Handle:vM;
2000 new bool:inProgress;
2001 decl String:id[64];
2002 new bool:stopped = false;
2003 for (new i = 0; i < size; i++)
2004 {
2005 GetArrayString(vote_manager_ids, i, id, sizeof(id));
2006 GetTrieValue(vote_managers, id, vM);
2007 GetTrieValue(vM, "in_progress", inProgress);
2008 if (inProgress)
2009 {
2010 DEBUG_MESSAGE("Ending vote in progress: %s", id)
2011 stopped = true;
2012 VoteCancelled(vM);
2013 }
2014 }
2015 if (!stopped)
2016 ReplyToCommand(client, "\x03[UMC]\x01 No map vote running!"); //TODO Translation?
2017 return Plugin_Handled;
2018}
2019
2020//************************************************************************************************//
2021// CORE VOTE MANAGER //
2022//************************************************************************************************//
2023
2024new bool:core_vote_active;
2025
2026public bool:VM_IsVoteInProgress()
2027{
2028 return IsVoteInProgress();
2029}
2030
2031//
2032public Action:VM_MapVote(duration, Handle:vote_items, const clients[], numClients,
2033 const String:startSound[])
2034{
2035 if (VM_IsVoteInProgress())
2036 {
2037 LogUMCMessage("Could not start core vote, another SM vote is already in progress.");
2038 return Plugin_Stop;
2039 }
2040
2041 new bool:verboseLogs = GetConVarBool(cvar_logging);
2042
2043 if (verboseLogs)
2044 LogUMCMessage("Adding Clients to Vote:");
2045
2046 DEBUG_MESSAGE("Attempting to start core vote...")
2047 decl clientArr[MAXPLAYERS+1];
2048 new count = 0;
2049 new client;
2050 for (new i = 0; i < numClients; i++)
2051 {
2052 client = clients[i];
2053 if (client != 0 && IsClientInGame(client))
2054 {
2055 if (verboseLogs)
2056 LogUMCMessage("%i: %N (%i)", i, client, client);
2057 clientArr[count++] = client;
2058 }
2059 }
2060
2061 if (count == 0)
2062 {
2063 LogUMCMessage("Could not start core vote, no players to display vote to!");
2064 return Plugin_Stop;
2065 }
2066
2067 new Handle:menu = BuildVoteMenu(vote_items, "Map Vote Menu Title", Handle_MapVoteResults);
2068
2069 core_vote_active = menu != INVALID_HANDLE && VoteMenu(menu, clientArr, count, duration);
2070
2071 if (core_vote_active)
2072 {
2073 DEBUG_MESSAGE("Setting CVA True")
2074
2075 if (strlen(startSound) > 0)
2076 EmitSoundToAllAny(startSound);
2077
2078 return Plugin_Continue;
2079 }
2080 else
2081 {
2082 DEBUG_MESSAGE("Setting CVA False -- Couldn't start vote")
2083 LogError("Could not start core vote.");
2084 return Plugin_Stop;
2085 }
2086}
2087
2088
2089//
2090public Action:VM_GroupVote(duration, Handle:vote_items, const clients[], numClients,
2091 const String:startSound[])
2092{
2093 if (VM_IsVoteInProgress())
2094 {
2095 LogUMCMessage("Could not start core vote, another SM vote is already in progress.");
2096 return Plugin_Stop;
2097 }
2098
2099 new bool:verboseLogs = GetConVarBool(cvar_logging);
2100
2101 if (verboseLogs)
2102 LogUMCMessage("Adding Clients to Vote:");
2103
2104 decl clientArr[MAXPLAYERS+1];
2105 new count = 0;
2106 new client;
2107 for (new i = 0; i < numClients; i++)
2108 {
2109 client = clients[i];
2110 if (client != 0 && IsClientInGame(client))
2111 {
2112 if (verboseLogs)
2113 LogUMCMessage("%i: %N (%i)", i, client, client);
2114 clientArr[count++] = client;
2115 }
2116 }
2117
2118 if (count == 0)
2119 {
2120 LogUMCMessage("Could not start core vote, no players to display vote to!");
2121 return Plugin_Stop;
2122 }
2123
2124 new Handle:menu = BuildVoteMenu(vote_items, "Group Vote Menu Title", Handle_MapVoteResults);
2125
2126 DEBUG_MESSAGE("Setting CVA True")
2127 core_vote_active = true;
2128
2129 if (menu != INVALID_HANDLE && VoteMenu(menu, clientArr, count, duration))
2130 {
2131 if (strlen(startSound) > 0)
2132 EmitSoundToAllAny(startSound);
2133
2134 return Plugin_Continue;
2135 }
2136
2137 DEBUG_MESSAGE("Setting CVA False -- Couldn't start vote")
2138 core_vote_active = false;
2139
2140 //ClearVoteArrays();
2141 LogError("Could not start core vote.");
2142 return Plugin_Stop;
2143}
2144
2145
2146//
2147Handle:BuildVoteMenu(Handle:vote_items, const String:title[], VoteHandler:callback)
2148{
2149 new bool:verboseLogs = GetConVarBool(cvar_logging);
2150
2151 if (verboseLogs)
2152 LogUMCMessage("VOTE MENU:");
2153
2154 //Begin creating menu
2155 new Handle:menu = (GetConVarBool(cvar_valvemenu))
2156 ? CreateMenuEx(GetMenuStyleHandle(MenuStyle_Valve), Handle_VoteMenu,
2157 MenuAction_DisplayItem|MenuAction_Display)
2158 : CreateMenu(Handle_VoteMenu, MenuAction_DisplayItem|MenuAction_Display);
2159
2160 SetVoteResultCallback(menu, callback); //Set callback
2161 SetMenuExitButton(menu, false); //Don't want an exit button.
2162
2163 //Set the title
2164 SetMenuTitle(menu, title);
2165
2166 //Keep track of slots taken up in the vote.
2167 new blockSlots = GetConVarInt(cvar_block_slots);
2168 new voteSlots = blockSlots;
2169
2170 if (GetConVarBool(cvar_novote))
2171 {
2172 SetMenuOptionFlags(menu, MENUFLAG_BUTTON_NOVOTE);
2173 voteSlots++;
2174
2175 if (verboseLogs)
2176 LogUMCMessage("1: No Vote");
2177 }
2178
2179 DEBUG_MESSAGE("Setup slot blocking.")
2180 //Add blocked slots if...
2181 // ...the cvar for blocked slots is enabled.
2182 AddSlotBlockingToMenu(menu, blockSlots);
2183
2184 new size = GetArraySize(vote_items);
2185
2186 //Throw an error and return nothing if...
2187 // ...the number of items in the vote is less than 2 (hence no point in voting).
2188 if (size <= 1)
2189 {
2190 DEBUG_MESSAGE("Not enough items in the vote. Aborting.")
2191 LogError("VOTING: Not enough options to run a vote. %i options available.", size);
2192 CloseHandle(menu);
2193 return INVALID_HANDLE;
2194 }
2195
2196 new Handle:voteItem;
2197 decl String:info[MAP_LENGTH], String:display[MAP_LENGTH];
2198 int check = 0;
2199 for (new i = check; i < size*0.25; i++)
2200 {
2201 voteSlots++;
2202
2203 voteItem = GetArrayCell(vote_items, i);
2204 GetTrieString(voteItem, "info", info, sizeof(info));
2205 GetTrieString(voteItem, "display", display, sizeof(display));
2206
2207 AddMenuItem(menu, info, display);
2208
2209#if UMC_DEBUG
2210 if (StrEqual(info, EXTEND_MAP_OPTION))
2211 DEBUG_MESSAGE("Adding Extend Option to vote menu. Position: %i", voteSlots)
2212 else if (StrEqual(info, DONT_CHANGE_OPTION))
2213 DEBUG_MESSAGE("Adding Don't Change Option to vote menu. Position: %i", voteSlots)
2214#endif
2215
2216 if (verboseLogs)
2217 LogUMCMessage("%i: %s (%s)", voteSlots, display, info);
2218
2219 check = i;
2220 }
2221 for (new i = check; i < size*0.5; i++)
2222 {
2223 voteSlots++;
2224
2225 voteItem = GetArrayCell(vote_items, i);
2226 GetTrieString(voteItem, "info", info, sizeof(info));
2227 GetTrieString(voteItem, "display", display, sizeof(display));
2228
2229 AddMenuItem(menu, info, display);
2230
2231#if UMC_DEBUG
2232 if (StrEqual(info, EXTEND_MAP_OPTION))
2233 DEBUG_MESSAGE("Adding Extend Option to vote menu. Position: %i", voteSlots)
2234 else if (StrEqual(info, DONT_CHANGE_OPTION))
2235 DEBUG_MESSAGE("Adding Don't Change Option to vote menu. Position: %i", voteSlots)
2236#endif
2237
2238 if (verboseLogs)
2239 LogUMCMessage("%i: %s (%s)", voteSlots, display, info);
2240
2241 check++;
2242 }
2243 for (new i = check; i < size*0.75; i++)
2244 {
2245 voteSlots++;
2246
2247 voteItem = GetArrayCell(vote_items, i);
2248 GetTrieString(voteItem, "info", info, sizeof(info));
2249 GetTrieString(voteItem, "display", display, sizeof(display));
2250
2251 AddMenuItem(menu, info, display);
2252
2253#if UMC_DEBUG
2254 if (StrEqual(info, EXTEND_MAP_OPTION))
2255 DEBUG_MESSAGE("Adding Extend Option to vote menu. Position: %i", voteSlots)
2256 else if (StrEqual(info, DONT_CHANGE_OPTION))
2257 DEBUG_MESSAGE("Adding Don't Change Option to vote menu. Position: %i", voteSlots)
2258#endif
2259
2260 if (verboseLogs)
2261 LogUMCMessage("%i: %s (%s)", voteSlots, display, info);
2262
2263 check++;
2264 }
2265 for (new i = check; i < size; i++)
2266 {
2267 voteSlots++;
2268
2269 voteItem = GetArrayCell(vote_items, i);
2270 GetTrieString(voteItem, "info", info, sizeof(info));
2271 GetTrieString(voteItem, "display", display, sizeof(display));
2272
2273 AddMenuItem(menu, info, display);
2274
2275#if UMC_DEBUG
2276 if (StrEqual(info, EXTEND_MAP_OPTION))
2277 DEBUG_MESSAGE("Adding Extend Option to vote menu. Position: %i", voteSlots)
2278 else if (StrEqual(info, DONT_CHANGE_OPTION))
2279 DEBUG_MESSAGE("Adding Don't Change Option to vote menu. Position: %i", voteSlots)
2280#endif
2281
2282 if (verboseLogs)
2283 LogUMCMessage("%i: %s (%s)", voteSlots, display, info);
2284
2285 check++;
2286 }
2287
2288 DEBUG_MESSAGE("Setting proper pagination.")
2289 SetCorrectMenuPagination(menu, voteSlots);
2290 DEBUG_MESSAGE("Vote menu built successfully.")
2291 return menu; //Return the finished menu.
2292}
2293
2294
2295//
2296public VM_CancelVote()
2297{
2298 DEBUG_MESSAGE("Vote Cancelled Callback -- Core")
2299 DEBUG_MESSAGE("Is Core Vote still active? %i", core_vote_active)
2300 if (core_vote_active)
2301 {
2302 DEBUG_MESSAGE("Vote Cancelled Callback -- Core Inner")
2303 DEBUG_MESSAGE("Vote Cancelled and Cancel Callback not yet called!")
2304 DEBUG_MESSAGE("Setting CVA False -- Cancelled")
2305 core_vote_active = false;
2306 CancelVote();
2307 }
2308}
2309
2310
2311//Adds slot blocking to a menu
2312AddSlotBlockingToMenu(Handle:menu, blockSlots)
2313{
2314 //Add blocked slots if...
2315 // ...the cvar for blocked slots is enabled.
2316 if (blockSlots > 3)
2317 AddMenuItem(menu, NOTHING_OPTION, "", ITEMDRAW_SPACER);
2318 if (blockSlots > 0)
2319 AddMenuItem(menu, NOTHING_OPTION, "Slot Block Message 1", ITEMDRAW_DISABLED);
2320 if (blockSlots > 1)
2321 AddMenuItem(menu, NOTHING_OPTION, "Slot Block Message 2", ITEMDRAW_DISABLED);
2322 if (blockSlots > 2)
2323 AddMenuItem(menu, NOTHING_OPTION, "", ITEMDRAW_SPACER);
2324 if (blockSlots > 4)
2325 AddMenuItem(menu, NOTHING_OPTION, "", ITEMDRAW_SPACER);
2326}
2327
2328
2329//Called when a vote has finished.
2330public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)
2331{
2332 switch(action)
2333 {
2334 case MenuAction_Display:
2335 {
2336 //LogUMCMessage("DEBUG: Display");
2337 new Handle:panel = Handle:param2;
2338
2339 decl String:phrase[255];
2340 GetMenuTitle(menu, phrase, sizeof(phrase));
2341
2342 decl String:buffer[255];
2343 FormatEx(buffer, sizeof(buffer), "%T", phrase, param1);
2344
2345 SetPanelTitle(panel, buffer);
2346 }
2347 case MenuAction_Select:
2348 {
2349 DEBUG_MESSAGE("MenuAction_Select")
2350 if (GetConVarBool(cvar_logging))
2351 LogUMCMessage("%L selected menu item %i", param1, param2);
2352 //TODO
2353 UMC_VoteManagerClientVoted("core", param1, INVALID_HANDLE);
2354 }
2355 case MenuAction_End:
2356 {
2357 DEBUG_MESSAGE("MenuAction_End")
2358 CloseHandle(menu);
2359 if (GetConVarBool(cvar_logging))
2360 LogUMCMessage("Vote has concluded.");
2361 }
2362 case MenuAction_VoteCancel:
2363 {
2364 DEBUG_MESSAGE("Vote Cancelled (Reason: %i)", param1)
2365 DEBUG_MESSAGE("Is Core Vote still active? %i", core_vote_active)
2366 if (core_vote_active)
2367 {
2368 //Vote was cancelled generically, notify UMC.
2369 DEBUG_MESSAGE("Setting CVA False -- Cancelled")
2370 core_vote_active = false;
2371 UMC_VoteManagerVoteCancelled("core");
2372 }
2373 }
2374 case MenuAction_DisplayItem:
2375 {
2376 //LogUMCMessage("DEBUG: DisplayItem");
2377 decl String:map[MAP_LENGTH], String:display[MAP_LENGTH];
2378 GetMenuItem(menu, param2, map, sizeof(map), _, display, sizeof(display));
2379
2380 if (StrEqual(map, EXTEND_MAP_OPTION) || StrEqual(map, DONT_CHANGE_OPTION) ||
2381 (StrEqual(map, NOTHING_OPTION) && strlen(display) > 0))
2382 {
2383 decl String:buffer[255];
2384 FormatEx(buffer, sizeof(buffer), "%T", display, param1);
2385
2386 return RedrawMenuItem(buffer);
2387 }
2388 }
2389 }
2390 return 0;
2391}
2392
2393
2394//Handles the results of a vote.
2395public Handle_MapVoteResults(Handle:menu, num_votes, num_clients, const client_info[][2], num_items,
2396 const item_info[][2])
2397{
2398 DEBUG_MESSAGE("Handling vote results...")
2399 DEBUG_MESSAGE("Setting CVA False -- Completed")
2400 core_vote_active = false;
2401
2402 new Handle:results = ConvertVoteResults(menu, num_clients, client_info, num_items, item_info);
2403
2404 UMC_VoteManagerVoteCompleted("core", results, Handle_Response);
2405
2406 //Free Memory
2407 new size = GetArraySize(results);
2408 new Handle:item;
2409 new Handle:clients;
2410 for (new i = 0; i < size; i++)
2411 {
2412 item = GetArrayCell(results, i);
2413 GetTrieValue(item, "clients", clients);
2414 CloseHandle(clients);
2415 CloseHandle(item);
2416 }
2417 CloseHandle(results);
2418}
2419
2420
2421//
2422public Handle_Response(UMC_VoteResponse:response, const String:param[])
2423{
2424 //Do Nothing
2425}
2426
2427
2428//Converts results of a vote to the format required for UMC to process votes.
2429Handle:ConvertVoteResults(Handle:menu, num_clients, const client_info[][2], num_items,
2430 const item_info[][2])
2431{
2432 new Handle:result = CreateArray();
2433 new itemIndex;
2434 new Handle:voteItem, Handle:voteClientArray;
2435 decl String:info[MAP_LENGTH], String:disp[MAP_LENGTH];
2436 for (new i = 0; i < num_items; i++)
2437 {
2438 itemIndex = item_info[i][VOTEINFO_ITEM_INDEX];
2439 GetMenuItem(menu, itemIndex, info, sizeof(info), _, disp, sizeof(disp));
2440
2441 voteItem = CreateTrie();
2442 voteClientArray = CreateArray();
2443
2444 SetTrieString(voteItem, "info", info);
2445 SetTrieString(voteItem, "display", disp);
2446 SetTrieValue(voteItem, "clients", voteClientArray);
2447
2448 PushArrayCell(result, voteItem);
2449
2450 for (new j = 0; j < num_clients; j++)
2451 {
2452 if (client_info[j][VOTEINFO_CLIENT_ITEM] == itemIndex)
2453 PushArrayCell(voteClientArray, client_info[j][VOTEINFO_CLIENT_INDEX]);
2454 }
2455 }
2456 return result;
2457}
2458
2459
2460//************************************************************************************************//
2461// VOTING UTILITIES //
2462//************************************************************************************************//
2463
2464//
2465DisableVoteInProgress(Handle:vM)
2466{
2467 DEBUG_MESSAGE("Vote no longer in progress!")
2468 SetTrieValue(vM, "in_progress", false);
2469}
2470
2471
2472//
2473FreeOptions(Handle:options)
2474{
2475 new size = GetArraySize(options);
2476 new Handle:item;
2477 for (new i = 0; i < size; i++)
2478 {
2479 item = GetArrayCell(options, i);
2480 CloseHandle(item);
2481 }
2482 CloseHandle(options);
2483}
2484
2485bool:IsVMVoteInProgress(Handle:voteManager)
2486{
2487 new Handle:progressCheck;
2488 GetTrieValue(voteManager, "checkprogress", progressCheck);
2489 new bool:result;
2490
2491 if (GetForwardFunctionCount(progressCheck) == 0)
2492 {
2493 result = IsVoteInProgress();
2494 }
2495 else
2496 {
2497 Call_StartForward(progressCheck);
2498 Call_Finish(result);
2499 }
2500
2501 return result;
2502}
2503
2504//
2505bool:PerformVote(Handle:voteManager, UMC_VoteType:type, Handle:options, time, const clients[],
2506 numClients, const String:startSound[])
2507{
2508 new Handle:handler;
2509 switch (type)
2510 {
2511 case VoteType_Map:
2512 {
2513 LogUMCMessage("Initiating Vote Type: Map");
2514 GetTrieValue(voteManager, "map", handler);
2515 }
2516 case VoteType_Group:
2517 {
2518 LogUMCMessage("Initiating Vote Type: Group");
2519 GetTrieValue(voteManager, "group", handler);
2520 }
2521 case VoteType_Tier:
2522 {
2523 LogUMCMessage("Initiating Vote Type: Stage 1 Tiered");
2524 GetTrieValue(voteManager, "group", handler);
2525 }
2526 }
2527
2528 new Action:result;
2529 Call_StartForward(handler);
2530 Call_PushCell(time);
2531 Call_PushCell(options);
2532 Call_PushArray(clients, numClients);
2533 Call_PushCell(numClients);
2534 Call_PushString(startSound);
2535 Call_Finish(result);
2536
2537 new bool:started = result == Plugin_Continue;
2538
2539 if (started)
2540 {
2541 SetTrieValue(voteManager, "in_progress", true);
2542 SetTrieValue(voteManager, "active", true);
2543 }
2544
2545 return started;
2546}
2547
2548
2549enum UMC_BuildOptionsError
2550{
2551 BuildOptionsError_InvalidMapcycle,
2552 BuildOptionsError_NoMapGroups,
2553 BuildOptionsError_NotEnoughOptions,
2554 BuildOptionsError_Success
2555};
2556
2557
2558//Build and returns a new vote menu.
2559Handle:BuildVoteItems(Handle:vM, Handle:kv, Handle:mapcycle, &UMC_VoteType:type, bool:scramble,
2560 bool:allowDupes, bool:strictNoms, bool:exclude, bool:extend, bool:dontChange)
2561{
2562 new Handle:result = CreateArray();
2563 new UMC_BuildOptionsError:error;
2564
2565 switch (type)
2566 {
2567 case (VoteType_Map):
2568 {
2569 error = BuildMapVoteItems(vM, result, kv, mapcycle, scramble, extend, dontChange,
2570 allowDupes, strictNoms, .exclude=exclude);
2571 }
2572 case (VoteType_Group):
2573 {
2574 error = BuildCatVoteItems(vM, result, kv, mapcycle, scramble, extend, dontChange, strictNoms,
2575 exclude);
2576 }
2577 case (VoteType_Tier):
2578 {
2579 error = BuildCatVoteItems(vM, result, kv, mapcycle, scramble, extend, dontChange, strictNoms,
2580 exclude);
2581 }
2582 }
2583
2584 if ((type == VoteType_Group || type == VoteType_Tier) && error == BuildOptionsError_NotEnoughOptions)
2585 {
2586 type = VoteType_Map;
2587 error = BuildMapVoteItems(vM, result, kv, mapcycle, scramble, extend, dontChange,
2588 allowDupes, strictNoms, .exclude=exclude);
2589 }
2590
2591 if (error == BuildOptionsError_InvalidMapcycle || error == BuildOptionsError_NoMapGroups)
2592 {
2593 CloseHandle(result);
2594 result = INVALID_HANDLE;
2595 }
2596
2597 return result;
2598}
2599
2600
2601//Builds and returns a menu for a map vote.
2602UMC_BuildOptionsError:BuildMapVoteItems(Handle:voteManager, Handle:result, Handle:okv,
2603 Handle:mapcycle, bool:scramble, bool:extend,
2604 bool:dontChange, bool:ignoreDupes=false,
2605 bool:strictNoms=false, bool:ignoreInvoteSetting=false,
2606 bool:exclude=true)
2607{
2608 DEBUG_MESSAGE("MAPVOTE - Building map vote menu.")
2609 //Throw an error and return nothing if...
2610 // ...the mapcycle is invalid.
2611 if (okv == INVALID_HANDLE)
2612 {
2613 LogError("VOTING: Cannot build map vote menu, rotation file is invalid.");
2614 return BuildOptionsError_InvalidMapcycle;
2615 }
2616
2617 DEBUG_MESSAGE("Preparing mapcycle for traversal.")
2618 //Duplicate the kv handle, because we will be deleting some keys.
2619 KvRewind(okv); //rewind original
2620 new Handle:kv = CreateKeyValues("umc_rotation"); //new handle
2621 KvCopySubkeys(okv, kv); //copy everything to the new handle
2622
2623 //Filter mapcycle
2624 if (exclude)
2625 FilterMapcycle(kv, mapcycle, .deleteEmpty=false);
2626
2627#if UMC_DEBUG
2628 LogKv(kv);
2629#endif
2630
2631 DEBUG_MESSAGE("Checking for groups.")
2632 //Log an error and return nothing if...
2633 // ...it cannot find a category.
2634 if (!KvGotoFirstSubKey(kv))
2635 {
2636 LogError("VOTING: No map groups found in rotation. Vote menu was not built.");
2637 CloseHandle(kv);
2638 return BuildOptionsError_NoMapGroups;
2639 }
2640
2641 DEBUG_MESSAGE("Preparing vote data storage.")
2642 ClearVoteArrays(voteManager);
2643
2644 DEBUG_MESSAGE("Getting options from cvars.")
2645 //Determine how we're logging
2646 new bool:verboseLogs = GetConVarBool(cvar_logging);
2647
2648 DEBUG_MESSAGE("Initializing Buffers")
2649 //Buffers
2650 new String:mapName[MAP_LENGTH]; //Name of the map
2651 new String:display[MAP_LENGTH]; //String to be displayed in the vote
2652 new String:gDisp[MAP_LENGTH];
2653 new String:catName[MAP_LENGTH]; //Name of the category.
2654
2655 //Other variables
2656 new voteCounter = 0; //Number of maps in the vote currently
2657 new numNoms = 0; //Number of nominated maps in the vote.
2658 new Handle:nominationsFromCat = INVALID_HANDLE; //adt_array containing all nominations from the
2659 //current category.
2660 new Handle:tempCatNoms = INVALID_HANDLE;
2661 new Handle:trie = INVALID_HANDLE; //a nomination
2662 new Handle:nameArr = INVALID_HANDLE; //adt_array of map names from nominations
2663 new Handle:weightArr = INVALID_HANDLE; //adt_array of map weights from nominations.
2664
2665 new Handle:map_vote;
2666 GetTrieValue(voteManager, "map_vote", map_vote);
2667
2668 new Handle:map_vote_display = CreateArray(ByteCountToCells(MAP_LENGTH));
2669
2670 new nomIndex, position, numMapsFromCat, nomCounter, inVote, index; //, cIndex;
2671
2672 new tierAmount = GetConVarInt(cvar_vote_tieramount);
2673
2674 new Handle:nomKV;
2675 decl String:nomGroup[MAP_LENGTH];
2676
2677 DEBUG_MESSAGE("Performing Traversal")
2678 //Add maps to vote array from current category.
2679 do
2680 {
2681 WeightMapGroup(kv, mapcycle);
2682
2683 //Store the name of the current category.
2684 KvGetSectionName(kv, catName, sizeof(catName));
2685
2686 DEBUG_MESSAGE("Fetching map group data")
2687
2688 //Get the map-display template from the categeory definition.
2689 KvGetString(kv, "display-template", gDisp, sizeof(gDisp), "{MAP}");
2690
2691 DEBUG_MESSAGE("Fetching Nominations")
2692
2693 //Get all nominations for the current category.
2694 if (exclude)
2695 {
2696 tempCatNoms = GetCatNominations(catName);
2697 nominationsFromCat = FilterNominationsArray(tempCatNoms);
2698#if UMC_DEBUG
2699 DEBUG_MESSAGE("Unfiltered:")
2700 PrintNominationArray(tempCatNoms);
2701 DEBUG_MESSAGE("Filtered:")
2702 PrintNominationArray(nominationsFromCat);
2703#endif
2704 CloseHandle(tempCatNoms);
2705 }
2706 else
2707 nominationsFromCat = GetCatNominations(catName);
2708
2709 //Get the amount of nominations for the current category.
2710 numNoms = GetArraySize(nominationsFromCat);
2711
2712 DEBUG_MESSAGE("Calculating amount of maps needed to be fetched.")
2713
2714 //Get the total amount of maps to appear in the vote from this category.
2715 inVote = ignoreInvoteSetting
2716 ? tierAmount
2717 : KvGetNum(kv, "maps_invote", 1);
2718
2719 if (verboseLogs)
2720 {
2721 if (ignoreInvoteSetting)
2722 LogUMCMessage("VOTE MENU: (Verbose) Second stage tiered vote. See cvar \"sm_umc_vote_tieramount.\"");
2723 LogUMCMessage("VOTE MENU: (Verbose) Fetching %i maps from group '%s'", inVote, catName);
2724 }
2725
2726 //Calculate the number of maps we still need to fetch from the mapcycle.
2727 numMapsFromCat = inVote - numNoms;
2728
2729 DEBUG_MESSAGE("Determining proper nomination processing algorithm.")
2730
2731 //Populate vote with nomination maps from this category if...
2732 // ...we do not need to fetch any maps from the mapcycle AND
2733 // ...the number of nominated maps in the vote is limited to the maps_invote setting for
2734 // the category.
2735 if (numMapsFromCat < 0 && strictNoms)
2736 {
2737 //////
2738 //The piece of code inside this block is for the case where the current category's
2739 //nominations exceeds it's number of maps allowed in the vote.
2740 //
2741 //In order to solve this problem, we first fetch all nominations where the map has
2742 //appropriate min and max players for the amount of players on the server, and then
2743 //randomly pick from this pool based on the weights if the maps, until the number
2744 //of maps in the vote from this category is reached.
2745 //////
2746
2747 DEBUG_MESSAGE("Performing strict nomination algorithm.")
2748
2749 if (verboseLogs)
2750 {
2751 LogUMCMessage(
2752 "VOTE MENU: (Verbose) Number of nominations (%i) exceeds allowable maps in vote for the map group '%s'. Limiting nominated maps to %i. (See cvar \"sm_umc_nominate_strict\")",
2753 numNoms, catName, inVote
2754 );
2755 }
2756
2757 //No nominations have been fetched from pool of possible nomination.
2758 nomCounter = 0;
2759
2760 //Populate vote array with nominations from this category if...
2761 // ...we have nominations from this category.
2762 if (numNoms > 0)
2763 {
2764 //Initialize name and weight adt_arrays.
2765 nameArr = CreateArray(ByteCountToCells(MAP_LENGTH));
2766 weightArr = CreateArray();
2767 new Handle:cycleArr = CreateArray();
2768
2769 DEBUG_MESSAGE("Fetching data from all nominations in the map group.")
2770
2771 //Store data from a nomination for...
2772 // ...each index of the adt_array of nominations from this category.
2773 for (new i = 0; i < numNoms; i++)
2774 {
2775 DEBUG_MESSAGE("Fetching nomination data.")
2776 //Store nomination.
2777 trie = GetArrayCell(nominationsFromCat, i);
2778
2779 //Get the map name from the nomination.
2780 GetTrieString(trie, MAP_TRIE_MAP_KEY, mapName, sizeof(mapName));
2781
2782 DEBUG_MESSAGE("Determining what to do with the nomination.")
2783
2784 //Add map to list of possible maps to be added to vote from the nominations
2785 //if...
2786 // ...the map is valid (correct number of players, correct time)
2787 if (!ignoreDupes && FindStringInVoteArray(mapName, MAP_TRIE_MAP_KEY, map_vote) != -1)
2788 {
2789 DEBUG_MESSAGE("Skipping repeated nomination.")
2790 if (verboseLogs)
2791 {
2792 LogUMCMessage(
2793 "VOTE MENU: (Verbose) Skipping nominated map '%s' from map group '%s' because it already is in the vote.",
2794 mapName, catName
2795 );
2796 }
2797 }
2798 else
2799 {
2800 DEBUG_MESSAGE("Adding nomination to the possible vote pool.")
2801 //Increment number of noms fetched.
2802 nomCounter++;
2803
2804 //Fetch mapcycle for weighting
2805 GetTrieValue(trie, "mapcycle", nomKV);
2806
2807 //Add map name to the pool.
2808 PushArrayString(nameArr, mapName);
2809
2810 //Add map weight to the pool.
2811 PushArrayCell(weightArr, GetMapWeight(nomKV, mapName, catName));
2812
2813 PushArrayCell(cycleArr, trie);
2814 }
2815 }
2816
2817 //After we have parsed every map from the list of nominations...
2818
2819 DEBUG_MESSAGE("Choosing nominations from the pool to be inserted into the vote.")
2820
2821 //Populate vote array with maps from the pool if...
2822 // ...the number of nominations fetched is greater than zero.
2823 if (nomCounter > 0)
2824 {
2825 //Add a nominated map from the pool into the vote arrays for...
2826 // ...the number of available spots there are from the category.
2827 new min = (inVote < nomCounter) ? inVote : nomCounter;
2828
2829 DEBUG_MESSAGE("Begin parsing nomination pool.")
2830
2831 for (new i = 0; i < min; i++)
2832 {
2833 DEBUG_MESSAGE("Fetching a random nomination from the pool.")
2834 //Get a random map from the pool.
2835 GetWeightedRandomSubKey(mapName, sizeof(mapName), weightArr, nameArr, index);
2836
2837 new Handle:nom = GetArrayCell(cycleArr, index);
2838 GetTrieValue(nom, "mapcycle", nomKV);
2839
2840 GetTrieString(nom, "nom_group", nomGroup, sizeof(nomGroup));
2841
2842 DEBUG_MESSAGE("Determining where to place the map in the vote.")
2843 //Get the position in the vote array to add the map to
2844 position = GetNextMenuIndex(voteCounter, scramble);
2845
2846 DEBUG_MESSAGE("Fetching display info for the map from the mapcycle.")
2847
2848 //Template
2849 new Handle:dispKV = CreateKeyValues("umc_mapcycle");
2850 KvCopySubkeys(nomKV, dispKV);
2851 GetMapDisplayString(
2852 dispKV, nomGroup, mapName, gDisp, display, sizeof(display)
2853 );
2854 CloseHandle(dispKV);
2855
2856 /* KvJumpToKey(nomKV, nomGroup);
2857 KvJumpToKey(nomKV, mapName);
2858 KvGetString(nomKV, "display", display, sizeof(display), gDisp);
2859
2860 DEBUG_MESSAGE("Setting proper display string.")
2861 if (strlen(display) == 0)
2862 strcopy(display, sizeof(display), mapName);
2863 else
2864 ReplaceString(display, sizeof(display), "{MAP}", mapName, false);
2865
2866 KvGoBack(nomKV);
2867 KvGoBack(nomKV); */
2868
2869 DEBUG_MESSAGE("Adding nomination to the vote.")
2870
2871 DEBUG_MESSAGE("*MEMLEAKTEST* Creating map trie (BuildMapVoteItems) [3]")
2872 new Handle:map = CreateMapTrie(mapName, catName);
2873
2874 new Handle:nomMapcycle = CreateKeyValues("umc_mapcycle");
2875 KvCopySubkeys(nomKV, nomMapcycle);
2876
2877 SetTrieValue(map, "mapcycle", nomMapcycle);
2878
2879 DEBUG_MESSAGE("*MEMLEAKTEST* Inserting map trie created at [3] into vote manager storage")
2880 InsertArrayCell(map_vote, position, map);
2881 InsertArrayString(map_vote_display, position, display);
2882
2883 //Increment number of maps added to the vote.
2884 voteCounter++;
2885
2886 DEBUG_MESSAGE("Preventing nomination and map from being picked again.")
2887
2888 //Delete the map so it can't be picked again.
2889 KvDeleteSubKey(kv, mapName);
2890
2891 //Remove map from pool.
2892 RemoveFromArray(nameArr, index);
2893 RemoveFromArray(weightArr, index);
2894 RemoveFromArray(cycleArr, index);
2895
2896 if (verboseLogs)
2897 {
2898 LogUMCMessage(
2899 "VOTE MENU: (Verbose) Nominated map '%s' from group '%s' was added to the vote.",
2900 mapName, catName
2901 );
2902 }
2903 }
2904 }
2905
2906 //Close handles for the pool.
2907 CloseHandle(nameArr);
2908 CloseHandle(weightArr);
2909 CloseHandle(cycleArr);
2910
2911 //Update numMapsFromCat to reflect the actual amount still required.
2912 numMapsFromCat = inVote - nomCounter;
2913 }
2914 }
2915 //Otherwise, we fill the vote with nominations then fill the rest with random maps from the
2916 //mapcycle.
2917 else
2918 {
2919 DEBUG_MESSAGE("Adding all nominations to the vote.")
2920 //Add nomination to the vote array for..
2921 // ...each index in the nomination array.
2922 for (new i = 0; i < numNoms; i++)
2923 {
2924 DEBUG_MESSAGE("Fetching nomination info.")
2925 //Get map name.
2926 new Handle:nom = GetArrayCell(nominationsFromCat, i);
2927 GetTrieString(nom, MAP_TRIE_MAP_KEY, mapName, sizeof(mapName));
2928
2929 DEBUG_MESSAGE("Determining what to do with the nomination.")
2930
2931 //Add nominated map to the vote array if...
2932 // ...the map isn't already in the vote AND
2933 // ...the server has a valid number of players for the map.
2934 if (!ignoreDupes
2935 && FindStringInVoteArray(mapName, MAP_TRIE_MAP_KEY, map_vote) != -1)
2936 {
2937 DEBUG_MESSAGE("Skipping repeated nomination.")
2938 if (verboseLogs)
2939 {
2940 LogUMCMessage(
2941 "VOTE MENU: (Verbose) Skipping nominated map '%s' from map group '%s' because it is already in the vote.",
2942 mapName, catName
2943 );
2944 }
2945 }
2946 else
2947 {
2948 GetTrieValue(nom, "mapcycle", nomKV);
2949 GetTrieString(nom, "nom_group", nomGroup, sizeof(nomGroup));
2950
2951 //Get extra fields from the map
2952 new Handle:dispKV = CreateKeyValues("umc_mapcycle");
2953 KvCopySubkeys(nomKV, dispKV);
2954 GetMapDisplayString(dispKV, nomGroup, mapName, gDisp, display, sizeof(display));
2955 CloseHandle(dispKV);
2956
2957 /* KvJumpToKey(nomKV, nomGroup);
2958 KvJumpToKey(nomKV, mapName);
2959 KvGetString(nomKV, "display", display, sizeof(display), gDisp);
2960
2961 DEBUG_MESSAGE("Setting proper display string.")
2962 if (strlen(display) == 0)
2963 strcopy(display, sizeof(display), mapName);
2964 else
2965 ReplaceString(display, sizeof(display), "{MAP}", mapName, false);
2966
2967 KvGoBack(nomKV);
2968 KvGoBack(nomKV); */
2969
2970 DEBUG_MESSAGE("Determining where to place the map in the vote.")
2971 //Get the position in the vote array to add the map to.
2972 position = GetNextMenuIndex(voteCounter, scramble);
2973
2974 DEBUG_MESSAGE("Adding nomination to the vote.")
2975
2976 DEBUG_MESSAGE("*MEMLEAKTEST* Creating map trie (BuildMapVoteItems) [4]")
2977 new Handle:map = CreateMapTrie(mapName, catName);
2978
2979 new Handle:nomMapcycle = CreateKeyValues("umc_mapcycle");
2980 KvCopySubkeys(nomKV, nomMapcycle);
2981
2982 SetTrieValue(map, "mapcycle", nomMapcycle);
2983
2984 DEBUG_MESSAGE("*MEMLEAKTEST* Inserting map trie created at [4] into vote manager storage")
2985 InsertArrayCell(map_vote, position, map);
2986 InsertArrayString(map_vote_display, position, display);
2987
2988 //Increment number of maps added to the vote.
2989 voteCounter++;
2990
2991 DEBUG_MESSAGE("Preventing map from being picked again.")
2992
2993 //Delete the map so it cannot be picked again.
2994 KvDeleteSubKey(kv, mapName);
2995
2996 if (verboseLogs)
2997 {
2998 LogUMCMessage(
2999 "VOTE MENU: (Verbose) Nominated map '%s' from group '%s' was added to the vote.",
3000 mapName, catName
3001 );
3002 }
3003 }
3004 }
3005 }
3006
3007 //////
3008 //At this point in the algorithm, we have already handled nominations for this category.
3009 //If there are maps which still need to be added to the vote, we will be fetching them
3010 //from the mapcycle directly.
3011 //////
3012
3013 DEBUG_MESSAGE("Finished processing nominations.")
3014
3015 if (verboseLogs)
3016 {
3017 LogUMCMessage("VOTE MENU: (Verbose) Finished parsing nominations for map group '%s'",
3018 catName);
3019 if (numMapsFromCat > 0)
3020 {
3021 LogUMCMessage("VOTE MENU: (Verbose) Still need to fetch %i maps from the group.",
3022 numMapsFromCat);
3023 }
3024 }
3025
3026 DEBUG_MESSAGE("Current gDisp value: %s", gDisp)
3027
3028 //We no longer need the nominations array, so we close the handle.
3029 CloseHandle(nominationsFromCat);
3030
3031 DEBUG_MESSAGE("Begin filling remaining spots in the vote.")
3032 //Add a map to the vote array from the current category while...
3033 // ...maps still need to be added from the current category.
3034 while (numMapsFromCat > 0)
3035 {
3036 DEBUG_MESSAGE("Attempting to fetch a map from the group.")
3037 //Skip the category if...
3038 // ...there are no more maps that can be added to the vote.
3039 if (!GetRandomMap(kv, mapName, sizeof(mapName)))
3040 {
3041 if (verboseLogs)
3042 LogUMCMessage("VOTE MENU: (Verbose) No more maps in map group '%s'", catName);
3043 DEBUG_MESSAGE("No more maps in group. Continuing to next group.")
3044 break;
3045 }
3046
3047 //The name of the selected map is now stored in mapName.
3048
3049 DEBUG_MESSAGE("Checking to make sure the map isn't already in the vote.")
3050 //Remove the map from the category (so it cannot be selected again) and repick a map
3051 //if...
3052 // ...the map has already been added to the vote (through nomination or another
3053 // category
3054 if (!ignoreDupes && FindStringInVoteArray(mapName, MAP_TRIE_MAP_KEY, map_vote) != -1)
3055 {
3056 DEBUG_MESSAGE("Map found in vote. Removing from mapcycle.")
3057 KvDeleteSubKey(kv, mapName);
3058 if (verboseLogs)
3059 {
3060 LogUMCMessage(
3061 "VOTE MENU: (Verbose) Skipping selected map '%s' from map group '%s' because it is already in the vote.",
3062 mapName, catName
3063 );
3064 }
3065 continue;
3066 }
3067
3068 //At this point we have a map which we are going to add to the vote array.
3069
3070 if (verboseLogs)
3071 {
3072 LogUMCMessage(
3073 "VOTE MENU: (Verbose) Selected map '%s' from group '%s' was added to the vote.",
3074 mapName, catName
3075 );
3076 }
3077
3078 DEBUG_MESSAGE("Searching for map in nominations.")
3079 //Find this map in the list of nominations.
3080 nomIndex = FindNominationIndex(mapName, catName);
3081
3082 //Remove the nomination if...
3083 // ...it was found.
3084 if (nomIndex != -1)
3085 {
3086 DEBUG_MESSAGE("Map found in nominations.")
3087 new Handle:nom = GetArrayCell(nominations_arr, nomIndex);
3088
3089 DEBUG_MESSAGE("Calling nomination removal forward.")
3090 new owner;
3091 GetTrieValue(nom, "client", owner);
3092
3093 Call_StartForward(nomination_reset_forward);
3094 Call_PushString(mapName);
3095 Call_PushCell(owner);
3096 Call_Finish();
3097
3098 DEBUG_MESSAGE("Removing nomination.")
3099 new Handle:oldnomKV;
3100 GetTrieValue(nom, "mapcycle", oldnomKV);
3101 CloseHandle(oldnomKV);
3102 CloseHandle(nom);
3103 RemoveFromArray(nominations_arr, nomIndex);
3104 if (verboseLogs)
3105 {
3106 LogUMCMessage("VOTE MENU: (Verbose) Removing selected map '%s' from nominations.",
3107 mapName);
3108 }
3109 }
3110
3111 DEBUG_MESSAGE("Fetching display info for the map from the mapcycle.")
3112 //Get extra fields from the map
3113 new Handle:dispKV = CreateKeyValues("umc_mapcycle");
3114 KvCopySubkeys(okv, dispKV);
3115 GetMapDisplayString(dispKV, catName, mapName, gDisp, display, sizeof(display));
3116 CloseHandle(dispKV);
3117
3118 DEBUG_MESSAGE("Display name for %s: '%s'", mapName, display)
3119
3120 /* KvJumpToKey(kv, mapName);
3121 KvGetString(kv, "display", display, sizeof(display), gDisp);
3122
3123 DEBUG_MESSAGE("Setting proper display string.")
3124 if (strlen(display) == 0)
3125 strcopy(display, sizeof(display), mapName);
3126 else
3127 ReplaceString(display, sizeof(display), "{MAP}", mapName, false);
3128
3129 KvGoBack(kv); */
3130
3131 DEBUG_MESSAGE("Determining where to place the map in the vote.")
3132 //Get the position in the vote array to add the map to.
3133 position = GetNextMenuIndex(voteCounter, scramble);
3134
3135 DEBUG_MESSAGE("Adding map to the vote.")
3136
3137 DEBUG_MESSAGE("*MEMLEAKTEST* Creating map trie (BuildMapVoteItems) [5]")
3138 new Handle:map = CreateMapTrie(mapName, catName);
3139
3140 new Handle:mapMapcycle = CreateKeyValues("umc_mapcycle");
3141 KvCopySubkeys(mapcycle, mapMapcycle);
3142
3143 SetTrieValue(map, "mapcycle", mapMapcycle);
3144
3145 DEBUG_MESSAGE("*MEMLEAKTEST* Inserting map trie created at [5] into vote manager storage")
3146 InsertArrayCell(map_vote, position, map);
3147 InsertArrayString(map_vote_display, position, display);
3148
3149 //Increment number of maps added to the vote.
3150 voteCounter++;
3151
3152 DEBUG_MESSAGE("Preventing map from being picked again.")
3153 //Delete the map from the KV so we can't pick it again.
3154 KvDeleteSubKey(kv, mapName);
3155
3156 //One less map to be added to the vote from this category.
3157 numMapsFromCat--;
3158 }
3159 }
3160 while (KvGotoNextKey(kv)); //Do this for each category.
3161
3162 //We no longer need the copy of the mapcycle
3163 CloseHandle(kv);
3164
3165 new Handle:infoArr = BuildNumArray(voteCounter);
3166
3167 new Handle:voteItem = INVALID_HANDLE;
3168 decl String:buffer[MAP_LENGTH];
3169 for (new i = 0; i < voteCounter; i++)
3170 {
3171 voteItem = CreateTrie();
3172 GetArrayString(infoArr, i, buffer, sizeof(buffer));
3173 SetTrieString(voteItem, "info", buffer);
3174 GetArrayString(map_vote_display, i, buffer, sizeof(buffer));
3175 SetTrieString(voteItem, "display", buffer);
3176 PushArrayCell(result, voteItem);
3177 }
3178
3179 CloseHandle(map_vote_display);
3180 CloseHandle(infoArr);
3181
3182 if (extend)
3183 {
3184 voteItem = CreateTrie();
3185 SetTrieString(voteItem, "info", EXTEND_MAP_OPTION);
3186 SetTrieString(voteItem, "display", "Extend Map");
3187 if (GetConVarBool(cvar_extend_display))
3188 {
3189 InsertArrayCell(result, 0, voteItem);
3190 DEBUG_MESSAGE("Adding Extend Option to start of options list...")
3191 }
3192 else
3193 {
3194 PushArrayCell(result, voteItem);
3195 DEBUG_MESSAGE("Adding Extend Option to end of options list...")
3196 }
3197 }
3198
3199 if (dontChange)
3200 {
3201 voteItem = CreateTrie();
3202 SetTrieString(voteItem, "info", DONT_CHANGE_OPTION);
3203 SetTrieString(voteItem, "display", "Don't Change");
3204 if (GetConVarBool(cvar_dontchange_display))
3205 {
3206 InsertArrayCell(result, 0, voteItem);
3207 DEBUG_MESSAGE("Adding Don't Change Option to start of options list...")
3208 }
3209 else
3210 {
3211 PushArrayCell(result, voteItem);
3212 DEBUG_MESSAGE("Adding Don't Change Option to end of options list...")
3213 }
3214 }
3215
3216 return BuildOptionsError_Success;
3217}
3218
3219
3220//Builds and returns a menu for a group vote.
3221UMC_BuildOptionsError:BuildCatVoteItems(Handle:vM, Handle:result, Handle:okv, Handle:mapcycle,
3222 bool:scramble, bool:extend, bool:dontChange,
3223 bool:strictNoms=false, bool:exclude=true)
3224{
3225 //Throw an error and return nothing if...
3226 // ...the mapcycle is invalid.
3227 if (okv == INVALID_HANDLE)
3228 {
3229 LogError("VOTING: Cannot build map group vote menu, rotation file is invalid.");
3230 return BuildOptionsError_InvalidMapcycle;
3231 }
3232
3233 //Rewind our mapcycle.
3234 KvRewind(okv); //rewind original
3235 new Handle:kv = CreateKeyValues("umc_rotation"); //new handle
3236 KvCopySubkeys(okv, kv);
3237
3238 //Log an error and return nothing if...
3239 // ...it cannot find a category.
3240 if (!KvGotoFirstSubKey(kv))
3241 {
3242 LogError("VOTING: No map groups found in rotation. Vote menu was not built.");
3243 CloseHandle(kv);
3244 return BuildOptionsError_NoMapGroups;
3245 }
3246
3247 ClearVoteArrays(vM);
3248
3249 new bool:verboseLogs = GetConVarBool(cvar_logging);
3250
3251 decl String:catName[MAP_LENGTH]; //Buffer to store category name in.
3252 decl String:mapName[MAP_LENGTH];
3253 decl String:nomGroup[MAP_LENGTH];
3254 new voteCounter = 0; //Number of categories in the vote.
3255 new Handle:catArray = CreateArray(ByteCountToCells(MAP_LENGTH), 0); //Array of categories in the vote.
3256
3257 new Handle:catNoms = INVALID_HANDLE;
3258 new Handle:nom = INVALID_HANDLE;
3259 new size;
3260 new bool:haveNoms = false;
3261
3262 new Handle:nomKV;
3263 new Handle:nomMapcycle;
3264
3265 //Add the current category to the vote.
3266 do
3267 {
3268 KvGetSectionName(kv, catName, sizeof(catName));
3269
3270 haveNoms = false;
3271
3272 if (exclude)
3273 {
3274 catNoms = GetCatNominations(catName);
3275 size = GetArraySize(catNoms);
3276 for (new i = 0; i < size; i++)
3277 {
3278 nom = GetArrayCell(catNoms, i);
3279 GetTrieValue(nom, "mapcycle", nomMapcycle);
3280
3281 nomKV = CreateKeyValues("umc_rotation");
3282 KvCopySubkeys(nomMapcycle, nomKV);
3283
3284 GetTrieString(nom, "nom_group", nomGroup, sizeof(nomGroup));
3285
3286 GetTrieString(nom, MAP_TRIE_MAP_KEY, mapName, sizeof(mapName));
3287
3288 KvJumpToKey(nomKV, nomGroup);
3289
3290 if (IsValidMapFromCat(nomKV, nomMapcycle, mapName, .isNom=true))
3291 {
3292 haveNoms = true;
3293 CloseHandle(nomKV);
3294 break;
3295 }
3296
3297 CloseHandle(nomKV);
3298 }
3299 CloseHandle(catNoms);
3300 }
3301 else if (!KvGotoFirstSubKey(kv))
3302 {
3303 if (verboseLogs)
3304 {
3305 LogUMCMessage(
3306 "VOTE MENU: (Verbose) Skipping empty map group '%s'.",
3307 catName
3308 );
3309 }
3310 continue;
3311 }
3312 else
3313 {
3314 KvGoBack(kv);
3315 haveNoms = true;
3316 }
3317
3318 //Skip this category if...
3319 // ...the server doesn't have the required amount of players or all maps are excluded OR
3320 // ...the number of maps in the vote from the category is less than 1.
3321 if (!haveNoms)
3322 {
3323 if (!IsValidCat(kv, mapcycle))
3324 {
3325 if (verboseLogs)
3326 {
3327 LogUMCMessage(
3328 "VOTE MENU: (Verbose) Skipping excluded map group '%s'.",
3329 catName
3330 );
3331 }
3332 continue;
3333 }
3334 else if (KvGetNum(kv, "maps_invote", 1) < 1 && strictNoms)
3335 {
3336 if (verboseLogs)
3337 {
3338 LogUMCMessage(
3339 "VOTE MENU: (Verbose) Skipping map group '%s' due to \"maps_invote\" setting of 0.",
3340 catName
3341 );
3342 }
3343 continue;
3344 }
3345 }
3346
3347 if (verboseLogs)
3348 LogUMCMessage("VOTE MENU: (Verbose) Group '%s' was added to the vote.", catName);
3349
3350 //Add category to the vote array...
3351 InsertArrayString(catArray, GetNextMenuIndex(voteCounter, scramble), catName);
3352
3353 //Increment number of categories in the vote.
3354 voteCounter++;
3355 }
3356 while (KvGotoNextKey(kv)); //Do this for each category.
3357
3358 //No longer need the copied mapcycle
3359 CloseHandle(kv);
3360
3361 //Fall back to a map vote if only one group is available.
3362 if (GetArraySize(catArray) == 1)
3363 {
3364 CloseHandle(catArray);
3365 LogUMCMessage("Not enough groups available for group vote, performing map vote with only group available.");
3366 return BuildOptionsError_NotEnoughOptions;
3367 }
3368
3369 new Handle:voteItem = INVALID_HANDLE;
3370 decl String:buffer[MAP_LENGTH];
3371 for (new i = 0; i < voteCounter; i++)
3372 {
3373 voteItem = CreateTrie();
3374 GetArrayString(catArray, i, buffer, sizeof(buffer));
3375 SetTrieString(voteItem, "info", buffer);
3376 SetTrieString(voteItem, "display", buffer);
3377 PushArrayCell(result, voteItem);
3378 }
3379
3380 CloseHandle(catArray);
3381
3382 if (extend)
3383 {
3384 voteItem = CreateTrie();
3385 SetTrieString(voteItem, "info", EXTEND_MAP_OPTION);
3386 SetTrieString(voteItem, "display", "Extend Map");
3387 if (GetConVarBool(cvar_extend_display))
3388 {
3389 InsertArrayCell(result, 0, voteItem);
3390 DEBUG_MESSAGE("Adding Extend Option to start of options list...")
3391 }
3392 else
3393 {
3394 PushArrayCell(result, voteItem);
3395 DEBUG_MESSAGE("Adding Extend Option to end of options list...")
3396 }
3397 }
3398
3399 if (dontChange)
3400 {
3401 voteItem = CreateTrie();
3402 SetTrieString(voteItem, "info", DONT_CHANGE_OPTION);
3403 SetTrieString(voteItem, "display", "Don't Change");
3404 if (GetConVarBool(cvar_dontchange_display))
3405 {
3406 InsertArrayCell(result, 0, voteItem);
3407 DEBUG_MESSAGE("Adding Don't Change Option to start of options list...")
3408 }
3409 else
3410 {
3411 PushArrayCell(result, voteItem);
3412 DEBUG_MESSAGE("Adding Don't Change Option to end of options list...")
3413 }
3414 }
3415
3416 return BuildOptionsError_Success;
3417}
3418
3419
3420//Calls the templating system to format a map's display string.
3421// kv: Mapcycle containing the template info to use
3422// group: Group of the map we're getting display info for.
3423// map: Name of the map we're getting display info for.
3424// buffer: Buffer to store the display string.
3425// maxlen: Maximum length of the buffer.
3426GetMapDisplayString(Handle:kv, const String:group[], const String:map[], const String:template[],
3427 String:buffer[], maxlen)
3428{
3429 strcopy(buffer, maxlen, "");
3430 if (KvJumpToKey(kv, group))
3431 {
3432 if (KvJumpToKey(kv, map))
3433 {
3434 KvGetString(kv, "display", buffer, maxlen, template);
3435 KvGoBack(kv);
3436 }
3437 KvGoBack(kv);
3438 }
3439
3440 Call_StartForward(template_forward);
3441 Call_PushStringEx(
3442 buffer, maxlen,
3443 SM_PARAM_STRING_UTF8|SM_PARAM_STRING_COPY,
3444 SM_PARAM_COPYBACK
3445 );
3446 Call_PushCell(maxlen);
3447 Call_PushCell(kv);
3448 Call_PushString(map);
3449 Call_PushString(group);
3450 Call_Finish();
3451
3452 DEBUG_MESSAGE("GMDS Buffer: %s", buffer)
3453}
3454
3455
3456//Replaces {MAP} and {NOMINATED} in template strings.
3457public UMC_OnFormatTemplateString(String:template[], maxlen, Handle:kv, const String:map[],
3458 const String:group[])
3459{
3460 DEBUG_MESSAGE("OFTS: '%s' '%s' '%s'", map, group, template)
3461
3462 if (strlen(template) == 0)
3463 {
3464 strcopy(template, maxlen, map);
3465 return;
3466 }
3467
3468 ReplaceString(template, maxlen, "{MAP}", map, false);
3469
3470 decl String:nomString[16];
3471 GetConVarString(cvar_nomdisp, nomString, sizeof(nomString));
3472 ReplaceString(template, maxlen, "{NOMINATED}", nomString, false);
3473
3474 DEBUG_MESSAGE("OFTS End: %s", template)
3475}
3476
3477
3478//Selects a random map from a category based off of the supplied weights for the maps.
3479// kv: a mapcycle whose traversal stack is currently at the level of the category to choose
3480// from.
3481// buffer: a string to store the selected map in
3482// key: the key containing the weight information (for maps, 'weight', for cats, 'group_weight')
3483// excluded: an adt_array of maps to exclude from the selection.
3484//bool:GetRandomMap(Handle:kv, String:buffer[], size, Handle:excluded, Handle:excludedCats,
3485// bool:isNom=false, bool:forMapChange=true, bool:memory=true)
3486bool:GetRandomMap(Handle:kv, String:buffer[], size)
3487{
3488 decl String:catName[MAP_LENGTH];
3489 KvGetSectionName(kv, catName, sizeof(catName));
3490
3491 DEBUG_MESSAGE("Preparing mapcycle for random map selection.")
3492 //Return failure if...
3493 // ...there are no maps in the category.
3494 if (!KvGotoFirstSubKey(kv))
3495 {
3496 DEBUG_MESSAGE("No maps found in map group %s. Return false.", catName)
3497 return false;
3498 }
3499
3500 DEBUG_MESSAGE("Preparing to traverse maps in the group.")
3501 new index = 0; //counter of maps in the random pool
3502 new Handle:nameArr = CreateArray(ByteCountToCells(MAP_LENGTH)); //Array to store possible map names
3503 new Handle:weightArr = CreateArray(); //Array to store possible map weights.
3504 decl String:temp[MAP_LENGTH]; //Buffer to store map names in.
3505
3506 //Add a map to the random pool.
3507 do
3508 {
3509 //Get the name of the map.
3510 KvGetSectionName(kv, temp, sizeof(temp));
3511
3512 DEBUG_MESSAGE("Adding map %s to the pool.", temp)
3513
3514 //Add the map to the random pool.
3515 PushArrayCell(weightArr, GetWeight(kv));
3516 PushArrayString(nameArr, temp);
3517
3518 //One more map in the pool.
3519 index++;
3520 }
3521 while (KvGotoNextKey(kv)); //Do this for each map.
3522
3523 DEBUG_MESSAGE("Finished populating random pool.")
3524
3525 //Go back to the category level.
3526 KvGoBack(kv);
3527
3528 //Close pool and fail if...
3529 // ...no maps are selectable.
3530 if (index == 0)
3531 {
3532 DEBUG_MESSAGE("No maps found in pool. Returning false.")
3533 CloseHandle(nameArr);
3534 CloseHandle(weightArr);
3535 return false;
3536 }
3537
3538 DEBUG_MESSAGE("Getting random map from the pool.")
3539
3540 //Use weights to randomly select a map from the pool.
3541 new bool:result = GetWeightedRandomSubKey(buffer, size, weightArr, nameArr, _);
3542
3543 //Close the pool.
3544 CloseHandle(nameArr);
3545 CloseHandle(weightArr);
3546
3547 //Done!
3548 return result;
3549}
3550
3551
3552//Searches array for given string. Returns -1 on failure.
3553FindStringInVoteArray(const String:target[], const String:val[], Handle:arr)
3554{
3555 new size = GetArraySize(arr);
3556 decl String:buffer[MAP_LENGTH];
3557 for (new i = 0; i < size; i++)
3558 {
3559 GetTrieString(GetArrayCell(arr, i), val, buffer, sizeof(buffer));
3560 if (StrEqual(buffer, target))
3561 return i;
3562 }
3563 return -1;
3564}
3565
3566
3567//Catches the case where a vote occurred but nobody voted.
3568VoteCancelled(Handle:vM)
3569{
3570 DEBUG_MESSAGE("*VoteCancelled*")
3571 new Handle:handler;
3572 new bool:vote_inprogress;//, bool:vote_active;
3573 GetTrieValue(vM, "in_progress", vote_inprogress);
3574 //GetTrieValue(vM, "active", vote_active);
3575 if (vote_inprogress)
3576 {
3577 DEBUG_MESSAGE("Cancelling vote that is in progress...")
3578
3579 GetTrieValue(vM, "cancel", handler);
3580
3581 /*if (vote_active)
3582 {
3583 LogUMCMessage("Map vote ended with no votes.");
3584
3585 //Reset flags
3586 SetTrieValue(vM, "active", false);
3587 //vote_active = false;
3588
3589 //Cleanup the vote
3590 }*/
3591
3592 ClearVoteArrays(vM);
3593 EmptyStorage(vM);
3594 DeleteVoteParams(vM);
3595 VoteFailed(vM);
3596
3597 Call_StartForward(handler);
3598 Call_Finish();
3599 }
3600#if UMC_DEBUG
3601 else
3602 {
3603 DEBUG_MESSAGE("Vote not in progress, nothing to cancel!")
3604 }
3605#endif
3606}
3607
3608
3609//Utility function to clear all the voting storage arrays.
3610ClearVoteArrays(Handle:voteManager)
3611{
3612 new Handle:map_vote;
3613 GetTrieValue(voteManager, "map_vote", map_vote);
3614
3615 new size = GetArraySize(map_vote);
3616 new Handle:mapTrie;
3617 new Handle:kv;
3618 for (new i = 0; i < size; i++)
3619 {
3620 mapTrie = GetArrayCell(map_vote, i);
3621 GetTrieValue(mapTrie, "mapcycle", kv);
3622 CloseHandle(kv);
3623 DEBUG_MESSAGE("*MEMLEAKTEST* Closing map trie (ClearVoteArrays) {3, 4, 5}")
3624 CloseHandle(mapTrie);
3625 }
3626 ClearArray(map_vote);
3627}
3628
3629
3630//Get the winner from a vote.
3631any:GetWinner(Handle:vM)
3632{
3633 new Handle:vote_storage;
3634 GetTrieValue(vM, "vote_storage", vote_storage);
3635
3636 new counter = 1;
3637 new Handle:voteItem = GetArrayCell(vote_storage, 0);
3638 new Handle:voteClients = INVALID_HANDLE;
3639 GetTrieValue(voteItem, "clients", voteClients);
3640 new most_votes = GetArraySize(voteClients);
3641 new num_items = GetArraySize(vote_storage);
3642 while (counter < num_items)
3643 {
3644 GetTrieValue(GetArrayCell(vote_storage, counter), "clients", voteClients);
3645 if (GetArraySize(voteClients) < most_votes)
3646 break;
3647 counter++;
3648 }
3649 if (counter > 1)
3650 return GetArrayCell(vote_storage, GetRandomInt(0, counter - 1));
3651 else
3652 return GetArrayCell(vote_storage, 0);
3653}
3654
3655
3656//Generates a list of categories to be excluded from the second stage of a tiered vote.
3657stock Handle:MakeSecondTieredCatExclusion(Handle:kv, const String:cat[])
3658{
3659 //Log an error and return nothing if...
3660 // ...there are no categories in the cycle (for some reason).
3661 if (!KvJumpToKey(kv, cat))
3662 {
3663 LogError("TIERED VOTE: Cannot create second stage of vote, rotation file is invalid (no groups were found.)");
3664 return INVALID_HANDLE;
3665 }
3666
3667 //Array to return at the end.
3668 new Handle:result = CreateKeyValues("umc_rotation");
3669 KvJumpToKey(result, cat, true);
3670
3671 KvCopySubkeys(kv, result);
3672
3673 //Return to the root.
3674 KvGoBack(kv);
3675 KvGoBack(result);
3676
3677 //Success!
3678 return result;
3679}
3680
3681
3682//Updates the display for the interval between tiered votes.
3683DisplayTierMessage(timeleft)
3684{
3685 decl String:msg[255], String:notification[10];
3686 FormatEx(msg, sizeof(msg), "%t", "Another Vote", timeleft);
3687 GetConVarString(cvar_vote_tierdisplay, notification, sizeof(notification));
3688 DisplayServerMessage(msg, notification);
3689}
3690
3691
3692//Empties the vote storage
3693EmptyStorage(Handle:vM)
3694{
3695 new Handle:vote_storage;
3696 GetTrieValue(vM, "vote_storage", vote_storage);
3697
3698 new size = GetArraySize(vote_storage);
3699 for (new i = 0; i < size; i++)
3700 RemoveFromStorage(vM, 0);
3701
3702 SetTrieValue(vM, "total_votes", 0);
3703}
3704
3705
3706//Removes a vote item from the storage
3707RemoveFromStorage(Handle:vM, index)
3708{
3709 new Handle:vote_storage, total_votes;
3710 GetTrieValue(vM, "vote_storage", vote_storage);
3711 GetTrieValue(vM, "total_votes", total_votes);
3712
3713 new Handle:stored = GetArrayCell(vote_storage, index);
3714 new Handle:clients = INVALID_HANDLE;
3715 GetTrieValue(stored, "clients", clients);
3716 SetTrieValue(vM, "total_votes", total_votes - GetArraySize(clients));
3717 CloseHandle(clients);
3718 CloseHandle(stored);
3719 RemoveFromArray(vote_storage, index);
3720}
3721
3722
3723//Gets the winning info for the vote
3724GetVoteWinner(Handle:vM, String:info[], maxinfo, &Float:percentage, String:disp[]="", maxdisp=0)
3725{
3726 new total_votes;
3727 GetTrieValue(vM, "total_votes", total_votes);
3728
3729 new Handle:winner = GetWinner(vM);
3730 new Handle:clients = INVALID_HANDLE;
3731 GetTrieString(winner, "info", info, maxinfo);
3732 GetTrieString(winner, "display", disp, maxdisp);
3733 GetTrieValue(winner, "clients", clients);
3734 percentage = float(GetArraySize(clients)) / total_votes * 100;
3735}
3736
3737
3738//Finds the index of the given vote item in the storage array. Returns -1 on failure.
3739FindVoteInStorage(Handle:vote_storage, const String:info[])
3740{
3741 new arraySize = GetArraySize(vote_storage);
3742 new Handle:vote = INVALID_HANDLE;
3743 decl String:infoBuf[255];
3744 for (new i = 0; i < arraySize; i++)
3745 {
3746 vote = GetArrayCell(vote_storage, i);
3747 GetTrieString(vote, "info", infoBuf, sizeof(infoBuf));
3748 if (StrEqual(info, infoBuf))
3749 return i;
3750 }
3751 return -1;
3752}
3753
3754
3755//Comparison function for stored vote items. Used for sorting.
3756public CompareStoredVoteItems(index1, index2, Handle:array, Handle:hndl)
3757{
3758 new size1, size2;
3759 new Handle:vote = INVALID_HANDLE;
3760 new Handle:clientArray = INVALID_HANDLE;
3761 vote = GetArrayCell(array, index1);
3762 GetTrieValue(vote, "clients", clientArray);
3763 size1 = GetArraySize(clientArray);
3764 vote = GetArrayCell(array, index2);
3765 GetTrieValue(vote, "clients", clientArray);
3766 size2 = GetArraySize(clientArray);
3767 return size2 - size1;
3768}
3769
3770
3771//Adds vote results to the vote storage
3772AddToStorage(Handle:vM, Handle:vote_results)
3773{
3774 new Handle:vote_storage;
3775 GetTrieValue(vM, "vote_storage", vote_storage);
3776
3777 SetTrieValue(vM, "prev_vote_count", GetArraySize(vote_storage));
3778 DEBUG_MESSAGE("Old storage size (PVC): %i", GetArraySize(vote_storage))
3779
3780 new num_items = GetArraySize(vote_results);
3781 new storageIndex;
3782 new num_votes = 0;
3783 new Handle:voteItem = INVALID_HANDLE;
3784 new Handle:voteClientArray = INVALID_HANDLE;
3785 decl String:infoBuffer[255], String:dispBuffer[255];
3786 for (new i = 0; i < num_items; i++)
3787 {
3788 voteItem = GetArrayCell(vote_results, i);
3789 GetTrieString(voteItem, "info", infoBuffer, sizeof(infoBuffer));
3790 storageIndex = FindVoteInStorage(vote_storage, infoBuffer);
3791 GetTrieValue(voteItem, "clients", voteClientArray);
3792 num_votes += GetArraySize(voteClientArray);
3793 if (storageIndex == -1)
3794 {
3795 new Handle:newItem = CreateTrie();
3796 SetTrieString(newItem, "info", infoBuffer);
3797 GetTrieString(voteItem, "display", dispBuffer, sizeof(dispBuffer));
3798 SetTrieString(newItem, "display", dispBuffer);
3799 SetTrieValue(newItem, "clients", CloneArray(voteClientArray));
3800 PushArrayCell(vote_storage, newItem);
3801 }
3802 else
3803 {
3804 new Handle:storageClientArray;
3805 GetTrieValue(GetArrayCell(vote_storage, storageIndex), "client", storageClientArray);
3806 ArrayAppend(storageClientArray, voteClientArray);
3807 }
3808 }
3809 SortADTArrayCustom(vote_storage, CompareStoredVoteItems);
3810
3811 new total_votes;
3812 GetTrieValue(vM, "total_votes", total_votes);
3813 SetTrieValue(vM, "total_votes", total_votes + num_votes);
3814
3815 DEBUG_MESSAGE("New storage size: %i", GetArraySize(vote_storage))
3816}
3817
3818
3819//Handles the results of a vote
3820Handle:ProcessVoteResults(Handle:vM, Handle:vote_results)
3821{
3822 new Handle:result = CreateTrie();
3823
3824 DEBUG_MESSAGE("Processing vote results")
3825
3826 //Vote is no longer running.
3827 //vote_active = false;
3828 SetTrieValue(vM, "active", false);
3829
3830 //Adds these results to the storage.
3831 AddToStorage(vM, vote_results);
3832
3833 //Perform a runoff vote if it is necessary.
3834 if (NeedRunoff(vM))
3835 {
3836 new remaining_runoffs, prev_vote_count;
3837 GetTrieValue(vM, "remaining_runoffs", remaining_runoffs);
3838 GetTrieValue(vM, "prev_vote_count", prev_vote_count);
3839
3840 DEBUG_MESSAGE("Will Runoff? (RR: %i) (PVC: %i)", remaining_runoffs, prev_vote_count)
3841
3842 //If we can't runoff anymore
3843 if (remaining_runoffs == 0 || prev_vote_count == 2)
3844 {
3845 DEBUG_MESSAGE("Can't runoff, performing failure action.")
3846
3847 //Retrieve
3848 new UMC_RunoffFailAction:stored_fail_action;
3849 GetTrieValue(vM, "stored_fail_action", stored_fail_action);
3850
3851 if (stored_fail_action == RunoffFailAction_Accept)
3852 {
3853 ProcessVoteWinner(vM, result);
3854 }
3855 else if (stored_fail_action == RunoffFailAction_Nothing)
3856 {
3857 new total_votes;
3858 GetTrieValue(vM, "total_votes", total_votes);
3859 new Float:percentage;
3860 GetVoteWinner(vM, "", 0, percentage);
3861 PrintToChatAll(
3862 "\x03[UMC]\x01 %t (%t)",
3863 "Vote Failed",
3864 "Vote Win Percentage",
3865 percentage,
3866 total_votes
3867 );
3868 LogUMCMessage("MAPVOTE: Vote failed, winning map did not reach threshold.");
3869 VoteFailed(vM);
3870 DeleteVoteParams(vM);
3871 ClearVoteArrays(vM);
3872 SetTrieValue(result, "response", VoteResponse_Fail);
3873 }
3874 EmptyStorage(vM);
3875 }
3876 else
3877 {
3878 DoRunoffVote(vM, result);
3879 }
3880 }
3881 else //Otherwise set the results.
3882 {
3883 ProcessVoteWinner(vM, result);
3884 EmptyStorage(vM);
3885 }
3886 return result;
3887}
3888
3889
3890//Processes the winner from the vote.
3891ProcessVoteWinner(Handle:vM, Handle:response)
3892{
3893 //Detemine winner information.
3894 decl String:winner[255], String:disp[255];
3895 new Float:percentage;
3896 GetVoteWinner(vM, winner, sizeof(winner), percentage, disp, sizeof(disp));
3897
3898 new UMC_VoteType:stored_type;
3899 GetTrieValue(vM, "stored_type", stored_type);
3900
3901 SetTrieValue(response, "response", VoteResponse_Success);
3902 SetTrieString(response, "param", disp);
3903
3904 switch (stored_type)
3905 {
3906 case VoteType_Map:
3907 Handle_MapVoteWinner(vM, winner, disp, percentage);
3908 case VoteType_Group:
3909 Handle_CatVoteWinner(vM, winner, disp, percentage);
3910 case VoteType_Tier:
3911 {
3912 SetTrieValue(response, "response", VoteResponse_Tiered);
3913 Handle_TierVoteWinner(vM, winner, disp, percentage);
3914 }
3915 }
3916}
3917
3918
3919//Determines if a runoff vote is needed.
3920bool:NeedRunoff(Handle:vM)
3921{
3922 //Retrive
3923 new Float:stored_threshold, total_votes;
3924 new Handle:vote_storage;
3925 GetTrieValue(vM, "stored_threshold", stored_threshold);
3926 GetTrieValue(vM, "total_votes", total_votes);
3927 GetTrieValue(vM, "vote_storage", vote_storage);
3928
3929 DEBUG_MESSAGE("Determining if the vote meets the defined threshold of %f", stored_threshold)
3930
3931 //Get the winning vote item.
3932 new Handle:voteItem = GetArrayCell(vote_storage, 0);
3933 new Handle:clients = INVALID_HANDLE;
3934 GetTrieValue(voteItem, "clients", clients);
3935
3936 new numClients = GetArraySize(clients);
3937 new bool:result = (float(numClients) / total_votes) < stored_threshold;
3938 return result;
3939}
3940
3941
3942//Sets up a runoff vote.
3943DoRunoffVote(Handle:vM, Handle:response)
3944{
3945 DEBUG_MESSAGE("Performing runoff vote")
3946
3947 new remaining_runoffs;
3948 GetTrieValue(vM, "remaining_runoffs", remaining_runoffs);
3949 SetTrieValue(vM, "remaining_runoffs", remaining_runoffs - 1);
3950
3951 //Array to store clients the menu will be displayed to.
3952 new Handle:runoffClients = CreateArray();
3953
3954 //Build the runoff vote based off of the results of the failed vote.
3955 new Handle:runoffOptions = BuildRunoffOptions(vM, runoffClients);
3956
3957 //Setup the timer if...
3958 // ...the menu was built successfully
3959 if (runoffOptions != INVALID_HANDLE)
3960 {
3961 new clients[MAXPLAYERS+1];
3962 new numClients;
3963
3964 //Empty storage and add all clients if we're revoting completely.
3965 if (!GetConVarBool(cvar_runoff_selective))
3966 {
3967 DEBUG_MESSAGE("Non-selective runoff vote: erasing storage and adding all clients.")
3968 ClearArray(runoffClients);
3969 EmptyStorage(vM);
3970
3971 /* decl String:adminFlags[64];
3972 GetTrieString(vM, "stored_adminflags", adminFlags, sizeof(adminFlags)); */
3973
3974 new users[MAXPLAYERS+1];
3975 GetTrieArray2(vM, "stored_users", users, sizeof(users), numClients);
3976 ConvertUserIDsToClients(users, clients, numClients);
3977
3978 //runoffClients = GetClientsWithFlags(adminFlags);
3979 ConvertArray(clients, numClients, runoffClients);
3980 }
3981
3982 //Setup timer to delay the start of the runoff vote.
3983 SetTrieValue(vM, "runoff_delay", 7);
3984
3985 //Display the first message
3986 DisplayRunoffMessage(8);
3987
3988 DEBUG_MESSAGE("Runoff timer created. Runoff vote will be displayed in %i seconds.", 8)
3989
3990 //Setup data pack to go along with the timer.
3991 new Handle:pack;
3992 CreateDataTimer(
3993 1.0,
3994 Handle_RunoffVoteTimer,
3995 pack,
3996 TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT
3997 );
3998 //Add info to the pack.
3999 WritePackCell(pack, _:vM);
4000 WritePackCell(pack, _:runoffOptions);
4001 WritePackCell(pack, _:runoffClients);
4002 SetTrieValue(response, "response", VoteResponse_Runoff);
4003 }
4004 else //Otherwise, cleanup
4005 {
4006 LogError("RUNOFF: Unable to create runoff vote menu, runoff aborted.");
4007 CloseHandle(runoffClients);
4008 VoteFailed(vM);
4009 EmptyStorage(vM);
4010 DeleteVoteParams(vM);
4011 ClearVoteArrays(vM);
4012 SetTrieValue(response, "response", VoteResponse_Fail);
4013 }
4014}
4015
4016
4017//Builds a runoff vote menu.
4018// clientArray: adt_array to be populated with clients whose votes were eliminated
4019Handle:BuildRunoffOptions(Handle:vM, Handle:clientArray)
4020{
4021 new Handle:vote_storage, Float:stored_threshold;
4022 GetTrieValue(vM, "vote_storage", vote_storage);
4023 GetTrieValue(vM, "stored_threshold", stored_threshold);
4024
4025 new bool:verboseLogs = GetConVarBool(cvar_logging);
4026 if (verboseLogs)
4027 LogUMCMessage("RUNOFF MENU: (Verbose) Building runoff vote menu.");
4028
4029 new Float:runoffThreshold = stored_threshold;
4030
4031 //Copy the current total number of votes. Needed because the number will change as we remove items.
4032 new totalVotes;
4033 GetTrieValue(vM, "total_votes", totalVotes);
4034
4035 new Handle:voteItem = INVALID_HANDLE;
4036 new Handle:voteClients = INVALID_HANDLE;
4037 new voteNumVotes;
4038 new num_items = GetArraySize(vote_storage);
4039
4040 //Array determining which clients have voted
4041 new bool:clientVotes[MAXPLAYERS + 1];
4042 for (new i = 0; i < num_items; i++)
4043 {
4044 voteItem = GetArrayCell(vote_storage, i);
4045 GetTrieValue(voteItem, "clients", voteClients);
4046 voteNumVotes = GetArraySize(voteClients);
4047
4048 for (new j = 0; j < voteNumVotes; j++)
4049 clientVotes[GetArrayCell(voteClients, j)] = true;
4050 }
4051 for (new i = 1; i <= MaxClients; i++)
4052 {
4053 if (!clientVotes[i])
4054 PushArrayCell(clientArray, i);
4055 }
4056
4057 new Handle:winning = GetArrayCell(vote_storage, 0);
4058 new winningNumVotes;
4059 new Handle:winningClients = INVALID_HANDLE;
4060 GetTrieValue(winning, "clients", winningClients);
4061 winningNumVotes = GetArraySize(winningClients);
4062
4063 //Starting max possible percentage of the winning item in this vote.
4064 new Float:percent = float(winningNumVotes) / float(totalVotes) * 100;
4065 new Float:newPercent;
4066
4067 //Max number of maps in the runoff vote
4068 new maxMaps;
4069 GetTrieValue(vM, "stored_runoffmaps_max", maxMaps);
4070 new bool:checkMax = maxMaps > 1;
4071
4072 //Starting at the item with the least votes, calculate the new possible max percentage
4073 //of the winning item. Stop when this percentage is greater than the threshold.
4074 for (new i = num_items - 1; i > 1; i--)
4075 {
4076 voteItem = GetArrayCell(vote_storage, i);
4077 GetTrieValue(voteItem, "clients", voteClients);
4078 voteNumVotes = GetArraySize(voteClients);
4079 ArrayAppend(clientArray, voteClients);
4080
4081 newPercent = float(voteNumVotes) / float(totalVotes) * 100;
4082 percent += newPercent;
4083
4084 if (verboseLogs)
4085 {
4086 new String:dispBuf[255];
4087 GetTrieString(voteItem, "display", dispBuf, sizeof(dispBuf));
4088 LogUMCMessage(
4089 "RUNOFF MENU: (Verbose) '%s' was removed from the vote. It had %i votes (%.f%% of total)",
4090 dispBuf, voteNumVotes, newPercent
4091 );
4092 }
4093
4094 //No longer store the map
4095 RemoveFromStorage(vM, i);
4096 num_items--;
4097
4098 //Stop if...
4099 // ...the new percentage is over the threshold AND
4100 // ...the number of maps in the vote is under the max.
4101 if (percent >= runoffThreshold && (!checkMax || num_items <= maxMaps))
4102 break;
4103 }
4104
4105 if (verboseLogs)
4106 {
4107 LogUMCMessage(
4108 "RUNOFF MENU: (Verbose) Stopped removing options from the vote. Maximum possible winning vote percentage is %.f%%.",
4109 percent
4110 );
4111 }
4112
4113 //Start building the new vote menu.
4114 new Handle:newMenu = CreateArray();
4115
4116 //Populate the new menu with what remains of the storage.
4117 new count = 0;
4118 decl String:info[255], String:disp[255];
4119 new Handle:item;
4120 for (new i = 0; i < num_items; i++)
4121 {
4122 voteItem = GetArrayCell(vote_storage, i);
4123 GetTrieString(voteItem, "info", info, sizeof(info));
4124 GetTrieString(voteItem, "display", disp, sizeof(disp));
4125
4126 DEBUG_MESSAGE("*MEMLEAKTEST* Creating VoteOption Trie for Runoff (BuildRunoffOptions) [6]")
4127 item = CreateTrie();
4128 SetTrieString(item, "info", info);
4129 SetTrieString(item, "display", disp);
4130 PushArrayCell(newMenu, item);
4131
4132 count++;
4133 }
4134
4135 //Log an error and do nothing if...
4136 // ...there weren't enough items added to the runoff vote.
4137 // *This shouldn't happen if the algorithm is working correctly*
4138 if (count < 2)
4139 {
4140
4141 for (new i = 0; i < count; i++)
4142 {
4143 DEBUG_MESSAGE("*MEMLEAKTEST* Closing VoteOptionTrie for Runoff error (BuildRunoffOptions) {6}")
4144 CloseHandle(GetArrayCell(newMenu, i));
4145 }
4146 CloseHandle(newMenu);
4147 LogError(
4148 "RUNOFF: Not enough remaining maps to perform runoff vote. %i maps remaining. Please notify plugin author.",
4149 count
4150 );
4151 return INVALID_HANDLE;
4152 }
4153
4154 return newMenu;
4155}
4156
4157
4158//Called when the runoff timer for an end-of-map vote completes.
4159public Action:Handle_RunoffVoteTimer(Handle:timer, Handle:datapack)
4160{
4161 ResetPack(datapack);
4162 new Handle:vM = Handle:ReadPackCell(datapack);
4163
4164 new bool:vote_inprogress;
4165 GetTrieValue(vM, "in_progress", vote_inprogress);
4166
4167 if (!vote_inprogress)
4168 {
4169 VoteFailed(vM);
4170 EmptyStorage(vM);
4171 DeleteVoteParams(vM);
4172 ClearVoteArrays(vM);
4173
4174 new Handle:options = Handle:ReadPackCell(datapack);
4175 new Handle:clients = Handle:ReadPackCell(datapack);
4176 FreeOptions(options);
4177 CloseHandle(clients);
4178
4179 return Plugin_Stop;
4180 }
4181
4182 new runoff_delay;
4183 GetTrieValue(vM, "runoff_delay", runoff_delay);
4184
4185 DisplayRunoffMessage(runoff_delay);
4186
4187 //Display a message and continue timer if...
4188 // ...the timer hasn't finished yet.
4189 if (runoff_delay > 0)
4190 {
4191 if (strlen(countdown_sound) > 0)
4192 EmitSoundToAllAny(countdown_sound);
4193
4194 SetTrieValue(vM, "runoff_delay", runoff_delay - 1);
4195 return Plugin_Continue;
4196 }
4197
4198 LogUMCMessage("RUNOFF: Starting runoff vote.");
4199
4200 //Log an error and do nothing if...
4201 // ...another vote is currently running for some reason.
4202 if (IsVMVoteInProgress(vM))
4203 {
4204 //LogUMCMessage("RUNOFF: There is a vote already in progress, cannot start a new vote.");
4205 return Plugin_Continue;
4206 }
4207
4208 new Handle:options = Handle:ReadPackCell(datapack);
4209 new Handle:voteClients = Handle:ReadPackCell(datapack);
4210
4211 new clients[MAXPLAYERS+1];
4212 new numClients = GetArraySize(voteClients);
4213 ConvertAdtArray(voteClients, clients, sizeof(clients));
4214
4215 CloseHandle(voteClients);
4216
4217 new UMC_VoteType:type;
4218 GetTrieValue(vM, "stored_type", type);
4219 new time;
4220 GetTrieValue(vM, "stored_votetime", time);
4221 decl String:sound[PLATFORM_MAX_PATH];
4222 GetTrieString(vM, "stored_runoff_sound", sound, sizeof(sound));
4223
4224 new bool:vote_active = PerformVote(vM, type, options, time, clients, numClients, sound);
4225 if (!vote_active)
4226 {
4227 DeleteVoteParams(vM);
4228 ClearVoteArrays(vM);
4229 EmptyStorage(vM);
4230 VoteFailed(vM);
4231 }
4232
4233 FreeOptions(options);
4234
4235 /* //Play the vote start sound if...
4236 // ...the filename is defined.
4237 if (strlen(stored_runoff_sound) > 0)
4238 EmitSoundToAllAny(stored_runoff_sound);
4239 //Otherwise, play the sound for end-of-map votes if...
4240 // ...the filename is defined.
4241 else if (strlen(stored_start_sound) > 0)
4242 EmitSoundToAllAny(stored_start_sound);
4243
4244 //Run the vote to selected client only if...
4245 // ...the cvar to do so is enabled.
4246 if (GetConVarBool(cvar_runoff_selective))
4247 {
4248 //Log an error if...
4249 // ...the vote cannot start for some reason.
4250 if (!VoteMenu(runoff_menu, clients, numClients, stored_votetime))
4251 LogUMCMessage("RUNOFF: Menu already has a vote in progress, cannot start a new vote.");
4252 else
4253 {
4254 PrintToChatAll("\x03[UMC]\x01 %t", "Selective Runoff");
4255 vote_active = true;
4256 }
4257 }
4258 //Otherwise, just display it to everybody.
4259 else
4260 {
4261 //Log an error if...
4262 // ...the vote cannot start for some reason.
4263 if (!VoteMenuToAllWithFlags(runoff_menu, stored_votetime, stored_adminflags))
4264 LogUMCMessage("RUNOFF: Menu already has a vote in progress, cannot start a new vote.");
4265 else
4266 vote_active = true;
4267 } */
4268
4269 return Plugin_Stop;
4270}
4271
4272
4273//Displays a notification for the impending runoff vote.
4274DisplayRunoffMessage(timeRemaining)
4275{
4276 decl String:msg[255], String:notification[10];
4277 if (timeRemaining > 5)
4278 FormatEx(msg, sizeof(msg), "%t", "Runoff Msg");
4279 else
4280 FormatEx(msg, sizeof(msg), "%t", "Another Vote", timeRemaining);
4281 GetConVarString(cvar_runoff_display, notification, sizeof(notification));
4282 DisplayServerMessage(msg, notification);
4283}
4284
4285
4286//Handles the winner of an end-of-map map vote.
4287public Handle_MapVoteWinner(Handle:vM, const String:info[], const String:disp[],
4288 Float:percentage)
4289{
4290 //vote_completed = true;
4291
4292 new total_votes;
4293 GetTrieValue(vM, "total_votes", total_votes);
4294
4295 //Print a message and extend the current map if...
4296 // ...the server voted to extend the map.
4297 if (StrEqual(info, EXTEND_MAP_OPTION))
4298 {
4299 PrintToChatAll(
4300 "\x03[UMC]\x01 %t %t (%t)",
4301 "End of Map Vote Over",
4302 "Map Extended",
4303 "Vote Win Percentage",
4304 percentage,
4305 total_votes
4306 );
4307 LogUMCMessage("MAPVOTE: Players voted to extend the map.");
4308 ExtendMap(vM);
4309 }
4310 else if (StrEqual(info, DONT_CHANGE_OPTION))
4311 {
4312 PrintToChatAll(
4313 "\x03[UMC]\x01 %t %t (%t)",
4314 "End of Map Vote Over",
4315 "Map Unchanged",
4316 "Vote Win Percentage",
4317 percentage,
4318 total_votes
4319 );
4320
4321 LogUMCMessage("MAPVOTE: Players voted to stay on the map (Don't Change).");
4322 VoteFailed(vM);
4323 }
4324 else //Otherwise, we print a message and then set the new map.
4325 {
4326 PrintToChatAll(
4327 "\x03[UMC]\x01 %t %t (%t)",
4328 "End of Map Vote Over",
4329 "End of Map Vote Map Won",
4330 disp,
4331 "Vote Win Percentage",
4332 percentage,
4333 total_votes
4334 );
4335
4336 new Handle:map_vote, UMC_ChangeMapTime:change_map_when;
4337 GetTrieValue(vM, "map_vote", map_vote);
4338 GetTrieValue(vM, "change_map_when", change_map_when);
4339 decl String:stored_reason[PLATFORM_MAX_PATH];
4340 GetTrieString(vM, "stored_reason", stored_reason, sizeof(stored_reason));
4341
4342 //Find the index of the winning map in the stored vote array.
4343 new index = StringToInt(info);
4344 decl String:map[MAP_LENGTH], String:group[MAP_LENGTH];
4345
4346 new Handle:mapData = GetArrayCell(map_vote, index);
4347 GetTrieString(mapData, MAP_TRIE_MAP_KEY, map, sizeof(map));
4348 GetTrieString(mapData, MAP_TRIE_GROUP_KEY, group, sizeof(group));
4349
4350 new Handle:mapcycle;
4351 GetTrieValue(mapData, "mapcycle", mapcycle);
4352
4353 //Set it.
4354 DisableVoteInProgress(vM);
4355 DoMapChange(change_map_when, mapcycle, map, group, stored_reason, disp);
4356
4357 LogUMCMessage("MAPVOTE: Players voted for map '%s' from group '%s'", map, group);
4358 }
4359
4360 decl String:stored_end_sound[PLATFORM_MAX_PATH];
4361 GetTrieString(vM, "stored_end_sound", stored_end_sound, sizeof(stored_end_sound));
4362
4363 //Play the vote completed sound if...
4364 // ...the vote completed sound is defined.
4365 if (strlen(stored_end_sound) > 0)
4366 EmitSoundToAllAny(stored_end_sound);
4367
4368 //No longer need the vote array.
4369 ClearVoteArrays(vM);
4370 DeleteVoteParams(vM);
4371}
4372
4373
4374//Handles the winner of an end-of-map category vote.
4375public Handle_CatVoteWinner(Handle:vM, const String:cat[], const String:disp[],
4376 Float:percentage)
4377{
4378 DEBUG_MESSAGE("Handling group vote winner: %s", cat)
4379 //vote_completed = true;
4380
4381 new total_votes;
4382 GetTrieValue(vM, "total_votes", total_votes);
4383
4384 //Print a message and extend the map if...
4385 // ...the server voted to extend the map.
4386 if (StrEqual(cat, EXTEND_MAP_OPTION))
4387 {
4388 DEBUG_MESSAGE("Map was extended")
4389 PrintToChatAll(
4390 "\x03[UMC]\x01 %t %t (%t)",
4391 "End of Map Vote Over",
4392 "Map Extended",
4393 "Vote Win Percentage",
4394 percentage,
4395 total_votes
4396 );
4397 LogUMCMessage("Players voted to extend the map.");
4398 ExtendMap(vM);
4399 }
4400 else if (StrEqual(cat, DONT_CHANGE_OPTION))
4401 {
4402 DEBUG_MESSAGE("Map was not changed.")
4403 PrintToChatAll(
4404 "\x03[UMC]\x01 %t %t (%t)",
4405 "End of Map Vote Over",
4406 "Map Unchanged",
4407 "Vote Win Percentage",
4408 percentage,
4409 total_votes
4410 );
4411
4412 LogUMCMessage("Players voted to stay on the map (Don't Change).");
4413 VoteFailed(vM);
4414 }
4415 else //Otherwise, we pick a random map from the category and set that as the next map.
4416 {
4417 decl String:map[MAP_LENGTH];
4418
4419 DEBUG_MESSAGE("Rewinding and copying the mapcycle")
4420
4421 new Handle:stored_kv, Handle:stored_mapcycle;
4422 new UMC_ChangeMapTime:change_map_when;
4423 decl String:stored_reason[PLATFORM_MAX_PATH];
4424 new bool:stored_exclude;
4425 GetTrieValue(vM, "stored_kv", stored_kv);
4426 GetTrieValue(vM, "stored_mapcycle", stored_mapcycle);
4427 GetTrieValue(vM, "change_map_when", change_map_when);
4428 GetTrieString(vM, "stored_reason", stored_reason, sizeof(stored_reason));
4429 GetTrieValue(vM, "stored_exclude", stored_exclude);
4430
4431 //Rewind the mapcycle.
4432 KvRewind(stored_kv); //rewind original
4433 new Handle:kv = CreateKeyValues("umc_rotation");
4434 KvCopySubkeys(stored_kv, kv);
4435
4436 //Jump to the category in the mapcycle.
4437 KvJumpToKey(kv, cat);
4438
4439 if (stored_exclude)
4440 {
4441 DEBUG_MESSAGE("Filtering the map group")
4442 FilterMapGroup(kv, stored_mapcycle);
4443#if UMC_DEBUG
4444 LogKv(kv);
4445#endif
4446 }
4447
4448 DEBUG_MESSAGE("Weighting map group")
4449
4450 WeightMapGroup(kv, stored_mapcycle);
4451
4452 new Handle:nominationsFromCat;
4453
4454 //An adt_array of nominations from the given category.
4455 if (stored_exclude)
4456 {
4457 DEBUG_MESSAGE("Filtering Nominations")
4458 new Handle:tempCatNoms = GetCatNominations(cat);
4459 nominationsFromCat = FilterNominationsArray(tempCatNoms);
4460 CloseHandle(tempCatNoms);
4461 }
4462 else
4463 nominationsFromCat = GetCatNominations(cat);
4464
4465 //if...
4466 // ...there are nominations for this category.
4467 if (GetArraySize(nominationsFromCat) > 0)
4468 {
4469 DEBUG_MESSAGE("Processing nomination(s)")
4470
4471 //Array of nominated map names.
4472 new Handle:nameArr = CreateArray(ByteCountToCells(MAP_LENGTH));
4473
4474 //Array of nominated map weights (linked to the previous by index).
4475 new Handle:weightArr = CreateArray();
4476
4477 new Handle:cycleArr = CreateArray();
4478
4479 //Buffer to store the map name
4480 decl String:nameBuffer[MAP_LENGTH];
4481 decl String:nomGroup[MAP_LENGTH];
4482
4483 //A nomination.
4484 new Handle:trie = INVALID_HANDLE;
4485
4486 new Handle:nomKV;
4487
4488 new index;
4489
4490 //Add nomination to name and weight array for...
4491 // ...each nomination in the nomination array for this category.
4492 new arraySize = GetArraySize(nominationsFromCat);
4493 for (new i = 0; i < arraySize; i++)
4494 {
4495 //Get the nomination at the current index.
4496 trie = GetArrayCell(nominationsFromCat, i);
4497
4498 //Get the map name from the nomination.
4499 GetTrieString(trie, MAP_TRIE_MAP_KEY, nameBuffer, sizeof(nameBuffer));
4500
4501 GetTrieValue(trie, "mapcycle", nomKV);
4502
4503 //Add the map to the map name array.
4504 PushArrayString(nameArr, nameBuffer);
4505 PushArrayCell(weightArr, GetMapWeight(nomKV, nameBuffer, cat));
4506 PushArrayCell(cycleArr, trie);
4507 }
4508
4509 //Pick a random map from the nominations if...
4510 // ...there are nominations to choose from.
4511 if (GetWeightedRandomSubKey(map, sizeof(map), weightArr, nameArr, index))
4512 {
4513 DEBUG_MESSAGE("Selecting random nomination")
4514 trie = GetArrayCell(cycleArr, index);
4515 GetTrieValue(trie, "mapcycle", nomKV);
4516 GetTrieString(trie, "nom_group", nomGroup, sizeof(nomGroup));
4517 DisableVoteInProgress(vM);
4518 DoMapChange(change_map_when, nomKV, map, nomGroup, stored_reason, map);
4519 }
4520 else //Otherwise, we select a map randomly from the category.
4521 {
4522 DEBUG_MESSAGE("Couldn't select a random nomination [you shouldn't ever see this...]")
4523 GetRandomMap(kv, map, sizeof(map));
4524 DisableVoteInProgress(vM);
4525 DoMapChange(change_map_when, stored_mapcycle, map, cat, stored_reason, map);
4526 }
4527
4528 //Close the handles for the storage arrays.
4529 CloseHandle(nameArr);
4530 CloseHandle(weightArr);
4531 CloseHandle(cycleArr);
4532 }
4533 else //Otherwise, there are no nominations to worry about so we just pick a map randomly
4534 //from the winning category.
4535 {
4536 DEBUG_MESSAGE("No nominations, selecting a random map from the winning group")
4537 GetRandomMap(kv, map, sizeof(map)); //, stored_exmaps, stored_exgroups);
4538 DisableVoteInProgress(vM);
4539 DoMapChange(change_map_when, stored_mapcycle, map, cat, stored_reason, map);
4540 DEBUG_MESSAGE("Map selected was %s", map)
4541 }
4542
4543 //We no longer need the adt_array to store nominations.
4544 CloseHandle(nominationsFromCat);
4545
4546 //We no longer need the copy of the mapcycle.
4547 CloseHandle(kv);
4548
4549 PrintToChatAll(
4550 "\x03[UMC]\x01 %t %t (%t)",
4551 "End of Map Vote Over",
4552 "End of Map Vote Group Won",
4553 map, cat,
4554 "Vote Win Percentage",
4555 percentage,
4556 total_votes
4557 );
4558 LogUMCMessage(
4559 "MAPVOTE: Players voted for map group '%s' and the map '%s' was randomly selected.",
4560 cat, map
4561 );
4562 }
4563
4564 decl String:stored_end_sound[PLATFORM_MAX_PATH];
4565 GetTrieString(vM, "stored_end_sound", stored_end_sound, sizeof(stored_end_sound));
4566
4567 //Play the vote completed sound if...
4568 // ...the vote completed sound is defined.
4569 if (strlen(stored_end_sound) > 0)
4570 EmitSoundToAllAny(stored_end_sound);
4571
4572 DeleteVoteParams(vM);
4573}
4574
4575
4576//Handles the winner of an end-of-map tiered vote.
4577public Handle_TierVoteWinner(Handle:vM, const String:cat[], const String:disp[], Float:percentage)
4578{
4579 DEBUG_MESSAGE("Handling Tiered Endvote Winner \"%s\"", cat)
4580
4581 new total_votes;
4582 GetTrieValue(vM, "total_votes", total_votes);
4583
4584 //Print a message and extend the map if...
4585 // ...the server voted to extend the map.
4586 if (StrEqual(cat, EXTEND_MAP_OPTION))
4587 {
4588 DEBUG_MESSAGE("Endvote - Extending the map.")
4589
4590 PrintToChatAll(
4591 "\x03[UMC]\x01 %t %t (%t)",
4592 "End of Map Vote Over",
4593 "Map Extended",
4594 "Vote Win Percentage",
4595 percentage,
4596 total_votes
4597 );
4598 LogUMCMessage("MAPVOTE: Players voted to extend the map.");
4599 DeleteVoteParams(vM);
4600 ExtendMap(vM);
4601 }
4602 else if (StrEqual(cat, DONT_CHANGE_OPTION))
4603 {
4604 PrintToChatAll(
4605 "\x03[UMC]\x01 %t %t (%t)",
4606 "End of Map Vote Over",
4607 "Map Unchanged",
4608 "Vote Win Percentage",
4609 percentage,
4610 total_votes
4611 );
4612
4613 LogUMCMessage("MAPVOTE: Players voted to stay on the map (Don't Change).");
4614 DeleteVoteParams(vM);
4615 VoteFailed(vM);
4616 }
4617 else //Otherwise, we set up the second stage of the tiered vote
4618 {
4619 DEBUG_MESSAGE("Setting up second part of Tiered V.")
4620 LogUMCMessage("MAPVOTE (Tiered): Players voted for map group '%s'", cat);
4621
4622 new vMapCount;
4623
4624 DEBUG_MESSAGE("Counting the number of nominations from the winning group.")
4625 //Get the number of valid nominations from the group
4626 new Handle:tempNoms = GetCatNominations(cat);
4627
4628 new bool:stored_exclude;
4629 GetTrieValue(vM, "stored_exclude", stored_exclude);
4630
4631 if (stored_exclude)
4632 {
4633 new Handle:catNoms = FilterNominationsArray(tempNoms);
4634 vMapCount = GetArraySize(catNoms);
4635 CloseHandle(catNoms);
4636 }
4637 else
4638 {
4639 vMapCount = GetArraySize(tempNoms);
4640 }
4641 CloseHandle(tempNoms);
4642
4643 new Handle:stored_kv;
4644 GetTrieValue(vM, "stored_kv", stored_kv);
4645
4646 //Jump to the map group
4647 KvRewind(stored_kv);
4648 new Handle:kv = CreateKeyValues("umc_rotation");
4649 KvCopySubkeys(stored_kv, kv);
4650
4651 DEBUG_MESSAGE("Valid Maps: %i", vMapCount)
4652
4653 if (!KvJumpToKey(kv, cat))
4654 {
4655 LogError("KV Error: Unable to find map group \"%s\". Try removing any punctuation from the group's name.", cat);
4656 CloseHandle(kv);
4657 return;
4658 }
4659
4660 if (stored_exclude)
4661 {
4662 new Handle:stored_mapcycle;
4663 GetTrieValue(vM, "stored_mapcycle", stored_mapcycle);
4664 FilterMapGroup(kv, stored_mapcycle);
4665 }
4666
4667 DEBUG_MESSAGE("Counting the number of available maps from the winning group.")
4668 //Get the number of valid maps from the group
4669 vMapCount += CountMapsFromGroup(kv);
4670
4671 DEBUG_MESSAGE("Valid Maps: %i", vMapCount)
4672
4673 //Return to the root.
4674 KvGoBack(kv);
4675
4676 DEBUG_MESSAGE("Determining if we need to run a second vote.")
4677 //Just parse the results as a normal map group vote if...
4678 // ...the total number of valid maps is 1.
4679 if (vMapCount <= 1)
4680 {
4681 DEBUG_MESSAGE("Only 1 map available, no vote required.")
4682 LogUMCMessage(
4683 "MAPVOTE (Tiered): Only one valid map found in group. Handling results as a Map Group Vote."
4684 );
4685 CloseHandle(kv);
4686 Handle_CatVoteWinner(vM, cat, disp, percentage);
4687 return;
4688 }
4689
4690 DEBUG_MESSAGE("Starting countdown timer for the second vote.")
4691
4692 //Setup timer to delay the next vote for a few seconds.
4693 SetTrieValue(vM, "tiered_delay", 4);
4694
4695 //Display the first message
4696 DisplayTierMessage(5);
4697
4698 new Handle:tieredKV = MakeSecondTieredCatExclusion(kv, cat);
4699
4700#if UMC_DEBUG
4701 DEBUG_MESSAGE("Group for Tiered Vote:")
4702 LogKv(tieredKV);
4703#endif
4704
4705 CloseHandle(kv);
4706
4707 new Handle:pack = CreateDataPack();
4708
4709 //Setup timer to delay the next vote for a few seconds.
4710 CreateDataTimer(
4711 1.0,
4712 Handle_TieredVoteTimer,
4713 pack,
4714 TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT
4715 );
4716 WritePackCell(pack, _:vM);
4717 WritePackCell(pack, _:tieredKV);
4718 }
4719
4720 DEBUG_MESSAGE("Playing vote complete sound.")
4721
4722 decl String:stored_end_sound[PLATFORM_MAX_PATH];
4723 GetTrieString(vM, "stored_end_sound", stored_end_sound, sizeof(stored_end_sound));
4724
4725 //Play the vote completed sound if...
4726 // ...the vote completed sound is defined.
4727 if (strlen(stored_end_sound) > 0)
4728 EmitSoundToAllAny(stored_end_sound);
4729
4730 DEBUG_MESSAGE("Finished handling Tiered winner.")
4731}
4732
4733
4734//Called when the timer for the tiered end-of-map vote triggers.
4735public Action:Handle_TieredVoteTimer(Handle:timer, Handle:pack)
4736{
4737 ResetPack(pack);
4738 new Handle:vM = Handle:ReadPackCell(pack);
4739
4740 new bool:vote_inprogress;
4741 GetTrieValue(vM, "in_progress", vote_inprogress);
4742
4743 if (!vote_inprogress)
4744 {
4745 VoteFailed(vM);
4746 DeleteVoteParams(vM);
4747 return Plugin_Stop;
4748 }
4749
4750 new tiered_delay;
4751 GetTrieValue(vM, "tiered_delay", tiered_delay);
4752
4753 DisplayTierMessage(tiered_delay);
4754
4755 if (tiered_delay > 0)
4756 {
4757 if (strlen(countdown_sound) > 0)
4758 EmitSoundToAllAny(countdown_sound);
4759
4760 SetTrieValue(vM, "tiered_delay", tiered_delay - 1);
4761 return Plugin_Continue;
4762 }
4763
4764 if (IsVMVoteInProgress(vM))
4765 {
4766 return Plugin_Continue;
4767 }
4768
4769 new Handle:tieredKV = Handle:ReadPackCell(pack);
4770
4771 //Log a message
4772 LogUMCMessage("MAPVOTE (Tiered): Starting second stage of tiered vote.");
4773
4774 new Handle:stored_mapcycle, bool:stored_scramble, bool:stored_ignoredupes,
4775 bool:stored_strictnoms, bool:stored_exclude;
4776 GetTrieValue(vM, "stored_mapcycle", stored_mapcycle);
4777 GetTrieValue(vM, "stored_scramble", stored_scramble);
4778 GetTrieValue(vM, "stored_ignoredupes", stored_ignoredupes);
4779 GetTrieValue(vM, "stored_strictnoms", stored_strictnoms);
4780 GetTrieValue(vM, "stored_exclude", stored_exclude);
4781
4782 //Initialize the menu.
4783 new Handle:options = CreateArray();
4784
4785 new UMC_BuildOptionsError:error = BuildMapVoteItems(
4786 vM, options, stored_mapcycle,
4787 tieredKV, stored_scramble, false,
4788 false, stored_ignoredupes,
4789 stored_strictnoms, true, stored_exclude);
4790
4791 if (error == BuildOptionsError_Success)
4792 {
4793 //Play the vote start sound if...
4794 // ...the vote start sound is defined.
4795 /* if (strlen(stored_start_sound) > 0)
4796 EmitSoundToAllAny(stored_start_sound);
4797
4798 //Display the menu.
4799 VoteMenuToAllWithFlags(menu, stored_votetime, stored_adminflags); */
4800
4801 decl String:stored_start_sound[PLATFORM_MAX_PATH]; //, String:adminFlags[64];
4802 GetTrieString(vM, "stored_start_sound", stored_start_sound, sizeof(stored_start_sound));
4803 //GetTrieString(vM, "stored_adminflags", adminFlags, sizeof(adminFlags));
4804
4805 new users[MAXPLAYERS+1];
4806 new numClients;
4807 new clients[MAXPLAYERS+1];
4808 GetTrieArray2(vM, "stored_users", users, sizeof(users), numClients);
4809 ConvertUserIDsToClients(users, clients, numClients);
4810
4811 SetTrieValue(vM, "stored_type", VoteType_Map);
4812
4813 new stored_votetime;
4814 GetTrieValue(vM, "stored_votetime", stored_votetime);
4815
4816 //vote_active = true;
4817 new bool:vote_active = PerformVote(vM, VoteType_Map, options, stored_votetime, clients,
4818 numClients, stored_start_sound);
4819
4820 FreeOptions(options);
4821
4822 if (!vote_active)
4823 {
4824 DeleteVoteParams(vM);
4825 ClearVoteArrays(vM);
4826 VoteFailed(vM);
4827 }
4828 }
4829 else
4830 {
4831 LogError("MAPVOTE (Tiered): Unable to create second stage vote menu. Vote aborted.");
4832 VoteFailed(vM);
4833 DeleteVoteParams(vM);
4834 }
4835
4836 return Plugin_Stop;
4837}
4838
4839
4840//Extend the current map.
4841ExtendMap(Handle:vM)
4842{
4843 DisableVoteInProgress(vM);
4844 //vote_completed = false;
4845
4846 new Float:extend_timestep;
4847 new extend_roundstep;
4848 new extend_fragstep;
4849 GetTrieValue(vM, "extend_timestep", extend_timestep);
4850 GetTrieValue(vM, "extend_roundstep", extend_roundstep);
4851 GetTrieValue(vM, "extend_fragstep", extend_fragstep);
4852
4853 if (cvar_maxrounds != INVALID_HANDLE && GetConVarInt(cvar_maxrounds) > 0)
4854 SetConVarInt(cvar_maxrounds, GetConVarInt(cvar_maxrounds) + extend_roundstep);
4855 if (cvar_winlimit != INVALID_HANDLE && GetConVarInt(cvar_winlimit) > 0)
4856 SetConVarInt(cvar_winlimit, GetConVarInt(cvar_winlimit) + extend_roundstep);
4857 if (cvar_fraglimit != INVALID_HANDLE && GetConVarInt(cvar_fraglimit) > 0)
4858 SetConVarInt(cvar_fraglimit, GetConVarInt(cvar_fraglimit) + extend_fragstep);
4859
4860 //Extend the time limit.
4861 ExtendMapTimeLimit(RoundToNearest(extend_timestep * 60));
4862
4863 //Execute the extend command
4864 decl String:command[64];
4865 GetConVarString(cvar_extend_command, command, sizeof(command));
4866 if (strlen(command) > 0)
4867 ServerCommand(command);
4868
4869 //Call the extend forward.
4870 Call_StartForward(extend_forward);
4871 Call_Finish();
4872
4873 //Log some stuff.
4874 LogUMCMessage("MAPVOTE: Map extended.");
4875}
4876
4877
4878//Called when the vote has failed.
4879VoteFailed(Handle:vM)
4880{
4881 DisableVoteInProgress(vM);
4882
4883 Call_StartForward(failure_forward);
4884 Call_Finish();
4885}
4886
4887
4888//Sets the next map and when to change to it.
4889DoMapChange(UMC_ChangeMapTime:when, Handle:kv, const String:map[], const String:group[],
4890 const String:reason[], const String:display[]="")
4891{
4892 //Set the next map group
4893 strcopy(next_cat, sizeof(next_cat), group);
4894
4895 //Set the next map in SM
4896 LogUMCMessage("Setting nextmap to: %s", map);
4897 SetNextMap(map);
4898
4899 //Set the built in nextmap cvar
4900 //if (cvar_nextmap != INVALID_HANDLE)
4901 // SetConVarString(cvar_nextmap, map);
4902
4903 //GE:S Fix
4904 if (cvar_nextlevel != INVALID_HANDLE)
4905 SetConVarString(cvar_nextlevel, map);
4906
4907 //Call UMC forward for next map being set
4908 new Handle:new_kv = INVALID_HANDLE;
4909 if (kv != INVALID_HANDLE)
4910 {
4911 new_kv = CreateKeyValues("umc_rotation");
4912 KvCopySubkeys(kv, new_kv);
4913 }
4914 else
4915 LogUMCMessage("Mapcycle handle is invalid. Map change reason: %s", reason);
4916
4917 Call_StartForward(nextmap_forward);
4918 Call_PushCell(new_kv);
4919 Call_PushString(map);
4920 Call_PushString(group);
4921 Call_PushString(display);
4922 Call_Finish();
4923
4924 if (new_kv != INVALID_HANDLE)
4925 CloseHandle(new_kv);
4926
4927 //Perform the map change setup
4928 switch (when)
4929 {
4930 case ChangeMapTime_Now: //We change the map in 5 seconds.
4931 {
4932 decl String:game[20];
4933 GetGameFolderName(game, sizeof(game));
4934 if (!StrEqual(game, "gesource", false))
4935 {
4936 //Routine by Tsunami to end the map
4937 new iGameEnd = FindEntityByClassname(-1, "game_end");
4938 if (iGameEnd == -1 && (iGameEnd = CreateEntityByName("game_end")) == -1)
4939 {
4940 ForceChangeInFive(map, reason);
4941 }
4942 else
4943 {
4944 AcceptEntityInput(iGameEnd, "EndGame");
4945 }
4946 }
4947 else
4948 ForceChangeInFive(map, reason);
4949 }
4950 case ChangeMapTime_RoundEnd: //We change the map at the end of the round.
4951 {
4952 LogUMCMessage("%s: Map will change to '%s' at the end of the round.", reason, map);
4953
4954 change_map_round = true;
4955
4956 //Print a message.
4957 PrintToChatAll("\x03[UMC]\x01 %t", "Map Change at Round End");
4958 }
4959 }
4960}
4961
4962
4963//Deletes the stored parameters for the vote.
4964DeleteVoteParams(Handle:vM)
4965{
4966 DEBUG_MESSAGE("Deleting Vote Parameters")
4967
4968 new Handle:stored_kv, Handle:stored_mapcycle;
4969 GetTrieValue(vM, "stored_kv", stored_kv);
4970 GetTrieValue(vM, "stored_mapcycle", stored_mapcycle);
4971
4972 CloseHandle(stored_kv);
4973 CloseHandle(stored_mapcycle);
4974
4975 SetTrieValue(vM, "stored_kv", INVALID_HANDLE);
4976 SetTrieValue(vM, "stored_mapcycle", INVALID_HANDLE);
4977}
4978
4979
4980//************************************************************************************************//
4981// VALIDITY TESTING //
4982//************************************************************************************************//
4983
4984//Checks to see if the server has the required number of players for the given map, and is in the
4985//required time range.
4986// kv: a mapcycle whose traversal stack is currently at the level of the map's category.
4987// map: the map to check
4988bool:IsValidMapFromCat(Handle:kv, Handle:mapcycle, const String:map[], bool:isNom=false,
4989 bool:forMapChange=true)
4990{
4991 decl String:catName[MAP_LENGTH];
4992 KvGetSectionName(kv, catName, sizeof(catName));
4993
4994 //Return that the map is not valid if...
4995 // ...the map doesn't exist in the category.
4996 if (!KvJumpToKey(kv, map))
4997 {
4998 DEBUG_MESSAGE("Could not find map '%s' in group '%s'", map, catName)
4999 return false;
5000 }
5001
5002 //Determine if the map is valid, store the answer.
5003 new bool:result = IsValidMap(kv, mapcycle, catName, isNom, forMapChange);
5004
5005 //Rewind back to the category.
5006 KvGoBack(kv);
5007
5008 //Return the result.
5009 return result;
5010}
5011
5012
5013//Determines if the server has the required number of players for the given map.
5014// kv: a mapcycle whose traversal stack is currently at the level of the map.
5015bool:IsValidMap(Handle:kv, Handle:mapcycle, const String:groupName[], bool:isNom=false,
5016 bool:forMapChange=true)
5017{
5018 decl String:mapName[MAP_LENGTH];
5019 KvGetSectionName(kv, mapName, sizeof(mapName));
5020
5021 if (!IsMapValid(mapName))
5022 {
5023 LogUMCMessage("WARNING: Map \"%s\" does not exist on the server. (Group: \"%s\")",
5024 mapName, groupName);
5025 return false;
5026 }
5027
5028 new Action:result;
5029
5030 new Handle:new_kv = CreateKeyValues("umc_rotation");
5031 KvCopySubkeys(mapcycle, new_kv);
5032
5033 Call_StartForward(exclude_forward);
5034 Call_PushCell(new_kv);
5035 Call_PushString(mapName);
5036 Call_PushString(groupName);
5037 Call_PushCell(isNom);
5038 Call_PushCell(forMapChange);
5039 Call_Finish(result);
5040
5041 CloseHandle(new_kv);
5042
5043 new bool:re = result == Plugin_Continue;
5044
5045 return re;
5046}
5047
5048
5049//Determines if the server has the required number of players for the given category and the
5050//required time.
5051// kv: a mapcycle whose traversal stack is currently at the level of the category.
5052bool:IsValidCat(Handle:kv, Handle:mapcycle, bool:isNom=false, bool:forMapChange=true)
5053{
5054 //Get the name of the cat.
5055 decl String:catName[MAP_LENGTH];
5056 KvGetSectionName(kv, catName, sizeof(catName));
5057
5058 //Return that the map is invalid if...
5059 // ...there are no maps to check.
5060 if (!KvGotoFirstSubKey(kv))
5061 return false;
5062
5063 //Check to see if the server's player count satisfies the min/max conditions for a map in the
5064 //category.
5065 do
5066 {
5067 //Return to the category level of the mapcycle and return true if...
5068 // ...a map was found to be satisfied by the server's player count.
5069 if (IsValidMap(kv, mapcycle, catName, isNom, forMapChange))
5070 {
5071 KvGoBack(kv);
5072 return true;
5073 }
5074 }
5075 while (KvGotoNextKey(kv)); //Goto the next map in the category.
5076
5077 //Return to the category level.
5078 KvGoBack(kv);
5079
5080 //No maps in the category can be played with the current amount of players on the server.
5081 return false;
5082}
5083
5084
5085//Counts the number of maps in the given group.
5086CountMapsFromGroup(Handle:kv)
5087{
5088 new result = 0;
5089 if (!KvGotoFirstSubKey(kv))
5090 return result;
5091
5092 do
5093 {
5094 result++;
5095 }
5096 while (KvGotoNextKey(kv));
5097
5098 KvGoBack(kv);
5099
5100 return result;
5101}
5102
5103
5104//Calculates the weight of a map by running it through all of the weight modifiers.
5105Float:GetMapWeight(Handle:mapcycle, const String:map[], const String:group[])
5106{
5107 //Get the starting weight
5108 current_weight = 1.0;
5109
5110 new Handle:kv = CreateKeyValues("umc_rotation");
5111 KvCopySubkeys(mapcycle, kv);
5112
5113 reweight_active = true;
5114
5115 Call_StartForward(reweight_forward);
5116 Call_PushCell(kv);
5117 Call_PushString(map);
5118 Call_PushString(group);
5119 Call_Finish();
5120
5121 reweight_active = false;
5122
5123 CloseHandle(kv);
5124
5125 //And return our calculated weight.
5126 return (current_weight >= 0.0) ? current_weight : 0.0;
5127}
5128
5129
5130//Calculates the weight of a map group
5131Float:GetMapGroupWeight(Handle:originalMapcycle, const String:group[])
5132{
5133 current_weight = 1.0;
5134
5135 new Handle:kv = CreateKeyValues("umc_rotation");
5136 KvCopySubkeys(originalMapcycle, kv);
5137
5138 reweight_active = true;
5139
5140 Call_StartForward(reweight_group_forward);
5141 Call_PushCell(kv);
5142 Call_PushString(group);
5143 Call_Finish();
5144
5145 reweight_active = false;
5146
5147 CloseHandle(kv);
5148
5149 return (current_weight >= 0.0) ? current_weight : 0.0;
5150}
5151
5152
5153//Calculates weights for a mapcycle
5154WeightMapcycle(Handle:kv, Handle:originalMapcycle)
5155{
5156 if (!KvGotoFirstSubKey(kv))
5157 return;
5158
5159 decl String:group[MAP_LENGTH];
5160 do
5161 {
5162 KvGetSectionName(kv, group, sizeof(group));
5163
5164 KvSetFloat(kv, WEIGHT_KEY, GetMapGroupWeight(originalMapcycle, group));
5165
5166 WeightMapGroup(kv, originalMapcycle);
5167 }
5168 while (KvGotoNextKey(kv));
5169
5170 KvGoBack(kv);
5171}
5172
5173
5174//Calculates weights for a map group.
5175WeightMapGroup(Handle:kv, Handle:originalMapcycle)
5176{
5177 decl String:map[MAP_LENGTH], String:group[MAP_LENGTH];
5178 KvGetSectionName(kv, group, sizeof(group));
5179
5180 if (!KvGotoFirstSubKey(kv))
5181 return;
5182
5183 do
5184 {
5185 KvGetSectionName(kv, map, sizeof(map));
5186
5187 KvSetFloat(kv, WEIGHT_KEY, GetMapWeight(originalMapcycle, map, group));
5188 }
5189 while (KvGotoNextKey(kv));
5190
5191 KvGoBack(kv);
5192}
5193
5194
5195//Returns the weight of a given map or map group
5196Float:GetWeight(Handle:kv)
5197{
5198 return KvGetFloat(kv, WEIGHT_KEY, 1.0);
5199}
5200
5201
5202//Filters a mapcycle with all invalid entries filtered out.
5203FilterMapcycle(Handle:kv, Handle:originalMapcycle, bool:isNom=false, bool:forMapChange=true,
5204 bool:deleteEmpty=true)
5205{
5206 //Do nothing if there are no map groups.
5207 if (!KvGotoFirstSubKey(kv))
5208 return;
5209
5210 DEBUG_MESSAGE("Starting mapcycle filtering.")
5211 decl String:group[MAP_LENGTH];
5212 for ( ; ; )
5213 {
5214 //Filter all the maps.
5215 FilterMapGroup(kv, originalMapcycle, isNom, forMapChange);
5216
5217 //Delete the group if there are no valid maps in it.
5218 if (deleteEmpty)
5219 {
5220 if (!KvGotoFirstSubKey(kv))
5221 {
5222 KvGetSectionName(kv, group, sizeof(group));
5223
5224 DEBUG_MESSAGE("Removing empty group \"%s\".", group)
5225 if (KvDeleteThis(kv) == -1)
5226 {
5227 DEBUG_MESSAGE("Mapcycle filtering completed.")
5228 return;
5229 }
5230 else
5231 continue;
5232 }
5233
5234 KvGoBack(kv);
5235 }
5236
5237 if (!KvGotoNextKey(kv))
5238 break;
5239 }
5240
5241 //Return to the root.
5242 KvGoBack(kv);
5243
5244 DEBUG_MESSAGE("Mapcycle filtering completed.")
5245}
5246
5247
5248//Filters the kv at the level of the map group.
5249FilterMapGroup(Handle:kv, Handle:mapcycle, bool:isNom=false, bool:forMapChange=true)
5250{
5251 decl String:group[MAP_LENGTH];
5252 KvGetSectionName(kv, group, sizeof(group));
5253
5254 if (!KvGotoFirstSubKey(kv))
5255 return;
5256
5257 DEBUG_MESSAGE("Starting filtering of map group \"%s\".", group)
5258
5259 decl String:mapName[MAP_LENGTH];
5260 for ( ; ; )
5261 {
5262 if (!IsValidMap(kv, mapcycle, group, isNom, forMapChange))
5263 {
5264 KvGetSectionName(kv, mapName, sizeof(mapName));
5265 DEBUG_MESSAGE("Removing invalid map \"%s\" from group \"%s\".", mapName, group)
5266 if (KvDeleteThis(kv) == -1)
5267 {
5268 DEBUG_MESSAGE("Map Group filtering completed for group \"%s\".", group)
5269 return;
5270 }
5271 }
5272 else
5273 {
5274 if (!KvGotoNextKey(kv))
5275 break;
5276 }
5277 }
5278
5279 KvGoBack(kv);
5280
5281 DEBUG_MESSAGE("Map Group filtering completed for group \"%s\".", group)
5282}
5283
5284
5285//************************************************************************************************//
5286// NOMINATIONS //
5287//************************************************************************************************//
5288
5289//Filters an array of nominations so that only valid maps remain.
5290Handle:FilterNominationsArray(Handle:nominations, bool:forMapChange=true)
5291{
5292 new Handle:result = CreateArray();
5293
5294 new size = GetArraySize(nominations);
5295 new Handle:nom;
5296 decl String:gBuffer[MAP_LENGTH], String:mBuffer[MAP_LENGTH];
5297 new Handle:mapcycle;
5298 new Handle:kv;
5299 for (new i = 0; i < size; i++)
5300 {
5301 nom = GetArrayCell(nominations, i);
5302 GetTrieString(nom, MAP_TRIE_MAP_KEY, mBuffer, sizeof(mBuffer));
5303 GetTrieString(nom, MAP_TRIE_GROUP_KEY, gBuffer, sizeof(gBuffer));
5304 GetTrieValue(nom, "mapcycle", mapcycle);
5305
5306 kv = CreateKeyValues("umc_rotation");
5307 KvCopySubkeys(mapcycle, kv);
5308
5309 if (!KvJumpToKey(kv, gBuffer))
5310 {
5311 DEBUG_MESSAGE("Could not find group '%s' in nomination mapcycle.", gBuffer)
5312 continue;
5313 }
5314
5315 if (IsValidMapFromCat(kv, mapcycle, mBuffer, .isNom=true, .forMapChange=forMapChange))
5316 PushArrayCell(result, nom);
5317
5318 CloseHandle(kv);
5319 }
5320
5321 return result;
5322}
5323
5324
5325//Nominated a map and group
5326bool:InternalNominateMap(Handle:kv, const String:map[], const String:group[], client,
5327 const String:nomGroup[])
5328{
5329 DEBUG_MESSAGE("Adding map '%s' from group '%s' to nominations.", map, group)
5330 if (FindNominationIndex(map, group) != -1)
5331 {
5332 DEBUG_MESSAGE("Map/Group is already in nominations.")
5333 return false;
5334 }
5335
5336 DEBUG_MESSAGE("*MEMLEAKTEST* Creating nomination trie (InternalNominateMap) [1]")
5337 //Create the nomination trie.
5338 new Handle:nomination = CreateMapTrie(map, StrEqual(nomGroup, INVALID_GROUP) ? group : nomGroup);
5339 SetTrieValue(nomination, "client", client); //Add the client
5340 SetTrieValue(nomination, "mapcycle", kv); //Add the mapcycle
5341 SetTrieString(nomination, "nom_group", group);
5342
5343 //Get and add the nominated map's weight.
5344 //SetTrieValue(nomination, "weight", GetArrayCell(nomination_weights[client], param2));
5345
5346 DEBUG_MESSAGE("Detecting client's old nomination")
5347 //Remove the client's old nomination, if it exists.
5348 new index = FindClientNomination(client);
5349 if (index != -1)
5350 {
5351 DEBUG_MESSAGE("Nomination found for client")
5352 new Handle:oldNom = GetArrayCell(nominations_arr, index);
5353
5354 decl String:oldName[MAP_LENGTH];
5355 GetTrieString(oldNom, MAP_TRIE_MAP_KEY, oldName, sizeof(oldName));
5356
5357 Call_StartForward(nomination_reset_forward);
5358 Call_PushString(oldName);
5359 Call_PushCell(client);
5360 Call_Finish();
5361
5362 DEBUG_MESSAGE("Removing old nomination")
5363 new Handle:nomKV;
5364 GetTrieValue(oldNom, "mapcycle", nomKV);
5365 CloseHandle(nomKV);
5366 CloseHandle(oldNom);
5367 RemoveFromArray(nominations_arr, index);
5368 }
5369
5370 DEBUG_MESSAGE("*MEMLEAKTEST* Adding new nomination made at [1] to nomination array")
5371 //Add the nomination to the nomination array.
5372 PushArrayCell(nominations_arr, nomination);
5373
5374 return true;
5375}
5376
5377
5378//Returns the index of the given client in the nomination pool. -1 is returned if the client isn't
5379//in the pool.
5380FindClientNomination(client)
5381{
5382 new buffer;
5383 new arraySize = GetArraySize(nominations_arr);
5384 for (new i = 0; i < arraySize; i++)
5385 {
5386 GetTrieValue(GetArrayCell(nominations_arr, i), "client", buffer);
5387 if (buffer == client)
5388 return i;
5389 }
5390 return -1;
5391}
5392
5393
5394//Utility function to find the index of a map in the nomination pool.
5395FindNominationIndex(const String:map[], const String:group[])
5396{
5397 decl String:mName[MAP_LENGTH];
5398 decl String:gName[MAP_LENGTH];
5399 new Handle:nom = INVALID_HANDLE;
5400 new arraySize = GetArraySize(nominations_arr);
5401 for (new i = 0; i < arraySize; i++)
5402 {
5403 nom = GetArrayCell(nominations_arr, i);
5404 GetTrieString(nom, MAP_TRIE_MAP_KEY, mName, sizeof(mName));
5405 GetTrieString(nom, MAP_TRIE_GROUP_KEY, gName, sizeof(gName));
5406 if (StrEqual(mName, map, false) && StrEqual(gName, group, false))
5407 return i;
5408 }
5409 return -1;
5410}
5411
5412
5413//Utility function to get all nominations from a group.
5414Handle:GetCatNominations(const String:cat[])
5415{
5416 new Handle:arr1 = FilterNominations(MAP_TRIE_GROUP_KEY, cat);
5417 new Handle:arr2 = FilterNominations(MAP_TRIE_GROUP_KEY, INVALID_GROUP);
5418 ArrayAppend(arr1, arr2);
5419 CloseHandle(arr2);
5420 return arr1;
5421}
5422
5423
5424//Utility function to filter out nominations whose value for the given key matches the given value.
5425Handle:FilterNominations(const String:key[], const String:value[])
5426{
5427 new Handle:result = CreateArray();
5428 new Handle:buffer;
5429 decl String:temp[255];
5430 new arraySize = GetArraySize(nominations_arr);
5431 for (new i = 0; i < arraySize; i++)
5432 {
5433 buffer = GetArrayCell(nominations_arr, i);
5434 GetTrieString(GetArrayCell(nominations_arr, i), key, temp, sizeof(temp));
5435 if (StrEqual(temp, value, false))
5436 PushArrayCell(result, buffer);
5437 }
5438 return result;
5439}
5440
5441
5442//Clears all stored nominations.
5443ClearNominations()
5444{
5445 new size = GetArraySize(nominations_arr);
5446 new Handle:nomination = INVALID_HANDLE;
5447 new owner;
5448 decl String:map[MAP_LENGTH];
5449 for (new i = 0; i < size; i++)
5450 {
5451 nomination = GetArrayCell(nominations_arr, i);
5452 GetTrieString(nomination, MAP_TRIE_MAP_KEY, map, sizeof(map));
5453 GetTrieValue(nomination, "client", owner);
5454
5455 Call_StartForward(nomination_reset_forward);
5456 Call_PushString(map);
5457 Call_PushCell(owner);
5458 Call_Finish();
5459
5460 new Handle:nomKV;
5461 GetTrieValue(nomination, "mapcycle", nomKV);
5462 CloseHandle(nomKV);
5463 CloseHandle(nomination);
5464 }
5465 ClearArray(nominations_arr);
5466}
5467
5468
5469//************************************************************************************************//
5470// RANDOM NEXTMAP //
5471//************************************************************************************************//
5472
5473//bool:GetRandomMapFromCycle(Handle:kv, const String:group[], String:buffer[], size, String:gBuffer[],
5474// gSize, Handle:exMaps, Handle:exGroups, numEGroups, bool:isNom=false,
5475// bool:forMapChange=true)
5476bool:GetRandomMapFromCycle(Handle:kv, const String:group[], String:buffer[], size, String:gBuffer[],
5477 gSize)
5478{
5479 //Buffer to store the name of the category we will be looking for a map in.
5480 decl String:gName[MAP_LENGTH];
5481
5482#if UMC_DEBUG
5483 if (!StrEqual(group, INVALID_GROUP, false))
5484 {
5485 DEBUG_MESSAGE("Searching for random map in group %s.", group)
5486 }
5487#endif
5488
5489 strcopy(gName, sizeof(gName), group);
5490
5491#if UMC_DEBUG
5492 DEBUG_MESSAGE("group: %s, gName: %s", group, gName)
5493 new bool:p1 = StrEqual(gName, INVALID_GROUP, false);
5494 new bool:p2 = p1 || !KvJumpToKey(kv, gName);
5495 if (p1 || p2)
5496 {
5497 DEBUG_MESSAGE("Picking random group. P1: %i, P2: %i", p1, p2)
5498 LogKv(kv);
5499#else
5500 if (StrEqual(gName, INVALID_GROUP, false) || !KvJumpToKey(kv, gName))
5501 {
5502#endif
5503 if (!GetRandomCat(kv, gName, sizeof(gName)))
5504 {
5505 LogError(
5506 "RANDOM MAP: Cannot pick a random map, no available map groups found in rotation."
5507 );
5508 return false;
5509 }
5510 KvJumpToKey(kv, gName);
5511 }
5512
5513 //Buffer to store the name of the new map.
5514 decl String:mapName[MAP_LENGTH];
5515
5516 //Log an error and fail if...
5517 // ...there were no maps found in the category.
5518 if (!GetRandomMap(kv, mapName, sizeof(mapName)))//, exMaps, exGroups, isNom, forMapChange))
5519 {
5520 LogError(
5521 "RANDOM MAP: Cannot pick a random map, no available maps found. Parent Group: %s",
5522 gName
5523 );
5524 return false;
5525 }
5526
5527 KvGoBack(kv);
5528
5529 //Copy results into the buffers.
5530 strcopy(buffer, size, mapName);
5531 strcopy(gBuffer, gSize, gName);
5532
5533 //Return success!
5534 return true;
5535}
5536
5537
5538//Selects a random category based off of the supplied weights for the categories.
5539// kv: a mapcycle whose traversal stack is currently at the root level.
5540// buffer: a string to store the selected category in.
5541// key: the key containing the weight information (most likely 'group_weight')
5542// excluded: adt_array of excluded maps
5543//bool:GetRandomCat(Handle:kv, String:buffer[], size, Handle:excludedCats, numExcludedCats,
5544// Handle:excluded, bool:isNom=false, bool:forMapChange=true)
5545bool:GetRandomCat(Handle:kv, String:buffer[], size)
5546{
5547 DEBUG_MESSAGE("Getting a random group")
5548
5549 //Fail if...
5550 // ...there are no categories in the mapcycle.
5551 if (!KvGotoFirstSubKey(kv))
5552 return false;
5553
5554 new index = 0; //counter of categories in the random pool
5555 new Handle:nameArr = CreateArray(ByteCountToCells(MAP_LENGTH)); //Array to store possible category names.
5556 new Handle:weightArr = CreateArray(); //Array to store possible category weights.
5557
5558 DEBUG_MESSAGE("Starting traversal")
5559 //Add a category to the random pool.
5560 do
5561 {
5562 decl String:temp[MAP_LENGTH]; //Buffer to store the name of the category.
5563
5564 //Get the name of the category.
5565 KvGetSectionName(kv, temp, sizeof(temp));
5566
5567 DEBUG_MESSAGE("Group %s added to random pool.", temp)
5568
5569 //Add the category to the random pool.
5570 PushArrayCell(weightArr, GetWeight(kv));
5571 PushArrayString(nameArr, temp);
5572
5573 //One more category in the pool.
5574 index++;
5575 }
5576 while (KvGotoNextKey(kv)); //Do this for each category.
5577
5578 //Return to the root level.
5579 KvGoBack(kv);
5580
5581 DEBUG_MESSAGE("Finished traversal.")
5582
5583 //Fail if...
5584 // ...no categories are selectable.
5585 if (index == 0)
5586 {
5587 CloseHandle(nameArr);
5588 CloseHandle(weightArr);
5589 return false;
5590 }
5591
5592 DEBUG_MESSAGE("Selecting a random group.")
5593
5594 //Use weights to randomly select a category from the pool.
5595 new bool:result = GetWeightedRandomSubKey(buffer, size, weightArr, nameArr);
5596
5597 //Close the pool.
5598 CloseHandle(nameArr);
5599 CloseHandle(weightArr);
5600
5601#if UMC_DEBUG
5602 if (result)
5603 DEBUG_MESSAGE("Selected group %s", buffer)
5604#endif
5605
5606 //Booyah!
5607 return result;
5608}