├── .travis.yml ├── LICENSE ├── README.md └── addons └── sourcemod ├── scripting ├── csgo_votestart_test.sp ├── include │ ├── implodeexplode.inc │ └── nativevotes.inc ├── nativevotes-basecommands.sp ├── nativevotes.sp ├── nativevotes │ ├── data-keyvalues.sp │ └── game.sp ├── nativevotes_kickvote_immunity.sp ├── nativevotes_mapchooser.sp ├── nativevotes_nominations.sp ├── nativevotes_rockthevote.sp ├── nativevotes_votemanager_test.sp ├── nativevotes_votetest.sp ├── votedelay_changelevel.sp ├── votediagnostics.sp └── votefailed.sp └── translations ├── es └── nativevotes.phrases.txt ├── hu └── nativevotes.phrases.txt ├── nativevotes.phrases.txt └── pl └── nativevotes.phrases.txt /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: clang 3 | sudo: false 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - g++-multilib 9 | 10 | install: 11 | - wget http://www.sourcemod.net/smdrop/1.8/sourcemod-1.8.0-git5919-linux.tar.gz -O /tmp/sourcemod.tar.gz 12 | - tar -xzvf /tmp/sourcemod.tar.gz 13 | 14 | before_script: 15 | - chmod +rx addons/sourcemod/scripting/spcomp 16 | - chmod +rx addons/sourcemod/scripting/compile.sh 17 | - cd addons/sourcemod/scripting/ 18 | 19 | script: 20 | - ./spcomp nativevotes.sp 21 | - ./spcomp nativevotes-basecommands.sp 22 | - ./spcomp nativevotes_votetest.sp 23 | - ./spcomp nativevotes_votemanager_test.sp 24 | - ./spcomp nativevotes_mapchooser.sp 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 |
GNU GENERAL PUBLIC LICENSE
  4 |                        Version 3, 29 June 2007
  5 | 
  6 |  Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/}
  7 |  Everyone is permitted to copy and distribute verbatim copies
  8 |  of this license document, but changing it is not allowed.
  9 | 
 10 |                             Preamble
 11 | 
 12 |   The GNU General Public License is a free, copyleft license for
 13 | software and other kinds of works.
 14 | 
 15 |   The licenses for most software and other practical works are designed
 16 | to take away your freedom to share and change the works.  By contrast,
 17 | the GNU General Public License is intended to guarantee your freedom to
 18 | share and change all versions of a program--to make sure it remains free
 19 | software for all its users.  We, the Free Software Foundation, use the
 20 | GNU General Public License for most of our software; it applies also to
 21 | any other work released this way by its authors.  You can apply it to
 22 | your programs, too.
 23 | 
 24 |   When we speak of free software, we are referring to freedom, not
 25 | price.  Our General Public Licenses are designed to make sure that you
 26 | have the freedom to distribute copies of free software (and charge for
 27 | them if you wish), that you receive source code or can get it if you
 28 | want it, that you can change the software or use pieces of it in new
 29 | free programs, and that you know you can do these things.
 30 | 
 31 |   To protect your rights, we need to prevent others from denying you
 32 | these rights or asking you to surrender the rights.  Therefore, you have
 33 | certain responsibilities if you distribute copies of the software, or if
 34 | you modify it: responsibilities to respect the freedom of others.
 35 | 
 36 |   For example, if you distribute copies of such a program, whether
 37 | gratis or for a fee, you must pass on to the recipients the same
 38 | freedoms that you received.  You must make sure that they, too, receive
 39 | or can get the source code.  And you must show them these terms so they
 40 | know their rights.
 41 | 
 42 |   Developers that use the GNU GPL protect your rights with two steps:
 43 | (1) assert copyright on the software, and (2) offer you this License
 44 | giving you legal permission to copy, distribute and/or modify it.
 45 | 
 46 |   For the developers' and authors' protection, the GPL clearly explains
 47 | that there is no warranty for this free software.  For both users' and
 48 | authors' sake, the GPL requires that modified versions be marked as
 49 | changed, so that their problems will not be attributed erroneously to
 50 | authors of previous versions.
 51 | 
 52 |   Some devices are designed to deny users access to install or run
 53 | modified versions of the software inside them, although the manufacturer
 54 | can do so.  This is fundamentally incompatible with the aim of
 55 | protecting users' freedom to change the software.  The systematic
 56 | pattern of such abuse occurs in the area of products for individuals to
 57 | use, which is precisely where it is most unacceptable.  Therefore, we
 58 | have designed this version of the GPL to prohibit the practice for those
 59 | products.  If such problems arise substantially in other domains, we
 60 | stand ready to extend this provision to those domains in future versions
 61 | of the GPL, as needed to protect the freedom of users.
 62 | 
 63 |   Finally, every program is threatened constantly by software patents.
 64 | States should not allow patents to restrict development and use of
 65 | software on general-purpose computers, but in those that do, we wish to
 66 | avoid the special danger that patents applied to a free program could
 67 | make it effectively proprietary.  To prevent this, the GPL assures that
 68 | patents cannot be used to render the program non-free.
 69 | 
 70 |   The precise terms and conditions for copying, distribution and
 71 | modification follow.
 72 | 
 73 |                        TERMS AND CONDITIONS
 74 | 
 75 |   0. Definitions.
 76 | 
 77 |   "This License" refers to version 3 of the GNU General Public License.
 78 | 
 79 |   "Copyright" also means copyright-like laws that apply to other kinds of
 80 | works, such as semiconductor masks.
 81 | 
 82 |   "The Program" refers to any copyrightable work licensed under this
 83 | License.  Each licensee is addressed as "you".  "Licensees" and
 84 | "recipients" may be individuals or organizations.
 85 | 
 86 |   To "modify" a work means to copy from or adapt all or part of the work
 87 | in a fashion requiring copyright permission, other than the making of an
 88 | exact copy.  The resulting work is called a "modified version" of the
 89 | earlier work or a work "based on" the earlier work.
 90 | 
 91 |   A "covered work" means either the unmodified Program or a work based
 92 | on the Program.
 93 | 
 94 |   To "propagate" a work means to do anything with it that, without
 95 | permission, would make you directly or secondarily liable for
 96 | infringement under applicable copyright law, except executing it on a
 97 | computer or modifying a private copy.  Propagation includes copying,
 98 | distribution (with or without modification), making available to the
 99 | public, and in some countries other activities as well.
100 | 
101 |   To "convey" a work means any kind of propagation that enables other
102 | parties to make or receive copies.  Mere interaction with a user through
103 | a computer network, with no transfer of a copy, is not conveying.
104 | 
105 |   An interactive user interface displays "Appropriate Legal Notices"
106 | to the extent that it includes a convenient and prominently visible
107 | feature that (1) displays an appropriate copyright notice, and (2)
108 | tells the user that there is no warranty for the work (except to the
109 | extent that warranties are provided), that licensees may convey the
110 | work under this License, and how to view a copy of this License.  If
111 | the interface presents a list of user commands or options, such as a
112 | menu, a prominent item in the list meets this criterion.
113 | 
114 |   1. Source Code.
115 | 
116 |   The "source code" for a work means the preferred form of the work
117 | for making modifications to it.  "Object code" means any non-source
118 | form of a work.
119 | 
120 |   A "Standard Interface" means an interface that either is an official
121 | standard defined by a recognized standards body, or, in the case of
122 | interfaces specified for a particular programming language, one that
123 | is widely used among developers working in that language.
124 | 
125 |   The "System Libraries" of an executable work include anything, other
126 | than the work as a whole, that (a) is included in the normal form of
127 | packaging a Major Component, but which is not part of that Major
128 | Component, and (b) serves only to enable use of the work with that
129 | Major Component, or to implement a Standard Interface for which an
130 | implementation is available to the public in source code form.  A
131 | "Major Component", in this context, means a major essential component
132 | (kernel, window system, and so on) of the specific operating system
133 | (if any) on which the executable work runs, or a compiler used to
134 | produce the work, or an object code interpreter used to run it.
135 | 
136 |   The "Corresponding Source" for a work in object code form means all
137 | the source code needed to generate, install, and (for an executable
138 | work) run the object code and to modify the work, including scripts to
139 | control those activities.  However, it does not include the work's
140 | System Libraries, or general-purpose tools or generally available free
141 | programs which are used unmodified in performing those activities but
142 | which are not part of the work.  For example, Corresponding Source
143 | includes interface definition files associated with source files for
144 | the work, and the source code for shared libraries and dynamically
145 | linked subprograms that the work is specifically designed to require,
146 | such as by intimate data communication or control flow between those
147 | subprograms and other parts of the work.
148 | 
149 |   The Corresponding Source need not include anything that users
150 | can regenerate automatically from other parts of the Corresponding
151 | Source.
152 | 
153 |   The Corresponding Source for a work in source code form is that
154 | same work.
155 | 
156 |   2. Basic Permissions.
157 | 
158 |   All rights granted under this License are granted for the term of
159 | copyright on the Program, and are irrevocable provided the stated
160 | conditions are met.  This License explicitly affirms your unlimited
161 | permission to run the unmodified Program.  The output from running a
162 | covered work is covered by this License only if the output, given its
163 | content, constitutes a covered work.  This License acknowledges your
164 | rights of fair use or other equivalent, as provided by copyright law.
165 | 
166 |   You may make, run and propagate covered works that you do not
167 | convey, without conditions so long as your license otherwise remains
168 | in force.  You may convey covered works to others for the sole purpose
169 | of having them make modifications exclusively for you, or provide you
170 | with facilities for running those works, provided that you comply with
171 | the terms of this License in conveying all material for which you do
172 | not control copyright.  Those thus making or running the covered works
173 | for you must do so exclusively on your behalf, under your direction
174 | and control, on terms that prohibit them from making any copies of
175 | your copyrighted material outside their relationship with you.
176 | 
177 |   Conveying under any other circumstances is permitted solely under
178 | the conditions stated below.  Sublicensing is not allowed; section 10
179 | makes it unnecessary.
180 | 
181 |   3. Protecting Users' Legal Rights From Anti-Circumvention Law.
182 | 
183 |   No covered work shall be deemed part of an effective technological
184 | measure under any applicable law fulfilling obligations under article
185 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
186 | similar laws prohibiting or restricting circumvention of such
187 | measures.
188 | 
189 |   When you convey a covered work, you waive any legal power to forbid
190 | circumvention of technological measures to the extent such circumvention
191 | is effected by exercising rights under this License with respect to
192 | the covered work, and you disclaim any intention to limit operation or
193 | modification of the work as a means of enforcing, against the work's
194 | users, your or third parties' legal rights to forbid circumvention of
195 | technological measures.
196 | 
197 |   4. Conveying Verbatim Copies.
198 | 
199 |   You may convey verbatim copies of the Program's source code as you
200 | receive it, in any medium, provided that you conspicuously and
201 | appropriately publish on each copy an appropriate copyright notice;
202 | keep intact all notices stating that this License and any
203 | non-permissive terms added in accord with section 7 apply to the code;
204 | keep intact all notices of the absence of any warranty; and give all
205 | recipients a copy of this License along with the Program.
206 | 
207 |   You may charge any price or no price for each copy that you convey,
208 | and you may offer support or warranty protection for a fee.
209 | 
210 |   5. Conveying Modified Source Versions.
211 | 
212 |   You may convey a work based on the Program, or the modifications to
213 | produce it from the Program, in the form of source code under the
214 | terms of section 4, provided that you also meet all of these conditions:
215 | 
216 |     a) The work must carry prominent notices stating that you modified
217 |     it, and giving a relevant date.
218 | 
219 |     b) The work must carry prominent notices stating that it is
220 |     released under this License and any conditions added under section
221 |     7.  This requirement modifies the requirement in section 4 to
222 |     "keep intact all notices".
223 | 
224 |     c) You must license the entire work, as a whole, under this
225 |     License to anyone who comes into possession of a copy.  This
226 |     License will therefore apply, along with any applicable section 7
227 |     additional terms, to the whole of the work, and all its parts,
228 |     regardless of how they are packaged.  This License gives no
229 |     permission to license the work in any other way, but it does not
230 |     invalidate such permission if you have separately received it.
231 | 
232 |     d) If the work has interactive user interfaces, each must display
233 |     Appropriate Legal Notices; however, if the Program has interactive
234 |     interfaces that do not display Appropriate Legal Notices, your
235 |     work need not make them do so.
236 | 
237 |   A compilation of a covered work with other separate and independent
238 | works, which are not by their nature extensions of the covered work,
239 | and which are not combined with it such as to form a larger program,
240 | in or on a volume of a storage or distribution medium, is called an
241 | "aggregate" if the compilation and its resulting copyright are not
242 | used to limit the access or legal rights of the compilation's users
243 | beyond what the individual works permit.  Inclusion of a covered work
244 | in an aggregate does not cause this License to apply to the other
245 | parts of the aggregate.
246 | 
247 |   6. Conveying Non-Source Forms.
248 | 
249 |   You may convey a covered work in object code form under the terms
250 | of sections 4 and 5, provided that you also convey the
251 | machine-readable Corresponding Source under the terms of this License,
252 | in one of these ways:
253 | 
254 |     a) Convey the object code in, or embodied in, a physical product
255 |     (including a physical distribution medium), accompanied by the
256 |     Corresponding Source fixed on a durable physical medium
257 |     customarily used for software interchange.
258 | 
259 |     b) Convey the object code in, or embodied in, a physical product
260 |     (including a physical distribution medium), accompanied by a
261 |     written offer, valid for at least three years and valid for as
262 |     long as you offer spare parts or customer support for that product
263 |     model, to give anyone who possesses the object code either (1) a
264 |     copy of the Corresponding Source for all the software in the
265 |     product that is covered by this License, on a durable physical
266 |     medium customarily used for software interchange, for a price no
267 |     more than your reasonable cost of physically performing this
268 |     conveying of source, or (2) access to copy the
269 |     Corresponding Source from a network server at no charge.
270 | 
271 |     c) Convey individual copies of the object code with a copy of the
272 |     written offer to provide the Corresponding Source.  This
273 |     alternative is allowed only occasionally and noncommercially, and
274 |     only if you received the object code with such an offer, in accord
275 |     with subsection 6b.
276 | 
277 |     d) Convey the object code by offering access from a designated
278 |     place (gratis or for a charge), and offer equivalent access to the
279 |     Corresponding Source in the same way through the same place at no
280 |     further charge.  You need not require recipients to copy the
281 |     Corresponding Source along with the object code.  If the place to
282 |     copy the object code is a network server, the Corresponding Source
283 |     may be on a different server (operated by you or a third party)
284 |     that supports equivalent copying facilities, provided you maintain
285 |     clear directions next to the object code saying where to find the
286 |     Corresponding Source.  Regardless of what server hosts the
287 |     Corresponding Source, you remain obligated to ensure that it is
288 |     available for as long as needed to satisfy these requirements.
289 | 
290 |     e) Convey the object code using peer-to-peer transmission, provided
291 |     you inform other peers where the object code and Corresponding
292 |     Source of the work are being offered to the general public at no
293 |     charge under subsection 6d.
294 | 
295 |   A separable portion of the object code, whose source code is excluded
296 | from the Corresponding Source as a System Library, need not be
297 | included in conveying the object code work.
298 | 
299 |   A "User Product" is either (1) a "consumer product", which means any
300 | tangible personal property which is normally used for personal, family,
301 | or household purposes, or (2) anything designed or sold for incorporation
302 | into a dwelling.  In determining whether a product is a consumer product,
303 | doubtful cases shall be resolved in favor of coverage.  For a particular
304 | product received by a particular user, "normally used" refers to a
305 | typical or common use of that class of product, regardless of the status
306 | of the particular user or of the way in which the particular user
307 | actually uses, or expects or is expected to use, the product.  A product
308 | is a consumer product regardless of whether the product has substantial
309 | commercial, industrial or non-consumer uses, unless such uses represent
310 | the only significant mode of use of the product.
311 | 
312 |   "Installation Information" for a User Product means any methods,
313 | procedures, authorization keys, or other information required to install
314 | and execute modified versions of a covered work in that User Product from
315 | a modified version of its Corresponding Source.  The information must
316 | suffice to ensure that the continued functioning of the modified object
317 | code is in no case prevented or interfered with solely because
318 | modification has been made.
319 | 
320 |   If you convey an object code work under this section in, or with, or
321 | specifically for use in, a User Product, and the conveying occurs as
322 | part of a transaction in which the right of possession and use of the
323 | User Product is transferred to the recipient in perpetuity or for a
324 | fixed term (regardless of how the transaction is characterized), the
325 | Corresponding Source conveyed under this section must be accompanied
326 | by the Installation Information.  But this requirement does not apply
327 | if neither you nor any third party retains the ability to install
328 | modified object code on the User Product (for example, the work has
329 | been installed in ROM).
330 | 
331 |   The requirement to provide Installation Information does not include a
332 | requirement to continue to provide support service, warranty, or updates
333 | for a work that has been modified or installed by the recipient, or for
334 | the User Product in which it has been modified or installed.  Access to a
335 | network may be denied when the modification itself materially and
336 | adversely affects the operation of the network or violates the rules and
337 | protocols for communication across the network.
338 | 
339 |   Corresponding Source conveyed, and Installation Information provided,
340 | in accord with this section must be in a format that is publicly
341 | documented (and with an implementation available to the public in
342 | source code form), and must require no special password or key for
343 | unpacking, reading or copying.
344 | 
345 |   7. Additional Terms.
346 | 
347 |   "Additional permissions" are terms that supplement the terms of this
348 | License by making exceptions from one or more of its conditions.
349 | Additional permissions that are applicable to the entire Program shall
350 | be treated as though they were included in this License, to the extent
351 | that they are valid under applicable law.  If additional permissions
352 | apply only to part of the Program, that part may be used separately
353 | under those permissions, but the entire Program remains governed by
354 | this License without regard to the additional permissions.
355 | 
356 |   When you convey a copy of a covered work, you may at your option
357 | remove any additional permissions from that copy, or from any part of
358 | it.  (Additional permissions may be written to require their own
359 | removal in certain cases when you modify the work.)  You may place
360 | additional permissions on material, added by you to a covered work,
361 | for which you have or can give appropriate copyright permission.
362 | 
363 |   Notwithstanding any other provision of this License, for material you
364 | add to a covered work, you may (if authorized by the copyright holders of
365 | that material) supplement the terms of this License with terms:
366 | 
367 |     a) Disclaiming warranty or limiting liability differently from the
368 |     terms of sections 15 and 16 of this License; or
369 | 
370 |     b) Requiring preservation of specified reasonable legal notices or
371 |     author attributions in that material or in the Appropriate Legal
372 |     Notices displayed by works containing it; or
373 | 
374 |     c) Prohibiting misrepresentation of the origin of that material, or
375 |     requiring that modified versions of such material be marked in
376 |     reasonable ways as different from the original version; or
377 | 
378 |     d) Limiting the use for publicity purposes of names of licensors or
379 |     authors of the material; or
380 | 
381 |     e) Declining to grant rights under trademark law for use of some
382 |     trade names, trademarks, or service marks; or
383 | 
384 |     f) Requiring indemnification of licensors and authors of that
385 |     material by anyone who conveys the material (or modified versions of
386 |     it) with contractual assumptions of liability to the recipient, for
387 |     any liability that these contractual assumptions directly impose on
388 |     those licensors and authors.
389 | 
390 |   All other non-permissive additional terms are considered "further
391 | restrictions" within the meaning of section 10.  If the Program as you
392 | received it, or any part of it, contains a notice stating that it is
393 | governed by this License along with a term that is a further
394 | restriction, you may remove that term.  If a license document contains
395 | a further restriction but permits relicensing or conveying under this
396 | License, you may add to a covered work material governed by the terms
397 | of that license document, provided that the further restriction does
398 | not survive such relicensing or conveying.
399 | 
400 |   If you add terms to a covered work in accord with this section, you
401 | must place, in the relevant source files, a statement of the
402 | additional terms that apply to those files, or a notice indicating
403 | where to find the applicable terms.
404 | 
405 |   Additional terms, permissive or non-permissive, may be stated in the
406 | form of a separately written license, or stated as exceptions;
407 | the above requirements apply either way.
408 | 
409 |   8. Termination.
410 | 
411 |   You may not propagate or modify a covered work except as expressly
412 | provided under this License.  Any attempt otherwise to propagate or
413 | modify it is void, and will automatically terminate your rights under
414 | this License (including any patent licenses granted under the third
415 | paragraph of section 11).
416 | 
417 |   However, if you cease all violation of this License, then your
418 | license from a particular copyright holder is reinstated (a)
419 | provisionally, unless and until the copyright holder explicitly and
420 | finally terminates your license, and (b) permanently, if the copyright
421 | holder fails to notify you of the violation by some reasonable means
422 | prior to 60 days after the cessation.
423 | 
424 |   Moreover, your license from a particular copyright holder is
425 | reinstated permanently if the copyright holder notifies you of the
426 | violation by some reasonable means, this is the first time you have
427 | received notice of violation of this License (for any work) from that
428 | copyright holder, and you cure the violation prior to 30 days after
429 | your receipt of the notice.
430 | 
431 |   Termination of your rights under this section does not terminate the
432 | licenses of parties who have received copies or rights from you under
433 | this License.  If your rights have been terminated and not permanently
434 | reinstated, you do not qualify to receive new licenses for the same
435 | material under section 10.
436 | 
437 |   9. Acceptance Not Required for Having Copies.
438 | 
439 |   You are not required to accept this License in order to receive or
440 | run a copy of the Program.  Ancillary propagation of a covered work
441 | occurring solely as a consequence of using peer-to-peer transmission
442 | to receive a copy likewise does not require acceptance.  However,
443 | nothing other than this License grants you permission to propagate or
444 | modify any covered work.  These actions infringe copyright if you do
445 | not accept this License.  Therefore, by modifying or propagating a
446 | covered work, you indicate your acceptance of this License to do so.
447 | 
448 |   10. Automatic Licensing of Downstream Recipients.
449 | 
450 |   Each time you convey a covered work, the recipient automatically
451 | receives a license from the original licensors, to run, modify and
452 | propagate that work, subject to this License.  You are not responsible
453 | for enforcing compliance by third parties with this License.
454 | 
455 |   An "entity transaction" is a transaction transferring control of an
456 | organization, or substantially all assets of one, or subdividing an
457 | organization, or merging organizations.  If propagation of a covered
458 | work results from an entity transaction, each party to that
459 | transaction who receives a copy of the work also receives whatever
460 | licenses to the work the party's predecessor in interest had or could
461 | give under the previous paragraph, plus a right to possession of the
462 | Corresponding Source of the work from the predecessor in interest, if
463 | the predecessor has it or can get it with reasonable efforts.
464 | 
465 |   You may not impose any further restrictions on the exercise of the
466 | rights granted or affirmed under this License.  For example, you may
467 | not impose a license fee, royalty, or other charge for exercise of
468 | rights granted under this License, and you may not initiate litigation
469 | (including a cross-claim or counterclaim in a lawsuit) alleging that
470 | any patent claim is infringed by making, using, selling, offering for
471 | sale, or importing the Program or any portion of it.
472 | 
473 |   11. Patents.
474 | 
475 |   A "contributor" is a copyright holder who authorizes use under this
476 | License of the Program or a work on which the Program is based.  The
477 | work thus licensed is called the contributor's "contributor version".
478 | 
479 |   A contributor's "essential patent claims" are all patent claims
480 | owned or controlled by the contributor, whether already acquired or
481 | hereafter acquired, that would be infringed by some manner, permitted
482 | by this License, of making, using, or selling its contributor version,
483 | but do not include claims that would be infringed only as a
484 | consequence of further modification of the contributor version.  For
485 | purposes of this definition, "control" includes the right to grant
486 | patent sublicenses in a manner consistent with the requirements of
487 | this License.
488 | 
489 |   Each contributor grants you a non-exclusive, worldwide, royalty-free
490 | patent license under the contributor's essential patent claims, to
491 | make, use, sell, offer for sale, import and otherwise run, modify and
492 | propagate the contents of its contributor version.
493 | 
494 |   In the following three paragraphs, a "patent license" is any express
495 | agreement or commitment, however denominated, not to enforce a patent
496 | (such as an express permission to practice a patent or covenant not to
497 | sue for patent infringement).  To "grant" such a patent license to a
498 | party means to make such an agreement or commitment not to enforce a
499 | patent against the party.
500 | 
501 |   If you convey a covered work, knowingly relying on a patent license,
502 | and the Corresponding Source of the work is not available for anyone
503 | to copy, free of charge and under the terms of this License, through a
504 | publicly available network server or other readily accessible means,
505 | then you must either (1) cause the Corresponding Source to be so
506 | available, or (2) arrange to deprive yourself of the benefit of the
507 | patent license for this particular work, or (3) arrange, in a manner
508 | consistent with the requirements of this License, to extend the patent
509 | license to downstream recipients.  "Knowingly relying" means you have
510 | actual knowledge that, but for the patent license, your conveying the
511 | covered work in a country, or your recipient's use of the covered work
512 | in a country, would infringe one or more identifiable patents in that
513 | country that you have reason to believe are valid.
514 | 
515 |   If, pursuant to or in connection with a single transaction or
516 | arrangement, you convey, or propagate by procuring conveyance of, a
517 | covered work, and grant a patent license to some of the parties
518 | receiving the covered work authorizing them to use, propagate, modify
519 | or convey a specific copy of the covered work, then the patent license
520 | you grant is automatically extended to all recipients of the covered
521 | work and works based on it.
522 | 
523 |   A patent license is "discriminatory" if it does not include within
524 | the scope of its coverage, prohibits the exercise of, or is
525 | conditioned on the non-exercise of one or more of the rights that are
526 | specifically granted under this License.  You may not convey a covered
527 | work if you are a party to an arrangement with a third party that is
528 | in the business of distributing software, under which you make payment
529 | to the third party based on the extent of your activity of conveying
530 | the work, and under which the third party grants, to any of the
531 | parties who would receive the covered work from you, a discriminatory
532 | patent license (a) in connection with copies of the covered work
533 | conveyed by you (or copies made from those copies), or (b) primarily
534 | for and in connection with specific products or compilations that
535 | contain the covered work, unless you entered into that arrangement,
536 | or that patent license was granted, prior to 28 March 2007.
537 | 
538 |   Nothing in this License shall be construed as excluding or limiting
539 | any implied license or other defenses to infringement that may
540 | otherwise be available to you under applicable patent law.
541 | 
542 |   12. No Surrender of Others' Freedom.
543 | 
544 |   If conditions are imposed on you (whether by court order, agreement or
545 | otherwise) that contradict the conditions of this License, they do not
546 | excuse you from the conditions of this License.  If you cannot convey a
547 | covered work so as to satisfy simultaneously your obligations under this
548 | License and any other pertinent obligations, then as a consequence you may
549 | not convey it at all.  For example, if you agree to terms that obligate you
550 | to collect a royalty for further conveying from those to whom you convey
551 | the Program, the only way you could satisfy both those terms and this
552 | License would be to refrain entirely from conveying the Program.
553 | 
554 |   13. Use with the GNU Affero General Public License.
555 | 
556 |   Notwithstanding any other provision of this License, you have
557 | permission to link or combine any covered work with a work licensed
558 | under version 3 of the GNU Affero General Public License into a single
559 | combined work, and to convey the resulting work.  The terms of this
560 | License will continue to apply to the part which is the covered work,
561 | but the special requirements of the GNU Affero General Public License,
562 | section 13, concerning interaction through a network will apply to the
563 | combination as such.
564 | 
565 |   14. Revised Versions of this License.
566 | 
567 |   The Free Software Foundation may publish revised and/or new versions of
568 | the GNU General Public License from time to time.  Such new versions will
569 | be similar in spirit to the present version, but may differ in detail to
570 | address new problems or concerns.
571 | 
572 |   Each version is given a distinguishing version number.  If the
573 | Program specifies that a certain numbered version of the GNU General
574 | Public License "or any later version" applies to it, you have the
575 | option of following the terms and conditions either of that numbered
576 | version or of any later version published by the Free Software
577 | Foundation.  If the Program does not specify a version number of the
578 | GNU General Public License, you may choose any version ever published
579 | by the Free Software Foundation.
580 | 
581 |   If the Program specifies that a proxy can decide which future
582 | versions of the GNU General Public License can be used, that proxy's
583 | public statement of acceptance of a version permanently authorizes you
584 | to choose that version for the Program.
585 | 
586 |   Later license versions may give you additional or different
587 | permissions.  However, no additional obligations are imposed on any
588 | author or copyright holder as a result of your choosing to follow a
589 | later version.
590 | 
591 |   15. Disclaimer of Warranty.
592 | 
593 |   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
594 | APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
595 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
596 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
597 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
598 | PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
599 | IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
600 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
601 | 
602 |   16. Limitation of Liability.
603 | 
604 |   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
605 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
606 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
607 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
608 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
609 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
610 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
611 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
612 | SUCH DAMAGES.
613 | 
614 |   17. Interpretation of Sections 15 and 16.
615 | 
616 |   If the disclaimer of warranty and limitation of liability provided
617 | above cannot be given local legal effect according to their terms,
618 | reviewing courts shall apply local law that most closely approximates
619 | an absolute waiver of all civil liability in connection with the
620 | Program, unless a warranty or assumption of liability accompanies a
621 | copy of the Program in return for a fee.
622 | 
623 |                      END OF TERMS AND CONDITIONS
624 | 
625 |             How to Apply These Terms to Your New Programs
626 | 
627 |   If you develop a new program, and you want it to be of the greatest
628 | possible use to the public, the best way to achieve this is to make it
629 | free software which everyone can redistribute and change under these terms.
630 | 
631 |   To do so, attach the following notices to the program.  It is safest
632 | to attach them to the start of each source file to most effectively
633 | state the exclusion of warranty; and each file should have at least
634 | the "copyright" line and a pointer to where the full notice is found.
635 | 
636 |     {one line to give the program's name and a brief idea of what it does.}
637 |     Copyright (C) {year}  {name of author}
638 | 
639 |     This program is free software: you can redistribute it and/or modify
640 |     it under the terms of the GNU General Public License as published by
641 |     the Free Software Foundation, either version 3 of the License, or
642 |     (at your option) any later version.
643 | 
644 |     This program is distributed in the hope that it will be useful,
645 |     but WITHOUT ANY WARRANTY; without even the implied warranty of
646 |     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
647 |     GNU General Public License for more details.
648 | 
649 |     You should have received a copy of the GNU General Public License
650 |     along with this program.  If not, see {http://www.gnu.org/licenses/}.
651 | 
652 | Also add information on how to contact you by electronic and paper mail.
653 | 
654 |   If the program does terminal interaction, make it output a short
655 | notice like this when it starts in an interactive mode:
656 | 
657 |     sourcemod-optin-multimod  Copyright (C) 2013  Ross Bemrose
658 |     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
659 |     This is free software, and you are welcome to redistribute it
660 |     under certain conditions; type `show c' for details.
661 | 
662 | The hypothetical commands `show w' and `show c' should show the appropriate
663 | parts of the General Public License.  Of course, your program's commands
664 | might be different; for a GUI interface, you would use an "about box".
665 | 
666 |   You should also get your employer (if you work as a programmer) or school,
667 | if any, to sign a "copyright disclaimer" for the program, if necessary.
668 | For more information on this, and how to apply and follow the GNU GPL, see
669 | {http://www.gnu.org/licenses/}.
670 | 
671 |   The GNU General Public License does not permit incorporating your program
672 | into proprietary programs.  If your program is a subroutine library, you
673 | may consider it more useful to permit linking proprietary applications with
674 | the library.  If this is what you want to do, use the GNU Lesser General
675 | Public License instead of this License.  But first, please read
676 | {http://www.gnu.org/philosophy/why-not-lgpl.html}.
677 | 
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sourcemod-nativevotes 2 | ===================== 3 | 4 | NativeVotes SourceMod plugin 5 | 6 | NativeVotes is a SourceMod 1.4/1.5 Plugin that lets plugins use the L4D/L4D2/TF2/CSGO built-in vote screens. 7 | It supplies an API that plugins can use to create and display these votes. 8 | 9 | It replaces the BuiltinVotes SourceMod extension and has a few new features as well as bugfixes. 10 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/csgo_votestart_test.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * CSGO VoteStart Multiple Test 5 | * Test CS:GO Start Vote Panel 6 | * 7 | * CSGO VoteStart Multiple Test(C) 2014 Ross Bemrose (Powerlord). All rights reserved. 8 | * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. 9 | * ============================================================================= 10 | * 11 | * This program is free software; you can redistribute it and/or modify it under 12 | * the terms of the GNU General Public License, version 3.0, as published by the 13 | * Free Software Foundation. 14 | * 15 | * This program is distributed in the hope that it will be useful, but WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 18 | * details. 19 | * 20 | * You should have received a copy of the GNU General Public License along with 21 | * this program. If not, see . 22 | * 23 | * As a special exception, AlliedModders LLC gives you permission to link the 24 | * code of this program (as well as its derivative works) to "Half-Life 2," the 25 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 26 | * by the Valve Corporation. You must obey the GNU General Public License in 27 | * all respects for all other code used. Additionally, AlliedModders LLC grants 28 | * this exception to all derivative works. AlliedModders LLC defines further 29 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 30 | * or . 31 | * 32 | * Version: $Id$ 33 | */ 34 | 35 | #include 36 | #include 37 | 38 | #pragma semicolon 1 39 | 40 | #define VERSION "1.1" 41 | 42 | new bool:g_bVoteActive = false; 43 | 44 | public Plugin:myinfo = 45 | { 46 | name = "CSGO VoteStart Multiple Test", 47 | author = "Powerlord", 48 | description = "Test CS:GO VoteStart Panels", 49 | version = VERSION, 50 | url = "https://forums.alliedmods.net/showthread.php?t=208008" 51 | } 52 | 53 | public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) 54 | { 55 | if (GetEngineVersion() != Engine_CSGO) 56 | { 57 | strcopy(error, err_max, "Only runs on CS:GO"); 58 | return APLRes_Failure; 59 | } 60 | return APLRes_Success; 61 | } 62 | 63 | public OnPluginStart() 64 | { 65 | LoadTranslations("core.phrases"); 66 | 67 | RegAdminCmd("votemulti", Cmd_VoteMulti, ADMFLAG_VOTE, "Multiple Choice Vote"); 68 | RegAdminCmd("voteyesno", Cmd_VoteYesNo, ADMFLAG_VOTE, "YesNo Choice Vote"); 69 | RegAdminCmd("voteclear", Cmd_VoteFail, ADMFLAG_VOTE, "Multiple Choice Vote"); 70 | 71 | AddCommandListener(Listener_Vote, "vote"); 72 | } 73 | 74 | public Action:Cmd_VoteMulti(client, args) 75 | { 76 | new entity = FindEntityByClassname(-1, "vote_controller"); 77 | if (entity > -1) 78 | { 79 | SetEntProp(entity, Prop_Send, "m_bIsYesNoVote", false); 80 | //SetEntProp(entity, Prop_Send, "m_iActiveIssueIndex", 3); 81 | SetEntProp(entity, Prop_Send, "m_iOnlyTeamToVote", -1); 82 | for (new i = 0; i < 5; i++) 83 | { 84 | SetEntProp(entity, Prop_Send, "m_nVoteOptionCount", 0, _, i); 85 | } 86 | } 87 | 88 | new Handle:optionsEvent = CreateEvent("vote_options"); 89 | 90 | SetEventString(optionsEvent, "option1", "de_dust"); 91 | SetEventString(optionsEvent, "option2", "de_dust2"); 92 | SetEventString(optionsEvent, "option3", "cs_italy"); 93 | SetEventString(optionsEvent, "option4", "de_sugarcane"); 94 | SetEventString(optionsEvent, "option5", "ar_shoots"); 95 | SetEventInt(optionsEvent, "count", 5); 96 | FireEvent(optionsEvent); 97 | 98 | if (entity > -1) 99 | { 100 | SetEntProp(entity, Prop_Send, "m_nPotentialVotes", GetClientCount(true)); 101 | } 102 | 103 | LogMessage("Finished setting vote_controller properties."); 104 | 105 | g_bVoteActive = true; 106 | 107 | CreateTimer(0.2, Timer_StartMultiVote, GetClientUserId(client)); 108 | return Plugin_Handled; 109 | } 110 | 111 | public Action:Timer_StartMultiVote(Handle:timer, any:userid) 112 | { 113 | new client = GetClientOfUserId(userid); 114 | if (client <= 0 && client > MaxClients) 115 | { 116 | return Plugin_Continue; 117 | } 118 | 119 | new Handle:voteStart = StartMessageAll("VoteStart", USERMSG_RELIABLE); 120 | PbSetInt(voteStart, "team", -1); 121 | PbSetInt(voteStart, "ent_idx", 99); 122 | PbSetString(voteStart, "disp_str", "#SFUI_vote_nextlevel_choices"); 123 | PbSetString(voteStart, "details_str", ""); 124 | PbSetBool(voteStart, "is_yes_no_vote", false); 125 | PbSetString(voteStart, "other_team_str", "#SFUI_otherteam_vote_unimplemented"); 126 | PbSetInt(voteStart, "vote_type", 1); 127 | EndMessage(); 128 | 129 | return Plugin_Stop; 130 | } 131 | 132 | 133 | public Action:Cmd_VoteYesNo(client, args) 134 | { 135 | new entity = FindEntityByClassname(-1, "vote_controller"); 136 | if (entity > -1) 137 | { 138 | SetEntProp(entity, Prop_Send, "m_bIsYesNoVote", true); 139 | //SetEntProp(entity, Prop_Send, "m_iActiveIssueIndex", 2); 140 | SetEntProp(entity, Prop_Send, "m_iOnlyTeamToVote", -1); 141 | for (new i = 0; i < 5; i++) 142 | { 143 | SetEntProp(entity, Prop_Send, "m_nVoteOptionCount", 0, _, i); 144 | } 145 | } 146 | 147 | new Handle:optionsEvent = CreateEvent("vote_options"); 148 | SetEventString(optionsEvent, "option1", "Yes"); 149 | SetEventString(optionsEvent, "option2", "No"); 150 | SetEventString(optionsEvent, "option3", ""); 151 | SetEventString(optionsEvent, "option4", ""); 152 | SetEventString(optionsEvent, "option5", ""); 153 | SetEventInt(optionsEvent, "count", 2); 154 | FireEvent(optionsEvent); 155 | 156 | if (entity > -1) 157 | { 158 | SetEntProp(entity, Prop_Send, "m_nPotentialVotes", GetClientCount(true)); 159 | } 160 | 161 | g_bVoteActive = true; 162 | 163 | LogMessage("Finished setting vote_controller properties."); 164 | 165 | CreateTimer(0.2, Timer_StartYesNoVote, GetClientUserId(client)); 166 | return Plugin_Handled; 167 | } 168 | 169 | public Action:Timer_StartYesNoVote(Handle:timer, any:userid) 170 | { 171 | new client = GetClientOfUserId(userid); 172 | if (client <= 0 && client > MaxClients) 173 | { 174 | return Plugin_Continue; 175 | } 176 | 177 | new Handle:voteStart = StartMessageAll("VoteStart", USERMSG_RELIABLE); 178 | PbSetInt(voteStart, "team", -1); 179 | PbSetInt(voteStart, "ent_idx", client); 180 | PbSetString(voteStart, "disp_str", "#SFUI_vote_changelevel"); 181 | PbSetString(voteStart, "details_str", "de_dust2"); 182 | PbSetBool(voteStart, "is_yes_no_vote", true); 183 | PbSetString(voteStart, "other_team_str", "#SFUI_otherteam_vote_unimplemented"); 184 | PbSetInt(voteStart, "vote_type", 1); 185 | EndMessage(); 186 | 187 | return Plugin_Stop; 188 | } 189 | 190 | public Action:Cmd_VoteFail(client, args) 191 | { 192 | if (g_bVoteActive) 193 | VoteFail(); 194 | 195 | return Plugin_Handled; 196 | } 197 | 198 | public Action:Listener_Vote(client, const String:command[], argc) 199 | { 200 | if (!g_bVoteActive) 201 | { 202 | return Plugin_Continue; 203 | } 204 | 205 | new ReplySource:oldSource = SetCmdReplySource(SM_REPLY_TO_CHAT); 206 | ReplyToCommand(client, "Caught vote."); 207 | 208 | VoteFail(); 209 | 210 | SetCmdReplySource(oldSource); 211 | return Plugin_Handled; 212 | } 213 | 214 | VoteFail() 215 | { 216 | g_bVoteActive = false; 217 | new Handle:voteFailed = StartMessageAll("VoteFailed", USERMSG_RELIABLE); 218 | 219 | PbSetInt(voteFailed, "team", -1); 220 | PbSetInt(voteFailed, "reason", 0); 221 | EndMessage(); 222 | 223 | CreateTimer(5.0, Timer_ResetData); 224 | } 225 | 226 | public Action:Timer_ResetData(Handle:timer) 227 | { 228 | new entity = FindEntityByClassname(-1, "vote_controller"); 229 | if (entity > -1) 230 | { 231 | //SetEntProp(entity, Prop_Send, "m_iActiveIssueIndex", -1); 232 | for (new i = 0; i < 5; i++) 233 | { 234 | SetEntProp(entity, Prop_Send, "m_nVoteOptionCount", 0, _, i); 235 | } 236 | SetEntProp(entity, Prop_Send, "m_nPotentialVotes", 0); 237 | SetEntProp(entity, Prop_Send, "m_iOnlyTeamToVote", -1); 238 | SetEntProp(entity, Prop_Send, "m_bIsYesNoVote", true); 239 | } 240 | } -------------------------------------------------------------------------------- /addons/sourcemod/scripting/include/implodeexplode.inc: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * Implode/Explode functions for ArrayLists and StringMaps 5 | * Allow you to implode/explode strings directly to ADT functions 6 | * 7 | * Implode/Explode (C)2016 Powerlord (Ross Bemrose). All rights reserved. 8 | * ============================================================================= 9 | * 10 | * This program is free software; you can redistribute it and/or modify it under 11 | * the terms of the GNU General Public License, version 3.0, as published by the 12 | * Free Software Foundation. 13 | * 14 | * This program is distributed in the hope that it will be useful, but WITHOUT 15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | * details. 18 | * 19 | * You should have received a copy of the GNU General Public License along with 20 | * this program. If not, see . 21 | * 22 | * As a special exception, AlliedModders LLC gives you permission to link the 23 | * code of this program (as well as its derivative works) to "Half-Life 2," the 24 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 25 | * by the Valve Corporation. You must obey the GNU General Public License in 26 | * all respects for all other code used. Additionally, AlliedModders LLC grants 27 | * this exception to all derivative works. AlliedModders LLC defines further 28 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 29 | * or . 30 | * 31 | * Version: $Id$ 32 | */ 33 | #if defined _implodeexplode_included_ 34 | #endinput 35 | #endif 36 | #define _implodeexplode_included_ 37 | 38 | enum ImplodePart 39 | { 40 | ImplodePart_Key, 41 | ImplodePart_Value, 42 | ImplodePart_Both, 43 | } 44 | 45 | /** 46 | * Breaks a string into pieces and stores each piece into an ArrayList. 47 | * 48 | * @param text The string to split. 49 | * @param split The string to use as a split delimiter. 50 | * @param buffers An ArrayList of strings. 51 | * @param maxStringLength Maximum length of each string buffer. 52 | * @return Number of strings retrieved. 53 | */ 54 | stock int ExplodeStringToArrayList(const char[] text, const char[] split, ArrayList buffers, int maxStringLength) 55 | { 56 | int reloc_idx, idx, total; 57 | 58 | if (buffers == null || !split[0]) 59 | { 60 | return 0; 61 | } 62 | 63 | char[] item = new char[maxStringLength]; 64 | while ((idx = SplitString(text[reloc_idx], split, item, maxStringLength)) != -1) 65 | { 66 | reloc_idx += idx; 67 | ++total; 68 | buffers.PushString(item); 69 | } 70 | ++total; 71 | buffers.PushString(text[reloc_idx]); 72 | 73 | return buffers.Length; 74 | } 75 | 76 | /** 77 | * Get the size of a string needed to store all the keys, values, or both of a StringMap 78 | * 79 | * @param strings An ArrayList of strings. 80 | * @param joinLength The length of the join string to insert between each string. 81 | * @param valueMaxLength Maximum length for value strings, defaults to PLATFORM_MAX_PATH. Used temporarily. 82 | * @return Length this StringMap would take to be represented 83 | */ 84 | stock int GetArrayListImplodeSize(ArrayList strings, int joinLength, int valueMaxLength=PLATFORM_MAX_PATH) 85 | { 86 | if (strings == null) 87 | { 88 | ThrowError("ArrayList is not initialized."); 89 | } 90 | 91 | int numStrings = 0; 92 | int size = 0; 93 | 94 | numStrings = strings.Length; 95 | 96 | for (int i=0; i -1) 193 | { 194 | reloc_idx += idx; 195 | ++total; 196 | AssignExplodeStringMap(buffers, item, maxStringLength, part, keyValueSeparator); 197 | } 198 | 199 | ++total; 200 | AssignExplodeStringMap(buffers, text[reloc_idx], maxStringLength, part, keyValueSeparator); 201 | return total; 202 | } 203 | 204 | /** 205 | * An internal function used for ExplodeStringToStringMap 206 | */ 207 | stock static void AssignExplodeStringMap(StringMap buffers, const char[] item, int maxStringLength, ImplodePart part, const char[] keyValueSeparator) 208 | { 209 | switch (part) 210 | { 211 | case ImplodePart_Key, ImplodePart_Value: 212 | { 213 | buffers.SetString(item, item); 214 | } 215 | 216 | case ImplodePart_Both: 217 | { 218 | char[][] inner = new char[2][maxStringLength]; 219 | ExplodeString(item, keyValueSeparator, inner, 2, maxStringLength); 220 | buffers.SetString(inner[0], inner[1]); 221 | } 222 | } 223 | } 224 | 225 | /** 226 | * Get the size of a string needed to store all the keys, values, or both of a StringMap 227 | * 228 | * @param strings A StringMap of strings. 229 | * @param joinLength The length of the join string to insert between each string. 230 | * @param part Which part you want to turn into a list (keys, values, or both) 231 | * @param keyValueSeparatorLength Required for ImplodePart_Both, keys and values are separated with it. 232 | * @param valueMaxLength Maximum length for value strings, defaults to PLATFORM_MAX_PATH. Used temporarily for non ImplodePart_Key implodes. 233 | * @return Length this StringMap would take to be represented 234 | */ 235 | stock int GetStringMapImplodeSize(StringMap strings, int joinLength, ImplodePart part, int keyValueSeparatorLength=0, int valueMaxLength=PLATFORM_MAX_PATH) 236 | { 237 | if (strings == null) 238 | { 239 | ThrowError("StringMap is not initialized."); 240 | } 241 | 242 | int numStrings = 0; 243 | int size = 0; 244 | 245 | numStrings = strings.Size; 246 | 247 | StringMapSnapshot snapshot = strings.Snapshot(); 248 | 249 | for (int i=0; i. 22 | * 23 | * As a special exception, AlliedModders LLC gives you permission to link the 24 | * code of this program (as well as its derivative works) to "Half-Life 2," the 25 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 26 | * by the Valve Corporation. You must obey the GNU General Public License in 27 | * all respects for all other code used. Additionally, AlliedModders LLC grants 28 | * this exception to all derivative works. AlliedModders LLC defines further 29 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 30 | * or . 31 | * 32 | * Version: $Id$ 33 | */ 34 | 35 | #include 36 | #include 37 | 38 | #undef REQUIRE_PLUGIN 39 | #include 40 | 41 | #define VERSION "1.1" 42 | #pragma newdecls required 43 | #pragma semicolon 1 44 | 45 | TopMenu hTopMenu; 46 | 47 | public Plugin myinfo = 48 | { 49 | name = "NativeVotes Basic Commands", 50 | author = "Powerlord and AlliedModders LLC", 51 | description = "Revote and Cancel support for NativeVotes", 52 | version = VERSION, 53 | url = "https://forums.alliedmods.net/showthread.php?t=208008" 54 | } 55 | 56 | public void OnPluginStart() 57 | { 58 | LoadTranslations("core.phrases"); 59 | LoadTranslations("common.phrases"); 60 | 61 | AddCommandListener(Command_CancelVote, "sm_cancelvote"); 62 | AddCommandListener(Command_ReVote, "sm_revote"); 63 | } 64 | 65 | bool PerformCancelVote(int client) 66 | { 67 | if (!NativeVotes_IsVoteInProgress()) 68 | { 69 | return false; 70 | } 71 | 72 | ShowActivity2(client, "[NV] ", "%t", "Cancelled Vote"); 73 | 74 | NativeVotes_Cancel(); 75 | return true; 76 | } 77 | 78 | public Action Command_CancelVote(int client, const char[] command, int argc) 79 | { 80 | if (!CheckCommandAccess(client, "sm_cancelvote", ADMFLAG_VOTE)) 81 | { 82 | if (IsVoteInProgress()) 83 | { 84 | // Let basecommands handle it 85 | return Plugin_Continue; 86 | } 87 | 88 | ReplyToCommand(client, "%t", "No Access"); 89 | return Plugin_Stop; 90 | } 91 | 92 | if (PerformCancelVote(client)) 93 | { 94 | return Plugin_Stop; 95 | } 96 | else 97 | { 98 | return Plugin_Continue; 99 | } 100 | } 101 | 102 | public void AdminMenu_CancelVote(Handle topmenu, 103 | TopMenuAction action, 104 | TopMenuObject object_id, 105 | int param, 106 | char[] buffer, 107 | int maxlength) 108 | { 109 | if (action == TopMenuAction_DisplayOption) 110 | { 111 | Format(buffer, maxlength, "%T", "Cancel vote", param); 112 | } 113 | else if (action == TopMenuAction_SelectOption) 114 | { 115 | PerformCancelVote(param); 116 | RedisplayAdminMenu(topmenu, param); 117 | } 118 | else if (action == TopMenuAction_DrawOption) 119 | { 120 | buffer[0] = NativeVotes_IsVoteInProgress() ? ITEMDRAW_DEFAULT : ITEMDRAW_IGNORE; 121 | } 122 | } 123 | 124 | public Action Command_ReVote(int client, const char[] command, int argc) 125 | { 126 | if (client == 0) 127 | { 128 | return Plugin_Continue; 129 | } 130 | 131 | if (!NativeVotes_IsVoteInProgress()) 132 | { 133 | return Plugin_Continue; 134 | } 135 | 136 | if (!NativeVotes_IsClientInVotePool(client)) 137 | { 138 | if (IsVoteInProgress()) 139 | { 140 | // Let basecommands handle it 141 | return Plugin_Continue; 142 | } 143 | 144 | ReplyToCommand(client, "[NV] %t", "Cannot participate in vote"); 145 | return Plugin_Stop; 146 | } 147 | 148 | if (NativeVotes_RedrawClientVote(client)) 149 | { 150 | return Plugin_Stop; 151 | } 152 | else if (!IsVoteInProgress()) 153 | { 154 | ReplyToCommand(client, "[NV] %t", "Cannot change vote"); 155 | return Plugin_Stop; 156 | } 157 | 158 | return Plugin_Continue; 159 | } 160 | 161 | public void OnAdminMenuReady(Handle aTopMenu) 162 | { 163 | TopMenu topmenu = TopMenu.FromHandle(aTopMenu); 164 | 165 | /* Block us from being called twice */ 166 | if (topmenu == hTopMenu) 167 | { 168 | return; 169 | } 170 | 171 | /* Save the Handle */ 172 | hTopMenu = topmenu; 173 | 174 | TopMenuObject voting_commands = hTopMenu.FindCategory(ADMINMENU_VOTINGCOMMANDS); 175 | 176 | if (voting_commands != INVALID_TOPMENUOBJECT) 177 | { 178 | hTopMenu.AddItem("sm_cancelvote", AdminMenu_CancelVote, voting_commands, "sm_cancelvote", ADMFLAG_VOTE); 179 | } 180 | } 181 | 182 | public void OnLibraryRemoved(const char[] name) 183 | { 184 | if (strcmp(name, "adminmenu") == 0) 185 | { 186 | hTopMenu = null; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/nativevotes/data-keyvalues.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * NativeVotes 5 | * NativeVotes is a voting API plugin for L4D, L4D2, TF2, and CS:GO. 6 | * Based on the SourceMod voting API 7 | * 8 | * NativeVotes (C) 2011-2014 Ross Bemrose (Powerlord). All rights reserved. 9 | * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. 10 | * ============================================================================= 11 | * 12 | * This program is free software; you can redistribute it and/or modify it under 13 | * the terms of the GNU General Public License, version 3.0, as published by the 14 | * Free Software Foundation. 15 | * 16 | * This program is distributed in the hope that it will be useful, but WITHOUT 17 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 18 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 19 | * details. 20 | * 21 | * You should have received a copy of the GNU General Public License along with 22 | * this program. If not, see . 23 | * 24 | * As a special exception, AlliedModders LLC gives you permission to link the 25 | * code of this program (as well as its derivative works) to "Half-Life 2," the 26 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 27 | * by the Valve Corporation. You must obey the GNU General Public License in 28 | * all respects for all other code used. Additionally, AlliedModders LLC grants 29 | * this exception to all derivative works. AlliedModders LLC defines further 30 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 31 | * or . 32 | * 33 | * Version: $Id$ 34 | */ 35 | 36 | #if defined _nativevotes_data_included 37 | #endinput 38 | #endif 39 | 40 | #define _nativevotes_data_included 41 | 42 | #include 43 | 44 | #define INFO_LENGTH 128 45 | #define INFO "item_info" 46 | #define DISPLAY "item_display" 47 | 48 | bool Data_GetItemInfo(KeyValues vote, int item, char[] choice, int choiceSize) 49 | { 50 | if (item >= Data_GetItemCount(vote)) 51 | { 52 | return false; 53 | } 54 | 55 | ArrayList array = view_as(vote.GetNum(INFO, view_as(INVALID_HANDLE))); 56 | 57 | // Shouldn't happen, but just in case... 58 | if (array == null) 59 | { 60 | return false; 61 | } 62 | 63 | array.GetString(item, choice, choiceSize); 64 | return true; 65 | } 66 | 67 | bool Data_GetItemDisplay(KeyValues vote, int item, char[] choice, int choiceSize) 68 | { 69 | if (item >= Data_GetItemCount(vote)) 70 | { 71 | return false; 72 | } 73 | 74 | ArrayList array = view_as(vote.GetNum(DISPLAY, view_as(INVALID_HANDLE))); 75 | 76 | // Shouldn't happen, but just in case... 77 | if (array == null) 78 | { 79 | return false; 80 | } 81 | 82 | array.GetString(item, choice, choiceSize); 83 | return true; 84 | } 85 | 86 | int Data_GetItemCount(KeyValues vote) 87 | { 88 | ArrayList array = view_as(vote.GetNum(INFO, view_as(INVALID_HANDLE))); 89 | if (array == null) 90 | { 91 | return 0; 92 | } 93 | 94 | return array.Length; 95 | } 96 | 97 | int Data_GetTeam(KeyValues vote) 98 | { 99 | return vote.GetNum("team", NATIVEVOTES_ALL_TEAMS); 100 | } 101 | 102 | void Data_SetTeam(KeyValues vote, int team) 103 | { 104 | vote.SetNum("team", team); 105 | } 106 | 107 | int Data_GetInitiator(KeyValues vote) 108 | { 109 | return vote.GetNum("initiator", NATIVEVOTES_SERVER_INDEX); 110 | } 111 | 112 | void Data_SetInitiator(KeyValues vote, int initiator) 113 | { 114 | vote.SetNum("initiator", initiator); 115 | } 116 | 117 | void Data_GetDetails(KeyValues vote, char[] details, int maxlength) 118 | { 119 | vote.GetString("details", details, maxlength); 120 | } 121 | 122 | void Data_SetDetails(KeyValues vote, const char[] details) 123 | { 124 | vote.SetString("details", details); 125 | } 126 | 127 | void Data_GetTitle(KeyValues vote, char[] title, int maxlength) 128 | { 129 | // Shim for older code that sets custom vote titles in details 130 | vote.GetString("custom_title", title, maxlength); 131 | if (strlen(title) == 0) 132 | { 133 | vote.GetString("details", title, maxlength); 134 | } 135 | } 136 | 137 | void Data_SetTitle(KeyValues vote, const char[] title) 138 | { 139 | vote.SetString("custom_title", title); 140 | } 141 | 142 | int Data_GetTarget(KeyValues vote) 143 | { 144 | return vote.GetNum("target"); 145 | } 146 | 147 | void Data_SetTarget(KeyValues vote, int target) 148 | { 149 | vote.SetNum("target", target); 150 | } 151 | 152 | void Data_GetTargetSteam(KeyValues vote, char[] steamId, int maxlength) 153 | { 154 | vote.GetString("target_steam", steamId, maxlength); 155 | } 156 | 157 | void Data_SetTargetSteam(KeyValues vote, const char[] steamId) 158 | { 159 | vote.SetString("target_steam", steamId); 160 | } 161 | 162 | NativeVotesType Data_GetType(KeyValues vote) 163 | { 164 | return view_as(vote.GetNum("vote_type", view_as(NativeVotesType_Custom_YesNo))); 165 | } 166 | 167 | Handle Data_GetHandler(KeyValues vote) 168 | { 169 | if (vote == null) 170 | return null; 171 | 172 | return view_as(vote.GetNum("handler_callback")); 173 | } 174 | 175 | Handle Data_GetResultCallback(KeyValues vote) 176 | { 177 | if (vote == null) 178 | return null; 179 | 180 | return view_as(vote.GetNum("result_callback")); 181 | } 182 | 183 | int Data_GetFlags(KeyValues vote) 184 | { 185 | return vote.GetNum("flags"); 186 | } 187 | 188 | void Data_SetFlags(KeyValues vote, int flags) 189 | { 190 | if (flags & MENUFLAG_BUTTON_NOVOTE) 191 | { 192 | NativeVotesType voteType = Data_GetType(vote); 193 | 194 | // Strip novote if this is a YesNo vote 195 | if (Game_IsVoteTypeYesNo(voteType)) 196 | { 197 | flags &= ~MENUFLAG_BUTTON_NOVOTE; 198 | } 199 | } 200 | 201 | vote.SetNum("flags", flags); 202 | } 203 | 204 | NativeVote Data_CreateVote(NativeVotesType voteType, MenuAction actions) 205 | { 206 | Handle handler = CreateForward(ET_Single, Param_Cell, Param_Cell, Param_Cell, Param_Cell); 207 | Handle voteResults = CreateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Array, Param_Array, Param_Cell, Param_Array, Param_Array); 208 | 209 | KeyValues vote = CreateKeyValues("NativeVote"); 210 | 211 | vote.SetNum("handler_callback", view_as(handler)); 212 | vote.SetNum("vote_type", view_as(voteType)); 213 | vote.SetString("details", ""); 214 | vote.SetNum("target", -1); 215 | vote.SetString("target_steam", ""); 216 | vote.SetNum("actions", view_as(actions)); 217 | vote.SetNum("result_callback", view_as(voteResults)); 218 | vote.SetNum("initiator", NATIVEVOTES_SERVER_INDEX); 219 | if (g_EngineVersion == Engine_TF2) 220 | { 221 | vote.SetNum("team", NATIVEVOTES_TF2_ALL_TEAMS); 222 | } 223 | else 224 | { 225 | vote.SetNum("team", NATIVEVOTES_ALL_TEAMS); 226 | } 227 | vote.SetNum("flags", 0); 228 | vote.SetString("custom_title", ""); 229 | 230 | vote.SetNum(INFO, view_as(new ArrayList(ByteCountToCells(INFO_LENGTH)))); 231 | vote.SetNum(DISPLAY, view_as(new ArrayList(ByteCountToCells(INFO_LENGTH)))); 232 | 233 | return view_as(vote); 234 | } 235 | 236 | MenuAction Data_GetActions(KeyValues vote) 237 | { 238 | return view_as(vote.GetNum("actions")); 239 | } 240 | 241 | bool Data_AddItem(KeyValues vote, const char[] info, const char[] display) 242 | { 243 | ArrayList infoArray = view_as(vote.GetNum(INFO, view_as(INVALID_HANDLE))); 244 | ArrayList displayArray = view_as(vote.GetNum(DISPLAY, view_as(INVALID_HANDLE))); 245 | 246 | if (infoArray == null || displayArray == null || 247 | infoArray.Length >= Game_GetMaxItems() || 248 | displayArray.Length >= Game_GetMaxItems()) 249 | { 250 | return false; 251 | } 252 | 253 | infoArray.PushString(info); 254 | displayArray.PushString(display); 255 | 256 | return true; 257 | } 258 | 259 | bool Data_InsertItem(KeyValues vote, int position, const char[] info, const char[] display) 260 | { 261 | ArrayList infoArray = view_as(vote.GetNum(INFO, view_as(INVALID_HANDLE))); 262 | ArrayList displayArray = view_as(vote.GetNum(DISPLAY, view_as(INVALID_HANDLE))); 263 | 264 | if (infoArray == null || displayArray == null || 265 | infoArray.Length >= Game_GetMaxItems() || 266 | displayArray.Length >= Game_GetMaxItems() || 267 | position >= infoArray.Length) 268 | { 269 | return false; 270 | } 271 | 272 | infoArray.ShiftUp(position); 273 | displayArray.ShiftUp(position); 274 | 275 | infoArray.SetString(position, info); 276 | displayArray.SetString(position, display); 277 | 278 | return true; 279 | } 280 | 281 | bool Data_RemoveItem(KeyValues vote, int position) 282 | { 283 | ArrayList infoArray = view_as(vote.GetNum(INFO, view_as(INVALID_HANDLE))); 284 | ArrayList displayArray = view_as(vote.GetNum(DISPLAY, view_as(INVALID_HANDLE))); 285 | 286 | if (infoArray == null || displayArray == null || 287 | position >= infoArray.Length || position < 0) 288 | { 289 | return false; 290 | } 291 | 292 | infoArray.Erase(position); 293 | displayArray.Erase(position); 294 | 295 | return true; 296 | } 297 | 298 | void Data_RemoveAllItems(KeyValues vote) 299 | { 300 | ArrayList infoArray = view_as(vote.GetNum(INFO, view_as(INVALID_HANDLE))); 301 | ArrayList displayArray = view_as(vote.GetNum(DISPLAY, view_as(INVALID_HANDLE))); 302 | 303 | infoArray.Clear(); 304 | displayArray.Clear(); 305 | } 306 | 307 | void Data_CloseVote(KeyValues vote) 308 | { 309 | if (vote == null) 310 | { 311 | return; 312 | } 313 | 314 | Handle handler = Data_GetHandler(vote); 315 | if (handler != null) 316 | { 317 | delete handler; 318 | } 319 | 320 | Handle voteResults = Data_GetResultCallback(vote); 321 | if (voteResults != null) 322 | { 323 | delete voteResults; 324 | } 325 | 326 | ArrayList infoArray = view_as(vote.GetNum(INFO, view_as(INVALID_HANDLE))); 327 | if (infoArray != null) 328 | { 329 | delete infoArray; 330 | } 331 | 332 | ArrayList displayArray = view_as(vote.GetNum(DISPLAY, view_as(INVALID_HANDLE))); 333 | if (displayArray != null) 334 | { 335 | delete displayArray; 336 | } 337 | 338 | delete vote; 339 | } 340 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/nativevotes_kickvote_immunity.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * NativeVotes Kick Vote Immunity 5 | * Causes TF2 kick votes to fail against people who the current user can't target. 6 | * 7 | * Inspired by psychonic's [TF2] Basic Votekick Immunity 8 | * 9 | * NativeVotes Kick Vote Immunity (C)2014 Powerlord (Ross Bemrose). 10 | * All rights reserved. 11 | * ============================================================================= 12 | * 13 | * This program is free software; you can redistribute it and/or modify it under 14 | * the terms of the GNU General Public License, version 3.0, as published by the 15 | * Free Software Foundation. 16 | * 17 | * This program is distributed in the hope that it will be useful, but WITHOUT 18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 19 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 20 | * details. 21 | * 22 | * You should have received a copy of the GNU General Public License along with 23 | * this program. If not, see . 24 | * 25 | * As a special exception, AlliedModders LLC gives you permission to link the 26 | * code of this program (as well as its derivative works) to "Half-Life 2," the 27 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 28 | * by the Valve Corporation. You must obey the GNU General Public License in 29 | * all respects for all other code used. Additionally, AlliedModders LLC grants 30 | * this exception to all derivative works. AlliedModders LLC defines further 31 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 32 | * or . 33 | * 34 | * Version: $Id$ 35 | */ 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | #pragma semicolon 1 42 | #pragma newdecls required 43 | 44 | #define VERSION "3.0.0" 45 | 46 | ConVar g_Cvar_Votes; 47 | ConVar g_Cvar_KickVote; 48 | ConVar g_Cvar_KickVoteMvM; 49 | 50 | bool g_bRegistered = false; 51 | 52 | bool g_bMapActive = false; 53 | 54 | public Plugin myinfo = { 55 | name = "NativeVotes Kick Vote Immunity", 56 | author = "Powerlord", 57 | description = "Causes TF2 kick votes to fail against people who the current user can't target.", 58 | version = VERSION, 59 | url = "https://forums.alliedmods.net/showthread.php?t=208008" 60 | }; 61 | 62 | public void OnPluginStart() 63 | { 64 | g_Cvar_Votes = FindConVar("sv_allow_votes"); 65 | g_Cvar_KickVote = FindConVar("sv_vote_issue_kick_allowed"); 66 | g_Cvar_KickVoteMvM = FindConVar("sv_vote_issue_kick_allowed_mvm"); 67 | 68 | HookConVarChange(g_Cvar_Votes, Cvar_CheckEnable); 69 | HookConVarChange(g_Cvar_KickVote, Cvar_CheckEnable); 70 | HookConVarChange(g_Cvar_KickVoteMvM, Cvar_CheckEnable); 71 | 72 | LoadTranslations("common.phrases"); 73 | CreateConVar("nativevotes_kickvote_immunity_version", VERSION, "NativeVotes Kickvote Immunity version", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); 74 | } 75 | 76 | public void OnAllPluginsLoaded() 77 | { 78 | if (GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") != FeatureStatus_Available) 79 | { 80 | SetFailState("Requires NativeVotes 1.1 or newer"); 81 | } 82 | } 83 | 84 | public void OnConfigsExecuted() 85 | { 86 | g_bMapActive = true; 87 | CheckStatus(); 88 | } 89 | 90 | public void OnMapEnd() 91 | { 92 | g_bMapActive = false; 93 | } 94 | 95 | public void OnPluginEnd() 96 | { 97 | UnregisterVoteCommand(); 98 | } 99 | 100 | void RegisterVoteCommand() 101 | { 102 | if (!g_bRegistered && 103 | NativeVotes_AreVoteCommandsSupported()) 104 | { 105 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_Kick, KickVoteHandler); 106 | g_bRegistered = true; 107 | } 108 | } 109 | 110 | void UnregisterVoteCommand() 111 | { 112 | if (g_bRegistered) 113 | { 114 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Kick, KickVoteHandler); 115 | g_bRegistered = false; 116 | } 117 | } 118 | 119 | public void Cvar_CheckEnable(ConVar convar, const char[] oldValue, const char[] newValue) 120 | { 121 | if (!g_bMapActive) 122 | return; 123 | 124 | CheckStatus(); 125 | } 126 | 127 | void CheckStatus() 128 | { 129 | bool bIsMvM = IsMvM(); 130 | if (g_bRegistered) 131 | { 132 | if (!g_Cvar_Votes.BoolValue || 133 | (bIsMvM && !g_Cvar_KickVoteMvM.BoolValue) || 134 | (!bIsMvM && !g_Cvar_KickVote.BoolValue) 135 | ) 136 | { 137 | LogMessage("Disabling."); 138 | UnregisterVoteCommand(); 139 | } 140 | } 141 | else 142 | { 143 | if (g_Cvar_Votes.BoolValue && 144 | ((bIsMvM && g_Cvar_KickVoteMvM.BoolValue) || 145 | (!bIsMvM && g_Cvar_KickVote.BoolValue)) 146 | ) 147 | { 148 | LogMessage("Enabling."); 149 | RegisterVoteCommand(); 150 | } 151 | } 152 | } 153 | 154 | stock bool IsMvM() 155 | { 156 | return view_as(GameRules_GetProp("m_bPlayingMannVsMachine")); 157 | } 158 | 159 | public Action KickVoteHandler(int client, NativeVotesOverride overrideType, const char[] voteArgument, NativeVotesKickType kickType, int target) 160 | { 161 | if (!CanUserTarget(client, target)) 162 | { 163 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_CantKickAdmin); 164 | PrintToChat(client, "%t", "Unable to target"); 165 | return Plugin_Stop; 166 | } 167 | 168 | return Plugin_Continue; 169 | } -------------------------------------------------------------------------------- /addons/sourcemod/scripting/nativevotes_mapchooser.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * SourceMod NativeVotes Mapchooser Plugin 5 | * Creates a map vote at appropriate times, setting sm_nextmap to the winning 6 | * vote 7 | * Updated with NativeVotes support 8 | * 9 | * NativeVotes (C)2011-2016 Ross Bemrose (Powerlord). All rights reserved. 10 | * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. 11 | * ============================================================================= 12 | * 13 | * This program is free software; you can redistribute it and/or modify it under 14 | * the terms of the GNU General Public License, version 3.0, as published by the 15 | * Free Software Foundation. 16 | * 17 | * This program is distributed in the hope that it will be useful, but WITHOUT 18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 19 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 20 | * details. 21 | * 22 | * You should have received a copy of the GNU General Public License along with 23 | * this program. If not, see . 24 | * 25 | * As a special exception, AlliedModders LLC gives you permission to link the 26 | * code of this program (as well as its derivative works) to "Half-Life 2," the 27 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 28 | * by the Valve Corporation. You must obey the GNU General Public License in 29 | * all respects for all other code used. Additionally, AlliedModders LLC grants 30 | * this exception to all derivative works. AlliedModders LLC defines further 31 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 32 | * or . 33 | * 34 | * Version: $Id$ 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | #undef REQUIRE_PLUGIN 42 | #include 43 | #define REQUIRE_PLUGIN 44 | 45 | #pragma semicolon 1 46 | #pragma newdecls required 47 | 48 | #define VERSION "1.8.0 beta 1" 49 | 50 | public Plugin myinfo = 51 | { 52 | name = "NativeVotes MapChooser", 53 | author = "AlliedModders LLC and Powerlord", 54 | description = "Automated Map Voting", 55 | version = VERSION, 56 | url = "https://forums.alliedmods.net/showthread.php?t=208010" 57 | }; 58 | 59 | /* Valve ConVars */ 60 | ConVar g_Cvar_Winlimit; 61 | ConVar g_Cvar_Maxrounds; 62 | ConVar g_Cvar_Fraglimit; 63 | ConVar g_Cvar_Bonusroundtime; 64 | 65 | /* Plugin ConVars */ 66 | ConVar g_Cvar_StartTime; 67 | ConVar g_Cvar_StartRounds; 68 | ConVar g_Cvar_StartFrags; 69 | ConVar g_Cvar_ExtendTimeStep; 70 | ConVar g_Cvar_ExtendRoundStep; 71 | ConVar g_Cvar_ExtendFragStep; 72 | ConVar g_Cvar_ExcludeMaps; 73 | ConVar g_Cvar_IncludeMaps; 74 | ConVar g_Cvar_NoVoteMode; 75 | ConVar g_Cvar_Extend; 76 | ConVar g_Cvar_DontChange; 77 | ConVar g_Cvar_EndOfMapVote; 78 | ConVar g_Cvar_VoteDuration; 79 | ConVar g_Cvar_RunOff; 80 | ConVar g_Cvar_RunOffPercent; 81 | 82 | Handle g_VoteTimer = null; 83 | Handle g_RetryTimer = null; 84 | 85 | // g_MapList stores unresolved names so we can resolve them after every map change in the workshop updates. 86 | // g_OldMapList and g_NextMapList are resolved. g_NominateList depends on the nominations implementation. 87 | /* Data Handles */ 88 | ArrayList g_MapList; 89 | ArrayList g_NominateList; 90 | ArrayList g_NominateOwners; 91 | ArrayList g_OldMapList; 92 | ArrayList g_NextMapList; 93 | Menu g_VoteMenu; 94 | NativeVote g_VoteNative; 95 | 96 | int g_Extends; 97 | int g_TotalRounds; 98 | bool g_HasVoteStarted; 99 | bool g_WaitingForVote; 100 | bool g_MapVoteCompleted; 101 | bool g_ChangeMapAtRoundEnd; 102 | bool g_ChangeMapInProgress; 103 | int g_mapFileSerial = -1; 104 | 105 | MapChange g_ChangeTime; 106 | 107 | Handle g_NominationsResetForward = null; 108 | Handle g_MapVoteStartedForward = null; 109 | 110 | /* Upper bound of how many team there could be */ 111 | #define MAXTEAMS 10 112 | int g_winCount[MAXTEAMS]; 113 | 114 | #define VOTE_EXTEND "##extend##" 115 | #define VOTE_DONTCHANGE "##dontchange##" 116 | 117 | // NativeVotes 118 | bool g_NativeVotes; 119 | #define LIBRARY "nativevotes" 120 | 121 | public void OnPluginStart() 122 | { 123 | LoadTranslations("mapchooser.phrases"); 124 | LoadTranslations("common.phrases"); 125 | 126 | int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); 127 | g_MapList = new ArrayList(arraySize); 128 | g_NominateList = new ArrayList(arraySize); 129 | g_NominateOwners = new ArrayList(); 130 | g_OldMapList = new ArrayList(arraySize); 131 | g_NextMapList = new ArrayList(arraySize); 132 | CreateConVar("nativevotes_mapchooser_version", VERSION, "NativeVotes MapChooser version", FCVAR_DONTRECORD|FCVAR_NOTIFY|FCVAR_SPONLY); 133 | 134 | g_Cvar_EndOfMapVote = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0); 135 | 136 | g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0); 137 | g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use 0 on TF2 to start vote during bonus round time", _, true, 0.0); 138 | g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0); 139 | g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0); 140 | g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0); 141 | g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0); 142 | g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0); 143 | g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0); 144 | g_Cvar_NoVoteMode = CreateConVar("sm_mapvote_novote", "1", "Specifies whether or not MapChooser should pick a map if no votes are received.", _, true, 0.0, true, 1.0); 145 | g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0); 146 | g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0); 147 | g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0); 148 | g_Cvar_RunOff = CreateConVar("sm_mapvote_runoff", "0", "Hold run of votes if winning choice is less than a certain margin", _, true, 0.0, true, 1.0); 149 | g_Cvar_RunOffPercent = CreateConVar("sm_mapvote_runoffpercent", "50", "If winning choice has less than this percent of votes, hold a runoff", _, true, 0.0, true, 100.0); 150 | 151 | RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now."); 152 | RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap "); 153 | 154 | g_Cvar_Winlimit = FindConVar("mp_winlimit"); 155 | g_Cvar_Maxrounds = FindConVar("mp_maxrounds"); 156 | g_Cvar_Fraglimit = FindConVar("mp_fraglimit"); 157 | g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime"); 158 | 159 | if (g_Cvar_Winlimit || g_Cvar_Maxrounds) 160 | { 161 | char folder[64]; 162 | GetGameFolderName(folder, sizeof(folder)); 163 | 164 | if (strcmp(folder, "tf") == 0) 165 | { 166 | HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel); 167 | HookEvent("teamplay_restart_round", Event_TFRestartRound); 168 | HookEvent("arena_win_panel", Event_TeamPlayWinPanel); 169 | } 170 | else if (strcmp(folder, "nucleardawn") == 0) 171 | { 172 | HookEvent("round_win", Event_RoundEnd); 173 | } 174 | else 175 | { 176 | HookEvent("round_end", Event_RoundEnd); 177 | } 178 | } 179 | 180 | if (g_Cvar_Fraglimit) 181 | { 182 | HookEvent("player_death", Event_PlayerDeath); 183 | } 184 | 185 | AutoExecConfig(true, "mapchooser"); 186 | 187 | //Change the mp_bonusroundtime max so that we have time to display the vote 188 | //If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime 189 | if (g_Cvar_Bonusroundtime) 190 | { 191 | g_Cvar_Bonusroundtime.SetBounds(ConVarBound_Upper, true, 30.0); 192 | } 193 | 194 | g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell); 195 | g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore); 196 | } 197 | 198 | public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) 199 | { 200 | RegPluginLibrary("mapchooser"); 201 | 202 | CreateNative("NominateMap", Native_NominateMap); 203 | CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap); 204 | CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner); 205 | CreateNative("InitiateMapChooserVote", Native_InitiateVote); 206 | CreateNative("CanMapChooserStartVote", Native_CanVoteStart); 207 | CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone); 208 | CreateNative("GetExcludeMapList", Native_GetExcludeMapList); 209 | CreateNative("GetNominatedMapList", Native_GetNominatedMapList); 210 | CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled); 211 | 212 | return APLRes_Success; 213 | } 214 | 215 | public void OnAllPluginsLoaded() 216 | { 217 | if (FindPluginByFile("mapchooser.smx") != null) 218 | { 219 | LogMessage("Unloading mapchooser to prevent conflicts..."); 220 | ServerCommand("sm plugins unload mapchooser"); 221 | 222 | char oldPath[PLATFORM_MAX_PATH]; 223 | char newPath[PLATFORM_MAX_PATH]; 224 | 225 | BuildPath(Path_SM, oldPath, sizeof(oldPath), "plugins/mapchooser.smx"); 226 | BuildPath(Path_SM, newPath, sizeof(newPath), "plugins/disabled/mapchooser.smx"); 227 | if (RenameFile(newPath, oldPath)) 228 | { 229 | LogMessage("Moving mapchooser to disabled."); 230 | } 231 | } 232 | 233 | g_NativeVotes = LibraryExists(LIBRARY) && NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult); 234 | } 235 | 236 | public void OnLibraryAdded(const char[] name) 237 | { 238 | if (StrEqual(name, LIBRARY, false) && NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult)) 239 | { 240 | g_NativeVotes = true; 241 | } 242 | } 243 | 244 | public void OnLibraryRemoved(const char[] name) 245 | { 246 | if (StrEqual(name, LIBRARY, false)) 247 | { 248 | g_NativeVotes = false; 249 | } 250 | } 251 | 252 | public void OnConfigsExecuted() 253 | { 254 | if (ReadMapList(g_MapList, 255 | g_mapFileSerial, 256 | "mapchooser", 257 | MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) 258 | != null) 259 | 260 | { 261 | if (g_mapFileSerial == -1) 262 | { 263 | LogError("Unable to create a valid map list."); 264 | } 265 | } 266 | 267 | CreateNextVote(); 268 | SetupTimeleftTimer(); 269 | 270 | g_TotalRounds = 0; 271 | 272 | g_Extends = 0; 273 | 274 | g_MapVoteCompleted = false; 275 | 276 | g_NominateList.Clear(); 277 | g_NominateOwners.Clear(); 278 | 279 | for (int i=0; i g_Cvar_ExcludeMaps.IntValue) 310 | { 311 | g_OldMapList.Erase(0); 312 | } 313 | } 314 | 315 | public void OnClientDisconnect(int client) 316 | { 317 | int index = g_NominateOwners.FindValue(client); 318 | 319 | if (index == -1) 320 | { 321 | return; 322 | } 323 | 324 | char oldmap[PLATFORM_MAX_PATH]; 325 | g_NominateList.GetString(index, oldmap, sizeof(oldmap)); 326 | Call_StartForward(g_NominationsResetForward); 327 | Call_PushString(oldmap); 328 | Call_PushCell(g_NominateOwners.Get(index)); 329 | Call_Finish(); 330 | 331 | g_NominateOwners.Erase(index); 332 | g_NominateList.Erase(index); 333 | } 334 | 335 | public Action Command_SetNextmap(int client, int args) 336 | { 337 | if (args < 1) 338 | { 339 | ReplyToCommand(client, "[SM] Usage: sm_setnextmap "); 340 | return Plugin_Handled; 341 | } 342 | 343 | char map[PLATFORM_MAX_PATH]; 344 | char displayName[PLATFORM_MAX_PATH]; 345 | GetCmdArg(1, map, sizeof(map)); 346 | 347 | if (FindMap(map, displayName, sizeof(displayName)) == FindMap_NotFound) 348 | { 349 | ReplyToCommand(client, "[SM] %t", "Map was not found", map); 350 | return Plugin_Handled; 351 | } 352 | 353 | GetMapDisplayName(displayName, displayName, sizeof(displayName)); 354 | 355 | ShowActivity(client, "%t", "Changed Next Map", displayName); 356 | LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map); 357 | 358 | SetNextMap(map); 359 | g_MapVoteCompleted = true; 360 | 361 | return Plugin_Handled; 362 | } 363 | 364 | public void OnMapTimeLeftChanged() 365 | { 366 | if (g_MapList.Length) 367 | { 368 | SetupTimeleftTimer(); 369 | } 370 | } 371 | 372 | void SetupTimeleftTimer() 373 | { 374 | int time; 375 | if (GetMapTimeLeft(time) && time > 0) 376 | { 377 | int startTime = g_Cvar_StartTime.IntValue * 60; 378 | if (time - startTime < 0 && g_Cvar_EndOfMapVote.BoolValue && !g_MapVoteCompleted && !g_HasVoteStarted) 379 | { 380 | InitiateVote(MapChange_MapEnd, null); 381 | } 382 | else 383 | { 384 | if (g_VoteTimer != null) 385 | { 386 | KillTimer(g_VoteTimer); 387 | g_VoteTimer = null; 388 | } 389 | 390 | //g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); 391 | DataPack data; 392 | g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE); 393 | data.WriteCell(MapChange_MapEnd); 394 | data.WriteCell(INVALID_HANDLE); 395 | data.Reset(); 396 | } 397 | } 398 | } 399 | 400 | public Action Timer_StartMapVote(Handle timer, DataPack data) 401 | { 402 | if (timer == g_RetryTimer) 403 | { 404 | g_WaitingForVote = false; 405 | g_RetryTimer = null; 406 | } 407 | else 408 | { 409 | g_VoteTimer = null; 410 | } 411 | 412 | if (!g_MapList.Length || !g_Cvar_EndOfMapVote.BoolValue || g_MapVoteCompleted || g_HasVoteStarted) 413 | { 414 | return Plugin_Stop; 415 | } 416 | 417 | MapChange mapChange = view_as(data.ReadCell()); 418 | ArrayList hndl = view_as(data.ReadCell()); 419 | 420 | InitiateVote(mapChange, hndl); 421 | 422 | return Plugin_Stop; 423 | } 424 | 425 | public void Event_TFRestartRound(Event event, const char[] name, bool dontBroadcast) 426 | { 427 | /* Game got restarted - reset our round count tracking */ 428 | g_TotalRounds = 0; 429 | } 430 | 431 | public void Event_TeamPlayWinPanel(Event event, const char[] name, bool dontBroadcast) 432 | { 433 | if (g_ChangeMapAtRoundEnd) 434 | { 435 | g_ChangeMapAtRoundEnd = false; 436 | CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); 437 | g_ChangeMapInProgress = true; 438 | } 439 | 440 | int bluescore = event.GetInt("blue_score"); 441 | int redscore = event.GetInt("red_score"); 442 | 443 | if (event.GetInt("round_complete") == 1 || StrEqual(name, "arena_win_panel")) 444 | { 445 | g_TotalRounds++; 446 | 447 | if (!g_MapList.Length || g_HasVoteStarted || g_MapVoteCompleted || !g_Cvar_EndOfMapVote.BoolValue) 448 | { 449 | return; 450 | } 451 | 452 | CheckMaxRounds(g_TotalRounds); 453 | 454 | switch(event.GetInt("winning_team")) 455 | { 456 | case 3: 457 | { 458 | CheckWinLimit(bluescore); 459 | } 460 | case 2: 461 | { 462 | CheckWinLimit(redscore); 463 | } 464 | //We need to do nothing on winning_team == 0 this indicates stalemate. 465 | default: 466 | { 467 | return; 468 | } 469 | } 470 | } 471 | } 472 | /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */ 473 | public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) 474 | { 475 | if (g_ChangeMapAtRoundEnd) 476 | { 477 | g_ChangeMapAtRoundEnd = false; 478 | CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); 479 | g_ChangeMapInProgress = true; 480 | } 481 | 482 | int winner; 483 | if (strcmp(name, "round_win") == 0) 484 | { 485 | // Nuclear Dawn 486 | winner = event.GetInt("team"); 487 | } 488 | else 489 | { 490 | winner = event.GetInt("winner"); 491 | } 492 | 493 | if (winner == 0 || winner == 1 || !g_Cvar_EndOfMapVote.BoolValue) 494 | { 495 | return; 496 | } 497 | 498 | if (winner >= MAXTEAMS) 499 | { 500 | SetFailState("Mod exceed maximum team count - Please file a bug report."); 501 | } 502 | 503 | g_TotalRounds++; 504 | 505 | g_winCount[winner]++; 506 | 507 | if (!g_MapList.Length || g_HasVoteStarted || g_MapVoteCompleted) 508 | { 509 | return; 510 | } 511 | 512 | CheckWinLimit(g_winCount[winner]); 513 | CheckMaxRounds(g_TotalRounds); 514 | } 515 | 516 | public void CheckWinLimit(int winner_score) 517 | { 518 | if (g_Cvar_Winlimit) 519 | { 520 | int winlimit = g_Cvar_Winlimit.IntValue; 521 | if (winlimit) 522 | { 523 | if (winner_score >= (winlimit - g_Cvar_StartRounds.IntValue)) 524 | { 525 | InitiateVote(MapChange_MapEnd, null); 526 | } 527 | } 528 | } 529 | } 530 | 531 | public void CheckMaxRounds(int roundcount) 532 | { 533 | if (g_Cvar_Maxrounds) 534 | { 535 | int maxrounds = g_Cvar_Maxrounds.IntValue; 536 | if (maxrounds) 537 | { 538 | if (roundcount >= (maxrounds - g_Cvar_StartRounds.IntValue)) 539 | { 540 | InitiateVote(MapChange_MapEnd, null); 541 | } 542 | } 543 | } 544 | } 545 | 546 | public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) 547 | { 548 | if (!g_MapList.Length || !g_Cvar_Fraglimit || g_HasVoteStarted) 549 | { 550 | return; 551 | } 552 | 553 | if (!g_Cvar_Fraglimit.IntValue || !g_Cvar_EndOfMapVote.BoolValue) 554 | { 555 | return; 556 | } 557 | 558 | if (g_MapVoteCompleted) 559 | { 560 | return; 561 | } 562 | 563 | int fragger = GetClientOfUserId(event.GetInt("attacker")); 564 | 565 | if (!fragger) 566 | { 567 | return; 568 | } 569 | 570 | if (GetClientFrags(fragger) >= (g_Cvar_Fraglimit.IntValue - g_Cvar_StartFrags.IntValue)) 571 | { 572 | InitiateVote(MapChange_MapEnd, null); 573 | } 574 | } 575 | 576 | public Action Command_Mapvote(int client, int args) 577 | { 578 | InitiateVote(MapChange_MapEnd, null); 579 | 580 | return Plugin_Handled; 581 | } 582 | 583 | /** 584 | * Starts a new map vote 585 | * 586 | * @param when When the resulting map change should occur. 587 | * @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used. 588 | * @param noSpecials Block special vote options like extend/nochange (upgrade this to bitflags instead?) 589 | */ 590 | void InitiateVote(MapChange when, ArrayList inputlist=null) 591 | { 592 | g_WaitingForVote = true; 593 | 594 | if ((g_NativeVotes && NativeVotes_IsVoteInProgress()) || (!g_NativeVotes && IsVoteInProgress())) 595 | //if (IsVoteInProgress()) 596 | { 597 | // Can't start a vote, try again in 5 seconds. 598 | //g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); 599 | 600 | DataPack data; 601 | g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE); 602 | data.WriteCell(when); 603 | data.WriteCell(inputlist); 604 | data.Reset(); 605 | return; 606 | } 607 | 608 | /* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */ 609 | if (g_MapVoteCompleted && g_ChangeMapInProgress) 610 | { 611 | return; 612 | } 613 | 614 | g_ChangeTime = when; 615 | 616 | g_WaitingForVote = false; 617 | 618 | g_HasVoteStarted = true; 619 | if (g_NativeVotes) 620 | { 621 | g_VoteNative = new NativeVote(Handler_NV_MapVoteMenu, NativeVotesType_NextLevelMult, NATIVEVOTES_ACTIONS_DEFAULT | MenuAction_DisplayItem); 622 | g_VoteNative.VoteResultCallback = Handler_NV_MapVoteFinished; 623 | } 624 | else 625 | { 626 | g_VoteMenu = new Menu(Handler_MapVoteMenu, MENU_ACTIONS_ALL); 627 | g_VoteMenu.SetTitle("Vote Nextmap"); 628 | g_VoteMenu.VoteResultCallback = Handler_MapVoteFinished; 629 | } 630 | 631 | /* Call OnMapVoteStarted() Forward */ 632 | Call_StartForward(g_MapVoteStartedForward); 633 | Call_Finish(); 634 | 635 | /** 636 | * TODO: Make a proper decision on when to clear the nominations list. 637 | * Currently it clears when used, and stays if an external list is provided. 638 | * Is this the right thing to do? External lists will probably come from places 639 | * like sm_mapvote from the adminmenu in the future. 640 | */ 641 | 642 | char map[PLATFORM_MAX_PATH]; 643 | 644 | /* No input given - User our internal nominations and maplist */ 645 | if (inputlist == null) 646 | { 647 | int nominateCount = g_NominateList.Length; 648 | int voteSize = g_Cvar_IncludeMaps.IntValue; 649 | 650 | // New in 1.5.1 to fix missing extend vote 651 | if (g_NativeVotes) 652 | { 653 | if (voteSize > NativeVotes_GetMaxItems()) 654 | { 655 | voteSize = NativeVotes_GetMaxItems(); 656 | } 657 | 658 | if (g_Cvar_Extend.IntValue && g_Extends < g_Cvar_Extend.IntValue) 659 | { 660 | voteSize--; 661 | } 662 | } 663 | 664 | /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */ 665 | int nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount; 666 | 667 | for (int i=0; i= availableMaps) 713 | { 714 | //Run out of maps, this will have to do. 715 | break; 716 | } 717 | 718 | g_NextMapList.GetString(count, map, sizeof(map)); 719 | count++; 720 | 721 | /* Insert the map and increment our count */ 722 | char displayName[PLATFORM_MAX_PATH]; 723 | GetMapDisplayName(map, displayName, sizeof(displayName)); 724 | if (g_NativeVotes) 725 | { 726 | g_VoteNative.AddItem(map, displayName); 727 | } 728 | else 729 | { 730 | g_VoteMenu.AddItem(map, displayName); 731 | } 732 | i++; 733 | } 734 | 735 | /* Wipe out our nominations list - Nominations have already been informed of this */ 736 | g_NominateOwners.Clear(); 737 | g_NominateList.Clear(); 738 | } 739 | else //We were given a list of maps to start the vote with 740 | { 741 | int size = inputlist.Length; 742 | 743 | for (int i=0; i 0) 871 | { 872 | ExtendMapTimeLimit(g_Cvar_ExtendTimeStep.IntValue * 60); 873 | } 874 | } 875 | 876 | if (g_Cvar_Winlimit) 877 | { 878 | int winlimit = g_Cvar_Winlimit.IntValue; 879 | if (winlimit) 880 | { 881 | g_Cvar_Winlimit.IntValue = winlimit + g_Cvar_ExtendRoundStep.IntValue; 882 | } 883 | } 884 | 885 | if (g_Cvar_Maxrounds) 886 | { 887 | int maxrounds = g_Cvar_Maxrounds.IntValue; 888 | if (maxrounds) 889 | { 890 | g_Cvar_Maxrounds.IntValue = maxrounds + g_Cvar_ExtendRoundStep.IntValue; 891 | } 892 | } 893 | 894 | if (g_Cvar_Fraglimit) 895 | { 896 | int fraglimit = g_Cvar_Fraglimit.IntValue; 897 | if (fraglimit) 898 | { 899 | g_Cvar_Fraglimit.IntValue = fraglimit + g_Cvar_ExtendFragStep.IntValue; 900 | } 901 | } 902 | 903 | PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); 904 | LogAction(-1, -1, "Voting for next map has finished. The current map has been extended."); 905 | 906 | if (isNativeVotes) 907 | { 908 | g_VoteNative.DisplayPassEx(NativeVotesPass_Extend); 909 | } 910 | 911 | // We extended, so we'll have to vote again. 912 | g_HasVoteStarted = false; 913 | CreateNextVote(); 914 | SetupTimeleftTimer(); 915 | 916 | } 917 | else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) 918 | { 919 | PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); 920 | LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner"); 921 | 922 | if (isNativeVotes) 923 | { 924 | g_VoteNative.DisplayPassEx(NativeVotesPass_Extend); 925 | } 926 | 927 | g_HasVoteStarted = false; 928 | CreateNextVote(); 929 | SetupTimeleftTimer(); 930 | } 931 | else 932 | { 933 | if (g_ChangeTime == MapChange_MapEnd) 934 | { 935 | SetNextMap(map); 936 | } 937 | else if (g_ChangeTime == MapChange_Instant) 938 | { 939 | DataPack data; 940 | CreateDataTimer(2.0, Timer_ChangeMap, data); 941 | data.WriteString(map); 942 | g_ChangeMapInProgress = false; 943 | } 944 | else // MapChange_RoundEnd 945 | { 946 | SetNextMap(map); 947 | g_ChangeMapAtRoundEnd = true; 948 | } 949 | 950 | g_HasVoteStarted = false; 951 | g_MapVoteCompleted = true; 952 | 953 | if (isNativeVotes) 954 | { 955 | g_VoteNative.DisplayPass(displayName); 956 | } 957 | 958 | PrintToChatAll("[SM] %t", "Nextmap Voting Finished", displayName, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); 959 | LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map); 960 | } 961 | } 962 | 963 | public void Handler_NV_MapVoteFinished(NativeVote menu, 964 | int num_votes, 965 | int num_clients, 966 | const int[] client_indexes, 967 | const int[] client_votes, 968 | int num_items, 969 | const int[] item_indexes, 970 | const int[] item_votes) 971 | { 972 | if (g_Cvar_RunOff.BoolValue && num_items > 1) 973 | { 974 | float winningvotes = float(item_votes[0]); 975 | float required = num_votes * (g_Cvar_RunOffPercent.FloatValue / 100.0); 976 | 977 | if (winningvotes < required) 978 | { 979 | //Added in 1.5.1 980 | menu.DisplayFail(NativeVotesFail_NotEnoughVotes); 981 | 982 | /* Insufficient Winning margin - Lets do a runoff */ 983 | 984 | char map1[PLATFORM_MAX_PATH]; 985 | char map2[PLATFORM_MAX_PATH]; 986 | char info1[PLATFORM_MAX_PATH]; 987 | char info2[PLATFORM_MAX_PATH]; 988 | 989 | DataPack data; 990 | 991 | menu.GetItem(item_indexes[0], map1, sizeof(map1), info1, sizeof(info1)); 992 | menu.GetItem(item_indexes[1], map2, sizeof(map2), info2, sizeof(info2)); 993 | 994 | CreateDataTimer(3.0, Timer_NV_Runoff, data, TIMER_FLAG_NO_MAPCHANGE); 995 | 996 | data.WriteString(map1); 997 | data.WriteString(info1); 998 | data.WriteString(map2); 999 | data.WriteString(info2); 1000 | 1001 | data.Reset(); 1002 | 1003 | /* Notify */ 1004 | float map1percent = float(item_votes[0])/ float(num_votes) * 100; 1005 | float map2percent = float(item_votes[1])/ float(num_votes) * 100; 1006 | 1007 | 1008 | PrintToChatAll("[SM] %t", "Starting Runoff", g_Cvar_RunOffPercent.FloatValue, info1, map1percent, info2, map2percent); 1009 | LogMessage("Voting for next map was indecisive, beginning runoff vote"); 1010 | 1011 | return; 1012 | } 1013 | } 1014 | 1015 | Handler_NV_VoteFinishedGeneric(menu, num_votes, num_clients, client_indexes, client_votes, num_items, item_indexes, item_votes); 1016 | } 1017 | 1018 | // New in 1.5.1, used to fix runoff not working properly 1019 | public Action Timer_NV_Runoff(Handle timer, DataPack data) 1020 | { 1021 | char map[PLATFORM_MAX_PATH]; 1022 | char info[PLATFORM_MAX_PATH]; 1023 | 1024 | g_VoteNative = new NativeVote(Handler_NV_MapVoteMenu, NativeVotesType_NextLevelMult, NATIVEVOTES_ACTIONS_DEFAULT | MenuAction_DisplayItem); 1025 | g_VoteNative.VoteResultCallback = Handler_NV_VoteFinishedGeneric; 1026 | 1027 | data.ReadString(map, sizeof(map)); 1028 | data.ReadString(info, sizeof(info)); 1029 | g_VoteNative.AddItem(map, info); 1030 | 1031 | data.ReadString(map, sizeof(map)); 1032 | data.ReadString(info, sizeof(info)); 1033 | g_VoteNative.AddItem(map, info); 1034 | 1035 | int voteDuration = g_Cvar_VoteDuration.IntValue; 1036 | g_VoteNative.DisplayVoteToAll(voteDuration); 1037 | 1038 | return Plugin_Continue; 1039 | } 1040 | 1041 | public void Handler_MapVoteFinished(Menu menu, 1042 | int num_votes, 1043 | int num_clients, 1044 | const int[][] client_info, 1045 | int num_items, 1046 | const int[][] item_info) 1047 | { 1048 | if (g_Cvar_RunOff.BoolValue && num_items > 1) 1049 | { 1050 | float winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]); 1051 | float required = num_votes * (g_Cvar_RunOffPercent.FloatValue / 100.0); 1052 | 1053 | if (winningvotes < required) 1054 | { 1055 | /* Insufficient Winning margin - Lets do a runoff */ 1056 | g_VoteMenu = new Menu(Handler_MapVoteMenu, MENU_ACTIONS_ALL); 1057 | g_VoteMenu.SetTitle("Runoff Vote Nextmap"); 1058 | g_VoteMenu.VoteResultCallback = Handler_VoteFinishedGeneric; 1059 | 1060 | char map[PLATFORM_MAX_PATH]; 1061 | char info1[PLATFORM_MAX_PATH]; 1062 | char info2[PLATFORM_MAX_PATH]; 1063 | 1064 | menu.GetItem(item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info1, sizeof(info1)); 1065 | g_VoteMenu.AddItem(map, info1); 1066 | menu.GetItem(item_info[1][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info2, sizeof(info2)); 1067 | g_VoteMenu.AddItem(map, info2); 1068 | 1069 | int voteDuration = g_Cvar_VoteDuration.IntValue; 1070 | g_VoteMenu.ExitButton = false; 1071 | g_VoteMenu.DisplayVoteToAll(voteDuration); 1072 | 1073 | /* Notify */ 1074 | float map1percent = float(item_info[0][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100; 1075 | float map2percent = float(item_info[1][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100; 1076 | 1077 | 1078 | PrintToChatAll("[SM] %t", "Starting Runoff", g_Cvar_RunOffPercent.FloatValue, info1, map1percent, info2, map2percent); 1079 | LogMessage("Voting for next map was indecisive, beginning runoff vote"); 1080 | 1081 | return; 1082 | } 1083 | } 1084 | 1085 | Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info); 1086 | } 1087 | 1088 | public int Handler_MapVoteMenu(Menu menu, MenuAction action, int param1, int param2) 1089 | { 1090 | switch (action) 1091 | { 1092 | case MenuAction_End: 1093 | { 1094 | g_VoteMenu = null; 1095 | delete menu; 1096 | } 1097 | 1098 | case MenuAction_Display: 1099 | { 1100 | char buffer[255]; 1101 | Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1); 1102 | 1103 | Panel panel = view_as(param2); 1104 | panel.SetTitle(buffer); 1105 | } 1106 | 1107 | case MenuAction_DisplayItem: 1108 | { 1109 | if (menu.ItemCount - 1 == param2) 1110 | { 1111 | char map[PLATFORM_MAX_PATH], buffer[255]; 1112 | menu.GetItem(param2, map, sizeof(map)); 1113 | if (strcmp(map, VOTE_EXTEND, false) == 0) 1114 | { 1115 | Format(buffer, sizeof(buffer), "%T", "Extend Map", param1); 1116 | return RedrawMenuItem(buffer); 1117 | } 1118 | else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) 1119 | { 1120 | Format(buffer, sizeof(buffer), "%T", "Dont Change", param1); 1121 | return RedrawMenuItem(buffer); 1122 | } 1123 | } 1124 | } 1125 | 1126 | case MenuAction_VoteCancel: 1127 | { 1128 | // If we receive 0 votes, pick at random. 1129 | if (param1 == VoteCancel_NoVotes && g_Cvar_NoVoteMode.BoolValue) 1130 | { 1131 | int count = menu.ItemCount; 1132 | char map[PLATFORM_MAX_PATH]; 1133 | menu.GetItem(0, map, sizeof(map)); 1134 | 1135 | // Make sure the first map in the menu isn't one of the special items. 1136 | // This would mean there are no real maps in the menu, because the special items are added after all maps. Don't do anything if that's the case. 1137 | if (strcmp(map, VOTE_EXTEND, false) != 0 && strcmp(map, VOTE_DONTCHANGE, false) != 0) 1138 | { 1139 | // Get a random map from the list. 1140 | int item = GetRandomInt(0, count - 1); 1141 | menu.GetItem(item, map, sizeof(map)); 1142 | 1143 | // Make sure it's not one of the special items. 1144 | while (strcmp(map, VOTE_EXTEND, false) == 0 || strcmp(map, VOTE_DONTCHANGE, false) == 0) 1145 | { 1146 | item = GetRandomInt(0, count - 1); 1147 | menu.GetItem(item, map, sizeof(map)); 1148 | } 1149 | 1150 | SetNextMap(map); 1151 | g_MapVoteCompleted = true; 1152 | } 1153 | } 1154 | else 1155 | { 1156 | // We were actually cancelled. I guess we do nothing. 1157 | } 1158 | 1159 | g_HasVoteStarted = false; 1160 | } 1161 | } 1162 | 1163 | return 0; 1164 | } 1165 | 1166 | public int Handler_NV_MapVoteMenu(NativeVote menu, MenuAction action, int param1, int param2) 1167 | { 1168 | switch (action) 1169 | { 1170 | case MenuAction_End: 1171 | { 1172 | g_VoteMenu = null; 1173 | menu.Close(); 1174 | } 1175 | 1176 | case MenuAction_DisplayItem: 1177 | { 1178 | if (menu.ItemCount - 1 == param2) 1179 | { 1180 | char map[PLATFORM_MAX_PATH], buffer[255]; 1181 | menu.GetItem(param2, map, sizeof(map)); 1182 | if (strcmp(map, VOTE_EXTEND, false) == 0) 1183 | { 1184 | Format(buffer, sizeof(buffer), "%T", "Extend Map", param1); 1185 | return view_as(NativeVotes_RedrawVoteItem(buffer)); 1186 | } 1187 | else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) 1188 | { 1189 | Format(buffer, sizeof(buffer), "%T", "Dont Change", param1); 1190 | return view_as(NativeVotes_RedrawVoteItem(buffer)); 1191 | } 1192 | } 1193 | } 1194 | 1195 | case MenuAction_VoteCancel: 1196 | { 1197 | // If we receive 0 votes, pick at random. 1198 | if (param1 == VoteCancel_NoVotes && g_Cvar_NoVoteMode.BoolValue) 1199 | { 1200 | int count = menu.ItemCount; 1201 | char map[PLATFORM_MAX_PATH]; 1202 | char displayName[PLATFORM_MAX_PATH]; 1203 | menu.GetItem(0, map, sizeof(map)); 1204 | 1205 | // Make sure the first map in the menu isn't one of the special items. 1206 | // This would mean there are no real maps in the menu, because the special items are added after all maps. Don't do anything if that's the case. 1207 | if (strcmp(map, VOTE_EXTEND, false) != 0 && strcmp(map, VOTE_DONTCHANGE, false) != 0) 1208 | { 1209 | // Get a random map from the list. 1210 | int item = GetRandomInt(0, count - 1); 1211 | menu.GetItem(item, map, sizeof(map), displayName, sizeof(displayName)); 1212 | 1213 | // Make sure it's not one of the special items. 1214 | while (strcmp(map, VOTE_EXTEND, false) == 0 || strcmp(map, VOTE_DONTCHANGE, false) == 0) 1215 | { 1216 | item = GetRandomInt(0, count - 1); 1217 | menu.GetItem(item, map, sizeof(map), displayName, sizeof(displayName)); 1218 | } 1219 | 1220 | SetNextMap(map); 1221 | g_MapVoteCompleted = true; 1222 | menu.DisplayPass(displayName); 1223 | } 1224 | } 1225 | else if (param1 == VoteCancel_NoVotes) 1226 | { 1227 | // We didn't have enough votes. Display the note enough votes fail message. 1228 | menu.DisplayFail(NativeVotesFail_NotEnoughVotes); 1229 | } 1230 | else 1231 | { 1232 | // We were actually cancelled. Display the generic fail message 1233 | menu.DisplayFail(NativeVotesFail_Generic); 1234 | } 1235 | 1236 | g_HasVoteStarted = false; 1237 | } 1238 | } 1239 | 1240 | return 0; 1241 | } 1242 | 1243 | public Action Timer_ChangeMap(Handle hTimer, DataPack dp) 1244 | { 1245 | g_ChangeMapInProgress = false; 1246 | 1247 | char map[PLATFORM_MAX_PATH]; 1248 | 1249 | if (dp == null) 1250 | { 1251 | if (!GetNextMap(map, sizeof(map))) 1252 | { 1253 | //No passed map and no set nextmap. fail! 1254 | return Plugin_Stop; 1255 | } 1256 | } 1257 | else 1258 | { 1259 | dp.Reset(); 1260 | dp.ReadString(map, sizeof(map)); 1261 | } 1262 | 1263 | ForceChangeLevel(map, "Map Vote"); 1264 | 1265 | return Plugin_Stop; 1266 | } 1267 | 1268 | bool RemoveStringFromArray(ArrayList array, char[] str) 1269 | { 1270 | int index = array.FindString(str); 1271 | if (index != -1) 1272 | { 1273 | array.Erase(index); 1274 | return true; 1275 | } 1276 | 1277 | return false; 1278 | } 1279 | 1280 | void CreateNextVote() 1281 | { 1282 | g_NextMapList.Clear(); 1283 | 1284 | char map[PLATFORM_MAX_PATH]; 1285 | // tempMaps is a resolved map list 1286 | ArrayList tempMaps = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); 1287 | 1288 | for (int i = 0; i < g_MapList.Length; i++) 1289 | { 1290 | g_MapList.GetString(i, map, sizeof(map)); 1291 | if (FindMap(map, map, sizeof(map)) != FindMap_NotFound) 1292 | { 1293 | tempMaps.PushString(map); 1294 | } 1295 | } 1296 | 1297 | //GetCurrentMap always returns a resolved map 1298 | GetCurrentMap(map, sizeof(map)); 1299 | RemoveStringFromArray(tempMaps, map); 1300 | 1301 | if (g_Cvar_ExcludeMaps.IntValue && tempMaps.Length > g_Cvar_ExcludeMaps.IntValue) 1302 | { 1303 | for (int i = 0; i < g_OldMapList.Length; i++) 1304 | { 1305 | g_OldMapList.GetString(i, map, sizeof(map)); 1306 | RemoveStringFromArray(tempMaps, map); 1307 | } 1308 | } 1309 | 1310 | int limit = (g_Cvar_IncludeMaps.IntValue < tempMaps.Length ? g_Cvar_IncludeMaps.IntValue : tempMaps.Length); 1311 | for (int i = 0; i < limit; i++) 1312 | { 1313 | int b = GetRandomInt(0, tempMaps.Length - 1); 1314 | tempMaps.GetString(b, map, sizeof(map)); 1315 | g_NextMapList.PushString(map); 1316 | tempMaps.Erase(b); 1317 | } 1318 | 1319 | delete tempMaps; 1320 | } 1321 | 1322 | bool CanVoteStart() 1323 | { 1324 | if (g_WaitingForVote || g_HasVoteStarted) 1325 | { 1326 | return false; 1327 | } 1328 | 1329 | return true; 1330 | } 1331 | 1332 | NominateResult InternalNominateMap(char[] map, bool force, int owner) 1333 | { 1334 | if (!IsMapValid(map)) 1335 | { 1336 | return Nominate_InvalidMap; 1337 | } 1338 | 1339 | /* Map already in the vote */ 1340 | if (g_NominateList.FindString(map) != -1) 1341 | { 1342 | return Nominate_AlreadyInVote; 1343 | } 1344 | 1345 | int index; 1346 | 1347 | /* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */ 1348 | if (owner && ((index = g_NominateOwners.FindValue(owner)) != -1)) 1349 | { 1350 | char oldmap[PLATFORM_MAX_PATH]; 1351 | g_NominateList.GetString(index, oldmap, sizeof(oldmap)); 1352 | Call_StartForward(g_NominationsResetForward); 1353 | Call_PushString(oldmap); 1354 | Call_PushCell(owner); 1355 | Call_Finish(); 1356 | 1357 | g_NominateList.SetString(index, map); 1358 | return Nominate_Replaced; 1359 | } 1360 | 1361 | /* Too many nominated maps. */ 1362 | int maxIncludes = 0; 1363 | if (g_NativeVotes) 1364 | { 1365 | maxIncludes = NativeVotes_GetMaxItems(); 1366 | 1367 | if (g_Cvar_IncludeMaps.IntValue < maxIncludes) 1368 | { 1369 | maxIncludes = g_Cvar_IncludeMaps.IntValue; 1370 | } 1371 | 1372 | if (g_Cvar_Extend.BoolValue && g_Extends < g_Cvar_Extend.IntValue) 1373 | { 1374 | maxIncludes--; 1375 | } 1376 | } 1377 | else 1378 | { 1379 | maxIncludes = g_Cvar_IncludeMaps.IntValue; 1380 | } 1381 | 1382 | if (g_NominateList.Length >= maxIncludes && !force) 1383 | { 1384 | return Nominate_VoteFull; 1385 | } 1386 | 1387 | g_NominateList.PushString(map); 1388 | g_NominateOwners.Push(owner); 1389 | 1390 | while (g_NominateList.Length > g_Cvar_IncludeMaps.IntValue) 1391 | { 1392 | char oldmap[PLATFORM_MAX_PATH]; 1393 | g_NominateList.GetString(0, oldmap, sizeof(oldmap)); 1394 | Call_StartForward(g_NominationsResetForward); 1395 | Call_PushString(oldmap); 1396 | Call_PushCell(g_NominateOwners.Get(0)); 1397 | Call_Finish(); 1398 | 1399 | g_NominateList.Erase(0); 1400 | g_NominateOwners.Erase(0); 1401 | } 1402 | 1403 | return Nominate_Added; 1404 | } 1405 | 1406 | /* Add natives to allow nominate and initiate vote to be call */ 1407 | 1408 | /* native NominateResult NominateMap(const char[] map, bool force, int owner); */ 1409 | public int Native_NominateMap(Handle plugin, int numParams) 1410 | { 1411 | int len; 1412 | GetNativeStringLength(1, len); 1413 | 1414 | if (len <= 0) 1415 | { 1416 | return false; 1417 | } 1418 | 1419 | char[] map = new char[len+1]; 1420 | GetNativeString(1, map, len+1); 1421 | 1422 | return view_as(InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3))); 1423 | } 1424 | 1425 | bool InternalRemoveNominationByMap(char[] map) 1426 | { 1427 | for (int i = 0; i < g_NominateList.Length; i++) 1428 | { 1429 | char oldmap[PLATFORM_MAX_PATH]; 1430 | g_NominateList.GetString(i, oldmap, sizeof(oldmap)); 1431 | 1432 | if(strcmp(map, oldmap, false) == 0) 1433 | { 1434 | Call_StartForward(g_NominationsResetForward); 1435 | Call_PushString(oldmap); 1436 | Call_PushCell(g_NominateOwners.Get(i)); 1437 | Call_Finish(); 1438 | 1439 | g_NominateList.Erase(i); 1440 | g_NominateOwners.Erase(i); 1441 | 1442 | return true; 1443 | } 1444 | } 1445 | 1446 | return false; 1447 | } 1448 | 1449 | /* native bool RemoveNominationByMap(const char[] map); */ 1450 | public int Native_RemoveNominationByMap(Handle plugin, int numParams) 1451 | { 1452 | int len; 1453 | GetNativeStringLength(1, len); 1454 | 1455 | if (len <= 0) 1456 | { 1457 | return false; 1458 | } 1459 | 1460 | char[] map = new char[len+1]; 1461 | GetNativeString(1, map, len+1); 1462 | 1463 | return InternalRemoveNominationByMap(map); 1464 | } 1465 | 1466 | bool InternalRemoveNominationByOwner(int owner) 1467 | { 1468 | int index; 1469 | 1470 | if (owner && ((index = g_NominateOwners.FindValue(owner)) != -1)) 1471 | { 1472 | char oldmap[PLATFORM_MAX_PATH]; 1473 | g_NominateList.GetString(index, oldmap, sizeof(oldmap)); 1474 | 1475 | Call_StartForward(g_NominationsResetForward); 1476 | Call_PushString(oldmap); 1477 | Call_PushCell(owner); 1478 | Call_Finish(); 1479 | 1480 | g_NominateList.Erase(index); 1481 | g_NominateOwners.Erase(index); 1482 | 1483 | return true; 1484 | } 1485 | 1486 | return false; 1487 | } 1488 | 1489 | /* native bool RemoveNominationByOwner(int owner); */ 1490 | public int Native_RemoveNominationByOwner(Handle plugin, int numParams) 1491 | { 1492 | return InternalRemoveNominationByOwner(GetNativeCell(1)); 1493 | } 1494 | 1495 | /* native void InitiateMapChooserVote(MapChange when, ArrayList inputarray=null); */ 1496 | public int Native_InitiateVote(Handle plugin, int numParams) 1497 | { 1498 | MapChange when = view_as(GetNativeCell(1)); 1499 | ArrayList inputarray = view_as(GetNativeCell(2)); 1500 | 1501 | LogAction(-1, -1, "Starting map vote because outside request"); 1502 | InitiateVote(when, inputarray); 1503 | } 1504 | 1505 | /* native bool CanMapChooserStartVote(); */ 1506 | public int Native_CanVoteStart(Handle plugin, int numParams) 1507 | { 1508 | return CanVoteStart(); 1509 | } 1510 | 1511 | /* native bool HasEndOfMapVoteFinished(); */ 1512 | public int Native_CheckVoteDone(Handle plugin, int numParams) 1513 | { 1514 | return g_MapVoteCompleted; 1515 | } 1516 | 1517 | /* native bool EndOfMapVoteEnabled(); */ 1518 | public int Native_EndOfMapVoteEnabled(Handle plugin, int numParams) 1519 | { 1520 | return g_Cvar_EndOfMapVote.BoolValue; 1521 | } 1522 | 1523 | /* native void GetExcludeMapList(ArrayList array); */ 1524 | public int Native_GetExcludeMapList(Handle plugin, int numParams) 1525 | { 1526 | ArrayList array = view_as(GetNativeCell(1)); 1527 | 1528 | if (array == null) 1529 | { 1530 | return; 1531 | } 1532 | int size = g_OldMapList.Length; 1533 | char map[PLATFORM_MAX_PATH]; 1534 | 1535 | for (int i=0; i(GetNativeCell(1)); 1548 | ArrayList ownerarray = view_as(GetNativeCell(2)); 1549 | 1550 | if (maparray == null) 1551 | return; 1552 | 1553 | char map[PLATFORM_MAX_PATH]; 1554 | 1555 | for (int i = 0; i < g_NominateList.Length; i++) 1556 | { 1557 | g_NominateList.GetString(i, map, sizeof(map)); 1558 | maparray.PushString(map); 1559 | 1560 | // If the optional parameter for an owner list was passed, then we need to fill that out as well 1561 | if(ownerarray != null) 1562 | { 1563 | int index = g_NominateOwners.Get(i); 1564 | ownerarray.Push(index); 1565 | } 1566 | } 1567 | 1568 | return; 1569 | } 1570 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/nativevotes_nominations.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * SourceMod NativeVotes Rock The Vote Plugin 5 | * Creates a map vote when the required number of players have requested one. 6 | * Updated with NativeVotes support 7 | * 8 | * NativeVotes (C)2011-2016 Ross Bemrose (Powerlord). All rights reserved. 9 | * SourceMod (C)2004-2014 AlliedModders LLC. All rights reserved. 10 | * ============================================================================= 11 | * 12 | * This program is free software; you can redistribute it and/or modify it under 13 | * the terms of the GNU General Public License, version 3.0, as published by the 14 | * Free Software Foundation. 15 | * 16 | * This program is distributed in the hope that it will be useful, but WITHOUT 17 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 18 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 19 | * details. 20 | * 21 | * You should have received a copy of the GNU General Public License along with 22 | * this program. If not, see . 23 | * 24 | * As a special exception, AlliedModders LLC gives you permission to link the 25 | * code of this program (as well as its derivative works) to "Half-Life 2," the 26 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 27 | * by the Valve Corporation. You must obey the GNU General Public License in 28 | * all respects for all other code used. Additionally, AlliedModders LLC grants 29 | * this exception to all derivative works. AlliedModders LLC defines further 30 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 31 | * or . 32 | * 33 | * Version: $Id$ 34 | */ 35 | 36 | #include 37 | #include 38 | 39 | #undef REQUIRE_PLUGIN 40 | #include 41 | #define REQUIRE_PLUGIN 42 | 43 | #pragma semicolon 1 44 | #pragma newdecls required 45 | 46 | #define VERSION "1.8.0 beta 4" 47 | 48 | public Plugin myinfo = 49 | { 50 | name = "NativeVotes Map Nominations", 51 | author = "AlliedModders LLC and Powerlord", 52 | description = "Provides Map Nominations", 53 | version = VERSION, 54 | url = "https://forums.alliedmods.net/showthread.php?t=208010" 55 | }; 56 | 57 | ConVar g_Cvar_ExcludeOld; 58 | ConVar g_Cvar_ExcludeCurrent; 59 | 60 | Menu g_MapMenu = null; 61 | ArrayList g_MapList = null; 62 | int g_mapFileSerial = -1; 63 | 64 | #define MAPSTATUS_ENABLED (1<<0) 65 | #define MAPSTATUS_DISABLED (1<<1) 66 | #define MAPSTATUS_EXCLUDE_CURRENT (1<<2) 67 | #define MAPSTATUS_EXCLUDE_PREVIOUS (1<<3) 68 | #define MAPSTATUS_EXCLUDE_NOMINATED (1<<4) 69 | 70 | StringMap g_mapTrie = null; 71 | 72 | // NativeVotes 73 | bool g_NativeVotes; 74 | bool g_RegisteredMenusChangeLevel = false; 75 | bool g_RegisteredMenusNextLevel = false; 76 | 77 | #define LIBRARY "nativevotes" 78 | 79 | public void OnPluginStart() 80 | { 81 | LoadTranslations("common.phrases"); 82 | LoadTranslations("nominations.phrases"); 83 | 84 | int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); 85 | g_MapList = new ArrayList(arraySize); 86 | 87 | CreateConVar("nativevotes_nominations_version", VERSION, "NativeVotes Nominations version", FCVAR_DONTRECORD|FCVAR_NOTIFY|FCVAR_SPONLY); 88 | 89 | g_Cvar_ExcludeOld = CreateConVar("sm_nominate_excludeold", "1", "Specifies if the current map should be excluded from the Nominations list", 0, true, 0.00, true, 1.0); 90 | g_Cvar_ExcludeCurrent = CreateConVar("sm_nominate_excludecurrent", "1", "Specifies if the MapChooser excluded maps should also be excluded from Nominations", 0, true, 0.00, true, 1.0); 91 | 92 | RegConsoleCmd("sm_nominate", Command_Nominate); 93 | 94 | RegAdminCmd("sm_nominate_addmap", Command_Addmap, ADMFLAG_CHANGEMAP, "sm_nominate_addmap - Forces a map to be on the next mapvote."); 95 | 96 | g_mapTrie = new StringMap(); 97 | } 98 | 99 | public void OnPluginEnd() 100 | { 101 | RemoveVoteHandler(); 102 | } 103 | 104 | public void OnAllPluginsLoaded() 105 | { 106 | if (FindPluginByFile("nominations.smx") != null) 107 | { 108 | LogMessage("Unloading mapchooser to prevent conflicts..."); 109 | ServerCommand("sm plugins unload nominations"); 110 | 111 | char oldPath[PLATFORM_MAX_PATH]; 112 | char newPath[PLATFORM_MAX_PATH]; 113 | 114 | BuildPath(Path_SM, oldPath, sizeof(oldPath), "plugins/nominations.smx"); 115 | BuildPath(Path_SM, newPath, sizeof(newPath), "plugins/disabled/nominations.smx"); 116 | if (RenameFile(newPath, oldPath)) 117 | { 118 | LogMessage("Moving nominations to disabled."); 119 | } 120 | } 121 | 122 | g_NativeVotes = LibraryExists(LIBRARY) && 123 | GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && 124 | NativeVotes_AreVoteCommandsSupported(); 125 | 126 | if (g_NativeVotes) 127 | RegisterVoteHandler(); 128 | } 129 | 130 | public void OnLibraryAdded(const char[] name) 131 | { 132 | if (StrEqual(name, LIBRARY, false) && 133 | GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && 134 | NativeVotes_AreVoteCommandsSupported()) 135 | { 136 | g_NativeVotes = true; 137 | RegisterVoteHandler(); 138 | } 139 | } 140 | 141 | public void OnLibraryRemoved(const char[] name) 142 | { 143 | if (StrEqual(name, LIBRARY, false)) 144 | { 145 | g_NativeVotes = false; 146 | RemoveVoteHandler(); 147 | } 148 | } 149 | 150 | public void OnConfigsExecuted() 151 | { 152 | if (ReadMapList(g_MapList, 153 | g_mapFileSerial, 154 | "nominations", 155 | MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) 156 | == null) 157 | { 158 | if (g_mapFileSerial == -1) 159 | { 160 | SetFailState("Unable to create a valid map list."); 161 | } 162 | } 163 | 164 | delete g_MapMenu; 165 | } 166 | 167 | public void OnNominationRemoved(const char[] map, int owner) 168 | { 169 | int status; 170 | 171 | char resolvedMap[PLATFORM_MAX_PATH]; 172 | FindMap(map, resolvedMap, sizeof(resolvedMap)); 173 | 174 | /* Is the map in our list? */ 175 | if (!g_mapTrie.GetValue(resolvedMap, status)) 176 | { 177 | return; 178 | } 179 | 180 | /* Was the map disabled due to being nominated */ 181 | if ((status & MAPSTATUS_EXCLUDE_NOMINATED) != MAPSTATUS_EXCLUDE_NOMINATED) 182 | { 183 | return; 184 | } 185 | 186 | g_mapTrie.SetValue(resolvedMap, MAPSTATUS_ENABLED); 187 | } 188 | 189 | public Action Command_Addmap(int client, int args) 190 | { 191 | if (args < 1) 192 | { 193 | ReplyToCommand(client, "[SM] Usage: sm_nominate_addmap "); 194 | return Plugin_Handled; 195 | } 196 | 197 | char mapname[PLATFORM_MAX_PATH]; 198 | char resolvedMap[PLATFORM_MAX_PATH]; 199 | GetCmdArg(1, mapname, sizeof(mapname)); 200 | 201 | if (FindMap(mapname, resolvedMap, sizeof(resolvedMap)) == FindMap_NotFound) 202 | { 203 | // We couldn't resolve the map entry to a filename, so... 204 | ReplyToCommand(client, "%t", "Map was not found", mapname); 205 | return Plugin_Handled; 206 | } 207 | 208 | char displayName[PLATFORM_MAX_PATH]; 209 | GetMapDisplayName(resolvedMap, displayName, sizeof(displayName)); 210 | 211 | int status; 212 | if (!g_mapTrie.GetValue(resolvedMap, status)) 213 | { 214 | ReplyToCommand(client, "%t", "Map was not found", displayName); 215 | return Plugin_Handled; 216 | } 217 | 218 | NominateResult result = NominateMap(resolvedMap, true, 0); 219 | 220 | if (result > Nominate_Replaced) 221 | { 222 | /* We assume already in vote is the casue because the maplist does a Map Validity check and we forced, so it can't be full */ 223 | ReplyToCommand(client, "%t", "Map Already In Vote", displayName); 224 | 225 | return Plugin_Handled; 226 | } 227 | 228 | 229 | g_mapTrie.SetValue(resolvedMap, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); 230 | 231 | 232 | ReplyToCommand(client, "%t", "Map Inserted", displayName); 233 | LogAction(client, -1, "\"%L\" inserted map \"%s\".", client, mapname); 234 | 235 | return Plugin_Handled; 236 | } 237 | 238 | public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs) 239 | { 240 | if (!client) 241 | { 242 | return; 243 | } 244 | 245 | if (strcmp(sArgs, "nominate", false) == 0) 246 | { 247 | ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); 248 | 249 | AttemptNominate(client); 250 | 251 | SetCmdReplySource(old); 252 | } 253 | } 254 | 255 | public Action Command_Nominate(int client, int args) 256 | { 257 | if (!client) 258 | { 259 | return Plugin_Handled; 260 | } 261 | 262 | if (args == 0) 263 | { 264 | AttemptNominate(client); 265 | return Plugin_Handled; 266 | } 267 | 268 | char mapname[PLATFORM_MAX_PATH]; 269 | GetCmdArg(1, mapname, sizeof(mapname)); 270 | 271 | return Internal_CommandNominate(client, mapname, false); 272 | } 273 | 274 | Action Internal_CommandNominate(int client, const char[] mapname, bool isVoteMenu) 275 | { 276 | char resolvedMap[PLATFORM_MAX_PATH]; 277 | 278 | if (FindMap(mapname, resolvedMap, sizeof(resolvedMap)) == FindMap_NotFound) 279 | { 280 | // We couldn't resolve the map entry to a filename, so... 281 | ReplyToCommand(client, "%t", "Map was not found", mapname); 282 | 283 | if (isVoteMenu && g_NativeVotes) 284 | { 285 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotFound); 286 | } 287 | return Plugin_Handled; 288 | } 289 | 290 | char displayName[PLATFORM_MAX_PATH]; 291 | GetMapDisplayName(resolvedMap, displayName, sizeof(displayName)); 292 | 293 | int status; 294 | if (!g_mapTrie.GetValue(resolvedMap, status)) 295 | { 296 | ReplyToCommand(client, "%t", "Map was not found", displayName); 297 | if (isVoteMenu && g_NativeVotes) 298 | { 299 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); 300 | } 301 | return Plugin_Handled; 302 | } 303 | 304 | if ((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) 305 | { 306 | if ((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT) 307 | { 308 | if (isVoteMenu && g_NativeVotes) 309 | { 310 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); 311 | } 312 | ReplyToCommand(client, "[SM] %t", "Can't Nominate Current Map"); 313 | } 314 | 315 | if ((status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS) 316 | { 317 | if (isVoteMenu && g_NativeVotes) 318 | { 319 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); 320 | } 321 | ReplyToCommand(client, "[SM] %t", "Map in Exclude List"); 322 | } 323 | 324 | if ((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED) 325 | { 326 | if (isVoteMenu && g_NativeVotes) 327 | { 328 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); 329 | } 330 | ReplyToCommand(client, "[SM] %t", "Map Already Nominated"); 331 | } 332 | 333 | return Plugin_Handled; 334 | } 335 | 336 | NominateResult result = NominateMap(resolvedMap, false, client); 337 | 338 | if (result > Nominate_Replaced) 339 | { 340 | if (result == Nominate_AlreadyInVote) 341 | { 342 | if (isVoteMenu && g_NativeVotes) 343 | { 344 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); 345 | } 346 | ReplyToCommand(client, "%t", "Map Already In Vote", displayName); 347 | } 348 | else 349 | { 350 | if (isVoteMenu && g_NativeVotes) 351 | { 352 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); 353 | } 354 | ReplyToCommand(client, "[SM] %t", "Map Already Nominated"); 355 | } 356 | 357 | return Plugin_Handled; 358 | } 359 | 360 | /* Map was nominated! - Disable the menu item and update the trie */ 361 | 362 | g_mapTrie.SetValue(resolvedMap, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); 363 | 364 | char name[MAX_NAME_LENGTH]; 365 | GetClientName(client, name, sizeof(name)); 366 | PrintToChatAll("[SM] %t", "Map Nominated", name, displayName); 367 | 368 | return Plugin_Handled; 369 | } 370 | 371 | void AttemptNominate(int client) 372 | { 373 | if (g_MapMenu == null) 374 | { 375 | BuildMapMenu(); 376 | } 377 | g_MapMenu.SetTitle("%T", "Nominate Title", client); 378 | g_MapMenu.Display(client, MENU_TIME_FOREVER); 379 | 380 | return; 381 | } 382 | 383 | void BuildMapMenu() 384 | { 385 | g_mapTrie.Clear(); 386 | 387 | g_MapMenu = new Menu(Handler_MapSelectMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem); 388 | 389 | char map[PLATFORM_MAX_PATH]; 390 | 391 | ArrayList excludeMaps; 392 | char currentMap[PLATFORM_MAX_PATH]; 393 | 394 | if (g_Cvar_ExcludeOld.BoolValue) 395 | { 396 | excludeMaps = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); 397 | GetExcludeMapList(excludeMaps); 398 | } 399 | 400 | if (g_Cvar_ExcludeCurrent.BoolValue) 401 | { 402 | GetCurrentMap(currentMap, sizeof(currentMap)); 403 | } 404 | 405 | for (int i = 0; i < g_MapList.Length; i++) 406 | { 407 | int status = MAPSTATUS_ENABLED; 408 | 409 | g_MapList.GetString(i, map, sizeof(map)); 410 | 411 | FindMap(map, map, sizeof(map)); 412 | 413 | char displayName[PLATFORM_MAX_PATH]; 414 | GetMapDisplayName(map, displayName, sizeof(displayName)); 415 | 416 | if (g_Cvar_ExcludeCurrent.BoolValue) 417 | { 418 | if (StrEqual(map, currentMap)) 419 | { 420 | status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_CURRENT; 421 | } 422 | } 423 | 424 | /* Dont bother with this check if the current map check passed */ 425 | if (g_Cvar_ExcludeOld.BoolValue && status == MAPSTATUS_ENABLED) 426 | { 427 | if (excludeMaps.FindString(map) != -1) 428 | { 429 | status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_PREVIOUS; 430 | } 431 | } 432 | 433 | g_MapMenu.AddItem(map, displayName); 434 | g_mapTrie.SetValue(map, status); 435 | } 436 | 437 | g_MapMenu.ExitButton = true; 438 | 439 | delete excludeMaps; 440 | } 441 | 442 | public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int param2) 443 | { 444 | switch (action) 445 | { 446 | case MenuAction_Select: 447 | { 448 | char map[PLATFORM_MAX_PATH], name[MAX_NAME_LENGTH], displayName[PLATFORM_MAX_PATH]; 449 | menu.GetItem(param2, map, sizeof(map), _, displayName, sizeof(displayName)); 450 | 451 | GetClientName(param1, name, sizeof(name)); 452 | 453 | NominateResult result = NominateMap(map, false, param1); 454 | 455 | /* Don't need to check for InvalidMap because the menu did that already */ 456 | if (result == Nominate_AlreadyInVote) 457 | { 458 | PrintToChat(param1, "[SM] %t", "Map Already Nominated"); 459 | return 0; 460 | } 461 | else if (result == Nominate_VoteFull) 462 | { 463 | PrintToChat(param1, "[SM] %t", "Max Nominations"); 464 | return 0; 465 | } 466 | 467 | g_mapTrie.SetValue(map, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); 468 | 469 | if (result == Nominate_Replaced) 470 | { 471 | PrintToChatAll("[SM] %t", "Map Nomination Changed", name, displayName); 472 | return 0; 473 | } 474 | 475 | PrintToChatAll("[SM] %t", "Map Nominated", name, displayName); 476 | } 477 | 478 | case MenuAction_DrawItem: 479 | { 480 | char map[PLATFORM_MAX_PATH]; 481 | menu.GetItem(param2, map, sizeof(map)); 482 | 483 | int status; 484 | 485 | if (!g_mapTrie.GetValue(map, status)) 486 | { 487 | LogError("Menu selection of item not in trie. Major logic problem somewhere."); 488 | return ITEMDRAW_DEFAULT; 489 | } 490 | 491 | if ((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) 492 | { 493 | return ITEMDRAW_DISABLED; 494 | } 495 | 496 | return ITEMDRAW_DEFAULT; 497 | 498 | } 499 | 500 | case MenuAction_DisplayItem: 501 | { 502 | char map[PLATFORM_MAX_PATH], displayName[PLATFORM_MAX_PATH]; 503 | menu.GetItem(param2, map, sizeof(map), _, displayName, sizeof(displayName)); 504 | 505 | int status; 506 | 507 | if (!g_mapTrie.GetValue(map, status)) 508 | { 509 | LogError("Menu selection of item not in trie. Major logic problem somewhere."); 510 | return 0; 511 | } 512 | 513 | char display[PLATFORM_MAX_PATH + 64]; 514 | 515 | if ((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) 516 | { 517 | if ((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT) 518 | { 519 | Format(display, sizeof(display), "%s (%T)", displayName, "Current Map", param1); 520 | return RedrawMenuItem(display); 521 | } 522 | 523 | if ((status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS) 524 | { 525 | Format(display, sizeof(display), "%s (%T)", displayName, "Recently Played", param1); 526 | return RedrawMenuItem(display); 527 | } 528 | 529 | if ((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED) 530 | { 531 | Format(display, sizeof(display), "%s (%T)", displayName, "Nominated", param1); 532 | return RedrawMenuItem(display); 533 | } 534 | } 535 | 536 | return 0; 537 | } 538 | } 539 | 540 | return 0; 541 | } 542 | 543 | void RegisterVoteHandler() 544 | { 545 | if (!g_NativeVotes) 546 | return; 547 | 548 | if (!g_RegisteredMenusNextLevel) 549 | { 550 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_NextLevel, Menu_Nominate); 551 | g_RegisteredMenusNextLevel = true; 552 | } 553 | 554 | if (!g_RegisteredMenusChangeLevel) 555 | { 556 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_ChgLevel, Menu_Nominate); 557 | g_RegisteredMenusChangeLevel = true; 558 | } 559 | } 560 | 561 | void RemoveVoteHandler() 562 | { 563 | if (g_RegisteredMenusNextLevel) 564 | { 565 | if (g_NativeVotes) 566 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_NextLevel, Menu_Nominate); 567 | 568 | g_RegisteredMenusNextLevel = false; 569 | } 570 | 571 | if (g_RegisteredMenusChangeLevel) 572 | { 573 | if (g_NativeVotes) 574 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_ChgLevel, Menu_Nominate); 575 | 576 | g_RegisteredMenusChangeLevel = false; 577 | } 578 | 579 | } 580 | 581 | public Action Menu_Nominate(int client, NativeVotesOverride overrideType, const char[] voteArgument) 582 | { 583 | if (!client || NativeVotes_IsVoteInProgress()) 584 | { 585 | return Plugin_Handled; 586 | } 587 | 588 | if (strlen(voteArgument) == 0) 589 | { 590 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_SpecifyMap); 591 | return Plugin_Handled; 592 | } 593 | 594 | ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); 595 | 596 | Action myReturn = Internal_CommandNominate(client, voteArgument, true); 597 | 598 | SetCmdReplySource(old); 599 | 600 | return myReturn; 601 | } 602 | 603 | public Action NativeVotes_OverrideMaps(StringMap mapList) 604 | { 605 | if (g_MapMenu == null) 606 | { 607 | BuildMapMenu(); 608 | } 609 | 610 | if (g_mapTrie.Size == 0) 611 | { 612 | LogMessage("No maps loaded."); 613 | return Plugin_Continue; 614 | } 615 | 616 | // We don't care about the current list, replace it with our own 617 | mapList.Clear(); 618 | 619 | StringMapSnapshot snapshot = g_mapTrie.Snapshot(); 620 | int length = snapshot.Length; 621 | 622 | for (int i = 0; i < length; i++) 623 | { 624 | int size = snapshot.KeyBufferSize(i); 625 | char[] map = new char[size]; 626 | snapshot.GetKey(i, map, size); 627 | 628 | int flags; 629 | 630 | if (!g_mapTrie.GetValue(map, flags) || flags & MAPSTATUS_DISABLED == MAPSTATUS_DISABLED) 631 | { 632 | continue; 633 | } 634 | 635 | char displayName[PLATFORM_MAX_PATH]; 636 | GetMapDisplayName(map, displayName, sizeof(displayName)); 637 | 638 | // Display name first 639 | mapList.SetString(displayName, map); 640 | } 641 | 642 | delete snapshot; 643 | 644 | if (mapList.Size > 0) 645 | { 646 | return Plugin_Changed; 647 | } 648 | 649 | return Plugin_Continue; 650 | } 651 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/nativevotes_rockthevote.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * SourceMod NativeVotes Rock The Vote Plugin 5 | * Creates a map vote when the required number of players have requested one. 6 | * Updated with NativeVotes support 7 | * 8 | * NativeVotes (C)2011-2016 Ross Bemrose (Powerlord). All rights reserved. 9 | * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. 10 | * ============================================================================= 11 | * 12 | * This program is free software; you can redistribute it and/or modify it under 13 | * the terms of the GNU General Public License, version 3.0, as published by the 14 | * Free Software Foundation. 15 | * 16 | * This program is distributed in the hope that it will be useful, but WITHOUT 17 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 18 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 19 | * details. 20 | * 21 | * You should have received a copy of the GNU General Public License along with 22 | * this program. If not, see . 23 | * 24 | * As a special exception, AlliedModders LLC gives you permission to link the 25 | * code of this program (as well as its derivative works) to "Half-Life 2," the 26 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 27 | * by the Valve Corporation. You must obey the GNU General Public License in 28 | * all respects for all other code used. Additionally, AlliedModders LLC grants 29 | * this exception to all derivative works. AlliedModders LLC defines further 30 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 31 | * or . 32 | * 33 | * Version: $Id$ 34 | */ 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | #undef REQUIRE_PLUGIN 41 | #include 42 | #define REQUIRE_PLUGIN 43 | 44 | // Despite being labeled as TF2-only, this plugin does work on other games. 45 | // It's just identical to rockthevote.smx there 46 | #undef REQUIRE_EXTENSIONS 47 | #include 48 | #define REQUIRE_EXTENSIONS 49 | 50 | #pragma semicolon 1 51 | #pragma newdecls required 52 | 53 | #define VERSION "1.8.0 beta 1" 54 | 55 | public Plugin myinfo = 56 | { 57 | name = "NativeVotes Rock The Vote", 58 | author = "AlliedModders LLC and Powerlord", 59 | description = "Provides RTV Map Voting", 60 | version = VERSION, 61 | url = "https://forums.alliedmods.net/showthread.php?t=208010" 62 | }; 63 | 64 | ConVar g_Cvar_Needed; 65 | ConVar g_Cvar_MinPlayers; 66 | ConVar g_Cvar_InitialDelay; 67 | ConVar g_Cvar_Interval; 68 | ConVar g_Cvar_ChangeTime; 69 | ConVar g_Cvar_RTVPostVoteAction; 70 | 71 | bool g_CanRTV = false; // True if RTV loaded maps and is active. 72 | bool g_RTVAllowed = false; // True if RTV is available to players. Used to delay rtv votes. 73 | int g_Voters = 0; // Total voters connected. Doesn't include fake clients. 74 | int g_Votes = 0; // Total number of "say rtv" votes 75 | int g_VotesNeeded = 0; // Necessary votes before map vote begins. (voters * percent_needed) 76 | bool g_Voted[MAXPLAYERS+1] = {false, ...}; 77 | 78 | bool g_InChange = false; 79 | 80 | // NativeVotes 81 | bool g_NativeVotes; 82 | bool g_RegisteredMenusChangeLevel = false; 83 | int g_RTVTime = 0; 84 | bool g_Warmup = false; 85 | 86 | #define LIBRARY "nativevotes" 87 | 88 | 89 | public void OnPluginStart() 90 | { 91 | LoadTranslations("common.phrases"); 92 | LoadTranslations("rockthevote.phrases"); 93 | 94 | g_Cvar_Needed = CreateConVar("sm_rtv_needed", "0.60", "Percentage of players needed to rockthevote (Def 60%)", 0, true, 0.05, true, 1.0); 95 | g_Cvar_MinPlayers = CreateConVar("sm_rtv_minplayers", "0", "Number of players required before RTV will be enabled.", 0, true, 0.0, true, float(MAXPLAYERS)); 96 | g_Cvar_InitialDelay = CreateConVar("sm_rtv_initialdelay", "30.0", "Time (in seconds) before first RTV can be held", 0, true, 0.00); 97 | g_Cvar_Interval = CreateConVar("sm_rtv_interval", "240.0", "Time (in seconds) after a failed RTV before another can be held", 0, true, 0.00); 98 | g_Cvar_ChangeTime = CreateConVar("sm_rtv_changetime", "0", "When to change the map after a succesful RTV: 0 - Instant, 1 - RoundEnd, 2 - MapEnd", _, true, 0.0, true, 2.0); 99 | g_Cvar_RTVPostVoteAction = CreateConVar("sm_rtv_postvoteaction", "0", "What to do with RTV's after a mapvote has completed. 0 - Allow, success = instant change, 1 - Deny", _, true, 0.0, true, 1.0); 100 | 101 | RegConsoleCmd("sm_rtv", Command_RTV); 102 | 103 | AutoExecConfig(true, "rtv"); 104 | } 105 | 106 | public void OnPluginEnd() 107 | { 108 | RemoveVoteHandler(); 109 | } 110 | 111 | public void OnAllPluginsLoaded() 112 | { 113 | if (FindPluginByFile("rockthevote.smx") != null) 114 | { 115 | LogMessage("Unloading rockthevote to prevent conflicts..."); 116 | ServerCommand("sm plugins unload rockthevote"); 117 | 118 | char oldPath[PLATFORM_MAX_PATH]; 119 | char newPath[PLATFORM_MAX_PATH]; 120 | 121 | BuildPath(Path_SM, oldPath, sizeof(oldPath), "plugins/rockthevote.smx"); 122 | BuildPath(Path_SM, newPath, sizeof(newPath), "plugins/disabled/rockthevote.smx"); 123 | if (RenameFile(newPath, oldPath)) 124 | { 125 | LogMessage("Moving rockthevote to disabled."); 126 | } 127 | } 128 | 129 | g_NativeVotes = LibraryExists(LIBRARY) && 130 | GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && 131 | NativeVotes_AreVoteCommandsSupported(); 132 | 133 | if (g_NativeVotes) 134 | RegisterVoteHandler(); 135 | } 136 | 137 | public void OnLibraryAdded(const char[] name) 138 | { 139 | if (StrEqual(name, LIBRARY, false) && 140 | GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && 141 | NativeVotes_AreVoteCommandsSupported()) 142 | { 143 | g_NativeVotes = true; 144 | RegisterVoteHandler(); 145 | } 146 | } 147 | 148 | public void OnLibraryRemoved(const char[] name) 149 | { 150 | if (StrEqual(name, LIBRARY, false)) 151 | { 152 | g_NativeVotes = false; 153 | RemoveVoteHandler(); 154 | } 155 | } 156 | 157 | public void TF2_OnWaitingForPlayersStart() 158 | { 159 | g_Warmup = true; 160 | } 161 | 162 | public void TF2_OnWaitingForPlayersEnd() 163 | { 164 | g_Warmup = false; 165 | } 166 | 167 | public void OnMapStart() 168 | { 169 | g_Voters = 0; 170 | g_Votes = 0; 171 | g_VotesNeeded = 0; 172 | g_InChange = false; 173 | 174 | /* Handle late load */ 175 | for (int i=1; i<=MaxClients; i++) 176 | { 177 | if (IsClientConnected(i)) 178 | { 179 | OnClientConnected(i); 180 | } 181 | } 182 | } 183 | 184 | public void OnMapEnd() 185 | { 186 | g_Warmup = false; 187 | g_CanRTV = false; 188 | g_RTVAllowed = false; 189 | } 190 | 191 | public void OnConfigsExecuted() 192 | { 193 | g_CanRTV = true; 194 | g_RTVAllowed = false; 195 | g_RTVTime = GetTime() + g_Cvar_Interval.IntValue; 196 | CreateTimer(g_Cvar_InitialDelay.FloatValue, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE); 197 | } 198 | 199 | public void OnClientConnected(int client) 200 | { 201 | if(IsFakeClient(client)) 202 | return; 203 | 204 | g_Voted[client] = false; 205 | 206 | g_Voters++; 207 | g_VotesNeeded = RoundToFloor(float(g_Voters) * g_Cvar_Needed.FloatValue); 208 | 209 | return; 210 | } 211 | 212 | public void OnClientDisconnect(int client) 213 | { 214 | if(IsFakeClient(client)) 215 | return; 216 | 217 | if(g_Voted[client]) 218 | { 219 | g_Votes--; 220 | } 221 | 222 | g_Voters--; 223 | 224 | g_VotesNeeded = RoundToFloor(float(g_Voters) * g_Cvar_Needed.FloatValue); 225 | 226 | if (!g_CanRTV) 227 | { 228 | return; 229 | } 230 | 231 | if (g_Votes && 232 | g_Voters && 233 | g_Votes >= g_VotesNeeded && 234 | g_RTVAllowed ) 235 | { 236 | if (g_Cvar_RTVPostVoteAction.IntValue == 1 && HasEndOfMapVoteFinished()) 237 | { 238 | return; 239 | } 240 | 241 | StartRTV(); 242 | } 243 | } 244 | 245 | public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs) 246 | { 247 | if (!g_CanRTV || !client) 248 | { 249 | return; 250 | } 251 | 252 | if (strcmp(sArgs, "rtv", false) == 0 || strcmp(sArgs, "rockthevote", false) == 0) 253 | { 254 | ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); 255 | 256 | AttemptRTV(client); 257 | 258 | SetCmdReplySource(old); 259 | } 260 | } 261 | 262 | public Action Command_RTV(int client, int args) 263 | { 264 | if (!g_CanRTV || !client) 265 | { 266 | return Plugin_Handled; 267 | } 268 | 269 | AttemptRTV(client); 270 | 271 | return Plugin_Handled; 272 | } 273 | 274 | void AttemptRTV(int client, bool isVoteMenu=false) 275 | { 276 | if (!g_RTVAllowed || (g_Cvar_RTVPostVoteAction.IntValue == 1 && HasEndOfMapVoteFinished())) 277 | { 278 | ReplyToCommand(client, "[SM] %t", "RTV Not Allowed"); 279 | if (isVoteMenu && g_NativeVotes) 280 | { 281 | if (g_Warmup) 282 | { 283 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Waiting); 284 | } 285 | else 286 | { 287 | int timeleft = g_RTVTime - GetTime(); 288 | if (timeleft > 0) 289 | { 290 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Failed, timeleft); 291 | } 292 | else 293 | { 294 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Generic); 295 | } 296 | } 297 | } 298 | return; 299 | } 300 | 301 | if (!CanMapChooserStartVote()) 302 | { 303 | ReplyToCommand(client, "[SM] %t", "RTV Started"); 304 | return; 305 | } 306 | 307 | if (GetClientCount(true) < g_Cvar_MinPlayers.IntValue) 308 | { 309 | ReplyToCommand(client, "[SM] %t", "Minimal Players Not Met"); 310 | if (isVoteMenu && g_NativeVotes) 311 | { 312 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Loading); 313 | } 314 | return; 315 | } 316 | 317 | if (g_Voted[client]) 318 | { 319 | ReplyToCommand(client, "[SM] %t", "Already Voted", g_Votes, g_VotesNeeded); 320 | if (isVoteMenu && g_NativeVotes) 321 | { 322 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_AlreadyActive); 323 | } 324 | return; 325 | } 326 | 327 | char name[MAX_NAME_LENGTH]; 328 | GetClientName(client, name, sizeof(name)); 329 | 330 | g_Votes++; 331 | g_Voted[client] = true; 332 | 333 | PrintToChatAll("[SM] %t", "RTV Requested", name, g_Votes, g_VotesNeeded); 334 | 335 | if (g_Votes >= g_VotesNeeded) 336 | { 337 | StartRTV(); 338 | } 339 | } 340 | 341 | public Action Timer_DelayRTV(Handle timer) 342 | { 343 | g_RTVAllowed = true; 344 | } 345 | 346 | void StartRTV() 347 | { 348 | if (g_InChange) 349 | { 350 | return; 351 | } 352 | 353 | if (EndOfMapVoteEnabled() && HasEndOfMapVoteFinished()) 354 | { 355 | /* Change right now then */ 356 | char map[PLATFORM_MAX_PATH]; 357 | if (GetNextMap(map, sizeof(map))) 358 | { 359 | GetMapDisplayName(map, map, sizeof(map)); 360 | 361 | PrintToChatAll("[SM] %t", "Changing Maps", map); 362 | CreateTimer(5.0, Timer_ChangeMap, _, TIMER_FLAG_NO_MAPCHANGE); 363 | g_InChange = true; 364 | 365 | ResetRTV(); 366 | 367 | g_RTVAllowed = false; 368 | } 369 | return; 370 | } 371 | 372 | if (CanMapChooserStartVote()) 373 | { 374 | MapChange when = view_as(g_Cvar_ChangeTime.IntValue); 375 | InitiateMapChooserVote(when); 376 | 377 | ResetRTV(); 378 | 379 | g_RTVAllowed = false; 380 | g_RTVTime = GetTime() + g_Cvar_Interval.IntValue; 381 | 382 | CreateTimer(g_Cvar_Interval.FloatValue, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE); 383 | } 384 | } 385 | 386 | void ResetRTV() 387 | { 388 | g_Votes = 0; 389 | 390 | for (int i=1; i<=MAXPLAYERS; i++) 391 | { 392 | g_Voted[i] = false; 393 | } 394 | } 395 | 396 | public Action Timer_ChangeMap(Handle hTimer) 397 | { 398 | g_InChange = false; 399 | 400 | LogMessage("RTV changing map manually"); 401 | 402 | char map[PLATFORM_MAX_PATH]; 403 | if (GetNextMap(map, sizeof(map))) 404 | { 405 | ForceChangeLevel(map, "RTV after mapvote"); 406 | } 407 | 408 | return Plugin_Stop; 409 | } 410 | 411 | void RegisterVoteHandler() 412 | { 413 | if (!g_NativeVotes) 414 | return; 415 | 416 | if (!g_RegisteredMenusChangeLevel) 417 | { 418 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_ChgLevel, Menu_RTV); 419 | g_RegisteredMenusChangeLevel = true; 420 | } 421 | } 422 | 423 | void RemoveVoteHandler() 424 | { 425 | if (g_RegisteredMenusChangeLevel) 426 | { 427 | if (g_NativeVotes) 428 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_ChgLevel, Menu_RTV); 429 | 430 | g_RegisteredMenusChangeLevel = false; 431 | } 432 | 433 | } 434 | 435 | public Action Menu_RTV(int client, NativeVotesOverride overrideType, const char[] voteArgument) 436 | { 437 | if (!client || NativeVotes_IsVoteInProgress()) 438 | { 439 | return Plugin_Handled; 440 | } 441 | 442 | if (strlen(voteArgument) == 0) 443 | { 444 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_SpecifyMap); 445 | return Plugin_Handled; 446 | } 447 | 448 | ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); 449 | 450 | AttemptRTV(client, true); 451 | 452 | SetCmdReplySource(old); 453 | 454 | return Plugin_Handled; 455 | } 456 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/nativevotes_votemanager_test.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * NativeVotes VoteManager Test 5 | * Test the VoteManger functionality of NativeVotes 6 | * 7 | * NativeVotes VoteManager Test (C)2014 Powerlord (Ross Bemrose). All rights 8 | * reserved. 9 | * ============================================================================= 10 | * 11 | * This program is free software; you can redistribute it and/or modify it under 12 | * the terms of the GNU General Public License, version 3.0, as published by the 13 | * Free Software Foundation. 14 | * 15 | * This program is distributed in the hope that it will be useful, but WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 18 | * details. 19 | * 20 | * You should have received a copy of the GNU General Public License along with 21 | * this program. If not, see . 22 | * 23 | * As a special exception, AlliedModders LLC gives you permission to link the 24 | * code of this program (as well as its derivative works) to "Half-Life 2," the 25 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 26 | * by the Valve Corporation. You must obey the GNU General Public License in 27 | * all respects for all other code used. Additionally, AlliedModders LLC grants 28 | * this exception to all derivative works. AlliedModders LLC defines further 29 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 30 | * or . 31 | * 32 | * Version: $Id$ 33 | */ 34 | #include 35 | 36 | #pragma newdecls required 37 | #include "include/nativevotes" // Not optional 38 | 39 | #pragma semicolon 1 40 | 41 | #define VERSION "1.2.0" 42 | 43 | ConVar g_Cvar_Enabled; 44 | 45 | public Plugin myinfo = { 46 | name = "NativeVotes VoteManager Test", 47 | author = "Powerlord", 48 | description = "Test the VoteManger functionality of NativeVotes", 49 | version = VERSION, 50 | url = "https://forums.alliedmods.net/showthread.php?t=208008" 51 | }; 52 | 53 | public void OnPluginStart() 54 | { 55 | CreateConVar("nativevotes_votemanagertest_version", VERSION, "NativeVotes VoteManager Test version", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); 56 | g_Cvar_Enabled = CreateConVar("nativevotes_votemanagertest_enable", "1", "Enable NativeVotes VoteManager Test?", FCVAR_NOTIFY|FCVAR_DONTRECORD, true, 0.0, true, 1.0); 57 | HookConVarChange(g_Cvar_Enabled, EnabledChanged); 58 | } 59 | 60 | public void OnAllPluginsLoaded() 61 | { 62 | if (g_Cvar_Enabled.BoolValue) 63 | { 64 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_Restart, CallVoteTestHandler); 65 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_Kick, CallKickVoteHandler); 66 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_Scramble, CallVoteAdminTestHandler, CallVoteAdminVisHandler); 67 | } 68 | } 69 | 70 | public void OnPluginEnd() 71 | { 72 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Restart, CallVoteTestHandler); 73 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Kick, CallKickVoteHandler); 74 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Scramble, CallVoteAdminTestHandler, CallVoteAdminVisHandler); 75 | } 76 | 77 | public void EnabledChanged(ConVar convar, const char[] oldValue, const char[] newValue) 78 | { 79 | if (convar.BoolValue) 80 | { 81 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_Restart, CallVoteTestHandler); 82 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_Kick, CallKickVoteHandler); 83 | NativeVotes_RegisterVoteCommand(NativeVotesOverride_Scramble, CallVoteAdminTestHandler, CallVoteAdminVisHandler); 84 | } 85 | else 86 | { 87 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Restart, CallVoteTestHandler); 88 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Kick, CallKickVoteHandler); 89 | NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Scramble, CallVoteAdminTestHandler, CallVoteAdminVisHandler); 90 | } 91 | } 92 | 93 | public Action CallVoteTestHandler(int client, NativeVotesOverride overrideType) 94 | { 95 | ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); 96 | 97 | ReplyToCommand(client, "Attempted to call Restart vote"); 98 | 99 | SetCmdReplySource(old); 100 | 101 | return Plugin_Handled; 102 | } 103 | 104 | public Action CallVoteAdminVisHandler(int client, NativeVotesOverride overrideType) 105 | { 106 | if (CheckCommandAccess(client, "adminvotetest", ADMFLAG_VOTE, true)) 107 | { 108 | return Plugin_Continue; 109 | } 110 | 111 | return Plugin_Handled; 112 | } 113 | 114 | public Action CallVoteAdminTestHandler(int client, NativeVotesOverride overrideType) 115 | { 116 | ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); 117 | 118 | ReplyToCommand(client, "Attempted to call Admin-protected Scramble vote"); 119 | 120 | SetCmdReplySource(old); 121 | 122 | return Plugin_Handled; 123 | } 124 | 125 | public Action CallKickVoteHandler(int client, NativeVotesOverride overrideType, const char[] voteArgument, NativeVotesKickType kickType, int target) 126 | { 127 | ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); 128 | 129 | int targetClient = GetClientOfUserId(target); 130 | 131 | char sKickType[32]; 132 | NativeVotesType voteType = GetKickVoteTypeFromKickType(kickType, sKickType, sizeof(sKickType)); 133 | 134 | if (voteType == NativeVotesType_None) 135 | { 136 | ReplyToCommand(client, "No kick type found"); 137 | return Plugin_Handled; 138 | } 139 | 140 | if (targetClient == 0) 141 | { 142 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_PlayerNotFound, target); 143 | ReplyToCommand(client, "Attempted to call Kick (%s) vote on unknown userid %d", sKickType, target); 144 | return Plugin_Handled; 145 | } 146 | 147 | if (!CanUserTarget(client, targetClient)) 148 | { 149 | NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_CantKickAdmin, target); 150 | ReplyToCommand(client, "Attempted to call Kick (%s) vote on %N, but they have a higher immunity level than you.", sKickType, targetClient); 151 | return Plugin_Handled; 152 | } 153 | 154 | ReplyToCommand(client, "Calling Kick (%s) vote on %N", sKickType, targetClient); 155 | 156 | NativeVote vote = new NativeVote(KickVoteHandler, voteType); 157 | vote.Initiator = client; 158 | vote.SetTarget(targetClient); 159 | vote.DisplayVoteToAll(20); 160 | 161 | SetCmdReplySource(old); 162 | 163 | return Plugin_Handled; 164 | } 165 | 166 | public int KickVoteHandler(NativeVote vote, MenuAction action, int param1, int param2) 167 | { 168 | switch (action) 169 | { 170 | case MenuAction_End: 171 | { 172 | vote.Close(); 173 | } 174 | 175 | case MenuAction_VoteEnd: 176 | { 177 | int target = vote.GetTarget(); 178 | 179 | if (param1 == NATIVEVOTES_VOTE_YES) 180 | { 181 | if (target == 0) 182 | { 183 | vote.DisplayFail(NativeVotesFail_Generic); 184 | PrintToChatAll("User disconnected before kick."); 185 | } 186 | char name[MAX_NAME_LENGTH+1]; 187 | GetClientName(target, name, sizeof(name)); 188 | vote.DisplayPass(name); 189 | PrintToChatAll("Kick vote on %N passed.", target); 190 | } 191 | else 192 | { 193 | vote.DisplayFail(NativeVotesFail_Loses); 194 | PrintToChatAll("Kick vote failed."); 195 | } 196 | } 197 | 198 | case MenuAction_VoteCancel: 199 | { 200 | if (param1 == VoteCancel_NoVotes) 201 | { 202 | vote.DisplayFail(NativeVotesFail_NotEnoughVotes); 203 | PrintToChatAll("Kick vote had no votes."); 204 | } 205 | else 206 | { 207 | vote.DisplayFail(NativeVotesFail_Generic); 208 | PrintToChatAll("Kick vote was cancelled."); 209 | } 210 | } 211 | } 212 | } 213 | 214 | NativeVotesType GetKickVoteTypeFromKickType(NativeVotesKickType kickType, char[] sKickType, int maxlength) 215 | { 216 | NativeVotesType voteType; 217 | 218 | switch (kickType) 219 | { 220 | case NativeVotesKickType_Generic: 221 | { 222 | strcopy(sKickType, maxlength, "Generic"); 223 | voteType = NativeVotesType_Kick; 224 | } 225 | 226 | case NativeVotesKickType_Idle: 227 | { 228 | strcopy(sKickType, maxlength, "Idle"); 229 | voteType = NativeVotesType_KickIdle; 230 | } 231 | 232 | case NativeVotesKickType_Scamming: 233 | { 234 | strcopy(sKickType, maxlength, "Scamming"); 235 | voteType = NativeVotesType_KickScamming; 236 | } 237 | 238 | case NativeVotesKickType_Cheating: 239 | { 240 | strcopy(sKickType, maxlength, "Cheating"); 241 | voteType = NativeVotesType_KickCheating; 242 | } 243 | 244 | default: 245 | { 246 | strcopy(sKickType, maxlength, ""); 247 | voteType = NativeVotesType_None; 248 | } 249 | } 250 | 251 | return voteType; 252 | } -------------------------------------------------------------------------------- /addons/sourcemod/scripting/nativevotes_votetest.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * NativeVotes Vote Tester 5 | * Copyright (C) 2011-2013 Ross Bemrose (Powerlord). All rights reserved. 6 | * ============================================================================= 7 | * 8 | * This program is free software; you can redistribute it and/or modify it under 9 | * the terms of the GNU General Public License, version 3.0, as published by the 10 | * Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, but WITHOUT 13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 15 | * details. 16 | * 17 | * You should have received a copy of the GNU General Public License along with 18 | * this program. If not, see . 19 | * 20 | * As a special exception, AlliedModders LLC gives you permission to link the 21 | * code of this program (as well as its derivative works) to "Half-Life 2," the 22 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 23 | * by the Valve Corporation. You must obey the GNU General Public License in 24 | * all respects for all other code used. Additionally, AlliedModders LLC grants 25 | * this exception to all derivative works. AlliedModders LLC defines further 26 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 27 | * or . 28 | * 29 | * Version: $Id$ 30 | */ 31 | 32 | #include 33 | 34 | #pragma newdecls required 35 | #include "include/nativevotes" 36 | 37 | #define VERSION "1.1" 38 | 39 | public Plugin myinfo = 40 | { 41 | name = "NativeVotes Vote Tester", 42 | author = "Powerlord", 43 | description = "Various NativeVotes vote type tests", 44 | version = "1.0", 45 | url = "https://forums.alliedmods.net/showthread.php?t=208008" 46 | } 47 | 48 | public void OnPluginStart() 49 | { 50 | CreateConVar("nativevotestest_version", VERSION, "NativeVotes Vote Tester version", FCVAR_NOTIFY|FCVAR_DONTRECORD); 51 | 52 | RegAdminCmd("voteyesno", Cmd_TestYesNo, ADMFLAG_VOTE, "Test Yes/No votes"); 53 | RegAdminCmd("votemult", Cmd_TestMult, ADMFLAG_VOTE, "Test Multiple Choice votes"); 54 | RegAdminCmd("voteyesnocustom", Cmd_TestYesNoCustom, ADMFLAG_VOTE, "Test Multiple Choice vote with Custom Display text"); 55 | RegAdminCmd("votemultcustom", Cmd_TestMultCustom, ADMFLAG_VOTE, "Test Multiple Choice vote with Custom Display text"); 56 | RegAdminCmd("votenovote", Cmd_TestNoVote, ADMFLAG_VOTE, "Test Multiple Choice vote with \"No Vote\" option"); 57 | RegAdminCmd("votenovotecustom", Cmd_TestNoVoteCustom, ADMFLAG_VOTE, "Test Multiple Choice vote with \"No Vote\" option and Custom Display text"); 58 | } 59 | 60 | public Action Cmd_TestYesNo(int client, int args) 61 | { 62 | if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_YesNo)) 63 | { 64 | ReplyToCommand(client, "Game does not support Custom Yes/No votes."); 65 | return Plugin_Handled; 66 | } 67 | 68 | if (!NativeVotes_IsNewVoteAllowed()) 69 | { 70 | int seconds = NativeVotes_CheckVoteDelay(); 71 | ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); 72 | return Plugin_Handled; 73 | } 74 | 75 | NativeVote vote = new NativeVote(YesNoHandler, NativeVotesType_Custom_YesNo); 76 | 77 | vote.Initiator = client; 78 | vote.SetDetails("Test Yes/No Vote"); 79 | vote.DisplayVoteToAll(30); 80 | 81 | return Plugin_Handled; 82 | } 83 | 84 | public int YesNoHandler(NativeVote vote, MenuAction action, int param1, int param2) 85 | { 86 | switch (action) 87 | { 88 | case MenuAction_End: 89 | { 90 | vote.Close(); 91 | } 92 | 93 | case MenuAction_VoteCancel: 94 | { 95 | if (param1 == VoteCancel_NoVotes) 96 | { 97 | vote.DisplayFail(NativeVotesFail_NotEnoughVotes); 98 | } 99 | else 100 | { 101 | vote.DisplayFail(NativeVotesFail_Generic); 102 | } 103 | } 104 | 105 | case MenuAction_VoteEnd: 106 | { 107 | if (param1 == NATIVEVOTES_VOTE_NO) 108 | { 109 | vote.DisplayFail(NativeVotesFail_Loses); 110 | } 111 | else 112 | { 113 | vote.DisplayPass("Test Yes/No Vote Passed!"); 114 | // Do something because it passed 115 | } 116 | } 117 | } 118 | } 119 | 120 | public Action Cmd_TestMult(int client, int args) 121 | { 122 | if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) 123 | { 124 | ReplyToCommand(client, "Game does not support Custom Multiple Choice votes."); 125 | return Plugin_Handled; 126 | } 127 | 128 | if (!NativeVotes_IsNewVoteAllowed()) 129 | { 130 | int seconds = NativeVotes_CheckVoteDelay(); 131 | ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); 132 | return Plugin_Handled; 133 | } 134 | 135 | NativeVote vote = new NativeVote(MultHandler, NativeVotesType_Custom_Mult); 136 | 137 | vote.Initiator = client; 138 | vote.SetDetails("Test Mult Vote"); 139 | vote.AddItem("choice1", "Choice 1"); 140 | vote.AddItem("choice2", "Choice 2"); 141 | vote.AddItem("choice3", "Choice 3"); 142 | vote.AddItem("choice4", "Choice 4"); 143 | vote.AddItem("choice5", "Choice 5"); 144 | // 5 is currently the maximum number of choices in any game 145 | vote.DisplayVoteToAll(30); 146 | 147 | return Plugin_Handled; 148 | } 149 | 150 | public int MultHandler(NativeVote vote, MenuAction action, int param1, int param2) 151 | { 152 | switch (action) 153 | { 154 | case MenuAction_End: 155 | { 156 | vote.Close(); 157 | } 158 | 159 | case MenuAction_VoteCancel: 160 | { 161 | if (param1 == VoteCancel_NoVotes) 162 | { 163 | vote.DisplayFail(NativeVotesFail_NotEnoughVotes); 164 | } 165 | else 166 | { 167 | vote.DisplayFail(NativeVotesFail_Generic); 168 | } 169 | } 170 | 171 | case MenuAction_VoteEnd: 172 | { 173 | char info[64]; 174 | char display[64]; 175 | vote.GetItem(param1, info, sizeof(info), display, sizeof(display)); 176 | 177 | vote.DisplayPass(display); 178 | 179 | // Do something with info 180 | } 181 | } 182 | } 183 | 184 | public Action Cmd_TestYesNoCustom(int client, int args) 185 | { 186 | if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_YesNo)) 187 | { 188 | ReplyToCommand(client, "Game does not support Custom Yes/No votes."); 189 | return Plugin_Handled; 190 | } 191 | 192 | if (!NativeVotes_IsNewVoteAllowed()) 193 | { 194 | int seconds = NativeVotes_CheckVoteDelay(); 195 | ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); 196 | return Plugin_Handled; 197 | } 198 | 199 | NativeVote vote = new NativeVote(YesNoCustomHandler, NativeVotesType_Custom_YesNo, NATIVEVOTES_ACTIONS_DEFAULT|MenuAction_Display); 200 | 201 | vote.Initiator = client; 202 | vote.SetDetails("Test Yes/No Vote"); 203 | vote.DisplayVoteToAll(30); 204 | 205 | return Plugin_Handled; 206 | } 207 | 208 | public int YesNoCustomHandler(NativeVote vote, MenuAction action, int param1, int param2) 209 | { 210 | switch (action) 211 | { 212 | case MenuAction_End: 213 | { 214 | vote.Close(); 215 | } 216 | 217 | case MenuAction_Display: 218 | { 219 | char display[64]; 220 | Format(display, sizeof(display), "%N Test Yes/No Vote", param1); 221 | PrintToChat(param1, "New Menu Title: %s", display); 222 | NativeVotes_RedrawVoteTitle(display); 223 | return view_as(Plugin_Changed); 224 | } 225 | 226 | case MenuAction_VoteCancel: 227 | { 228 | if (param1 == VoteCancel_NoVotes) 229 | { 230 | vote.DisplayFail(NativeVotesFail_NotEnoughVotes); 231 | } 232 | else 233 | { 234 | vote.DisplayFail(NativeVotesFail_Generic); 235 | } 236 | } 237 | 238 | case MenuAction_VoteEnd: 239 | { 240 | if (param1 == NATIVEVOTES_VOTE_NO) 241 | { 242 | vote.DisplayFail(NativeVotesFail_Loses); 243 | } 244 | else 245 | { 246 | vote.DisplayPass("Test Custom Yes/No Vote Passed!"); 247 | // Do something because it passed 248 | } 249 | } 250 | } 251 | 252 | return 0; 253 | } 254 | 255 | public Action Cmd_TestMultCustom(int client, int args) 256 | { 257 | if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) 258 | { 259 | ReplyToCommand(client, "Game does not support Custom Multiple Choice votes."); 260 | return Plugin_Handled; 261 | } 262 | 263 | if (!NativeVotes_IsNewVoteAllowed()) 264 | { 265 | int seconds = NativeVotes_CheckVoteDelay(); 266 | ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); 267 | return Plugin_Handled; 268 | } 269 | 270 | NativeVote vote = new NativeVote(MultCustomHandler, NativeVotesType_Custom_Mult, NATIVEVOTES_ACTIONS_DEFAULT|MenuAction_Display|MenuAction_DisplayItem); 271 | 272 | vote.Initiator = client; 273 | vote.SetDetails("Test Mult Vote"); 274 | vote.AddItem("choice1", "Choice 1"); 275 | vote.AddItem("choice2", "Choice 2"); 276 | vote.AddItem("choice3", "Choice 3"); 277 | vote.AddItem("choice4", "Choice 4"); 278 | vote.AddItem("choice5", "Choice 5"); 279 | // 5 is currently the maximum number of choices in any game 280 | vote.DisplayVoteToAll(30); 281 | 282 | return Plugin_Handled; 283 | } 284 | 285 | public int MultCustomHandler(NativeVote vote, MenuAction action, int param1, int param2) 286 | { 287 | switch (action) 288 | { 289 | case MenuAction_End: 290 | { 291 | vote.Close(); 292 | } 293 | 294 | case MenuAction_Display: 295 | { 296 | char display[64]; 297 | Format(display, sizeof(display), "%N Test Mult Vote", param1); 298 | PrintToChat(param1, "New Menu Title: %s", display); 299 | NativeVotes_RedrawVoteTitle(display); 300 | return view_as(Plugin_Changed); 301 | } 302 | 303 | case MenuAction_VoteCancel: 304 | { 305 | if (param1 == VoteCancel_NoVotes) 306 | { 307 | vote.DisplayFail(NativeVotesFail_NotEnoughVotes); 308 | } 309 | else 310 | { 311 | vote.DisplayFail(NativeVotesFail_Generic); 312 | } 313 | } 314 | 315 | case MenuAction_VoteEnd: 316 | { 317 | char info[64]; 318 | char display[64]; 319 | vote.GetItem(param1, info, sizeof(info), display, sizeof(display)); 320 | 321 | // Do something with info 322 | //NativeVotes_DisplayPassCustom(vote, "%t Mult passed", "Translation Phrase"); 323 | vote.DisplayPassCustom("%s passed", display); 324 | } 325 | 326 | case MenuAction_DisplayItem: 327 | { 328 | char info[64]; 329 | char display[64]; 330 | 331 | char buffer[64]; 332 | 333 | vote.GetItem(param2, info, sizeof(info), display, sizeof(display)); 334 | 335 | // This is generally how you'd do translations, but normally with %T and a format phrase 336 | bool bReplace = false; 337 | if (StrEqual(info, "choice1")) 338 | { 339 | Format(buffer, sizeof(buffer), "%N %s", param1, display); 340 | bReplace = true; 341 | } 342 | else if (StrEqual(info, "choice2")) 343 | { 344 | Format(buffer, sizeof(buffer), "%N %s", param1, display); 345 | bReplace = true; 346 | } 347 | else if (StrEqual(info, "choice3")) 348 | { 349 | Format(buffer, sizeof(buffer), "%N %s", param1, display); 350 | bReplace = true; 351 | } 352 | else if (StrEqual(info, "choice4")) 353 | { 354 | Format(buffer, sizeof(buffer), "%N %s", param1, display); 355 | bReplace = true; 356 | } 357 | else if (StrEqual(info, "choice5")) 358 | { 359 | Format(buffer, sizeof(buffer), "%N %s", param1, display); 360 | bReplace = true; 361 | } 362 | 363 | PrintToChat(param1, "New Menu Item %d: %s", param2, buffer); 364 | 365 | if (bReplace) 366 | { 367 | NativeVotes_RedrawVoteItem(buffer); 368 | return view_as(Plugin_Changed); 369 | } 370 | } 371 | } 372 | 373 | return 0; 374 | } 375 | 376 | public Action Cmd_TestNoVote(int client, int args) 377 | { 378 | if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) 379 | { 380 | ReplyToCommand(client, "Game does not support Custom Multiple Choice votes."); 381 | return Plugin_Handled; 382 | } 383 | 384 | if (!NativeVotes_IsNewVoteAllowed()) 385 | { 386 | int seconds = NativeVotes_CheckVoteDelay(); 387 | ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); 388 | return Plugin_Handled; 389 | } 390 | 391 | NativeVote vote = new NativeVote(MultHandler, NativeVotesType_Custom_Mult); 392 | 393 | vote.NoVoteButton = true; 394 | vote.Initiator = client; 395 | vote.SetDetails("Test Mult Vote with NoVote"); 396 | vote.AddItem("choice1", "Choice 1"); 397 | vote.AddItem("choice2", "Choice 2"); 398 | vote.AddItem("choice3", "Choice 3"); 399 | vote.AddItem("choice4", "Choice 4"); 400 | vote.AddItem("choice5", "Choice 5"); 401 | // 5 is currently the maximum number of choices in any game, but No Vote should make the max 4... 402 | vote.DisplayVoteToAll(30); 403 | 404 | return Plugin_Handled; 405 | } 406 | 407 | public Action Cmd_TestNoVoteCustom(int client, int args) 408 | { 409 | if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) 410 | { 411 | ReplyToCommand(client, "Game does not support Custom Multiple Choice votes."); 412 | return Plugin_Handled; 413 | } 414 | 415 | if (!NativeVotes_IsNewVoteAllowed()) 416 | { 417 | int seconds = NativeVotes_CheckVoteDelay(); 418 | ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); 419 | return Plugin_Handled; 420 | } 421 | 422 | NativeVote vote = new NativeVote(MultCustomHandler, NativeVotesType_Custom_Mult, NATIVEVOTES_ACTIONS_DEFAULT|MenuAction_Display|MenuAction_DisplayItem); 423 | 424 | vote.NoVoteButton = true; 425 | vote.Initiator = client; 426 | vote.SetDetails("Test Mult Vote"); 427 | vote.AddItem("choice1", "Choice 1"); 428 | vote.AddItem("choice2", "Choice 2"); 429 | vote.AddItem("choice3", "Choice 3"); 430 | vote.AddItem("choice4", "Choice 4"); 431 | vote.AddItem("choice5", "Choice 5"); 432 | // 5 is currently the maximum number of choices in any game, but No Vote should make the max 4... 433 | vote.DisplayVoteToAll(30); 434 | 435 | return Plugin_Handled; 436 | } 437 | 438 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/votedelay_changelevel.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * Vote Delay: Changelevel 5 | * Delay ChangeLevel and NextLevel votes until X rounds have passed 6 | * 7 | * Vote Delay: Changelevel (C)2014 Powerlord (Ross Bemrose). All rights reserved. 8 | * ============================================================================= 9 | * 10 | * This program is free software; you can redistribute it and/or modify it under 11 | * the terms of the GNU General Public License, version 3.0, as published by the 12 | * Free Software Foundation. 13 | * 14 | * This program is distributed in the hope that it will be useful, but WITHOUT 15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 | * details. 18 | * 19 | * You should have received a copy of the GNU General Public License along with 20 | * this program. If not, see . 21 | * 22 | * As a special exception, AlliedModders LLC gives you permission to link the 23 | * code of this program (as well as its derivative works) to "Half-Life 2," the 24 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 25 | * by the Valve Corporation. You must obey the GNU General Public License in 26 | * all respects for all other code used. Additionally, AlliedModders LLC grants 27 | * this exception to all derivative works. AlliedModders LLC defines further 28 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 29 | * or . 30 | * 31 | * Version: $Id$ 32 | */ 33 | #include 34 | #include 35 | #pragma semicolon 1 36 | 37 | #define VERSION "1.0.1" 38 | 39 | #define VOTE_STRING_SIZE 32 40 | 41 | new Handle:g_Cvar_Enabled; 42 | new Handle:g_Cvar_FullRounds; 43 | new Handle:g_Cvar_Rounds; 44 | 45 | new g_VoteController = -1; 46 | new g_bUserBuf = false; 47 | 48 | // Stolen from NativeVotes 49 | enum NativeVotesCallFailType 50 | { 51 | NativeVotesCallFail_Generic = 0, /**< Generic fail. */ 52 | NativeVotesCallFail_Loading = 1, /**< L4D/L4D2: Players are still loading. */ 53 | NativeVotesCallFail_Recent = 2, /**< TF2/CS:GO: You can't call another vote yet: Argument is seconds until you can call another vote. */ 54 | NativeVotesCallFail_Disabled = 5, /**< TF2/CS:GO: Server has disabled that issue. */ 55 | NativeVotesCallFail_MapNotFound = 6, /**< TF2/CS:GO: Server does not have that map. */ 56 | NativeVotesCallFail_SpecifyMap = 7, /**< TF2/CS:GO: You must specify a map. */ 57 | NativeVotesCallFail_Failed = 8, /**< TF2/CS:GO: This vote failed recently: Argument is seconds until this vote can be called again. */ 58 | NativeVotesCallFail_WrongTeam = 9, /**< TF2/CS:GO: Team can't call this vote. */ 59 | NativeVotesCallFail_Waiting = 10, /**< TF2/CS:GO: Vote can't be called during Waiting For Players. */ 60 | NativeVotesCallFail_PlayerNotFound = 11, /**< TF2/CS:GO: Player to kick can't be found. Buggy in TF2. */ 61 | NativeVotesCallFail_Unknown = 11, 62 | NativeVotesCallFail_CantKickAdmin = 12, /**< TF2/CS:GO: Can't kick server admin. */ 63 | NativeVotesCallFail_ScramblePending = 13, /**< TF2/CS:GO: Team Scramble is pending. */ 64 | NativeVotesCallFail_Spectators = 14, /**< TF2/CS:GO: Spectators aren't allowed to call votes. */ 65 | NativeVotesCallFail_LevelSet = 15, /**< TF2: Next level already set. */ 66 | NativeVotesCallFail_Warmup = 15, /**< CSGO: Vote can't be called during Warmup */ 67 | NativeVotesCallFail_MapNotValid = 16, /**< TF2: Map is not in MapCycle. */ 68 | NativeVotesCallFail_KickTime = 17, /**< TF2: Cannot kick at this time: Argument is seconds until you can call another kick vote. */ 69 | NativeVotesCallFail_KickDuringRound = 18, /**< TF2: Cannot kick during a round. */ 70 | }; 71 | 72 | public Plugin:myinfo = { 73 | name = "Vote Delay: Changelevel", 74 | author = "Powerlord", 75 | description = "Delay vote change until after X rounds have gone by", 76 | version = VERSION, 77 | url = "" 78 | }; 79 | 80 | new roundCount = 1; 81 | 82 | public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) 83 | { 84 | new EngineVersion:engine = GetEngineVersion(); 85 | 86 | if (engine != Engine_TF2 && engine != Engine_CSGO) 87 | { 88 | strcopy(error, err_max, "Only works on TF2 and CS:GO"); 89 | return APLRes_Failure; 90 | } 91 | return APLRes_Success; 92 | } 93 | 94 | public OnPluginStart() 95 | { 96 | CreateConVar("votedelay_changelevel_version", VERSION, "Vote Delay: Changelevel version", FCVAR_PLUGIN|FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); 97 | g_Cvar_Enabled = CreateConVar("votedelay_changelevel_enable", "1", "Enable Vote Delay: Changelevel?", FCVAR_PLUGIN|FCVAR_NOTIFY|FCVAR_DONTRECORD, true, 0.0, true, 1.0); 98 | g_Cvar_FullRounds = CreateConVar("votedelay_changelevel_fullrounds", "1", "Full rounds only? Only applies to TF2.", FCVAR_PLUGIN, true, 0.0, true, 1.0); 99 | g_Cvar_Rounds = CreateConVar("votedelay_changelevel_rounds", "4", "During what round should votes become available?", FCVAR_PLUGIN, true, 0.0, true, 10.0); 100 | 101 | g_bUserBuf = (GetUserMessageType() == UM_Protobuf); 102 | 103 | AddCommandListener(Cmd_CallVote, "callvote"); 104 | HookEvent("round_end", Event_RoundEnd); 105 | HookEventEx("teamplay_round_win", Event_RoundWin); 106 | 107 | AutoExecConfig(true, "votedelay_changelevel"); 108 | } 109 | 110 | public OnMapStart() 111 | { 112 | roundCount = 1; 113 | } 114 | 115 | public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast) 116 | { 117 | if (!GameRules_GetProp("m_bWarmupPeriod")) 118 | { 119 | roundCount++; 120 | } 121 | } 122 | 123 | public Event_RoundWin(Handle:event, const String:name[], bool:dontBroadcast) 124 | { 125 | if (GetConVarBool(g_Cvar_FullRounds) && !GetEventInt(event, "full_round")) 126 | { 127 | return; 128 | } 129 | 130 | roundCount++; 131 | } 132 | 133 | public Action:Cmd_CallVote(client, const String:command[], argc) 134 | { 135 | if (client == 0) 136 | { 137 | return Plugin_Continue; 138 | } 139 | 140 | if (!GetConVarBool(g_Cvar_Enabled) || argc == 0 || IsNativeVoteInProgress()) 141 | { 142 | return Plugin_Continue; 143 | } 144 | 145 | decl String:voteCommand[VOTE_STRING_SIZE]; 146 | GetCmdArg(1, voteCommand, VOTE_STRING_SIZE); 147 | 148 | if (!StrEqual(voteCommand, "ChangeLevel", false) && !StrEqual(voteCommand, "NextLevel", false)) 149 | { 150 | return Plugin_Continue; 151 | } 152 | 153 | new voteRound = GetConVarInt(g_Cvar_Rounds); 154 | 155 | if (roundCount >= voteRound) 156 | { 157 | return Plugin_Continue; 158 | } 159 | 160 | new ReplySource:source = SetCmdReplySource(SM_REPLY_TO_CHAT); 161 | 162 | // For lack of a better reason 163 | TF2CSGO_CallVoteFail(client, NativeVotesCallFail_Disabled); 164 | ReplyToCommand(client, "%s vote is disabled until after round %d, current round is %d", voteCommand, voteRound, roundCount); 165 | 166 | SetCmdReplySource(source); 167 | 168 | return Plugin_Stop; 169 | } 170 | 171 | bool:IsNativeVoteInProgress() 172 | { 173 | if (CheckVoteController()) 174 | { 175 | new activeIndex = GetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex"); 176 | if (activeIndex > -1) 177 | { 178 | return true; 179 | } 180 | } 181 | 182 | return false; 183 | } 184 | 185 | bool:CheckVoteController() 186 | { 187 | new entity = -1; 188 | if (g_VoteController != -1) 189 | { 190 | entity = EntRefToEntIndex(g_VoteController); 191 | } 192 | 193 | if (entity == -1) 194 | { 195 | entity = FindEntityByClassname(-1, "vote_controller"); 196 | if (entity == -1) 197 | { 198 | LogError("Could not find Vote Controller."); 199 | return false; 200 | } 201 | 202 | g_VoteController = EntIndexToEntRef(entity); 203 | } 204 | return true; 205 | } 206 | 207 | TF2CSGO_CallVoteFail(client, NativeVotesCallFailType:reason, time=0) 208 | { 209 | new Handle:callVoteFail = StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE); 210 | 211 | if(g_bUserBuf) 212 | { 213 | PbSetInt(callVoteFail, "reason", _:reason); 214 | PbSetInt(callVoteFail, "time", time); 215 | } 216 | else 217 | { 218 | BfWriteByte(callVoteFail, _:reason); 219 | BfWriteShort(callVoteFail, time); 220 | } 221 | EndMessage(); 222 | } 223 | 224 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/votediagnostics.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * Sniff L4D; L4D2; TF2; and CS:GO vote events, user messages, and commands 5 | * 6 | * NativeVotes (C) 2011-2014 Ross Bemrose (Powerlord). All rights reserved. 7 | * ============================================================================= 8 | * 9 | * This program is free software; you can redistribute it and/or modify it under 10 | * the terms of the GNU General Public License, version 3.0, as published by the 11 | * Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, but WITHOUT 14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program. If not, see . 20 | * 21 | * As a special exception, AlliedModders LLC gives you permission to link the 22 | * code of this program (as well as its derivative works) to "Half-Life 2," the 23 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 24 | * by the Valve Corporation. You must obey the GNU General Public License in 25 | * all respects for all other code used. Additionally, AlliedModders LLC grants 26 | * this exception to all derivative works. AlliedModders LLC defines further 27 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 28 | * or . 29 | * 30 | * Version: $Id$ 31 | */ 32 | 33 | #pragma semicolon 1 34 | 35 | #include 36 | #include 37 | 38 | #define LOGFILE "vote_diagnostics.txt" 39 | 40 | #pragma newdecls required 41 | 42 | EngineVersion g_EngineVersion = Engine_Unknown; 43 | 44 | int g_VoteController = -1; 45 | 46 | #define MAX_ARG_SIZE 65 47 | 48 | #define DELAY 6.0 49 | 50 | public Plugin myinfo = 51 | { 52 | name = "L4D,L4D2,TF2,CS:GO Vote Sniffer", 53 | author = "Powerlord", 54 | description = "Sniff voting commands, events, and usermessages", 55 | version = "1.2.5", 56 | url = "http://www.sourcemod.net/" 57 | } 58 | 59 | public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) 60 | { 61 | if (!Game_IsGameSupported()) 62 | { 63 | strcopy(error, err_max, "Unsupported game"); 64 | return APLRes_Failure; 65 | } 66 | 67 | if (!late) 68 | { 69 | switch (g_EngineVersion) 70 | { 71 | case Engine_Left4Dead, Engine_Left4Dead2: 72 | { 73 | CreateTimer(30.0, L4DL4D2_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 74 | } 75 | 76 | case Engine_CSGO, Engine_TF2, Engine_SDK2013, Engine_Insurgency: 77 | { 78 | CreateTimer(30.0, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 79 | } 80 | } 81 | } 82 | return APLRes_Success; 83 | } 84 | 85 | bool CheckVoteController() 86 | { 87 | int entity = -1; 88 | if (g_VoteController != -1) 89 | { 90 | entity = EntRefToEntIndex(g_VoteController); 91 | } 92 | 93 | if (entity == -1) 94 | { 95 | entity = FindEntityByClassname(-1, "vote_controller"); 96 | if (entity == -1) 97 | { 98 | LogError("Could not find Vote Controller."); 99 | return false; 100 | } 101 | 102 | g_VoteController = EntIndexToEntRef(entity); 103 | } 104 | return true; 105 | } 106 | 107 | bool Game_IsGameSupported() 108 | { 109 | g_EngineVersion = GetEngineVersion(); 110 | 111 | switch (g_EngineVersion) 112 | { 113 | case Engine_Left4Dead, Engine_Left4Dead2, Engine_CSGO, Engine_TF2, Engine_SDK2013, Engine_Insurgency: 114 | { 115 | return true; 116 | } 117 | } 118 | 119 | return false; 120 | } 121 | 122 | public void OnPluginStart() 123 | { 124 | switch (g_EngineVersion) 125 | { 126 | case Engine_Left4Dead: 127 | { 128 | HookEventEx("vote_changed", L4DL4D2_EventVoteChanged); 129 | HookEventEx("vote_ended", L4D_EventVoteEnded); 130 | HookEventEx("vote_started", L4D_EventVoteStarted); 131 | HookEventEx("vote_passed", L4D_EventVotePassed); 132 | HookEventEx("vote_failed", L4D_EventVoteFailed); 133 | HookEventEx("vote_cast_yes", L4D_EventVoteYes); 134 | HookEventEx("vote_cast_no", L4D_EventVoteNo); 135 | 136 | HookUserMessage(GetUserMessageId("VoteRegistered"), L4DL4D2_MessageVoteRegistered); 137 | HookUserMessage(GetUserMessageId("CallVoteFailed"), L4DL4D2_MessageCallVoteFailed); 138 | } 139 | 140 | case Engine_Left4Dead2: 141 | { 142 | HookEventEx("vote_changed", L4DL4D2_EventVoteChanged); 143 | 144 | HookUserMessage(GetUserMessageId("VoteRegistered"), L4DL4D2_MessageVoteRegistered); 145 | HookUserMessage(GetUserMessageId("VoteStart"), L4D2_MessageVoteStart); 146 | HookUserMessage(GetUserMessageId("VotePass"), L4D2_MessageVotePass); 147 | HookUserMessage(GetUserMessageId("VoteFail"), L4D2_MessageVoteFail); 148 | HookUserMessage(GetUserMessageId("CallVoteFailed"), L4DL4D2_MessageCallVoteFailed); 149 | } 150 | 151 | case Engine_CSGO, Engine_TF2, Engine_SDK2013, Engine_Insurgency: 152 | { 153 | HookEventEx("vote_cast", TF2CSGO_EventVoteCast); 154 | HookEventEx("vote_options", TF2CSGO_EventVoteOptions); 155 | 156 | if (GetUserMessageType() == UM_Protobuf) 157 | { 158 | HookUserMessage(GetUserMessageId("VoteSetup"), CSGO_MessageVoteSetup); 159 | HookUserMessage(GetUserMessageId("VoteStart"), CSGO_MessageVoteStart); 160 | HookUserMessage(GetUserMessageId("VotePass"), CSGO_MessageVotePass); 161 | HookUserMessage(GetUserMessageId("VoteFailed"), CSGO_MessageVoteFail); 162 | HookUserMessage(GetUserMessageId("CallVoteFailed"), CSGO_MessageCallVoteFailed); 163 | } 164 | else 165 | { 166 | HookUserMessage(GetUserMessageId("VoteSetup"), TF2_MessageVoteSetup); 167 | HookUserMessage(GetUserMessageId("VoteStart"), TF2_MessageVoteStart); 168 | HookUserMessage(GetUserMessageId("VotePass"), TF2_MessageVotePass); 169 | HookUserMessage(GetUserMessageId("VoteFailed"), TF2_MessageVoteFail); 170 | HookUserMessage(GetUserMessageId("CallVoteFailed"), TF2_MessageCallVoteFailed); 171 | } 172 | } 173 | } 174 | 175 | AddCommandListener(CommandVote, "vote"); 176 | AddCommandListener(CommandCallVote, "callvote"); 177 | 178 | char gameName[64]; 179 | GetGameFolderName(gameName, sizeof(gameName)); 180 | 181 | LogToFile(LOGFILE, "Game: %s", gameName); 182 | } 183 | 184 | /* 185 | "vote_changed" 186 | { 187 | "yesVotes" "byte" 188 | "noVotes" "byte" 189 | "potentialVotes" "byte" 190 | } 191 | */ 192 | public void L4DL4D2_EventVoteChanged(Event event, const char[] name, bool dontBroadcast) 193 | { 194 | int yesVotes = event.GetInt("yesVotes"); 195 | int noVotes = event.GetInt("noVotes"); 196 | int potentialVotes = event.GetInt("potentialVotes"); 197 | LogMessage("Vote Changed event: yesVotes: %d, noVotes: %d, potentialVotes: %d", 198 | yesVotes, noVotes, potentialVotes); 199 | 200 | } 201 | 202 | /* 203 | VoteRegistered Structure 204 | - Byte Choice voted for, 0 = No, 1 = Yes 205 | 206 | */ 207 | public Action L4DL4D2_MessageVoteRegistered(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 208 | { 209 | int choice = BfReadByte(message); 210 | 211 | LogToFile(LOGFILE, "VoteRegistered Usermessage: choice: %d", choice); 212 | return Plugin_Continue; 213 | } 214 | 215 | /* 216 | CallVoteFailed 217 | - Byte Failure reason code (1-2, 5-15) 218 | - Short Time until new vote allowed for code 2 219 | 220 | message CCSUsrMsg_CallVoteFailed 221 | { 222 | optional int32 reason = 1; 223 | optional int32 time = 2; 224 | } 225 | */ 226 | public Action L4DL4D2_MessageCallVoteFailed(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 227 | { 228 | int reason; 229 | int time; 230 | 231 | reason = BfReadByte(message); 232 | 233 | LogToFile(LOGFILE, "CallVoteFailed Usermessage: reason: %d", reason, time); 234 | return Plugin_Continue; 235 | } 236 | 237 | /* 238 | "vote_started" 239 | { 240 | "issue" "string" 241 | "param1" "string" 242 | "team" "byte" 243 | "initiator" "long" // entity id of the player who initiated the vote 244 | } 245 | */ 246 | public void L4D_EventVoteStarted(Event event, const char[] name, bool dontBroadcast) 247 | { 248 | char issue[MAX_ARG_SIZE]; 249 | char param1[MAX_ARG_SIZE]; 250 | event.GetString("issue", issue, sizeof(issue)); 251 | event.GetString("param1", param1, sizeof(param1)); 252 | int team = event.GetInt("team"); 253 | int initiator = event.GetInt("initiator"); 254 | LogToFile(LOGFILE, "Vote Start Event: issue: \"%s\", param1: \"%s\", team: %d, initiator: %d", issue, param1, team, initiator); 255 | 256 | if (CheckVoteController()) 257 | { 258 | LogToFile(LOGFILE, "Active Index for issue %s: %d", issue, GetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex")); 259 | } 260 | } 261 | 262 | /* 263 | "vote_ended" 264 | { 265 | } 266 | */ 267 | public void L4D_EventVoteEnded(Event event, const char[] name, bool dontBroadcast) 268 | { 269 | LogToFile(LOGFILE, "Vote Ended Event"); 270 | 271 | CreateTimer(DELAY, L4DL4D2_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 272 | } 273 | 274 | /* 275 | "vote_passed" 276 | { 277 | "details" "string" 278 | "param1" "string" 279 | "team" "byte" 280 | } 281 | */ 282 | public void L4D_EventVotePassed(Event event, const char[] name, bool dontBroadcast) 283 | { 284 | char details[MAX_ARG_SIZE]; 285 | char param1[MAX_ARG_SIZE]; 286 | event.GetString("details", details, sizeof(details)); 287 | event.GetString("param1", param1, sizeof(param1)); 288 | int team = event.GetInt("team"); 289 | LogToFile(LOGFILE, "Vote Passed event: details: %s, param1: %s, team: %d", details, param1, team); 290 | } 291 | 292 | /* 293 | "vote_failed" 294 | { 295 | "team" "byte" 296 | } 297 | */ 298 | public void L4D_EventVoteFailed(Event event, const char[] name, bool dontBroadcast) 299 | { 300 | int team = event.GetInt("team"); 301 | LogToFile(LOGFILE, "Vote Failed event: team: %d", team); 302 | } 303 | 304 | /* 305 | "vote_cast_yes" 306 | { 307 | "team" "byte" 308 | "entityid" "long" // entity id of the voter 309 | } 310 | */ 311 | public void L4D_EventVoteYes(Event event, const char[] name, bool dontBroadcast) 312 | { 313 | int team = event.GetInt("team"); 314 | int client = event.GetInt("entityid"); 315 | LogToFile(LOGFILE, "Vote Cast Yes event: team: %d, client: %N", team, client); 316 | } 317 | 318 | /* 319 | "vote_cast_no" 320 | { 321 | "team" "byte" 322 | "entityid" "long" // entity id of the voter 323 | } 324 | */ 325 | public void L4D_EventVoteNo(Event event, const char[] name, bool dontBroadcast) 326 | { 327 | int team = event.GetInt("team"); 328 | int client = event.GetInt("entityid"); 329 | LogToFile(LOGFILE, "Vote Cast No event: team: %d, client: %N", team, client); 330 | } 331 | 332 | /* 333 | VoteStart Structure 334 | - Byte Team index or -1 for all 335 | - Byte Initiator client index (or 99 for Server?) 336 | - String Vote issue phrase 337 | - String Vote issue phrase argument 338 | - String Initiator name 339 | 340 | */ 341 | public Action L4D2_MessageVoteStart(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 342 | { 343 | char issue[MAX_ARG_SIZE]; 344 | char param1[MAX_ARG_SIZE]; 345 | char initiatorName[MAX_NAME_LENGTH]; 346 | 347 | int team = message.ReadByte(); 348 | int initiator = message.ReadByte(); 349 | 350 | message.ReadString(issue, MAX_ARG_SIZE); 351 | message.ReadString(param1, MAX_ARG_SIZE); 352 | message.ReadString(initiatorName, MAX_NAME_LENGTH); 353 | 354 | LogToFile(LOGFILE, "VoteStart Usermessage: team: %d, initiator: %d, issue: %s, param1: %s, player count: %d, initiatorName: %s", team, initiator, issue, param1, playersNum, initiatorName); 355 | if (CheckVoteController()) 356 | { 357 | LogToFile(LOGFILE, "Active Index for issue %s: %d", issue, GetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex")); 358 | } 359 | 360 | return Plugin_Continue; 361 | } 362 | 363 | /* 364 | VotePass Structure 365 | - Byte Team index or -1 for all 366 | - String Vote issue pass phrase 367 | - String Vote issue pass phrase argument 368 | 369 | */ 370 | public Action L4D2_MessageVotePass(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 371 | { 372 | char issue[MAX_ARG_SIZE]; 373 | char param1[MAX_ARG_SIZE]; 374 | int team = message.ReadByte(); 375 | 376 | message.ReadString(issue, MAX_ARG_SIZE); 377 | message.ReadString(param1, MAX_ARG_SIZE); 378 | 379 | LogToFile(LOGFILE, "VotePass Usermessage: team: %d, issue: %s, param1: %s", team, issue, param1); 380 | 381 | CreateTimer(DELAY, L4DL4D2_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 382 | return Plugin_Continue; 383 | } 384 | 385 | /* 386 | VoteFail Structure 387 | - Byte Team index or -1 for all 388 | 389 | */ 390 | public Action L4D2_MessageVoteFail(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 391 | { 392 | int team = message.ReadByte(); 393 | 394 | LogToFile(LOGFILE, "VoteFail Usermessage: team: %d", team); 395 | 396 | CreateTimer(DELAY, L4DL4D2_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 397 | 398 | return Plugin_Continue; 399 | } 400 | 401 | /* 402 | "vote_cast" 403 | { 404 | "vote_option" "byte" // which option the player voted on 405 | "team" "short" 406 | "entityid" "long" // entity id of the voter 407 | } 408 | */ 409 | public void TF2CSGO_EventVoteCast(Event event, const char[] name, bool dontBroadcast) 410 | { 411 | int vote_option = event.GetInt("vote_option"); 412 | int team = event.GetInt("team"); 413 | int entityid = event.GetInt("entityid"); 414 | LogToFile(LOGFILE, "Vote Cast event: vote_option: %d, team: %d, client: %N", vote_option, team, entityid); 415 | } 416 | 417 | /* 418 | "vote_options" 419 | { 420 | "count" "byte" // Number of options - up to MAX_VOTE_OPTIONS 421 | "option1" "string" 422 | "option2" "string" 423 | "option3" "string" 424 | "option4" "string" 425 | "option5" "string" 426 | } 427 | */ 428 | public void TF2CSGO_EventVoteOptions(Event event, const char[] name, bool dontBroadcast) 429 | { 430 | char option1[MAX_ARG_SIZE]; 431 | char option2[MAX_ARG_SIZE]; 432 | char option3[MAX_ARG_SIZE]; 433 | char option4[MAX_ARG_SIZE]; 434 | char option5[MAX_ARG_SIZE]; 435 | 436 | int count = event.GetInt("count"); 437 | event.GetString("option1", option1, sizeof(option1)); 438 | event.GetString("option2", option2, sizeof(option2)); 439 | event.GetString("option3", option3, sizeof(option3)); 440 | event.GetString("option4", option4, sizeof(option4)); 441 | event.GetString("option5", option5, sizeof(option5)); 442 | LogToFile(LOGFILE, "Vote Options event: count: %d, option1: %s, option2: %s, option3: %s, option4: %s, option5: %s", 443 | count, option1, option2, option3, option4, option5); 444 | } 445 | 446 | /* 447 | message CCSUsrMsg_VoteSetup 448 | { 449 | repeated string potential_issues = 1; 450 | } 451 | */ 452 | public Action CSGO_MessageVoteSetup(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) 453 | { 454 | char options[2049]; 455 | int count = message.GetRepeatedFieldCount("potential_issues"); 456 | for(int i = 0; i < count; i++) 457 | { 458 | char option[MAX_ARG_SIZE]; 459 | message.ReadString("potential_issues", option, MAX_ARG_SIZE, i); 460 | StrCat(options, sizeof(options), option); 461 | StrCat(options, sizeof(options), " "); 462 | } 463 | 464 | LogToFile(LOGFILE, "VoteSetup Usermessage: count: %d, options: %s", count, options); 465 | 466 | return Plugin_Continue; 467 | } 468 | 469 | /* 470 | VoteSetup 471 | - Byte Option count 472 | * issue multiple issues matching the number in the first byte 473 | 474 | issue 475 | - String Vote name 476 | - String Vote translation string 477 | - Byte Whether a vote is enabled or not. 478 | */ 479 | public Action TF2_MessageVoteSetup(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 480 | { 481 | char options[2049]; 482 | int count = message.ReadByte(); 483 | for (int i = 0; i < count; i++) 484 | { 485 | char option[MAX_ARG_SIZE*2+1]; 486 | char potential_issue[MAX_ARG_SIZE]; 487 | char translation[MAX_ARG_SIZE]; 488 | message.ReadString(potential_issue, MAX_ARG_SIZE); 489 | message.ReadString(translation, MAX_ARG_SIZE); 490 | 491 | int enabled = message.ReadByte(); 492 | Format(option, sizeof(option), "(%s, %s, %d) ", potential_issue, translation, enabled); 493 | 494 | StrCat(options, sizeof(options), option); 495 | } 496 | 497 | LogToFile(LOGFILE, "VoteSetup Usermessage: count: %d, options: %s", count, options); 498 | 499 | return Plugin_Continue; 500 | } 501 | 502 | /* 503 | message CCSUsrMsg_VoteStart 504 | { 505 | optional int32 team = 1; 506 | optional int32 ent_idx = 2; 507 | optional int32 vote_type = 3; 508 | optional string disp_str = 4; 509 | optional string details_str = 5; 510 | optional string other_team_str = 6; 511 | optional bool is_yes_no_vote = 7; 512 | 513 | } 514 | */ 515 | public Action CSGO_MessageVoteStart(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) 516 | { 517 | char issue[MAX_ARG_SIZE]; 518 | char otherTeamIssue[MAX_ARG_SIZE]; 519 | char param1[MAX_ARG_SIZE]; 520 | int team; 521 | int initiator; 522 | bool yesNo; 523 | int voteType; 524 | 525 | team = message.ReadInt("team"); 526 | initiator = message.ReadInt("ent_idx"); 527 | message.ReadString("disp_str", issue, MAX_ARG_SIZE); 528 | message.ReadString("details_str", param1, MAX_ARG_SIZE); 529 | yesNo = message.ReadBool("is_yes_no_vote"); 530 | message.ReadString("other_team_str", otherTeamIssue, MAX_ARG_SIZE); 531 | voteType = message.ReadInt("vote_type"); 532 | 533 | LogToFile(LOGFILE, "VoteStart Usermessage: team: %d, initiator: %d, issue: %s, otherTeamIssue: %s, param1: %s, yesNo: %d, player count: %d, voteType: %d", team, initiator, issue, otherTeamIssue, param1, yesNo, playersNum, voteType); 534 | 535 | CreateTimer(0.0, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 536 | 537 | return Plugin_Continue; 538 | } 539 | 540 | /* 541 | VoteStart Structure 542 | - Byte Team index or -1 for all 543 | - Byte Initiator client index or 99 for Server 544 | - String Vote issue phrase 545 | - String Vote issue phrase argument 546 | - Bool false for Yes/No, true for Multiple choice 547 | */ 548 | public Action TF2_MessageVoteStart(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 549 | { 550 | char issue[MAX_ARG_SIZE]; 551 | char param1[MAX_ARG_SIZE]; 552 | int team; 553 | int initiator; 554 | bool yesNo; 555 | 556 | team = message.ReadByte(); 557 | initiator = message.ReadByte(); 558 | message.ReadString(issue, MAX_ARG_SIZE); 559 | message.ReadString(param1, MAX_ARG_SIZE); 560 | yesNo = message.ReadBool(); 561 | 562 | LogToFile(LOGFILE, "VoteStart Usermessage: team: %d, initiator: %d, issue: %s, param1: %s, yesNo: %d, player count: %d", team, initiator, issue, param1, yesNo, playersNum); 563 | 564 | CreateTimer(0.0, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 565 | 566 | return Plugin_Continue; 567 | } 568 | 569 | /* 570 | message CCSUsrMsg_VotePass 571 | { 572 | optional int32 team = 1; 573 | optional int32 vote_type = 2; 574 | optional string disp_str= 3; 575 | optional string details_str = 4; 576 | } 577 | */ 578 | public Action CSGO_MessageVotePass(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) 579 | { 580 | char issue[MAX_ARG_SIZE]; 581 | char param1[MAX_ARG_SIZE]; 582 | int team; 583 | int voteType; 584 | 585 | team = message.ReadInt("team"); 586 | message.ReadString("disp_str", issue, MAX_ARG_SIZE); 587 | message.ReadString("details_str", param1, MAX_ARG_SIZE); 588 | voteType = message.ReadInt("vote_type"); 589 | 590 | LogToFile(LOGFILE, "VotePass Usermessage: team: %d, issue: %s, param1: %s, voteType: %d", team, issue, param1, voteType); 591 | 592 | CreateTimer(DELAY, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 593 | 594 | return Plugin_Continue; 595 | } 596 | 597 | /* 598 | VotePass Structure 599 | - Byte Team index or -1 for all 600 | - String Vote issue pass phrase 601 | - String Vote issue pass phrase argument 602 | */ 603 | public Action TF2_MessageVotePass(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 604 | { 605 | char issue[MAX_ARG_SIZE]; 606 | char param1[MAX_ARG_SIZE]; 607 | int team; 608 | 609 | team = message.ReadByte(); 610 | message.ReadString(issue, MAX_ARG_SIZE); 611 | message.ReadString(param1, MAX_ARG_SIZE); 612 | 613 | LogToFile(LOGFILE, "VotePass Usermessage: team: %d, issue: %s, param1: %s", team, issue, param1); 614 | 615 | CreateTimer(DELAY, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 616 | 617 | return Plugin_Continue; 618 | } 619 | 620 | /* 621 | message CCSUsrMsg_VoteFailed 622 | { 623 | optional int32 team = 1; 624 | optional int32 reason = 2; 625 | } 626 | */ 627 | public Action CSGO_MessageVoteFail(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) 628 | { 629 | int team = message.ReadInt("team"); 630 | int reason = message.ReadInt("reason"); 631 | 632 | LogToFile(LOGFILE, "VoteFail Usermessage: team: %d, reason: %d", team, reason); 633 | 634 | CreateTimer(DELAY, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 635 | 636 | return Plugin_Continue; 637 | } 638 | 639 | /* 640 | VoteFailed Structure 641 | - Byte Team index or -1 for all 642 | - Byte Failure reason code (0, 3-4) 643 | */ 644 | public Action TF2_MessageVoteFail(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 645 | { 646 | int team = message.ReadByte(); 647 | int reason = message.ReadByte(); 648 | 649 | LogToFile(LOGFILE, "VoteFail Usermessage: team: %d, reason: %d", team, reason); 650 | 651 | CreateTimer(DELAY, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); 652 | 653 | return Plugin_Continue; 654 | } 655 | 656 | public Action TF2CSGO_LogControllerValues(Handle timer) 657 | { 658 | if (!CheckVoteController()) 659 | { 660 | return Plugin_Continue; 661 | } 662 | 663 | int team = GetEntProp(g_VoteController, Prop_Send, "m_iOnlyTeamToVote"); 664 | int activeIssue = GetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex"); 665 | int potentialVotes = GetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes"); 666 | bool isYesNo = view_as(GetEntProp(g_VoteController, Prop_Send, "m_bIsYesNoVote")); 667 | int voteCounts[5]; 668 | for (int i = 0; i < 5; ++i) 669 | { 670 | voteCounts[i] = GetEntProp(g_VoteController, Prop_Send, "m_nVoteOptionCount", _, i); 671 | } 672 | 673 | LogToFile(LOGFILE, "Vote Controller, issue: %d, team: %d, potentialVotes: %d, yesNo: %d, count1: %d, count2: %d, count3: %d, count4: %d, count5: %d", 674 | activeIssue, team, potentialVotes, isYesNo, voteCounts[0], voteCounts[1], voteCounts[2], voteCounts[3], voteCounts[4]); 675 | return Plugin_Continue; 676 | } 677 | 678 | public Action L4DL4D2_LogControllerValues(Handle timer) 679 | { 680 | if (!CheckVoteController()) 681 | { 682 | return Plugin_Continue; 683 | } 684 | 685 | int team = GetEntProp(g_VoteController, Prop_Send, "m_onlyTeamToVote"); 686 | int activeIssue = GetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex"); 687 | int potentialVotes = GetEntProp(g_VoteController, Prop_Send, "potentialVotes"); 688 | int voteCounts[2]; 689 | voteCounts[0] = GetEntProp(g_VoteController, Prop_Send, "m_votesYes"); 690 | voteCounts[1] = GetEntProp(g_VoteController, Prop_Send, "m_votesNo"); 691 | 692 | LogToFile(LOGFILE, "Vote Controller, issue: %d, team: %d, potentialVotes: %d, countYes: %d, countNo: %d", 693 | activeIssue, team, potentialVotes, voteCounts[0], voteCounts[1]); 694 | return Plugin_Continue; 695 | } 696 | 697 | /* 698 | message CCSUsrMsg_CallVoteFailed 699 | { 700 | optional int32 reason = 1; 701 | optional int32 time = 2; 702 | } 703 | */ 704 | public Action CSGO_MessageCallVoteFailed(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) 705 | { 706 | int reason = message.ReadInt("reason"); 707 | int time = message.ReadInt("time"); 708 | 709 | LogToFile(LOGFILE, "CallVoteFailed Usermessage: reason: %d, time: %d", reason, time); 710 | return Plugin_Continue; 711 | } 712 | 713 | /* 714 | CallVoteFailed 715 | - Byte Failure reason code (1-2, 5-15) 716 | - Short Time until new vote allowed for code 2 717 | */ 718 | public Action TF2_MessageCallVoteFailed(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) 719 | { 720 | int reason = message.ReadByte(); 721 | int time = message.ReadShort(); 722 | 723 | LogToFile(LOGFILE, "CallVoteFailed Usermessage: reason: %d, time: %d", reason, time); 724 | return Plugin_Continue; 725 | } 726 | 727 | /* 728 | This is likely a client-side event as it never produced any values. 729 | 730 | "endmatch_mapvote_selecting_map" 731 | { 732 | "count" "byte" // Number of "ties" 733 | "slot1" "byte" 734 | "slot2" "byte" 735 | "slot3" "byte" 736 | "slot4" "byte" 737 | "slot5" "byte" 738 | "slot6" "byte" 739 | "slot7" "byte" 740 | "slot8" "byte" 741 | "slot9" "byte" 742 | "slot10" "byte" 743 | } 744 | */ 745 | /* 746 | public void CSGO_EventMapVote(Event event, const char[] name, bool dontBroadcast) 747 | { 748 | int count = event.GetInt("count"); 749 | int slot1 = event.GetInt("slot1"); 750 | int slot2 = event.GetInt("slot2"); 751 | int slot3 = event.GetInt("slot3"); 752 | int slot4 = event.GetInt("slot4"); 753 | int slot5 = event.GetInt("slot5"); 754 | int slot6 = event.GetInt("slot6"); 755 | int slot7 = event.GetInt("slot7"); 756 | int slot8 = event.GetInt("slot8"); 757 | int slot9 = event.GetInt("slot9"); 758 | int slot10 = event.GetInt("slot10"); 759 | 760 | LogToFile(LOGFILE, "Endmatch_Mapvote_SelectingMap event: count: %d, slot1: %d, slot2: %d, slot3: %d, slot4: %d, slot5: %d, slot6: %d, slot7: %d, slot8: %d, slot9: %d, slot10: %d ", 761 | count, slot1, slot2, slot3, slot4, slot5, slot6, slot7, slot8, slot9, slot10); 762 | } 763 | */ 764 | 765 | /* 766 | Vote command 767 | - String option1 through option5 (for TF2/CS:GO); Yes or No (for L4D/L4D2) 768 | */ 769 | public Action CommandVote(int client, const char[] command, int argc) 770 | { 771 | char vote[MAX_ARG_SIZE]; 772 | GetCmdArg(1, vote, sizeof(vote)); 773 | 774 | LogToFile(LOGFILE, "%N used vote command: %s %s", client, command, vote); 775 | return Plugin_Continue; 776 | } 777 | 778 | /* 779 | callvote command 780 | - String Vote type (Valid types are sent in the VoteSetup message) 781 | - String target (or type - target for Kick) 782 | */ 783 | public Action CommandCallVote(int client, const char[] command, int argc) 784 | { 785 | char args[255]; 786 | GetCmdArgString(args, sizeof(args)); 787 | 788 | LogToFile(LOGFILE, "callvote command: client: %N, command: %s", client, args); 789 | return Plugin_Continue; 790 | } 791 | -------------------------------------------------------------------------------- /addons/sourcemod/scripting/votefailed.sp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 : 3 | * ============================================================================= 4 | * Vote Failed 5 | * Send Vote Failed commands for TF2 and CS:GO 6 | * Used to test the VoteFailed and CallVoteFailed usermessages 7 | * 8 | * Vote Failed (C)2014 Powerlord (Ross Bemrose). All rights reserved. 9 | * ============================================================================= 10 | * 11 | * This program is free software; you can redistribute it and/or modify it under 12 | * the terms of the GNU General Public License, version 3.0, as published by the 13 | * Free Software Foundation. 14 | * 15 | * This program is distributed in the hope that it will be useful, but WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 18 | * details. 19 | * 20 | * You should have received a copy of the GNU General Public License along with 21 | * this program. If not, see . 22 | * 23 | * As a special exception, AlliedModders LLC gives you permission to link the 24 | * code of this program (as well as its derivative works) to "Half-Life 2," the 25 | * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software 26 | * by the Valve Corporation. You must obey the GNU General Public License in 27 | * all respects for all other code used. Additionally, AlliedModders LLC grants 28 | * this exception to all derivative works. AlliedModders LLC defines further 29 | * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), 30 | * or . 31 | * 32 | * Version: $Id$ 33 | */ 34 | #include 35 | #pragma semicolon 1 36 | #pragma newdecls required 37 | 38 | #define VERSION "1.0.0" 39 | 40 | bool g_bUserBuf; 41 | 42 | ConVar g_Cvar_Enabled; 43 | 44 | public Plugin myinfo = { 45 | name = "Vote Failed / Call Vote Failed displayer", 46 | author = "Powerlord", 47 | description = "Used to display specific vote failed and call vote failed messages to a user", 48 | version = VERSION, 49 | url = "https://forums.alliedmods.net/showthread.php?t=208008" 50 | }; 51 | 52 | public void OnPluginStart() 53 | { 54 | LoadTranslations("common.phrases"); 55 | 56 | CreateConVar("votefailed_version", VERSION, "Vote Failed version", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); 57 | g_Cvar_Enabled = CreateConVar("votefailed_enable", "1", "Enable Vote Failed?", FCVAR_NOTIFY|FCVAR_DONTRECORD, true, 0.0, true, 1.0); 58 | g_bUserBuf = (GetUserMessageType() == UM_Protobuf); 59 | 60 | RegAdminCmd("votefail", Cmd_VoteFailed, ADMFLAG_GENERIC, "Show Call Vote Fail dialog to user"); 61 | RegAdminCmd("callvotefail", Cmd_CallVoteFailed, ADMFLAG_GENERIC, "Show Call Vote Fail dialog to user"); 62 | } 63 | 64 | public Action Cmd_VoteFailed(int client, int args) 65 | { 66 | if (!g_Cvar_Enabled.BoolValue) 67 | { 68 | return Plugin_Continue; 69 | } 70 | 71 | if (client == 0) 72 | { 73 | ReplyToCommand(client, "%t", "Command is in-game only"); 74 | return Plugin_Handled; 75 | } 76 | 77 | if (args == 0) 78 | { 79 | ReplyToCommand(client, "Requires at least one argument"); 80 | return Plugin_Handled; 81 | } 82 | 83 | char sReason[5]; 84 | GetCmdArg(1, sReason, sizeof(sReason)); 85 | 86 | int reason = StringToInt(sReason); 87 | 88 | Handle voteFailed = StartMessageOne("VoteFailed", client, USERMSG_RELIABLE); 89 | 90 | if(g_bUserBuf) 91 | { 92 | PbSetInt(voteFailed, "team", 0); 93 | PbSetInt(voteFailed, "reason", reason); 94 | } 95 | else 96 | { 97 | BfWriteByte(voteFailed, 0); 98 | BfWriteByte(voteFailed, reason); 99 | } 100 | EndMessage(); 101 | 102 | return Plugin_Handled; 103 | } 104 | 105 | public Action Cmd_CallVoteFailed(int client, int args) 106 | { 107 | if (!g_Cvar_Enabled.BoolValue) 108 | { 109 | return Plugin_Continue; 110 | } 111 | 112 | if (client == 0) 113 | { 114 | ReplyToCommand(client, "%t", "Command is in-game only"); 115 | return Plugin_Handled; 116 | } 117 | 118 | if (args == 0) 119 | { 120 | ReplyToCommand(client, "Requires at least one argument"); 121 | return Plugin_Handled; 122 | } 123 | 124 | char sReason[5]; 125 | GetCmdArg(1, sReason, sizeof(sReason)); 126 | 127 | int reason = StringToInt(sReason); 128 | int time = 0; 129 | 130 | if (args > 1) 131 | { 132 | char sTime[10]; 133 | GetCmdArg(2, sTime, sizeof(sTime)); 134 | time = StringToInt(sTime); 135 | } 136 | 137 | Handle callVoteFail = StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE); 138 | if(g_bUserBuf) 139 | { 140 | PbSetInt(callVoteFail, "reason", reason); 141 | PbSetInt(callVoteFail, "time", time); 142 | } 143 | else 144 | { 145 | BfWriteByte(callVoteFail, reason); 146 | BfWriteShort(callVoteFail, time); 147 | } 148 | EndMessage(); 149 | 150 | return Plugin_Handled; 151 | } 152 | -------------------------------------------------------------------------------- /addons/sourcemod/translations/es/nativevotes.phrases.txt: -------------------------------------------------------------------------------- 1 | "Phrases" 2 | { 3 | "NativeVotes Revote" 4 | { 5 | "es" "votar de nuevo..." 6 | } 7 | 8 | "NativeVotes Revote No Time" 9 | { 10 | "es" "No hay tiempo suficiente para votar de nuevo." 11 | } 12 | } -------------------------------------------------------------------------------- /addons/sourcemod/translations/hu/nativevotes.phrases.txt: -------------------------------------------------------------------------------- 1 | "Phrases" 2 | { 3 | "NativeVotes Revote" 4 | { 5 | "hu" "Ujraszavazas..." 6 | } 7 | 8 | "NativeVotes Revote No Time" 9 | { 10 | "hu" "Nincs eleg ido az ujraszavazasra" 11 | } 12 | } -------------------------------------------------------------------------------- /addons/sourcemod/translations/nativevotes.phrases.txt: -------------------------------------------------------------------------------- 1 | "Phrases" 2 | { 3 | "NativeVotes Revote" 4 | { 5 | "en" "Revoting..." 6 | } 7 | 8 | "NativeVotes Revote No Time" 9 | { 10 | "en" "Not enough time to revote" 11 | } 12 | } -------------------------------------------------------------------------------- /addons/sourcemod/translations/pl/nativevotes.phrases.txt: -------------------------------------------------------------------------------- 1 | "Phrases" 2 | { 3 | "NativeVotes Revote" 4 | { 5 | "pl" "Ponowne glosowanie..." 6 | } 7 | 8 | "NativeVotes Revote No Time" 9 | { 10 | "pl" "Nie ma dosc czasu ponownie glosowac." 11 | } 12 | } --------------------------------------------------------------------------------