Anton Tarasenko
4 years ago
commit
834cc6463b
29 changed files with 3692 additions and 0 deletions
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE |
||||
Version 3, 29 June 2007 |
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> |
||||
Everyone is permitted to copy and distribute verbatim copies |
||||
of this license document, but changing it is not allowed. |
||||
|
||||
Preamble |
||||
|
||||
The GNU General Public License is a free, copyleft license for |
||||
software and other kinds of works. |
||||
|
||||
The licenses for most software and other practical works are designed |
||||
to take away your freedom to share and change the works. By contrast, |
||||
the GNU General Public License is intended to guarantee your freedom to |
||||
share and change all versions of a program--to make sure it remains free |
||||
software for all its users. We, the Free Software Foundation, use the |
||||
GNU General Public License for most of our software; it applies also to |
||||
any other work released this way by its authors. You can apply it to |
||||
your programs, too. |
||||
|
||||
When we speak of free software, we are referring to freedom, not |
||||
price. Our General Public Licenses are designed to make sure that you |
||||
have the freedom to distribute copies of free software (and charge for |
||||
them if you wish), that you receive source code or can get it if you |
||||
want it, that you can change the software or use pieces of it in new |
||||
free programs, and that you know you can do these things. |
||||
|
||||
To protect your rights, we need to prevent others from denying you |
||||
these rights or asking you to surrender the rights. Therefore, you have |
||||
certain responsibilities if you distribute copies of the software, or if |
||||
you modify it: responsibilities to respect the freedom of others. |
||||
|
||||
For example, if you distribute copies of such a program, whether |
||||
gratis or for a fee, you must pass on to the recipients the same |
||||
freedoms that you received. You must make sure that they, too, receive |
||||
or can get the source code. And you must show them these terms so they |
||||
know their rights. |
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps: |
||||
(1) assert copyright on the software, and (2) offer you this License |
||||
giving you legal permission to copy, distribute and/or modify it. |
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains |
||||
that there is no warranty for this free software. For both users' and |
||||
authors' sake, the GPL requires that modified versions be marked as |
||||
changed, so that their problems will not be attributed erroneously to |
||||
authors of previous versions. |
||||
|
||||
Some devices are designed to deny users access to install or run |
||||
modified versions of the software inside them, although the manufacturer |
||||
can do so. This is fundamentally incompatible with the aim of |
||||
protecting users' freedom to change the software. The systematic |
||||
pattern of such abuse occurs in the area of products for individuals to |
||||
use, which is precisely where it is most unacceptable. Therefore, we |
||||
have designed this version of the GPL to prohibit the practice for those |
||||
products. If such problems arise substantially in other domains, we |
||||
stand ready to extend this provision to those domains in future versions |
||||
of the GPL, as needed to protect the freedom of users. |
||||
|
||||
Finally, every program is threatened constantly by software patents. |
||||
States should not allow patents to restrict development and use of |
||||
software on general-purpose computers, but in those that do, we wish to |
||||
avoid the special danger that patents applied to a free program could |
||||
make it effectively proprietary. To prevent this, the GPL assures that |
||||
patents cannot be used to render the program non-free. |
||||
|
||||
The precise terms and conditions for copying, distribution and |
||||
modification follow. |
||||
|
||||
TERMS AND CONDITIONS |
||||
|
||||
0. Definitions. |
||||
|
||||
"This License" refers to version 3 of the GNU General Public License. |
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of |
||||
works, such as semiconductor masks. |
||||
|
||||
"The Program" refers to any copyrightable work licensed under this |
||||
License. Each licensee is addressed as "you". "Licensees" and |
||||
"recipients" may be individuals or organizations. |
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work |
||||
in a fashion requiring copyright permission, other than the making of an |
||||
exact copy. The resulting work is called a "modified version" of the |
||||
earlier work or a work "based on" the earlier work. |
||||
|
||||
A "covered work" means either the unmodified Program or a work based |
||||
on the Program. |
||||
|
||||
To "propagate" a work means to do anything with it that, without |
||||
permission, would make you directly or secondarily liable for |
||||
infringement under applicable copyright law, except executing it on a |
||||
computer or modifying a private copy. Propagation includes copying, |
||||
distribution (with or without modification), making available to the |
||||
public, and in some countries other activities as well. |
||||
|
||||
To "convey" a work means any kind of propagation that enables other |
||||
parties to make or receive copies. Mere interaction with a user through |
||||
a computer network, with no transfer of a copy, is not conveying. |
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" |
||||
to the extent that it includes a convenient and prominently visible |
||||
feature that (1) displays an appropriate copyright notice, and (2) |
||||
tells the user that there is no warranty for the work (except to the |
||||
extent that warranties are provided), that licensees may convey the |
||||
work under this License, and how to view a copy of this License. If |
||||
the interface presents a list of user commands or options, such as a |
||||
menu, a prominent item in the list meets this criterion. |
||||
|
||||
1. Source Code. |
||||
|
||||
The "source code" for a work means the preferred form of the work |
||||
for making modifications to it. "Object code" means any non-source |
||||
form of a work. |
||||
|
||||
A "Standard Interface" means an interface that either is an official |
||||
standard defined by a recognized standards body, or, in the case of |
||||
interfaces specified for a particular programming language, one that |
||||
is widely used among developers working in that language. |
||||
|
||||
The "System Libraries" of an executable work include anything, other |
||||
than the work as a whole, that (a) is included in the normal form of |
||||
packaging a Major Component, but which is not part of that Major |
||||
Component, and (b) serves only to enable use of the work with that |
||||
Major Component, or to implement a Standard Interface for which an |
||||
implementation is available to the public in source code form. A |
||||
"Major Component", in this context, means a major essential component |
||||
(kernel, window system, and so on) of the specific operating system |
||||
(if any) on which the executable work runs, or a compiler used to |
||||
produce the work, or an object code interpreter used to run it. |
||||
|
||||
The "Corresponding Source" for a work in object code form means all |
||||
the source code needed to generate, install, and (for an executable |
||||
work) run the object code and to modify the work, including scripts to |
||||
control those activities. However, it does not include the work's |
||||
System Libraries, or general-purpose tools or generally available free |
||||
programs which are used unmodified in performing those activities but |
||||
which are not part of the work. For example, Corresponding Source |
||||
includes interface definition files associated with source files for |
||||
the work, and the source code for shared libraries and dynamically |
||||
linked subprograms that the work is specifically designed to require, |
||||
such as by intimate data communication or control flow between those |
||||
subprograms and other parts of the work. |
||||
|
||||
The Corresponding Source need not include anything that users |
||||
can regenerate automatically from other parts of the Corresponding |
||||
Source. |
||||
|
||||
The Corresponding Source for a work in source code form is that |
||||
same work. |
||||
|
||||
2. Basic Permissions. |
||||
|
||||
All rights granted under this License are granted for the term of |
||||
copyright on the Program, and are irrevocable provided the stated |
||||
conditions are met. This License explicitly affirms your unlimited |
||||
permission to run the unmodified Program. The output from running a |
||||
covered work is covered by this License only if the output, given its |
||||
content, constitutes a covered work. This License acknowledges your |
||||
rights of fair use or other equivalent, as provided by copyright law. |
||||
|
||||
You may make, run and propagate covered works that you do not |
||||
convey, without conditions so long as your license otherwise remains |
||||
in force. You may convey covered works to others for the sole purpose |
||||
of having them make modifications exclusively for you, or provide you |
||||
with facilities for running those works, provided that you comply with |
||||
the terms of this License in conveying all material for which you do |
||||
not control copyright. Those thus making or running the covered works |
||||
for you must do so exclusively on your behalf, under your direction |
||||
and control, on terms that prohibit them from making any copies of |
||||
your copyrighted material outside their relationship with you. |
||||
|
||||
Conveying under any other circumstances is permitted solely under |
||||
the conditions stated below. Sublicensing is not allowed; section 10 |
||||
makes it unnecessary. |
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
||||
|
||||
No covered work shall be deemed part of an effective technological |
||||
measure under any applicable law fulfilling obligations under article |
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
||||
similar laws prohibiting or restricting circumvention of such |
||||
measures. |
||||
|
||||
When you convey a covered work, you waive any legal power to forbid |
||||
circumvention of technological measures to the extent such circumvention |
||||
is effected by exercising rights under this License with respect to |
||||
the covered work, and you disclaim any intention to limit operation or |
||||
modification of the work as a means of enforcing, against the work's |
||||
users, your or third parties' legal rights to forbid circumvention of |
||||
technological measures. |
||||
|
||||
4. Conveying Verbatim Copies. |
||||
|
||||
You may convey verbatim copies of the Program's source code as you |
||||
receive it, in any medium, provided that you conspicuously and |
||||
appropriately publish on each copy an appropriate copyright notice; |
||||
keep intact all notices stating that this License and any |
||||
non-permissive terms added in accord with section 7 apply to the code; |
||||
keep intact all notices of the absence of any warranty; and give all |
||||
recipients a copy of this License along with the Program. |
||||
|
||||
You may charge any price or no price for each copy that you convey, |
||||
and you may offer support or warranty protection for a fee. |
||||
|
||||
5. Conveying Modified Source Versions. |
||||
|
||||
You may convey a work based on the Program, or the modifications to |
||||
produce it from the Program, in the form of source code under the |
||||
terms of section 4, provided that you also meet all of these conditions: |
||||
|
||||
a) The work must carry prominent notices stating that you modified |
||||
it, and giving a relevant date. |
||||
|
||||
b) The work must carry prominent notices stating that it is |
||||
released under this License and any conditions added under section |
||||
7. This requirement modifies the requirement in section 4 to |
||||
"keep intact all notices". |
||||
|
||||
c) You must license the entire work, as a whole, under this |
||||
License to anyone who comes into possession of a copy. This |
||||
License will therefore apply, along with any applicable section 7 |
||||
additional terms, to the whole of the work, and all its parts, |
||||
regardless of how they are packaged. This License gives no |
||||
permission to license the work in any other way, but it does not |
||||
invalidate such permission if you have separately received it. |
||||
|
||||
d) If the work has interactive user interfaces, each must display |
||||
Appropriate Legal Notices; however, if the Program has interactive |
||||
interfaces that do not display Appropriate Legal Notices, your |
||||
work need not make them do so. |
||||
|
||||
A compilation of a covered work with other separate and independent |
||||
works, which are not by their nature extensions of the covered work, |
||||
and which are not combined with it such as to form a larger program, |
||||
in or on a volume of a storage or distribution medium, is called an |
||||
"aggregate" if the compilation and its resulting copyright are not |
||||
used to limit the access or legal rights of the compilation's users |
||||
beyond what the individual works permit. Inclusion of a covered work |
||||
in an aggregate does not cause this License to apply to the other |
||||
parts of the aggregate. |
||||
|
||||
6. Conveying Non-Source Forms. |
||||
|
||||
You may convey a covered work in object code form under the terms |
||||
of sections 4 and 5, provided that you also convey the |
||||
machine-readable Corresponding Source under the terms of this License, |
||||
in one of these ways: |
||||
|
||||
a) Convey the object code in, or embodied in, a physical product |
||||
(including a physical distribution medium), accompanied by the |
||||
Corresponding Source fixed on a durable physical medium |
||||
customarily used for software interchange. |
||||
|
||||
b) Convey the object code in, or embodied in, a physical product |
||||
(including a physical distribution medium), accompanied by a |
||||
written offer, valid for at least three years and valid for as |
||||
long as you offer spare parts or customer support for that product |
||||
model, to give anyone who possesses the object code either (1) a |
||||
copy of the Corresponding Source for all the software in the |
||||
product that is covered by this License, on a durable physical |
||||
medium customarily used for software interchange, for a price no |
||||
more than your reasonable cost of physically performing this |
||||
conveying of source, or (2) access to copy the |
||||
Corresponding Source from a network server at no charge. |
||||
|
||||
c) Convey individual copies of the object code with a copy of the |
||||
written offer to provide the Corresponding Source. This |
||||
alternative is allowed only occasionally and noncommercially, and |
||||
only if you received the object code with such an offer, in accord |
||||
with subsection 6b. |
||||
|
||||
d) Convey the object code by offering access from a designated |
||||
place (gratis or for a charge), and offer equivalent access to the |
||||
Corresponding Source in the same way through the same place at no |
||||
further charge. You need not require recipients to copy the |
||||
Corresponding Source along with the object code. If the place to |
||||
copy the object code is a network server, the Corresponding Source |
||||
may be on a different server (operated by you or a third party) |
||||
that supports equivalent copying facilities, provided you maintain |
||||
clear directions next to the object code saying where to find the |
||||
Corresponding Source. Regardless of what server hosts the |
||||
Corresponding Source, you remain obligated to ensure that it is |
||||
available for as long as needed to satisfy these requirements. |
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided |
||||
you inform other peers where the object code and Corresponding |
||||
Source of the work are being offered to the general public at no |
||||
charge under subsection 6d. |
||||
|
||||
A separable portion of the object code, whose source code is excluded |
||||
from the Corresponding Source as a System Library, need not be |
||||
included in conveying the object code work. |
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any |
||||
tangible personal property which is normally used for personal, family, |
||||
or household purposes, or (2) anything designed or sold for incorporation |
||||
into a dwelling. In determining whether a product is a consumer product, |
||||
doubtful cases shall be resolved in favor of coverage. For a particular |
||||
product received by a particular user, "normally used" refers to a |
||||
typical or common use of that class of product, regardless of the status |
||||
of the particular user or of the way in which the particular user |
||||
actually uses, or expects or is expected to use, the product. A product |
||||
is a consumer product regardless of whether the product has substantial |
||||
commercial, industrial or non-consumer uses, unless such uses represent |
||||
the only significant mode of use of the product. |
||||
|
||||
"Installation Information" for a User Product means any methods, |
||||
procedures, authorization keys, or other information required to install |
||||
and execute modified versions of a covered work in that User Product from |
||||
a modified version of its Corresponding Source. The information must |
||||
suffice to ensure that the continued functioning of the modified object |
||||
code is in no case prevented or interfered with solely because |
||||
modification has been made. |
||||
|
||||
If you convey an object code work under this section in, or with, or |
||||
specifically for use in, a User Product, and the conveying occurs as |
||||
part of a transaction in which the right of possession and use of the |
||||
User Product is transferred to the recipient in perpetuity or for a |
||||
fixed term (regardless of how the transaction is characterized), the |
||||
Corresponding Source conveyed under this section must be accompanied |
||||
by the Installation Information. But this requirement does not apply |
||||
if neither you nor any third party retains the ability to install |
||||
modified object code on the User Product (for example, the work has |
||||
been installed in ROM). |
||||
|
||||
The requirement to provide Installation Information does not include a |
||||
requirement to continue to provide support service, warranty, or updates |
||||
for a work that has been modified or installed by the recipient, or for |
||||
the User Product in which it has been modified or installed. Access to a |
||||
network may be denied when the modification itself materially and |
||||
adversely affects the operation of the network or violates the rules and |
||||
protocols for communication across the network. |
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, |
||||
in accord with this section must be in a format that is publicly |
||||
documented (and with an implementation available to the public in |
||||
source code form), and must require no special password or key for |
||||
unpacking, reading or copying. |
||||
|
||||
7. Additional Terms. |
||||
|
||||
"Additional permissions" are terms that supplement the terms of this |
||||
License by making exceptions from one or more of its conditions. |
||||
Additional permissions that are applicable to the entire Program shall |
||||
be treated as though they were included in this License, to the extent |
||||
that they are valid under applicable law. If additional permissions |
||||
apply only to part of the Program, that part may be used separately |
||||
under those permissions, but the entire Program remains governed by |
||||
this License without regard to the additional permissions. |
||||
|
||||
When you convey a copy of a covered work, you may at your option |
||||
remove any additional permissions from that copy, or from any part of |
||||
it. (Additional permissions may be written to require their own |
||||
removal in certain cases when you modify the work.) You may place |
||||
additional permissions on material, added by you to a covered work, |
||||
for which you have or can give appropriate copyright permission. |
||||
|
||||
Notwithstanding any other provision of this License, for material you |
||||
add to a covered work, you may (if authorized by the copyright holders of |
||||
that material) supplement the terms of this License with terms: |
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the |
||||
terms of sections 15 and 16 of this License; or |
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or |
||||
author attributions in that material or in the Appropriate Legal |
||||
Notices displayed by works containing it; or |
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or |
||||
requiring that modified versions of such material be marked in |
||||
reasonable ways as different from the original version; or |
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or |
||||
authors of the material; or |
||||
|
||||
e) Declining to grant rights under trademark law for use of some |
||||
trade names, trademarks, or service marks; or |
||||
|
||||
f) Requiring indemnification of licensors and authors of that |
||||
material by anyone who conveys the material (or modified versions of |
||||
it) with contractual assumptions of liability to the recipient, for |
||||
any liability that these contractual assumptions directly impose on |
||||
those licensors and authors. |
||||
|
||||
All other non-permissive additional terms are considered "further |
||||
restrictions" within the meaning of section 10. If the Program as you |
||||
received it, or any part of it, contains a notice stating that it is |
||||
governed by this License along with a term that is a further |
||||
restriction, you may remove that term. If a license document contains |
||||
a further restriction but permits relicensing or conveying under this |
||||
License, you may add to a covered work material governed by the terms |
||||
of that license document, provided that the further restriction does |
||||
not survive such relicensing or conveying. |
||||
|
||||
If you add terms to a covered work in accord with this section, you |
||||
must place, in the relevant source files, a statement of the |
||||
additional terms that apply to those files, or a notice indicating |
||||
where to find the applicable terms. |
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the |
||||
form of a separately written license, or stated as exceptions; |
||||
the above requirements apply either way. |
||||
|
||||
8. Termination. |
||||
|
||||
You may not propagate or modify a covered work except as expressly |
||||
provided under this License. Any attempt otherwise to propagate or |
||||
modify it is void, and will automatically terminate your rights under |
||||
this License (including any patent licenses granted under the third |
||||
paragraph of section 11). |
||||
|
||||
However, if you cease all violation of this License, then your |
||||
license from a particular copyright holder is reinstated (a) |
||||
provisionally, unless and until the copyright holder explicitly and |
||||
finally terminates your license, and (b) permanently, if the copyright |
||||
holder fails to notify you of the violation by some reasonable means |
||||
prior to 60 days after the cessation. |
||||
|
||||
Moreover, your license from a particular copyright holder is |
||||
reinstated permanently if the copyright holder notifies you of the |
||||
violation by some reasonable means, this is the first time you have |
||||
received notice of violation of this License (for any work) from that |
||||
copyright holder, and you cure the violation prior to 30 days after |
||||
your receipt of the notice. |
||||
|
||||
Termination of your rights under this section does not terminate the |
||||
licenses of parties who have received copies or rights from you under |
||||
this License. If your rights have been terminated and not permanently |
||||
reinstated, you do not qualify to receive new licenses for the same |
||||
material under section 10. |
||||
|
||||
9. Acceptance Not Required for Having Copies. |
||||
|
||||
You are not required to accept this License in order to receive or |
||||
run a copy of the Program. Ancillary propagation of a covered work |
||||
occurring solely as a consequence of using peer-to-peer transmission |
||||
to receive a copy likewise does not require acceptance. However, |
||||
nothing other than this License grants you permission to propagate or |
||||
modify any covered work. These actions infringe copyright if you do |
||||
not accept this License. Therefore, by modifying or propagating a |
||||
covered work, you indicate your acceptance of this License to do so. |
||||
|
||||
10. Automatic Licensing of Downstream Recipients. |
||||
|
||||
Each time you convey a covered work, the recipient automatically |
||||
receives a license from the original licensors, to run, modify and |
||||
propagate that work, subject to this License. You are not responsible |
||||
for enforcing compliance by third parties with this License. |
||||
|
||||
An "entity transaction" is a transaction transferring control of an |
||||
organization, or substantially all assets of one, or subdividing an |
||||
organization, or merging organizations. If propagation of a covered |
||||
work results from an entity transaction, each party to that |
||||
transaction who receives a copy of the work also receives whatever |
||||
licenses to the work the party's predecessor in interest had or could |
||||
give under the previous paragraph, plus a right to possession of the |
||||
Corresponding Source of the work from the predecessor in interest, if |
||||
the predecessor has it or can get it with reasonable efforts. |
||||
|
||||
You may not impose any further restrictions on the exercise of the |
||||
rights granted or affirmed under this License. For example, you may |
||||
not impose a license fee, royalty, or other charge for exercise of |
||||
rights granted under this License, and you may not initiate litigation |
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that |
||||
any patent claim is infringed by making, using, selling, offering for |
||||
sale, or importing the Program or any portion of it. |
||||
|
||||
11. Patents. |
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this |
||||
License of the Program or a work on which the Program is based. The |
||||
work thus licensed is called the contributor's "contributor version". |
||||
|
||||
A contributor's "essential patent claims" are all patent claims |
||||
owned or controlled by the contributor, whether already acquired or |
||||
hereafter acquired, that would be infringed by some manner, permitted |
||||
by this License, of making, using, or selling its contributor version, |
||||
but do not include claims that would be infringed only as a |
||||
consequence of further modification of the contributor version. For |
||||
purposes of this definition, "control" includes the right to grant |
||||
patent sublicenses in a manner consistent with the requirements of |
||||
this License. |
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free |
||||
patent license under the contributor's essential patent claims, to |
||||
make, use, sell, offer for sale, import and otherwise run, modify and |
||||
propagate the contents of its contributor version. |
||||
|
||||
In the following three paragraphs, a "patent license" is any express |
||||
agreement or commitment, however denominated, not to enforce a patent |
||||
(such as an express permission to practice a patent or covenant not to |
||||
sue for patent infringement). To "grant" such a patent license to a |
||||
party means to make such an agreement or commitment not to enforce a |
||||
patent against the party. |
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, |
||||
and the Corresponding Source of the work is not available for anyone |
||||
to copy, free of charge and under the terms of this License, through a |
||||
publicly available network server or other readily accessible means, |
||||
then you must either (1) cause the Corresponding Source to be so |
||||
available, or (2) arrange to deprive yourself of the benefit of the |
||||
patent license for this particular work, or (3) arrange, in a manner |
||||
consistent with the requirements of this License, to extend the patent |
||||
license to downstream recipients. "Knowingly relying" means you have |
||||
actual knowledge that, but for the patent license, your conveying the |
||||
covered work in a country, or your recipient's use of the covered work |
||||
in a country, would infringe one or more identifiable patents in that |
||||
country that you have reason to believe are valid. |
||||
|
||||
If, pursuant to or in connection with a single transaction or |
||||
arrangement, you convey, or propagate by procuring conveyance of, a |
||||
covered work, and grant a patent license to some of the parties |
||||
receiving the covered work authorizing them to use, propagate, modify |
||||
or convey a specific copy of the covered work, then the patent license |
||||
you grant is automatically extended to all recipients of the covered |
||||
work and works based on it. |
||||
|
||||
A patent license is "discriminatory" if it does not include within |
||||
the scope of its coverage, prohibits the exercise of, or is |
||||
conditioned on the non-exercise of one or more of the rights that are |
||||
specifically granted under this License. You may not convey a covered |
||||
work if you are a party to an arrangement with a third party that is |
||||
in the business of distributing software, under which you make payment |
||||
to the third party based on the extent of your activity of conveying |
||||
the work, and under which the third party grants, to any of the |
||||
parties who would receive the covered work from you, a discriminatory |
||||
patent license (a) in connection with copies of the covered work |
||||
conveyed by you (or copies made from those copies), or (b) primarily |
||||
for and in connection with specific products or compilations that |
||||
contain the covered work, unless you entered into that arrangement, |
||||
or that patent license was granted, prior to 28 March 2007. |
||||
|
||||
Nothing in this License shall be construed as excluding or limiting |
||||
any implied license or other defenses to infringement that may |
||||
otherwise be available to you under applicable patent law. |
||||
|
||||
12. No Surrender of Others' Freedom. |
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or |
||||
otherwise) that contradict the conditions of this License, they do not |
||||
excuse you from the conditions of this License. If you cannot convey a |
||||
covered work so as to satisfy simultaneously your obligations under this |
||||
License and any other pertinent obligations, then as a consequence you may |
||||
not convey it at all. For example, if you agree to terms that obligate you |
||||
to collect a royalty for further conveying from those to whom you convey |
||||
the Program, the only way you could satisfy both those terms and this |
||||
License would be to refrain entirely from conveying the Program. |
||||
|
||||
13. Use with the GNU Affero General Public License. |
||||
|
||||
Notwithstanding any other provision of this License, you have |
||||
permission to link or combine any covered work with a work licensed |
||||
under version 3 of the GNU Affero General Public License into a single |
||||
combined work, and to convey the resulting work. The terms of this |
||||
License will continue to apply to the part which is the covered work, |
||||
but the special requirements of the GNU Affero General Public License, |
||||
section 13, concerning interaction through a network will apply to the |
||||
combination as such. |
||||
|
||||
14. Revised Versions of this License. |
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of |
||||
the GNU General Public License from time to time. Such new versions will |
||||
be similar in spirit to the present version, but may differ in detail to |
||||
address new problems or concerns. |
||||
|
||||
Each version is given a distinguishing version number. If the |
||||
Program specifies that a certain numbered version of the GNU General |
||||
Public License "or any later version" applies to it, you have the |
||||
option of following the terms and conditions either of that numbered |
||||
version or of any later version published by the Free Software |
||||
Foundation. If the Program does not specify a version number of the |
||||
GNU General Public License, you may choose any version ever published |
||||
by the Free Software Foundation. |
||||
|
||||
If the Program specifies that a proxy can decide which future |
||||
versions of the GNU General Public License can be used, that proxy's |
||||
public statement of acceptance of a version permanently authorizes you |
||||
to choose that version for the Program. |
||||
|
||||
Later license versions may give you additional or different |
||||
permissions. However, no additional obligations are imposed on any |
||||
author or copyright holder as a result of your choosing to follow a |
||||
later version. |
||||
|
||||
15. Disclaimer of Warranty. |
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
||||
|
||||
16. Limitation of Liability. |
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
||||
SUCH DAMAGES. |
||||
|
||||
17. Interpretation of Sections 15 and 16. |
||||
|
||||
If the disclaimer of warranty and limitation of liability provided |
||||
above cannot be given local legal effect according to their terms, |
||||
reviewing courts shall apply local law that most closely approximates |
||||
an absolute waiver of all civil liability in connection with the |
||||
Program, unless a warranty or assumption of liability accompanies a |
||||
copy of the Program in return for a fee. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
How to Apply These Terms to Your New Programs |
||||
|
||||
If you develop a new program, and you want it to be of the greatest |
||||
possible use to the public, the best way to achieve this is to make it |
||||
free software which everyone can redistribute and change under these terms. |
||||
|
||||
To do so, attach the following notices to the program. It is safest |
||||
to attach them to the start of each source file to most effectively |
||||
state the exclusion of warranty; and each file should have at least |
||||
the "copyright" line and a pointer to where the full notice is found. |
||||
|
||||
<one line to give the program's name and a brief idea of what it does.> |
||||
Copyright (C) <year> <name of author> |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||
|
||||
Also add information on how to contact you by electronic and paper mail. |
||||
|
||||
If the program does terminal interaction, make it output a short |
||||
notice like this when it starts in an interactive mode: |
||||
|
||||
<program> Copyright (C) <year> <name of author> |
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
||||
This is free software, and you are welcome to redistribute it |
||||
under certain conditions; type `show c' for details. |
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate |
||||
parts of the General Public License. Of course, your program's commands |
||||
might be different; for a GUI interface, you would use an "about box". |
||||
|
||||
You should also get your employer (if you work as a programmer) or school, |
||||
if any, to sign a "copyright disclaimer" for the program, if necessary. |
||||
For more information on this, and how to apply and follow the GNU GPL, see |
||||
<https://www.gnu.org/licenses/>. |
||||
|
||||
The GNU General Public License does not permit incorporating your program |
||||
into proprietary programs. If your program is a subroutine library, you |
||||
may consider it more useful to permit linking proprietary applications with |
||||
the library. If this is what you want to do, use the GNU Lesser General |
||||
Public License instead of this License. But first, please read |
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>. |
@ -0,0 +1,85 @@
|
||||
/** |
||||
* This actor attaches itself to the ammo boxes |
||||
* and imitates their collision to let us detect when they're picked up. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class AmmoPickupStalker extends Actor; |
||||
|
||||
// Ammo box this stalker is attached to. |
||||
// If it is destroyed (not just picked up) - stalker must die too. |
||||
var private KFAmmoPickup target; |
||||
|
||||
// This variable is used to record if our 'target' ammo box was in |
||||
// active state ('Pickup') last time we've checked. |
||||
// We need this because ammo box's 'Touch' event can fire off first and |
||||
// force the box to sleep before stalker could catch same event. |
||||
// Without this variable we would have no way to know if player |
||||
// simply walked near the place of a sleeping box or actually grabbed it. |
||||
var private bool wasActive; |
||||
|
||||
// Static function that spawns a new stalker for the given box. |
||||
// Careful, as there's no checks for whether a stalker is |
||||
// already attached to it. |
||||
// Ensuring that is on the user of the function. |
||||
public final static function StalkAmmoPickup(KFAmmoPickup newTarget) |
||||
{ |
||||
local AmmoPickupStalker newStalker; |
||||
if (newTarget == none) return; |
||||
|
||||
newStalker = newTarget.Spawn(class'AmmoPickupStalker'); |
||||
newStalker.target = newTarget; |
||||
newStalker.SetBase(newTarget); |
||||
newStalker.SetCollision(true); |
||||
newStalker.SetCollisionSize(newTarget.collisionRadius, |
||||
newTarget.collisionHeight); |
||||
} |
||||
|
||||
event Touch(Actor other) |
||||
{ |
||||
local FixAmmoSelling ammoSellingFix; |
||||
if (target == none) return; |
||||
// If our box was sleeping for while (more than a tick), - |
||||
// player couldn't have gotten any ammo. |
||||
if (!wasActive && !target.IsInState('Pickup')) return; |
||||
|
||||
ammoSellingFix = FixAmmoSelling(class'FixAmmoSelling'.static.GetInstance()); |
||||
if (ammoSellingFix != none) |
||||
{ |
||||
ammoSellingFix.RecordAmmoPickup(Pawn(other), target); |
||||
} |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
if (target != none) |
||||
{ |
||||
wasActive = target.IsInState('Pickup'); |
||||
} |
||||
else |
||||
{ |
||||
Destroy(); |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
// Server-only, hidden |
||||
remoteRole = ROLE_None |
||||
bAlwaysRelevant = true |
||||
drawType = DT_None |
||||
} |
@ -0,0 +1,396 @@
|
||||
/** |
||||
* This feature addressed an oversight in vanilla code that |
||||
* allows clients to sell weapon's ammunition. |
||||
* Moreover, when being sold, ammunition cost is always multiplied by 0.75, |
||||
* without taking into an account possible discount a player might have. |
||||
* This allows cheaters to "print money" by buying and selling ammo over and |
||||
* over again ammunition for some weapons, |
||||
* notably pipe bombs (74% discount for lvl6 demolition) |
||||
* and crossbow (42% discount for lvl6 sharpshooter). |
||||
* |
||||
* This feature fixes this problem by setting 'pickupClass' variable in |
||||
* potentially abusable weapons to our own value that won't receive a discount. |
||||
* Luckily for us, it seems that pickup spawn and discount checks are the only |
||||
* two place where variable is directly checked in a vanilla game's code |
||||
* ('default.pickupClass' is used everywhere else), |
||||
* so we can easily deal with the side effects of such change. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSelling extends Feature |
||||
config(AcediaFixes); |
||||
|
||||
/** |
||||
* We will replace 'pickupClass' variable for all instances of potentially |
||||
* abusable weapons. That is weapons, that have a discount for their ammunition |
||||
* (via 'GetAmmoCostScaling' function in a corresponding perk class). |
||||
* They are defined (along with our pickup replacements) in 'rules' array. |
||||
* That array isn't configurable, since the abusable status is hardcoded into |
||||
* perk classes and the main mod that allows to change those (ServerPerks), |
||||
* also solves ammo selling by a more direct method |
||||
* (only available for the mods that replace player pawn class). |
||||
* This change already completely fixes ammo printing. |
||||
* Possible concern with changing the value of 'pickupClass' is that |
||||
* it might affect gameplay in too many ways. |
||||
* But, luckily for us, that value is only used when spawning a new pickup and |
||||
* in 'ServerBuyAmmo' function of 'KFPawn' |
||||
* (all the other places use it's default value instead). |
||||
* This means that the only two side-effects of our change are: |
||||
* 1. That wrong pickup class will be spawned. This problem is easily |
||||
* solved by replacing spawned actor in 'CheckReplacement'. |
||||
* 2. That ammo will be sold at a different (lower for us) price, |
||||
* while trader would still display and require the original price. |
||||
* This problem is solved by manually taking from player the difference |
||||
* between what he should have had to pay and what he actually paid. |
||||
* This brings us to the second issue - |
||||
* detecting when player bought the ammo. |
||||
* Unfortunately, it doesn't seem possible to detect with 100% certainty |
||||
* without replacing pawn or shop classes, |
||||
* so we have to eliminate other possibilities. |
||||
* There are seem to be three ways for players to get more ammo: |
||||
* 1. For some mod to give it; |
||||
* 2. Found it an ammo box; |
||||
* 3. To buy ammo (can only happen in trader). |
||||
* We don't want to provide mods with low-level API for bug fixes, |
||||
* so to ensure the compatibility, mods that want to increase ammo values |
||||
* will have to solve compatibility issue by themselves: |
||||
* either by reimplementing this fix (possibly the best option) |
||||
* or by giving players appropriate money along with the ammo. |
||||
* The only other case we have to eliminate is ammo boxes. |
||||
* First, all cases of ammo boxes outside the trader are easy to detect, |
||||
* since in this case we can be sure that player didn't buy ammo |
||||
* (and mods that can allow it can just get rid of |
||||
* 'ServerSellAmmo' function directly, similarly to how ServerPerks does it). |
||||
* We'll detect all the other boxes by attaching an auxiliary actor |
||||
* ('AmmoPickupStalker') to them, that will fire off 'Touch' event |
||||
* at the same time as ammo boxes. |
||||
* The only possible problem is that part of the ammo cost is |
||||
* taken with a slight delay, which leaves cheaters a window of opportunity |
||||
* to buy more than they can afford. |
||||
* This issue is addressed by each ammo type costing as little as possible |
||||
* (its' cost for corresponding perk at lvl6) |
||||
* and a flag that does allow players to go into negative dosh values |
||||
* (the cost is potential bugs in this fix itself, that |
||||
* can somewhat affect regular players). |
||||
*/ |
||||
|
||||
// Due to how this fix works, players with level below 6 get charged less |
||||
// than necessary by the shop and this fix must take the rest of |
||||
// the cost by itself. |
||||
// The problem is, due to how ammo purchase is coded, low-level (<6 lvl) |
||||
// players can actually buy more ammo for "fixed" weapons than they can afford |
||||
// by filling ammo for one or all weapons. |
||||
// Setting this flag to 'true' will allow us to still take full cost |
||||
// from them, putting them in "debt" (having negative dosh amount). |
||||
// If you don't want to have players with negative dosh values on your server |
||||
// as a side-effect of this fix, then leave this flag as 'false', |
||||
// letting low level players buy ammo cheaper |
||||
// (but not cheaper than lvl6 could). |
||||
// NOTE: this issue doesn't affect level 6 players. |
||||
// NOTE #2: this fix does give players below level 6 some |
||||
// technical advantage compared to vanilla game, but this advantage |
||||
// cannot exceed benefits of having level 6. |
||||
var private config const bool allowNegativeDosh; |
||||
|
||||
// This structure records what classes of weapons can be abused |
||||
// and what pickup class we should use to fix the exploit. |
||||
struct ReplacementRule |
||||
{ |
||||
var class<KFWeapon> abusableWeapon; |
||||
var class<KFWeaponPickup> pickupReplacement; |
||||
}; |
||||
|
||||
// Actual list of abusable weapons. |
||||
var private const array<ReplacementRule> rules; |
||||
|
||||
// We create one such record for any |
||||
// abusable weapon instance in the game to store: |
||||
struct WeaponRecord |
||||
{ |
||||
// The instance itself. |
||||
var KFWeapon weapon; |
||||
// Corresponding ammo instance |
||||
// (all abusable weapons only have one ammo type). |
||||
var KFAmmunition ammo; |
||||
// Last ammo amount we've seen, used to detect players gaining ammo |
||||
// (from either ammo boxes or buying it). |
||||
var int lastAmmoAmount; |
||||
}; |
||||
|
||||
// All weapons we've detected so far. |
||||
var private array<WeaponRecord> registeredWeapons; |
||||
|
||||
protected function OnEnabled() |
||||
{ |
||||
local KFWeapon nextWeapon; |
||||
local KFAmmoPickup nextPickup; |
||||
// Find all abusable weapons |
||||
foreach level.DynamicActors(class'KFMod.KFWeapon', nextWeapon) |
||||
{ |
||||
FixWeapon(nextWeapon); |
||||
} |
||||
// Start tracking all ammo boxes |
||||
foreach level.DynamicActors(class'KFMod.KFAmmoPickup', nextPickup) |
||||
{ |
||||
class'AmmoPickupStalker'.static.StalkAmmoPickup(nextPickup); |
||||
} |
||||
} |
||||
|
||||
protected function OnDisabled() |
||||
{ |
||||
local int i; |
||||
local AmmoPickupStalker nextStalker; |
||||
local array<AmmoPickupStalker> stalkers; |
||||
// Restore all the 'pickupClass' variables we've changed. |
||||
for (i = 0; i < registeredWeapons.length; i += 1) |
||||
{ |
||||
if (registeredWeapons[i].weapon != none) |
||||
{ |
||||
registeredWeapons[i].weapon.pickupClass = |
||||
registeredWeapons[i].weapon.default.pickupClass; |
||||
} |
||||
} |
||||
registeredWeapons.length = 0; |
||||
// Kill all the stalkers; |
||||
// to be safe, avoid destroying them directly in the iterator. |
||||
foreach level.DynamicActors(class'AmmoPickupStalker', nextStalker) |
||||
{ |
||||
stalkers[stalkers.length] = nextStalker; |
||||
} |
||||
for (i = 0; i < stalkers.length; i += 1) |
||||
{ |
||||
if (stalkers[i] != none) |
||||
{ |
||||
stalkers[i].Destroy(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Checks if given class is a one of our pickup replacer classes. |
||||
public static final function bool IsReplacer(class<Actor> pickupClass) |
||||
{ |
||||
local int i; |
||||
if (pickupClass == none) return false; |
||||
for (i = 0; i < default.rules.length; i += 1) |
||||
{ |
||||
if (pickupClass == default.rules[i].pickupReplacement) |
||||
{ |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
// 1. Checks if weapon can be abused and if it can, - fixes the problem. |
||||
// 2. Starts tracking abusable weapon to detect when player buys ammo for it. |
||||
public final function FixWeapon(KFWeapon potentialAbuser) |
||||
{ |
||||
local int i; |
||||
local WeaponRecord newRecord; |
||||
if (potentialAbuser == none) return; |
||||
|
||||
for (i = 0; i < registeredWeapons.length; i += 1) |
||||
{ |
||||
if (registeredWeapons[i].weapon == potentialAbuser) |
||||
{ |
||||
return; |
||||
} |
||||
} |
||||
for (i = 0; i < rules.length; i += 1) |
||||
{ |
||||
if (potentialAbuser.class == rules[i].abusableWeapon) |
||||
{ |
||||
potentialAbuser.pickupClass = rules[i].pickupReplacement; |
||||
newRecord.weapon = potentialAbuser; |
||||
registeredWeapons[registeredWeapons.length] = newRecord; |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Finds ammo instance for recorded weapon in it's owner's inventory. |
||||
private final function WeaponRecord FindAmmoInstance(WeaponRecord record) |
||||
{ |
||||
local Inventory invIter; |
||||
local KFAmmunition ammo; |
||||
if (record.weapon == none) return record; |
||||
if (record.weapon.instigator == none) return record; |
||||
|
||||
// Find instances anew |
||||
invIter = record.weapon.instigator.inventory; |
||||
while (invIter != none) |
||||
{ |
||||
if (record.weapon.ammoClass[0] == invIter.class) |
||||
{ |
||||
ammo = KFAmmunition(invIter); |
||||
} |
||||
invIter = invIter.inventory; |
||||
} |
||||
// Add missing instances |
||||
if (ammo != none) |
||||
{ |
||||
record.ammo = ammo; |
||||
record.lastAmmoAmount = ammo.ammoAmount; |
||||
} |
||||
return record; |
||||
} |
||||
|
||||
// Calculates how much more player should have paid for 'ammoAmount' |
||||
// amount of ammo, compared to how much trader took after our fix. |
||||
private final function float GetPriceCorrection |
||||
( |
||||
KFWeapon kfWeapon, |
||||
int ammoAmount |
||||
) |
||||
{ |
||||
local float boughtMagFraction; |
||||
// 'vanillaPrice' - price that would be calculated |
||||
// without our interference |
||||
// 'fixPrice' - price that will be calculated after |
||||
// we've replaced pickup class |
||||
local float vanillaPrice, fixPrice; |
||||
local KFPlayerReplicationInfo kfRI; |
||||
local class<KFWeaponPickup> vanillaPickupClass, fixPickupClass; |
||||
if (kfWeapon == none || kfWeapon.instigator == none) return 0.0; |
||||
fixPickupClass = class<KFWeaponPickup>(kfWeapon.pickupClass); |
||||
vanillaPickupClass = class<KFWeaponPickup>(kfWeapon.default.pickupClass); |
||||
if (fixPickupClass == none || vanillaPickupClass == none) return 0.0; |
||||
|
||||
// Calculate base prices |
||||
boughtMagFraction = (float(ammoAmount) / kfWeapon.default.magCapacity); |
||||
fixPrice = boughtMagFraction * fixPickupClass.default.AmmoCost; |
||||
vanillaPrice = boughtMagFraction * vanillaPickupClass.default.AmmoCost; |
||||
// Apply perk discount for vanilla price |
||||
// (we don't need to consider secondary ammo or husk gun special cases, |
||||
// since such weapons can't be abused via ammo dosh-printing) |
||||
kfRI = KFPlayerReplicationInfo(kfWeapon.instigator.playerReplicationInfo); |
||||
if (kfRI != none && kfRI.clientVeteranSkill != none) |
||||
{ |
||||
vanillaPrice *= kfRI.clientVeteranSkill.static. |
||||
GetAmmoCostScaling(kfRI, vanillaPickupClass); |
||||
} |
||||
// TWI's code rounds up ammo cost |
||||
// to the integer value whenever ammo is bought, |
||||
// so to calculate exactly how much we need to correct the cost, |
||||
// we must find difference between the final, rounded cost values. |
||||
return float(Max(0, int(vanillaPrice) - int(fixPrice))); |
||||
} |
||||
|
||||
// Takes current ammo and last recorded in 'record' value to calculate |
||||
// how much money to take from the player |
||||
// (calculations are done via 'GetPriceCorrection'). |
||||
private final function WeaponRecord TaxAmmoChange(WeaponRecord record) |
||||
{ |
||||
local int ammoDiff; |
||||
local KFPawn taxPayer; |
||||
local PlayerReplicationInfo replicationInfo; |
||||
taxPayer = KFPawn(record.weapon.instigator); |
||||
if (record.weapon == none || taxPayer == none) return record; |
||||
// No need to charge money if player couldn't have |
||||
// possibly bought the ammo. |
||||
if (!taxPayer.CanBuyNow()) return record; |
||||
// Find ammo difference with recorded value. |
||||
if (record.ammo != none) |
||||
{ |
||||
ammoDiff = Max(0, record.ammo.ammoAmount - record.lastAmmoAmount); |
||||
record.lastAmmoAmount = record.ammo.ammoAmount; |
||||
} |
||||
// Make player pay dosh |
||||
replicationInfo = taxPayer.playerReplicationInfo; |
||||
if (replicationInfo != none) |
||||
{ |
||||
replicationInfo.score -= GetPriceCorrection(record.weapon, ammoDiff); |
||||
// This shouldn't happen, since shop is supposed to make sure |
||||
// player has enough dosh to buy ammo at full price |
||||
// (actual price + our correction). |
||||
// But if user is extra concerned about it, - |
||||
// we can additionally for force the score above 0. |
||||
if (!allowNegativeDosh) |
||||
{ |
||||
replicationInfo.score = FMax(0, replicationInfo.score); |
||||
} |
||||
} |
||||
return record; |
||||
} |
||||
|
||||
// Changes our records to account for player picking up the ammo box, |
||||
// to avoid charging his for it. |
||||
public final function RecordAmmoPickup(Pawn pawnWithAmmo, KFAmmoPickup pickup) |
||||
{ |
||||
local int i; |
||||
local int newAmount; |
||||
// Check conditions from 'KFAmmoPickup' code ('Touch' function) |
||||
if (pickup == none) return; |
||||
if (pawnWithAmmo == none) return; |
||||
if (pawnWithAmmo.controller == none) return; |
||||
if (!pawnWithAmmo.bCanPickupInventory) return; |
||||
if (!FastTrace(pawnWithAmmo.location, pickup.location)) return; |
||||
|
||||
// Add relevant amount of ammo to our records |
||||
for (i = 0; i < registeredWeapons.length; i += 1) |
||||
{ |
||||
if (registeredWeapons[i].weapon == none) continue; |
||||
if (registeredWeapons[i].weapon.instigator == pawnWithAmmo) |
||||
{ |
||||
newAmount = registeredWeapons[i].lastAmmoAmount |
||||
+ registeredWeapons[i].ammo.ammoPickupAmount; |
||||
newAmount = Min(registeredWeapons[i].ammo.maxAmmo, newAmount); |
||||
registeredWeapons[i].lastAmmoAmount = newAmount; |
||||
} |
||||
} |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
local int i; |
||||
// For all the weapon records... |
||||
i = 0; |
||||
while (i < registeredWeapons.length) |
||||
{ |
||||
// ...remove dead records |
||||
if (registeredWeapons[i].weapon == none) |
||||
{ |
||||
registeredWeapons.Remove(i, 1); |
||||
continue; |
||||
} |
||||
// ...find ammo if it's missing |
||||
if (registeredWeapons[i].ammo == none) |
||||
{ |
||||
registeredWeapons[i] = FindAmmoInstance(registeredWeapons[i]); |
||||
} |
||||
// ...tax for ammo, if we can |
||||
registeredWeapons[i] = TaxAmmoChange(registeredWeapons[i]); |
||||
i += 1; |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
allowNegativeDosh = false |
||||
rules(0)=(abusableWeapon=class'KFMod.Crossbow',pickupReplacement=class'FixAmmoSellingClass_CrossbowPickup') |
||||
rules(1)=(abusableWeapon=class'KFMod.PipeBombExplosive',pickupReplacement=class'FixAmmoSellingClass_PipeBombPickup') |
||||
rules(2)=(abusableWeapon=class'KFMod.M79GrenadeLauncher',pickupReplacement=class'FixAmmoSellingClass_M79Pickup') |
||||
rules(3)=(abusableWeapon=class'KFMod.GoldenM79GrenadeLauncher',pickupReplacement=class'FixAmmoSellingClass_GoldenM79Pickup') |
||||
rules(4)=(abusableWeapon=class'KFMod.M32GrenadeLauncher',pickupReplacement=class'FixAmmoSellingClass_M32Pickup') |
||||
rules(5)=(abusableWeapon=class'KFMod.CamoM32GrenadeLauncher',pickupReplacement=class'FixAmmoSellingClass_CamoM32Pickup') |
||||
rules(6)=(abusableWeapon=class'KFMod.LAW',pickupReplacement=class'FixAmmoSellingClass_LAWPickup') |
||||
rules(7)=(abusableWeapon=class'KFMod.SPGrenadeLauncher',pickupReplacement=class'FixAmmoSellingClass_SPGrenadePickup') |
||||
rules(8)=(abusableWeapon=class'KFMod.SealSquealHarpoonBomber',pickupReplacement=class'FixAmmoSellingClass_SealSquealPickup') |
||||
rules(9)=(abusableWeapon=class'KFMod.SeekerSixRocketLauncher',pickupReplacement=class'FixAmmoSellingClass_SeekerSixPickup') |
||||
// Listeners |
||||
requiredListeners(0) = class'MutatorListener_FixAmmoSelling' |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for M32 to that |
||||
* of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_CamoM32Pickup extends CamoM32Pickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 42 |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for xbow to that |
||||
* of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_CrossbowPickup extends CrossbowPickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 11.6 |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for m79 to that |
||||
* of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_GoldenM79Pickup extends GoldenM79Pickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 7 |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for LAW to that |
||||
* of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_LAWPickup extends LAWPickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 21 |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for M32 to that |
||||
* of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_M32Pickup extends M32Pickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 42 |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for M79 to that |
||||
* of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_M79Pickup extends M79Pickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 7 |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for pipes |
||||
* to that of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_PipeBombPickup extends PipeBombPickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 195 |
||||
} |
@ -0,0 +1,27 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for |
||||
* orca grnade launcher to that of a level 6 player |
||||
* and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_SPGrenadePickup extends SPGrenadePickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 7 |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for harpoon |
||||
* to that of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_SealSquealPickup extends SealSquealPickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 21 |
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* A helper class for 'FixAmmoSelling' that sets ammo cost for seeker |
||||
* to that of a level 6 player and doesn't allow for a perk discount. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixAmmoSellingClass_SeekerSixPickup extends SeekerSixPickup; |
||||
|
||||
defaultproperties |
||||
{ |
||||
AmmoCost = 10.5 |
||||
} |
@ -0,0 +1,97 @@
|
||||
/** |
||||
* Overloaded mutator events listener to register every new |
||||
* spawned weapon and ammo pickup. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MutatorListener_FixAmmoSelling extends MutatorListenerBase |
||||
abstract; |
||||
|
||||
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
if (other == none) return true; |
||||
|
||||
// We need to replace pickup classes back, |
||||
// as they might not even exist on clients. |
||||
if (class'FixAmmoSelling'.static.IsReplacer(other.class)) |
||||
{ |
||||
ReplacePickupWith(Pickup(other)); |
||||
return false; |
||||
} |
||||
CheckAbusableWeapon(KFWeapon(other)); |
||||
// If it's ammo pickup - we need to stalk it |
||||
class'AmmoPickupStalker'.static.StalkAmmoPickup(KFAmmoPickup(other)); |
||||
return true; |
||||
} |
||||
|
||||
private static function CheckAbusableWeapon(KFWeapon newWeapon) |
||||
{ |
||||
local FixAmmoSelling ammoSellingFix; |
||||
if (newWeapon == none) return; |
||||
ammoSellingFix = FixAmmoSelling(class'FixAmmoSelling'.static.GetInstance()); |
||||
if (ammoSellingFix == none) return; |
||||
ammoSellingFix.FixWeapon(newWeapon); |
||||
} |
||||
|
||||
// This function recreates the logic of 'KFWeapon.DropFrom()', |
||||
// since standard 'ReplaceWith' function produces bad results. |
||||
private static function ReplacePickupWith(Pickup oldPickup) |
||||
{ |
||||
local Pawn instigator; |
||||
local Pickup newPickup; |
||||
local KFWeapon relevantWeapon; |
||||
if (oldPickup == none) return; |
||||
instigator = oldPickup.instigator; |
||||
if (instigator == none) return; |
||||
relevantWeapon = GetWeaponOfClass(instigator, oldPickup.inventoryType); |
||||
if (relevantWeapon == none) return; |
||||
|
||||
newPickup = relevantWeapon.Spawn( relevantWeapon.default.pickupClass,,, |
||||
relevantWeapon.location); |
||||
newPickup.InitDroppedPickupFor(relevantWeapon); |
||||
newPickup.velocity = relevantWeapon.velocity + |
||||
Vector(instigator.rotation) * 100; |
||||
if (instigator.health > 0) |
||||
KFWeaponPickup(newPickup).bThrown = true; |
||||
} |
||||
|
||||
// TODO: this is code duplication, some sort of solution is needed |
||||
static final function KFWeapon GetWeaponOfClass |
||||
( |
||||
Pawn playerPawn, |
||||
class<Inventory> weaponClass |
||||
) |
||||
{ |
||||
local Inventory invIter; |
||||
if (playerPawn == none) return none; |
||||
|
||||
invIter = playerPawn.inventory; |
||||
while (invIter != none) |
||||
{ |
||||
if (invIter.class == weaponClass) |
||||
{ |
||||
return KFWeapon(invIter); |
||||
} |
||||
invIter = invIter.inventory; |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedEvents = class'MutatorEvents' |
||||
} |
@ -0,0 +1,253 @@
|
||||
/** |
||||
* This feature addressed two dosh-related issues: |
||||
* 1. Crashing servers by spamming 'CashPickup' actors with 'TossCash'; |
||||
* 2. Breaking collision detection logic by stacking large amount of |
||||
* 'CashPickup' actors in one place, which allows one to either |
||||
* reach unintended locations or even instantly kill zeds. |
||||
* |
||||
* It fixes them by limiting speed, with which dosh can spawn, and |
||||
* allowing this limit to decrease when there's already too much dosh |
||||
* present on the map. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixDoshSpam extends Feature |
||||
config(AcediaFixes); |
||||
|
||||
/** |
||||
* First, we limit amount of dosh that can be spawned simultaneously. |
||||
* The simplest method is to place a cooldown on spawning 'CashPickup' actors, |
||||
* i.e. after spawning one 'CashPickup' we'd completely prevent spawning |
||||
* any other instances of it for a fixed amount of time. |
||||
* However, that might allow a malicious spammer to block others from |
||||
* throwing dosh, - all he needs to do is to spam dosh at right time intervals. |
||||
* We'll resolve this issue by recording how many 'CashPickup' actors |
||||
* each player has spawned as their "contribution" and decay |
||||
* that value with time, only allowing to spawn new dosh after |
||||
* contribution decayed to zero. Speed of decay is derived from current dosh |
||||
* spawning speed limit and decreases with amount of players |
||||
* with non-zero contributions (since it means that they're throwing dosh). |
||||
* Second issue is player amassing a large amount of dosh in one point |
||||
* that leads to skipping collision checks, which then allows players to pass |
||||
* through level geometry or enter zeds' collisions, instantly killing them. |
||||
* Since dosh disappears on it's own, the easiest method to prevent that is to |
||||
* severely limit how much dosh players can throw per second, |
||||
* so that there's never enough dosh laying around to affect collision logic. |
||||
* The downside to such severe limitations is that game behaves less |
||||
* vanilla-like, where you could throw away streams of dosh. |
||||
* To solve that we'll first use a more generous limit on dosh players can |
||||
* throw per second, but will track how much dosh is currently present |
||||
* in a level and linearly decelerate speed, according to that amount. |
||||
*/ |
||||
|
||||
// Highest and lowest speed with which players can throw dosh wads. |
||||
// It'll be evenly spread between all players. |
||||
// For example, if speed is set to 6 and only one player will be spamming dosh, |
||||
// - he'll be able to throw 6 wads of dosh per second; |
||||
// but if all 6 players are spamming it, - each will throw only 1 per second. |
||||
// NOTE: these speed values can be exceeded, since a player is guaranteed |
||||
// to be able to throw at least one wad of dosh, if he didn't do so in awhile. |
||||
// NOTE #2: if maximum value is less than minimum one, |
||||
// the lowest (maximum one) will be used. |
||||
var private config const float doshPerSecondLimitMax; |
||||
var private config const float doshPerSecondLimitMin; |
||||
// Amount of dosh pickups on the map at which we must set dosh per second |
||||
// to 'doshPerSecondLimitMin'. |
||||
// We use 'doshPerSecondLimitMax' when there's no dosh on the map and |
||||
// scale linearly between them as it's amount grows. |
||||
var private config const int criticalDoshAmount; |
||||
|
||||
// To limit dosh spawning speed we need some measure of |
||||
// time passage between ticks. |
||||
// This variable stores last value seen by us as a good approximation. |
||||
// It's a real (not in-game) time. |
||||
var private float lastTickDuration; |
||||
|
||||
// This structure records how much a certain player has |
||||
// contributed to an overall dosh creation. |
||||
struct DoshStreamPerPlayer |
||||
{ |
||||
var PlayerController player; |
||||
// Amount of dosh we remember this player creating, decays with time. |
||||
var float contribution; |
||||
}; |
||||
var private array<DoshStreamPerPlayer> currentContributors; |
||||
|
||||
// Wads of cash that are lying around on the map. |
||||
var private array<CashPickup> wads; |
||||
|
||||
protected function OnEnabled() |
||||
{ |
||||
local CashPickup nextCash; |
||||
// Find all wads of cash laying around on the map, |
||||
// so that we could accordingly limit the cash spam. |
||||
foreach level.DynamicActors(class'KFMod.CashPickup', nextCash) |
||||
{ |
||||
wads[wads.length] = nextCash; |
||||
} |
||||
} |
||||
|
||||
protected function OnDisabled() |
||||
{ |
||||
wads.length = 0; |
||||
currentContributors.length = 0; |
||||
} |
||||
|
||||
// Did player with this controller contribute to the latest dosh generation? |
||||
public final function bool IsContributor(PlayerController player) |
||||
{ |
||||
return (GetContributorIndex(player) >= 0); |
||||
} |
||||
|
||||
// Did we already reach allowed limit of dosh per second? |
||||
public final function bool IsDoshStreamOverLimit() |
||||
{ |
||||
local int i; |
||||
local float overallContribution; |
||||
overallContribution = 0.0; |
||||
for (i = 0; i < currentContributors.length; i += 1) |
||||
{ |
||||
overallContribution += currentContributors[i].contribution; |
||||
} |
||||
return (overallContribution > lastTickDuration * GetCurrentDPSLimit()); |
||||
} |
||||
|
||||
// What is our current dosh per second limit? |
||||
private final function float GetCurrentDPSLimit() |
||||
{ |
||||
local float speedScale; |
||||
if (doshPerSecondLimitMax < doshPerSecondLimitMin) |
||||
{ |
||||
return doshPerSecondLimitMax; |
||||
} |
||||
speedScale = Float(wads.length) / Float(criticalDoshAmount); |
||||
speedScale = FClamp(speedScale, 0.0, 1.0); |
||||
// At 0.0 scale (no dosh on the map) - use max speed |
||||
// At 1.0 scale (critical dosh on the map) - use min speed |
||||
return Lerp(speedScale, doshPerSecondLimitMax, doshPerSecondLimitMin); |
||||
} |
||||
|
||||
// Returns index of the contributor corresponding to the given controller. |
||||
// Returns '-1' if no connection correspond to the given controller. |
||||
// Returns '-1' if given controller is equal to 'none'. |
||||
private final function int GetContributorIndex(PlayerController player) |
||||
{ |
||||
local int i; |
||||
if (player == none) return -1; |
||||
|
||||
for (i = 0; i < currentContributors.length; i += 1) |
||||
{ |
||||
if (currentContributors[i].player == player) |
||||
{ |
||||
return i; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
// Adds given cash to given player contribution record and |
||||
// registers that cash in our wads array. |
||||
// Does nothing if given cash was already registered. |
||||
public final function AddContribution(PlayerController player, CashPickup cash) |
||||
{ |
||||
local int i; |
||||
local int playerIndex; |
||||
local DoshStreamPerPlayer newStreamRecord; |
||||
// Check if given dosh was already accounted for. |
||||
for (i = 0; i < wads.length; i += 1) |
||||
{ |
||||
if (cash == wads[i]) |
||||
{ |
||||
return; |
||||
} |
||||
} |
||||
wads[wads.length] = cash; |
||||
// Add contribution to player |
||||
playerIndex = GetContributorIndex(player); |
||||
if (playerIndex >= 0) |
||||
{ |
||||
currentContributors[playerIndex].contribution += 1.0; |
||||
return; |
||||
} |
||||
newStreamRecord.player = player; |
||||
newStreamRecord.contribution = 1.0; |
||||
currentContributors[currentContributors.length] = newStreamRecord; |
||||
} |
||||
|
||||
private final function ReducePlayerContributions(float trueTimePassed) |
||||
{ |
||||
local int i; |
||||
local float streamReduction; |
||||
streamReduction = trueTimePassed * |
||||
(GetCurrentDPSLimit() / currentContributors.length); |
||||
for (i = 0; i < currentContributors.length; i += 1) |
||||
{ |
||||
currentContributors[i].contribution -= streamReduction; |
||||
} |
||||
} |
||||
|
||||
// Clean out wads that disappeared or were picked up by players. |
||||
private final function CleanWadsArray() |
||||
{ |
||||
local int i; |
||||
i = 0; |
||||
while (i < wads.length) |
||||
{ |
||||
if (wads[i] == none) |
||||
{ |
||||
wads.Remove(i, 1); |
||||
} |
||||
else |
||||
{ |
||||
i += 1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Don't track players that no longer contribute to dosh generation. |
||||
private final function RemoveNonContributors() |
||||
{ |
||||
local int i; |
||||
local array<DoshStreamPerPlayer> updContributors; |
||||
for (i = 0; i < currentContributors.length; i += 1) |
||||
{ |
||||
// We want to keep on record even players that quit, |
||||
// since their contribution still must be accounted for. |
||||
if (currentContributors[i].contribution <= 0.0) continue; |
||||
updContributors[updContributors.length] = currentContributors[i]; |
||||
} |
||||
currentContributors = updContributors; |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
local float trueTimePassed; |
||||
trueTimePassed = delta * (1.1 / level.timeDilation); |
||||
CleanWadsArray(); |
||||
ReducePlayerContributions(trueTimePassed); |
||||
RemoveNonContributors(); |
||||
lastTickDuration = trueTimePassed; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
doshPerSecondLimitMax = 50 |
||||
doshPerSecondLimitMin = 5 |
||||
criticalDoshAmount = 25 |
||||
// Listeners |
||||
requiredListeners(0) = class'MutatorListener_FixDoshSpam' |
||||
} |
@ -0,0 +1,51 @@
|
||||
/** |
||||
* Overloaded mutator events listener to catch and, possibly, |
||||
* prevent spawning dosh actors. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MutatorListener_FixDoshSpam extends MutatorListenerBase |
||||
abstract; |
||||
|
||||
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
local FixDoshSpam doshFix; |
||||
local PlayerController player; |
||||
if (other.class != class'CashPickup') return true; |
||||
// This means this dosh wasn't spawned in 'TossCash' of 'KFPawn', |
||||
// so it isn't related to the exploit we're trying to fix. |
||||
if (other.instigator == none) return true; |
||||
doshFix = FixDoshSpam(class'FixDoshSpam'.static.GetInstance()); |
||||
if (doshFix == none) return true; |
||||
|
||||
// We only want to prevent spawning cash if we're already over |
||||
// the limit and the one trying to throw this cash contributed to it. |
||||
// We allow other players to throw at least one wad of cash. |
||||
player = PlayerController(other.instigator.controller); |
||||
if (doshFix.IsDoshStreamOverLimit() && doshFix.IsContributor(player)) |
||||
{ |
||||
return false; |
||||
} |
||||
// If we do spawn cash - record this contribution. |
||||
doshFix.AddContribution(player, CashPickup(other)); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedEvents = class'MutatorEvents' |
||||
} |
@ -0,0 +1,45 @@
|
||||
/** |
||||
* This rule detects any pickup events to allow us to |
||||
* properly record and/or fix pistols' prices. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class DualiesCostRule extends GameRules; |
||||
|
||||
function bool OverridePickupQuery |
||||
( |
||||
Pawn other, |
||||
Pickup item, |
||||
out byte allowPickup |
||||
) |
||||
{ |
||||
local KFWeaponPickup weaponPickup; |
||||
local FixDualiesCost dualiesCostFix; |
||||
weaponPickup = KFWeaponPickup(item); |
||||
dualiesCostFix = FixDualiesCost(class'FixDualiesCost'.static.GetInstance()); |
||||
if (weaponPickup != none && dualiesCostFix != none) |
||||
{ |
||||
dualiesCostFix.ApplyPendingValues(); |
||||
dualiesCostFix.StoreSinglePistolValues(); |
||||
dualiesCostFix.SetNextSellValue(weaponPickup.sellValue); |
||||
} |
||||
return super.OverridePickupQuery(other, item, allowPickup); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,455 @@
|
||||
/** |
||||
* This feature fixes several issues related to the selling price of both |
||||
* single and dual pistols, all originating from the existence of dual weapons. |
||||
* Most notable issue is the ability to "print" money by buying and |
||||
* selling pistols in a certain way. |
||||
* |
||||
* It fixes all of the issues by manually setting pistols' |
||||
* 'SellValue' variables to proper values. |
||||
* Fix only works with vanilla pistols, as it's unpredictable what |
||||
* custom ones can do and they can handle these issues on their own |
||||
* in a better way. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixDualiesCost extends Feature |
||||
config(AcediaFixes); |
||||
|
||||
/** |
||||
* Issues with pistols' cost may look varied and surface in |
||||
* a plethora of ways, but all of them originate from the two main errors |
||||
* in vanilla's code: |
||||
* 1. If you have a pistol in your inventory at the time when you |
||||
* buy/pickup another one - the sell value of resulting dualies is |
||||
* incorrectly set to the sell value of the second pistol; |
||||
* 2. When player has dual pistols and drops one on the floor, - |
||||
* the sell value for the one left with the player isn't set. |
||||
* All weapons in Killing Floor get sell value assigned to them |
||||
* (appropriately, in a 'SellValue' variable). This is to ensure that the sell |
||||
* price is set the moment players buys the gun. Otherwise, due to ridiculous |
||||
* perked discounts, you'd be able to buy a pistol at 30% price |
||||
* as sharpshooter, but sell at 75% of a price as any other perk, |
||||
* resulting in 45% of pure profit. |
||||
* Unfortunately, that's exactly what happens when 'SellValue' isn't set |
||||
* (left as it's default value of '-1'): sell value of such weapons is |
||||
* determined only at the moment of sale and depends on the perk of the seller, |
||||
* allowing for possible exploits. |
||||
* |
||||
* These issues are fixed by directly assigning |
||||
* proper values to 'SellValue'. To do that we need to detect when player |
||||
* buys/sells/drops/picks up weapons, which we accomplish by catching |
||||
* 'CheckReplacement' event for weapon instances. This approach has two issues. |
||||
* One is that, if vanilla's code sets an incorrect sell value, - |
||||
* it's doing it after weapon is spawned and, therefore, |
||||
* after 'CheckReplacement' call, so we have, instead, to remember to do |
||||
* it later, as early as possible |
||||
* (either the next tick or before another operation with weapons). |
||||
* Another issue is that when you have a pistol and pick up a pistol of |
||||
* the same type, - at the moment dualies instance is spawned, |
||||
* the original pistol in player's inventory is gone and we can't use |
||||
* it's sell value to calculate new value of dual pistols. |
||||
* This problem is solved by separately recording the value for every |
||||
* single pistol every tick. |
||||
* However, if pistol pickups are placed close enough together on the map, |
||||
* player can start touching them (which triggers a pickup) at the same time, |
||||
* picking them both in a single tick. This leaves us no room to record |
||||
* the value of a single pistol players picks up first. |
||||
* To get it we use game rules to catch 'OverridePickupQuery' event that's |
||||
* called before the first one gets destroyed, |
||||
* but after it's sell value was already set. |
||||
* Last issue is that when player picks up a second pistol - we don't know |
||||
* it's sell value and, therefore, can't calculate value of dual pistols. |
||||
* This is resolved by recording that value directly from a pickup, |
||||
* in abovementioned function 'OverridePickupQuery'. |
||||
* NOTE: 9mm is an exception due to the fact that you always have at least |
||||
* one and the last one can't be sold. We'll deal with it by setting |
||||
* the following rule: sell value of the un-droppable pistol is always 0 |
||||
* and the value of a pair of 9mms is the value of the single droppable pistol. |
||||
*/ |
||||
|
||||
// Some issues involve possible decrease in pistols' price and |
||||
// don't lead to exploit, but are still bugs and require fixing. |
||||
// If you have a Deagle in your inventory and then get another one |
||||
// (by either buying or picking it off the ground) - the price of resulting |
||||
// dual pistols will be set to the price of the last deagle, |
||||
// like the first one wasn't worth anything at all. |
||||
// In particular this means that (prices are off-perk for more clarity): |
||||
// 1. If you buy dual deagles (-1000 do$h) and then sell them at 75% of |
||||
// the cost (+750 do$h), you lose 250 do$h; |
||||
// 2. If you first buy a deagle (-500 do$h), then buy |
||||
// the second one (-500 do$h) and then sell them, you'll only get |
||||
// 75% of the cost of 1 deagle (+375 do$h), now losing 625 do$h; |
||||
// 3. So if you already have bought a deagle (-500 do$h), |
||||
// you can get a more expensive weapon by doing a stupid thing |
||||
// and first selling your Deagle (+375 do$h), |
||||
// then buying dual deagles (-1000 do$h). |
||||
// If you sell them after that, you'll gain 75% of the cost of |
||||
// dual deagles (+750 do$h), leaving you with losing only 375 do$h. |
||||
// Of course, situations described above are only relevant if you're planning |
||||
// to sell your weapons at some point and most people won't even notice it. |
||||
// But such an oversight still shouldn't exist in a game and we fix it by |
||||
// setting sell value of dualies as a sum of values of each pistol. |
||||
// Yet, fixing this issue leads to players having more expensive |
||||
// (while fairly priced) weapons than on vanilla, technically making |
||||
// the game easier. And some people might object to having that in |
||||
// a whitelisted bug-fixing feature. |
||||
// These people are, without a question, complete degenerates. |
||||
// But making mods for only non-mentally challenged isn't inclusive. |
||||
// So we add this option. |
||||
// Set it to 'false' if you only want to fix ammo printing |
||||
// and leave the rest of the bullshit as-is. |
||||
var private config const bool allowSellValueIncrease; |
||||
|
||||
// Describe all the possible pairs of dual pistols in a vanilla game. |
||||
struct DualiesPair |
||||
{ |
||||
var class<KFWeapon> single; |
||||
var class<KFWeapon> dual; |
||||
}; |
||||
var private const array<DualiesPair> dualiesClasses; |
||||
|
||||
// Describe sell values that need to be applied at earliest later point. |
||||
struct WeaponValuePair |
||||
{ |
||||
var KFWeapon weapon; |
||||
var float value; |
||||
}; |
||||
var private const array<WeaponValuePair> pendingValues; |
||||
|
||||
// Describe sell values of all currently existing single pistols. |
||||
struct WeaponDataRecord |
||||
{ |
||||
var KFWeapon reference; |
||||
var class<KFWeapon> class; |
||||
var float value; |
||||
// The whole point of this structure is to remember value of a weapon |
||||
// after it's destroyed. Since 'reference' will become 'none' by then, |
||||
// we will use the 'owner' reference to identify the weapon. |
||||
var Pawn owner; |
||||
}; |
||||
var private const array<WeaponDataRecord> storedValues; |
||||
|
||||
// Sell value of the last seen pickup in 'OverridePickupQuery' |
||||
var private int nextSellValue; |
||||
|
||||
protected function OnEnabled() |
||||
{ |
||||
local KFWeapon nextWeapon; |
||||
// Find all frags, that spawned when this fix wasn't running. |
||||
foreach level.DynamicActors(class'KFMod.KFWeapon', nextWeapon) |
||||
{ |
||||
RegisterSinglePistol(nextWeapon, false); |
||||
} |
||||
level.game.AddGameModifier(Spawn(class'DualiesCostRule')); |
||||
} |
||||
|
||||
protected function OnDisabled() |
||||
{ |
||||
local GameRules rulesIter; |
||||
local DualiesCostRule ruleToDestroy; |
||||
// Check first rule |
||||
if (level.game.gameRulesModifiers == none) return; |
||||
|
||||
ruleToDestroy = DualiesCostRule(level.game.gameRulesModifiers); |
||||
if (ruleToDestroy != none) |
||||
{ |
||||
level.game.gameRulesModifiers = ruleToDestroy.nextGameRules; |
||||
ruleToDestroy.Destroy(); |
||||
return; |
||||
} |
||||
// Check rest of the rules |
||||
rulesIter = level.game.gameRulesModifiers; |
||||
while (rulesIter != none) |
||||
{ |
||||
ruleToDestroy = DualiesCostRule(rulesIter.nextGameRules); |
||||
if (ruleToDestroy != none) |
||||
{ |
||||
rulesIter.nextGameRules = ruleToDestroy.nextGameRules; |
||||
ruleToDestroy.Destroy(); |
||||
} |
||||
rulesIter = rulesIter.nextGameRules; |
||||
} |
||||
} |
||||
|
||||
public final function SetNextSellValue(int newValue) |
||||
{ |
||||
nextSellValue = newValue; |
||||
} |
||||
|
||||
// Finds a weapon of a given class in given 'Pawn' 's inventory. |
||||
// Returns 'none' if weapon isn't there. |
||||
private final function KFWeapon GetWeaponOfClass |
||||
( |
||||
Pawn playerPawn, |
||||
class<KFWeapon> weaponClass |
||||
) |
||||
{ |
||||
local Inventory invIter; |
||||
if (playerPawn == none) return none; |
||||
|
||||
invIter = playerPawn.inventory; |
||||
while (invIter != none) |
||||
{ |
||||
if (invIter.class == weaponClass) |
||||
{ |
||||
return KFWeapon(invIter); |
||||
} |
||||
invIter = invIter.inventory; |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
// Gets weapon index in our record of dual pistol classes. |
||||
// Second variable determines whether we're searching for single |
||||
// or dual variant: |
||||
// ~ 'true' - searching for single |
||||
// ~ 'false' - for dual |
||||
// Returns '-1' if weapon isn't found |
||||
// (dual MK23 won't be found as a single weapon). |
||||
private final function int GetIndexAs(KFWeapon weapon, bool asSingle) |
||||
{ |
||||
local int i; |
||||
if (weapon == none) return -1; |
||||
|
||||
for (i = 0; i < dualiesClasses.length; i += 1) |
||||
{ |
||||
if (asSingle && dualiesClasses[i].single == weapon.class) |
||||
{ |
||||
return i; |
||||
} |
||||
if (!asSingle && dualiesClasses[i].dual == weapon.class) |
||||
{ |
||||
return i; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
// Calculates full cost of a weapon with a discount, |
||||
// dependent on it's instigator's perk. |
||||
private final function float GetFullCost(KFWeapon weapon) |
||||
{ |
||||
local float cost; |
||||
local class<KFWeaponPickup> pickupClass; |
||||
local KFPlayerReplicationInfo instigatorRI; |
||||
if (weapon == none) return 0.0; |
||||
pickupClass = class<KFWeaponPickup>(weapon.default.pickupClass); |
||||
if (pickupClass == none) return 0.0; |
||||
|
||||
cost = pickupClass.default.cost; |
||||
if (weapon.instigator != none) |
||||
{ |
||||
instigatorRI = |
||||
KFPlayerReplicationInfo(weapon.instigator.playerReplicationInfo); |
||||
} |
||||
if (instigatorRI != none && instigatorRI.clientVeteranSkill != none) |
||||
{ |
||||
cost *= instigatorRI.clientVeteranSkill.static |
||||
.GetCostScaling(instigatorRI, pickupClass); |
||||
} |
||||
return cost; |
||||
} |
||||
|
||||
// If passed weapon is a pistol - we start tracking it's value; |
||||
// Otherwise - do nothing. |
||||
public final function RegisterSinglePistol |
||||
( |
||||
KFWeapon singlePistol, |
||||
bool justSpawned |
||||
) |
||||
{ |
||||
local WeaponDataRecord newRecord; |
||||
if (singlePistol == none) return; |
||||
if (GetIndexAs(singlePistol, true) < 0) return; |
||||
|
||||
newRecord.reference = singlePistol; |
||||
newRecord.class = singlePistol.class; |
||||
newRecord.owner = singlePistol.instigator; |
||||
if (justSpawned) |
||||
{ |
||||
newRecord.value = nextSellValue; |
||||
} |
||||
else |
||||
{ |
||||
newRecord.value = singlePistol.sellValue; |
||||
} |
||||
storedValues[storedValues.length] = newRecord; |
||||
} |
||||
|
||||
// Fixes sell value after player throws one pistol out of a pair. |
||||
public final function FixCostAfterThrow(KFWeapon singlePistol) |
||||
{ |
||||
local int index; |
||||
local KFWeapon dualPistols; |
||||
if (singlePistol == none) return; |
||||
index = GetIndexAs(singlePistol, true); |
||||
if (index < 0) return; |
||||
dualPistols = GetWeaponOfClass( singlePistol.instigator, |
||||
dualiesClasses[index].dual); |
||||
if (dualPistols == none) return; |
||||
|
||||
// Sell value recorded into 'dualPistols' will end up as a value of |
||||
// a dropped pickup. |
||||
// Sell value of 'singlePistol' will be the value for the pistol, |
||||
// left in player's hands. |
||||
if (dualPistols.class == class'KFMod.Single') |
||||
{ |
||||
// 9mm is an exception. |
||||
// Remaining weapon costs nothing. |
||||
singlePistol.sellValue = 0; |
||||
// We don't change the sell value of the dropped weapon, |
||||
// as it's default behavior to transfer full value of a pair to it. |
||||
return; |
||||
} |
||||
// For other pistols - divide the value. |
||||
singlePistol.sellValue = dualPistols.sellValue / 2; |
||||
dualPistols.sellValue = singlePistol.sellValue; |
||||
} |
||||
|
||||
// Fixes sell value after buying a pair of dual pistols, |
||||
// if player already had a single version. |
||||
public final function FixCostAfterBuying(KFWeapon dualPistols) |
||||
{ |
||||
local int index; |
||||
local KFWeapon singlePistol; |
||||
local WeaponValuePair newPendingValue; |
||||
if (dualPistols == none) return; |
||||
index = GetIndexAs(dualPistols, false); |
||||
if (index < 0) return; |
||||
singlePistol = GetWeaponOfClass(dualPistols.instigator, |
||||
dualiesClasses[index].single); |
||||
if (singlePistol == none) return; |
||||
|
||||
// 'singlePistol' will get destroyed, so it's sell value is irrelevant. |
||||
// 'dualPistols' will be the new pair of pistols, but it's value will |
||||
// get overwritten by vanilla's code after this function. |
||||
// So we must add it to pending values to be changed later. |
||||
newPendingValue.weapon = dualPistols; |
||||
if (dualPistols.class == class'KFMod.Dualies') |
||||
{ |
||||
// 9mm is an exception. |
||||
// The value of pair of 9mms is the price of additional pistol, |
||||
// that defined as a price of a pair in game. |
||||
newPendingValue.value = GetFullCost(dualPistols) * 0.75; |
||||
} |
||||
else |
||||
{ |
||||
// Otherwise price of a pair is the price of two pistols: |
||||
// 'singlePistol.sellValue' - the one we had |
||||
// '(FullCost / 2) * 0.75' - and the one we bought |
||||
newPendingValue.value = singlePistol.sellValue |
||||
+ (GetFullCost(dualPistols) / 2) * 0.75; |
||||
} |
||||
pendingValues[pendingValues.length] = newPendingValue; |
||||
} |
||||
|
||||
// Fixes sell value after player picks up a single pistol, |
||||
// while already having one of the same time in his inventory. |
||||
public final function FixCostAfterPickUp(KFWeapon dualPistols) |
||||
{ |
||||
local int i; |
||||
local int index; |
||||
local KFWeapon singlePistol; |
||||
local WeaponValuePair newPendingValue; |
||||
if (dualPistols == none) return; |
||||
// In both cases of: |
||||
// 1. buying dualies, without having a single pistol of |
||||
// corresponding type; |
||||
// 2. picking up a second pistol, while having another one; |
||||
// by the time of 'CheckReplacement' (and, therefore, this function) |
||||
// is called, there's no longer any single pistol in player's inventory |
||||
// (in first case it never was there, in second - it got destroyed). |
||||
// To distinguish between those possibilities we can check the owner of |
||||
// the spawned weapon, since it's only set to instigator at the time of |
||||
// 'CheckReplacement' when player picks up a weapon. |
||||
// So we require that owner exists. |
||||
if (dualPistols.owner == none) return; |
||||
index = GetIndexAs(dualPistols, false); |
||||
if (index < 0) return; |
||||
singlePistol = GetWeaponOfClass(dualPistols.instigator, |
||||
dualiesClasses[index].single); |
||||
if (singlePistol != none) return; |
||||
|
||||
if (nextSellValue == -1) |
||||
{ |
||||
nextSellValue = GetFullCost(dualPistols) * 0.75; |
||||
} |
||||
for (i = 0; i < storedValues.length; i += 1) |
||||
{ |
||||
if (storedValues[i].reference != none) continue; |
||||
if (storedValues[i].class != dualiesClasses[index].single) continue; |
||||
if (storedValues[i].owner != dualPistols.instigator) continue; |
||||
newPendingValue.weapon = dualPistols; |
||||
newPendingValue.value = storedValues[i].value + nextSellValue; |
||||
pendingValues[pendingValues.length] = newPendingValue; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public final function ApplyPendingValues() |
||||
{ |
||||
local int i; |
||||
for (i = 0; i < pendingValues.length; i += 1) |
||||
{ |
||||
if (pendingValues[i].weapon == none) continue; |
||||
// Our fixes can only increase the correct ('!= -1') |
||||
// sell value of weapons, so if we only need to change sell value |
||||
// if we're allowed to increase it or it's incorrect. |
||||
if (allowSellValueIncrease || pendingValues[i].weapon.sellValue == -1) |
||||
{ |
||||
pendingValues[i].weapon.sellValue = pendingValues[i].value; |
||||
} |
||||
} |
||||
pendingValues.length = 0; |
||||
} |
||||
|
||||
public final function StoreSinglePistolValues() |
||||
{ |
||||
local int i; |
||||
i = 0; |
||||
while (i < storedValues.length) |
||||
{ |
||||
if (storedValues[i].reference == none) |
||||
{ |
||||
storedValues.Remove(i, 1); |
||||
continue; |
||||
} |
||||
storedValues[i].owner = storedValues[i].reference.instigator; |
||||
storedValues[i].value = storedValues[i].reference.sellValue; |
||||
i += 1; |
||||
} |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
ApplyPendingValues(); |
||||
StoreSinglePistolValues(); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
allowSellValueIncrease = true |
||||
// Inner variables |
||||
dualiesClasses(0)=(single=class'KFMod.Single',dual=class'KFMod.Dualies') |
||||
dualiesClasses(1)=(single=class'KFMod.Magnum44Pistol',dual=class'KFMod.Dual44Magnum') |
||||
dualiesClasses(2)=(single=class'KFMod.MK23Pistol',dual=class'KFMod.DualMK23Pistol') |
||||
dualiesClasses(3)=(single=class'KFMod.Deagle',dual=class'KFMod.DualDeagle') |
||||
dualiesClasses(4)=(single=class'KFMod.GoldenDeagle',dual=class'KFMod.GoldenDualDeagle') |
||||
dualiesClasses(5)=(single=class'KFMod.FlareRevolver',dual=class'KFMod.DualFlareRevolver') |
||||
// Listeners |
||||
requiredListeners(0) = class'MutatorListener_FixDualiesCost' |
||||
} |
@ -0,0 +1,43 @@
|
||||
/** |
||||
* Overloaded mutator events listener to catch when pistol-type weapons |
||||
* (single or dual) are spawned and to correct their price. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MutatorListener_FixDualiesCost extends MutatorListenerBase |
||||
abstract; |
||||
|
||||
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
local KFWeapon weapon; |
||||
local FixDualiesCost dualiesCostFix; |
||||
weapon = KFWeapon(other); |
||||
if (weapon == none) return true; |
||||
dualiesCostFix = FixDualiesCost(class'FixDualiesCost'.static.GetInstance()); |
||||
if (dualiesCostFix == none) return true; |
||||
|
||||
dualiesCostFix.RegisterSinglePistol(weapon, true); |
||||
dualiesCostFix.FixCostAfterThrow(weapon); |
||||
dualiesCostFix.FixCostAfterBuying(weapon); |
||||
dualiesCostFix.FixCostAfterPickUp(weapon); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedEvents = class'MutatorEvents' |
||||
} |
@ -0,0 +1,74 @@
|
||||
/** |
||||
* This rule detects suspicious attempts to deal damage and |
||||
* applies friendly fire scaling according to 'FixFFHack's rules. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FFHackRule extends GameRules; |
||||
|
||||
function int NetDamage |
||||
( |
||||
int originalDamage, |
||||
int damage, |
||||
Pawn injured, |
||||
Pawn instigator, |
||||
Vector hitLocation, |
||||
out Vector momentum, |
||||
class<DamageType> damageType |
||||
) |
||||
{ |
||||
local KFGameType gameType; |
||||
local FixFFHack ffHackFix; |
||||
gameType = KFGameType(level.game); |
||||
// Something is very wrong and we can just bail on this damage |
||||
if (damageType == none || gameType == none) return 0; |
||||
|
||||
// We only check when suspicious instigators that aren't a world |
||||
if (!damageType.default.bCausedByWorld && IsSuspicious(instigator)) |
||||
{ |
||||
ffHackFix = FixFFHack(class'FixFFHack'.static.GetInstance()); |
||||
if (ffHackFix != none && ffHackFix.ShouldScaleDamage(damageType)) |
||||
{ |
||||
// Remove pushback to avoid environmental kills |
||||
momentum = Vect(0.0, 0.0, 0.0); |
||||
damage *= gameType.friendlyFireScale; |
||||
} |
||||
} |
||||
return super.NetDamage( originalDamage, damage, injured, instigator, |
||||
hitLocation, momentum, damageType); |
||||
} |
||||
|
||||
private function bool IsSuspicious(Pawn instigator) |
||||
{ |
||||
// Instigator vanished |
||||
if (instigator == none) return true; |
||||
|
||||
// Instigator already became spectator |
||||
if (KFPawn(instigator) != none) |
||||
{ |
||||
if (instigator.playerReplicationInfo != none) |
||||
{ |
||||
return instigator.playerReplicationInfo.bOnlySpectator; |
||||
} |
||||
return true; // Replication info is gone => suspicious |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,153 @@
|
||||
/** |
||||
* This feature fixes a bug that can allow players to bypass server's |
||||
* friendly fire limitations and teamkill. |
||||
* Usual fixes apply friendly fire scale to suspicious damage themselves, which |
||||
* also disables some of the environmental damage. |
||||
* In order to avoid that, this fix allows server owner to define precisely |
||||
* to what damage types to apply the friendly fire scaling. |
||||
* It should be all damage types related to projectiles. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixFFHack extends Feature |
||||
config(AcediaFixes); |
||||
|
||||
/** |
||||
* It's possible to bypass friendly fire damage scaling and always deal |
||||
* full damage to other players, if one were to either leave the server or |
||||
* spectate right after shooting a projectile. We use game rules to catch |
||||
* such occurrences and apply friendly fire scaling to weapons, |
||||
* specified by server admins. |
||||
* To specify required subset of weapons, one must first |
||||
* chose a general rule (scale by default / don't scale by default) and then, |
||||
* optionally, add exceptions to it. |
||||
* Choosing 'scaleByDefault == true' as a general rule will make this fix |
||||
* behave in the similar way to 'KFExplosiveFix' by mutant and will disable |
||||
* some environmental sources of damage on some maps. One can then add relevant |
||||
* damage classes as exceptions to fix that downside, but making an extensive |
||||
* list of such sources might prove problematic. |
||||
* On the other hand, setting 'scaleByDefault == false' will allow to get |
||||
* rid of team-killing exploits by simply adding damage types of all |
||||
* projectile weapons, used on a server. This fix comes with such filled-in |
||||
* list of all vanilla projectile classes. |
||||
*/ |
||||
|
||||
// Defines a general rule for choosing whether or not to apply |
||||
// friendly fire scaling. |
||||
// This can be overwritten by exceptions ('alwaysScale' or 'neverScale'). |
||||
// Enabling scaling by default without any exceptions in 'neverScale' will |
||||
// make this fix behave almost identically to Mutant's 'Explosives Fix Mutator'. |
||||
var private config const bool scaleByDefault; |
||||
// Damage types, for which we should always reapply friendly fire scaling. |
||||
var private config const array< class<DamageType> > alwaysScale; |
||||
// Damage types, for which we should never reapply friendly fire scaling. |
||||
var private config const array< class<DamageType> > neverScale; |
||||
|
||||
protected function OnEnabled() |
||||
{ |
||||
level.game.AddGameModifier(Spawn(class'FFHackRule')); |
||||
} |
||||
|
||||
protected function OnDisabled() |
||||
{ |
||||
local GameRules rulesIter; |
||||
local FFHackRule ruleToDestroy; |
||||
// Check first rule |
||||
if (level.game.gameRulesModifiers == none) return; |
||||
|
||||
ruleToDestroy = FFHackRule(level.game.gameRulesModifiers); |
||||
if (ruleToDestroy != none) |
||||
{ |
||||
level.game.gameRulesModifiers = ruleToDestroy.nextGameRules; |
||||
ruleToDestroy.Destroy(); |
||||
return; |
||||
} |
||||
// Check rest of the rules |
||||
rulesIter = level.game.gameRulesModifiers; |
||||
while (rulesIter != none) |
||||
{ |
||||
ruleToDestroy = FFHackRule(rulesIter.nextGameRules); |
||||
if (ruleToDestroy != none) |
||||
{ |
||||
rulesIter.nextGameRules = ruleToDestroy.nextGameRules; |
||||
ruleToDestroy.Destroy(); |
||||
} |
||||
rulesIter = rulesIter.nextGameRules; |
||||
} |
||||
} |
||||
|
||||
// Checks general rule and exception list |
||||
public final function bool ShouldScaleDamage(class<DamageType> damageType) |
||||
{ |
||||
local int i; |
||||
local array< class<DamageType> > exceptions; |
||||
if (damageType == none) return false; |
||||
|
||||
if (scaleByDefault) |
||||
exceptions = neverScale; |
||||
else |
||||
exceptions = alwaysScale; |
||||
for (i = 0; i < exceptions.length; i += 1) |
||||
{ |
||||
if (exceptions[i] == damageType) |
||||
{ |
||||
return (!scaleByDefault); |
||||
} |
||||
} |
||||
return scaleByDefault; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
scaleByDefault = false |
||||
// Vanilla damage types for projectiles |
||||
alwaysScale(0) = class'KFMod.DamTypeCrossbuzzsawHeadShot' |
||||
alwaysScale(1) = class'KFMod.DamTypeCrossbuzzsaw' |
||||
alwaysScale(2) = class'KFMod.DamTypeFrag' |
||||
alwaysScale(3) = class'KFMod.DamTypePipeBomb' |
||||
alwaysScale(4) = class'KFMod.DamTypeM203Grenade' |
||||
alwaysScale(5) = class'KFMod.DamTypeM79Grenade' |
||||
alwaysScale(6) = class'KFMod.DamTypeM79GrenadeImpact' |
||||
alwaysScale(7) = class'KFMod.DamTypeM32Grenade' |
||||
alwaysScale(8) = class'KFMod.DamTypeLAW' |
||||
alwaysScale(9) = class'KFMod.DamTypeLawRocketImpact' |
||||
alwaysScale(10) = class'KFMod.DamTypeFlameNade' |
||||
alwaysScale(11) = class'KFMod.DamTypeFlareRevolver' |
||||
alwaysScale(12) = class'KFMod.DamTypeFlareProjectileImpact' |
||||
alwaysScale(13) = class'KFMod.DamTypeBurned' |
||||
alwaysScale(14) = class'KFMod.DamTypeTrenchgun' |
||||
alwaysScale(15) = class'KFMod.DamTypeHuskGun' |
||||
alwaysScale(16) = class'KFMod.DamTypeCrossbow' |
||||
alwaysScale(17) = class'KFMod.DamTypeCrossbowHeadShot' |
||||
alwaysScale(18) = class'KFMod.DamTypeM99SniperRifle' |
||||
alwaysScale(19) = class'KFMod.DamTypeM99HeadShot' |
||||
alwaysScale(20) = class'KFMod.DamTypeShotgun' |
||||
alwaysScale(21) = class'KFMod.DamTypeNailGun' |
||||
alwaysScale(22) = class'KFMod.DamTypeDBShotgun' |
||||
alwaysScale(23) = class'KFMod.DamTypeKSGShotgun' |
||||
alwaysScale(24) = class'KFMod.DamTypeBenelli' |
||||
alwaysScale(25) = class'KFMod.DamTypeSPGrenade' |
||||
alwaysScale(26) = class'KFMod.DamTypeSPGrenadeImpact' |
||||
alwaysScale(27) = class'KFMod.DamTypeSeekerSixRocket' |
||||
alwaysScale(28) = class'KFMod.DamTypeSeekerRocketImpact' |
||||
alwaysScale(29) = class'KFMod.DamTypeSealSquealExplosion' |
||||
alwaysScale(30) = class'KFMod.DamTypeRocketImpact' |
||||
alwaysScale(31) = class'KFMod.DamTypeBlowerThrower' |
||||
alwaysScale(32) = class'KFMod.DamTypeSPShotgun' |
||||
alwaysScale(33) = class'KFMod.DamTypeZEDGun' |
||||
alwaysScale(34) = class'KFMod.DamTypeZEDGunMKII' |
||||
} |
@ -0,0 +1,234 @@
|
||||
/** |
||||
* This feature fixes a vulnerability in a code of 'Frag' that can allow |
||||
* player to throw grenades even when he no longer has any. |
||||
* There's also no cooldowns on the throw, which can lead to a server crash. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixInfiniteNades extends Feature |
||||
config(AcediaFixes); |
||||
|
||||
/** |
||||
* It is possible to call 'ServerThrow' function from client, |
||||
* forcing it to get executed on a server. This function consumes the grenade |
||||
* ammo and spawns a nade, but it doesn't check if player had any grenade ammo |
||||
* in the first place, allowing you him to throw however many grenades |
||||
* he wants. Moreover, unlike a regular throwing method, calling this function |
||||
* allows to spawn many grenades without any delay, |
||||
* which can lead to a server crash. |
||||
* |
||||
* This fix tracks every instance of 'Frag' weapon that's responsible for |
||||
* throwing grenades and records how much ammo they have have. |
||||
* This is necessary, because whatever means we use, when we get a say in |
||||
* preventing grenade from spawning the ammo was already reduced. |
||||
* This means that we can't distinguished between a player abusing a bug by |
||||
* throwing grenade when he doesn't have necessary ammo and player throwing |
||||
* his last nade, as in both cases current ammo visible to us will be 0. |
||||
* Then, before every nade throw, it checks if player has enough ammo and |
||||
* blocks grenade from spawning if he doesn't. |
||||
* We change a 'FireModeClass[0]' from 'FragFire' to 'FixedFragFire' and |
||||
* only call 'super.DoFireEffect()' if we decide spawning grenade |
||||
* should be allowed. The side effect is a change in server's 'FireModeClass'. |
||||
*/ |
||||
|
||||
// Setting this flag to 'true' will allow to throw grenades by calling |
||||
// 'ServerThrow' directly, as long as player has necessary ammo. |
||||
// This can allow some players to throw grenades much quicker than intended, |
||||
// therefore it's suggested to keep this flag set to 'false'. |
||||
var private config const bool ignoreTossFlags; |
||||
|
||||
// Records how much ammo given frag grenade ('Frag') has. |
||||
struct FragAmmoRecord |
||||
{ |
||||
var public Frag fragReference; |
||||
var public int amount; |
||||
}; |
||||
var private array<FragAmmoRecord> ammoRecords; |
||||
|
||||
protected function OnEnabled() |
||||
{ |
||||
local Frag nextFrag; |
||||
// Find all frags, that spawned when this fix wasn't running. |
||||
foreach level.DynamicActors(class'KFMod.Frag', nextFrag) |
||||
{ |
||||
RegisterFrag(nextFrag); |
||||
} |
||||
RecreateFrags(); |
||||
} |
||||
|
||||
protected function OnDisabled() |
||||
{ |
||||
RecreateFrags(); |
||||
ammoRecords.length = 0; |
||||
} |
||||
|
||||
// Returns index of the connection corresponding to the given controller. |
||||
// Returns '-1' if no connection correspond to the given controller. |
||||
// Returns '-1' if given controller is equal to 'none'. |
||||
private final function int GetAmmoIndex(Frag fragToCheck) |
||||
{ |
||||
local int i; |
||||
if (fragToCheck == none) return -1; |
||||
|
||||
for (i = 0; i < ammoRecords.length; i += 1) |
||||
{ |
||||
if (ammoRecords[i].fragReference == fragToCheck) |
||||
{ |
||||
return i; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
// Recreates all the 'Frag' actors, to change their fire mode mid-game. |
||||
private final function RecreateFrags() |
||||
{ |
||||
local int i; |
||||
local float maxAmmo, currentAmmo; |
||||
local Frag newFrag; |
||||
local Pawn fragOwner; |
||||
local array<FragAmmoRecord> oldRecords; |
||||
oldRecords = ammoRecords; |
||||
for (i = 0; i < oldRecords.length; i += 1) |
||||
{ |
||||
// Check if we even need to recreate that instance of 'Frag' |
||||
if (oldRecords[i].fragReference == none) continue; |
||||
fragOwner = oldRecords[i].fragReference.instigator; |
||||
if (fragOwner == none) continue; |
||||
// Recreate |
||||
oldRecords[i].fragReference.Destroy(); |
||||
fragOwner.CreateInventory("KFMod.Frag"); |
||||
newFrag = GetPawnFrag(fragOwner); |
||||
// Restore ammo amount |
||||
if (newFrag != none) |
||||
{ |
||||
newFrag.GetAmmoCount(maxAmmo, currentAmmo); |
||||
newFrag.AddAmmo(oldRecords[i].amount - Int(currentAmmo), 0); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Utility function to help find a 'Frag' instance in a given pawn's inventory. |
||||
static private final function Frag GetPawnFrag(Pawn pawnWithFrag) |
||||
{ |
||||
local Frag foundFrag; |
||||
local Inventory invIter; |
||||
if (pawnWithFrag == none) return none; |
||||
invIter = pawnWithFrag.inventory; |
||||
while (invIter != none) |
||||
{ |
||||
foundFrag = Frag(invIter); |
||||
if (foundFrag != none) |
||||
{ |
||||
return foundFrag; |
||||
} |
||||
invIter = invIter.inventory; |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
// Utility function for extracting current ammo amount from a frag class. |
||||
private final function int GetFragAmmo(Frag fragReference) |
||||
{ |
||||
local float maxAmmo; |
||||
local float currentAmmo; |
||||
if (fragReference == none) return 0; |
||||
|
||||
fragReference.GetAmmoCount(maxAmmo, currentAmmo); |
||||
return Int(currentAmmo); |
||||
} |
||||
|
||||
// Attempts to add new 'Frag' instance to our records. |
||||
public final function RegisterFrag(Frag newFrag) |
||||
{ |
||||
local int index; |
||||
local FragAmmoRecord newRecord; |
||||
index = GetAmmoIndex(newFrag); |
||||
if (index >= 0) return; |
||||
|
||||
newRecord.fragReference = newFrag; |
||||
newRecord.amount = GetFragAmmo(newFrag); |
||||
ammoRecords[ammoRecords.length] = newRecord; |
||||
} |
||||
|
||||
// This function tells our fix that there was a nade throw and we should |
||||
// reduce current 'Frag' ammo in our records. |
||||
// Returns 'true' if we had ammo for that, and 'false' if we didn't. |
||||
public final function bool RegisterNadeThrow(Frag relevantFrag) |
||||
{ |
||||
if (CanThrowGrenade(relevantFrag)) |
||||
{ |
||||
ReduceGrenades(relevantFrag); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
// Can we throw grenade according to our rules? |
||||
// A throw can be prevented if: |
||||
// - we think that player doesn't have necessary ammo; |
||||
// - Player isn't currently 'tossing' a nade, |
||||
// meaning it was a direct call of 'ServerThrow'. |
||||
private final function bool CanThrowGrenade(Frag fragToCheck) |
||||
{ |
||||
local int index; |
||||
// Nothing to check |
||||
if (fragToCheck == none) return false; |
||||
// No ammo |
||||
index = GetAmmoIndex(fragToCheck); |
||||
if (index < 0) return false; |
||||
if (ammoRecords[index].amount <= 0) return false; |
||||
// Not tossing |
||||
if (ignoreTossFlags) return true; |
||||
if (!fragToCheck.bTossActive || fragToCheck.bTossSpawned) return false; |
||||
return true; |
||||
} |
||||
|
||||
// Reduces recorded amount of ammo in our records for the given nade. |
||||
private final function ReduceGrenades(Frag relevantFrag) |
||||
{ |
||||
local int index; |
||||
index = GetAmmoIndex(relevantFrag); |
||||
if (index < 0) return; |
||||
ammoRecords[index].amount -= 1; |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
local int i; |
||||
// Update our ammo records with current, correct data. |
||||
i = 0; |
||||
while (i < ammoRecords.length) |
||||
{ |
||||
if (ammoRecords[i].fragReference != none) |
||||
{ |
||||
ammoRecords[i].amount = GetFragAmmo(ammoRecords[i].fragReference); |
||||
i += 1; |
||||
} |
||||
else |
||||
{ |
||||
ammoRecords.Remove(i, 1); |
||||
} |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
ignoreTossFlags = false |
||||
// Listeners |
||||
requiredListeners(0) = class'MutatorListener_FixInfiniteNades' |
||||
} |
@ -0,0 +1,36 @@
|
||||
/** |
||||
* A replacement for vanilla 'FragFire' fire class for 'Frag' weapon that |
||||
* adds additional ammo check in accordance to ammo records |
||||
* of 'FixInfiniteNades'. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixedFragFire extends KFMod.FragFire; |
||||
|
||||
function DoFireEffect() |
||||
{ |
||||
local FixInfiniteNades nadeFix; |
||||
nadeFix = FixInfiniteNades(class'FixInfiniteNades'.static.GetInstance()); |
||||
if (nadeFix == none || nadeFix.RegisterNadeThrow(Frag(weapon))) |
||||
{ |
||||
super.DoFireEffect(); |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,44 @@
|
||||
/** |
||||
* Overloaded mutator events listener to catch |
||||
* new 'Frag' weapons and 'Nade' projectiles. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MutatorListener_FixInfiniteNades extends MutatorListenerBase |
||||
abstract; |
||||
|
||||
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
local Frag relevantFrag; |
||||
local FixInfiniteNades nadeFix; |
||||
nadeFix = FixInfiniteNades(class'FixInfiniteNades'.static.GetInstance()); |
||||
if (nadeFix == none) return true; |
||||
|
||||
// Handle detecting new frag (weapons that allows to throw nades) |
||||
relevantFrag = Frag(other); |
||||
if (relevantFrag != none) |
||||
{ |
||||
nadeFix.RegisterFrag(relevantFrag); |
||||
relevantFrag.FireModeClass[0] = class'FixedFragFire'; |
||||
return true; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,226 @@
|
||||
/** |
||||
* This feature addressed two inventory issues: |
||||
* 1. Players carrying amount of weapons that shouldn't be allowed by the |
||||
* weight limit. |
||||
* 2. Players carrying two variants of the same gun. |
||||
* For example carrying both M32 and camo M32. |
||||
* Single and dual version of the same weapon are also considered |
||||
* the same gun, so you can't carry both MK23 and dual MK23 or |
||||
* dual handcannons and golden handcannon. |
||||
* |
||||
* It fixes them by doing repeated checks to find violations of those rules |
||||
* and destroys all droppable weapons of people that use this exploit. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixInventoryAbuse extends Feature |
||||
config(AcediaFixes); |
||||
|
||||
// How often (in seconds) should we do our inventory validations? |
||||
// We shouldn't really worry about performance, but there's also no need to |
||||
// do this check too often. |
||||
var private config const float checkInterval; |
||||
|
||||
struct DualiesPair |
||||
{ |
||||
var class<KFWeaponPickup> single; |
||||
var class<KFWeaponPickup> dual; |
||||
}; |
||||
// For this fix to properly work, this array must contain an entry for |
||||
// every dual weapon in the game (like pistols, with single and dual versions). |
||||
// It's made configurable in case of custom dual weapons. |
||||
var private config const array<DualiesPair> dualiesClasses; |
||||
|
||||
protected function OnEnabled() |
||||
{ |
||||
local float actualInterval; |
||||
actualInterval = checkInterval; |
||||
if (actualInterval <= 0) |
||||
{ |
||||
actualInterval = 0.25; |
||||
} |
||||
SetTimer(actualInterval, true); |
||||
} |
||||
|
||||
protected function OnDisabled() |
||||
{ |
||||
SetTimer(0.0f, false); |
||||
} |
||||
|
||||
// Did player with this controller contribute to the latest dosh generation? |
||||
private final function bool IsWeightLimitViolated(KFHumanPawn playerPawn) |
||||
{ |
||||
if (playerPawn == none) return false; |
||||
return (playerPawn.currentWeight > playerPawn.maxCarryWeight); |
||||
} |
||||
|
||||
// Returns a root pickup class. |
||||
// For non-dual weapons, root class is defined as either: |
||||
// 1. the first variant (reskin), if there are variants for that weapon; |
||||
// 2. and as the class itself, if there are no variants. |
||||
// For dual weapons (all dual pistols) root class is defined as |
||||
// a root of their single version. |
||||
// This definition is useful because: |
||||
// ~ Vanilla game rules are such that player can only have two weapons |
||||
// in the inventory if they have different roots; |
||||
// ~ Root is easy to find. |
||||
private final function class<KFWeaponPickup> GetRootPickupClass(KFWeapon weapon) |
||||
{ |
||||
local int i; |
||||
local class<KFWeaponPickup> root; |
||||
if (weapon == none) return none; |
||||
// Start with a pickup of the given weapons |
||||
root = class<KFWeaponPickup>(weapon.default.pickupClass); |
||||
if (root == none) return none; |
||||
|
||||
// In case it's a dual version - find corresponding single pickup class |
||||
// (it's root would be the same). |
||||
for (i = 0; i < dualiesClasses.length; i += 1) |
||||
{ |
||||
if (dualiesClasses[i].dual == root) |
||||
{ |
||||
root = dualiesClasses[i].single; |
||||
break; |
||||
} |
||||
} |
||||
// Take either first variant class or the class itself, - |
||||
// it's going to be root by definition. |
||||
if (root.default.variantClasses.length > 0) |
||||
{ |
||||
root = class<KFWeaponPickup>(root.default.variantClasses[0]); |
||||
} |
||||
return root; |
||||
} |
||||
|
||||
// Returns 'true' if passed pawn has two weapons that are just variants of |
||||
// each other (they have the same root, see 'GetRootPickupClass'). |
||||
private final function bool HasDuplicateGuns(KFHumanPawn playerPawn) |
||||
{ |
||||
local int i, j; |
||||
local Inventory inv; |
||||
local KFWeapon nextWeapon; |
||||
local class<KFWeaponPickup> rootClass; |
||||
local array< class<Pickup> > rootList; |
||||
if (playerPawn == none) return false; |
||||
|
||||
// First find a root for every weapon in the pawn's inventory. |
||||
for (inv = playerPawn.inventory; inv != none; inv = inv.inventory) |
||||
{ |
||||
nextWeapon = KFWeapon(inv); |
||||
if (nextWeapon == none) continue; |
||||
if (nextWeapon.bKFNeverThrow) continue; |
||||
rootClass = GetRootPickupClass(nextWeapon); |
||||
if (rootClass != none) |
||||
{ |
||||
rootList[rootList.length] = rootClass; |
||||
} |
||||
} |
||||
// Then just check obtained roots for duplicates. |
||||
for (i = 0; i < rootList.length; i += 1) |
||||
{ |
||||
for (j = i + 1; j < rootList.length; j += 1) |
||||
{ |
||||
if (rootList[i] == rootList[j]) |
||||
{ |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private final function Vector DropWeapon(KFWeapon weaponToDrop) |
||||
{ |
||||
local Vector x, y, z; |
||||
local Vector weaponVelocity; |
||||
local Vector dropLocation; |
||||
local KFHumanPawn playerPawn; |
||||
if (weaponToDrop == none) return Vect(0, 0, 0); |
||||
playerPawn = KFHumanPawn(weaponToDrop.instigator); |
||||
if (playerPawn == none) return Vect(0, 0, 0); |
||||
|
||||
// Calculations from 'PlayerController.ServerThrowWeapon' |
||||
weaponVelocity = Vector(playerPawn.GetViewRotation()); |
||||
weaponVelocity *= (playerPawn.velocity dot weaponVelocity) + 150; |
||||
weaponVelocity += Vect(0, 0, 100); |
||||
// Calculations from 'Pawn.TossWeapon' |
||||
GetAxes(playerPawn.rotation, x, y, z); |
||||
dropLocation = playerPawn.location + 0.8 * playerPawn.collisionRadius * x - |
||||
0.5 * playerPawn.collisionRadius * y; |
||||
// Do the drop |
||||
weaponToDrop.velocity = weaponVelocity; |
||||
weaponToDrop.DropFrom(dropLocation); |
||||
} |
||||
|
||||
// Kill the gun devil! |
||||
private final function DropEverything(KFHumanPawn playerPawn) |
||||
{ |
||||
local int i; |
||||
local Inventory inv; |
||||
local KFWeapon nextWeapon; |
||||
local array<KFWeapon> weaponList; |
||||
if (playerPawn == none) return; |
||||
// Going through the linked list while removing items can be tricky, |
||||
// so just find all weapons first. |
||||
for (inv = playerPawn.inventory; inv != none; inv = inv.inventory) |
||||
{ |
||||
nextWeapon = KFWeapon(inv); |
||||
if (nextWeapon == none) continue; |
||||
if (nextWeapon.bKFNeverThrow) continue; |
||||
weaponList[weaponList.length] = nextWeapon; |
||||
} |
||||
// And destroy them later. |
||||
for(i = 0; i < weaponList.length; i += 1) |
||||
{ |
||||
DropWeapon(weaponList[i]); |
||||
} |
||||
} |
||||
|
||||
event Timer() |
||||
{ |
||||
local int i; |
||||
local KFHumanPawn nextPawn; |
||||
local ConnectionService service; |
||||
local array<ConnectionService.Connection> connections; |
||||
service = ConnectionService(class'ConnectionService'.static.GetInstance()); |
||||
if (service == none) return; |
||||
|
||||
connections = service.GetActiveConnections(); |
||||
for (i = 0; i < connections.length; i += 1) |
||||
{ |
||||
nextPawn = none; |
||||
if (connections[i].controllerReference != none) |
||||
{ |
||||
nextPawn = KFHumanPawn(connections[i].controllerReference.pawn); |
||||
} |
||||
if (IsWeightLimitViolated(nextPawn) || HasDuplicateGuns(nextPawn)) |
||||
{ |
||||
DropEverything(nextPawn); |
||||
} |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
checkInterval = 0.25 |
||||
dualiesClasses(0)=(single=class'KFMod.SinglePickup',dual=class'KFMod.DualiesPickup') |
||||
dualiesClasses(1)=(single=class'KFMod.Magnum44Pickup',dual=class'KFMod.Dual44MagnumPickup') |
||||
dualiesClasses(2)=(single=class'KFMod.MK23Pickup',dual=class'KFMod.DualMK23Pickup') |
||||
dualiesClasses(3)=(single=class'KFMod.DeaglePickup',dual=class'KFMod.DualDeaglePickup') |
||||
dualiesClasses(4)=(single=class'KFMod.GoldenDeaglePickup',dual=class'KFMod.GoldenDualDeaglePickup') |
||||
dualiesClasses(5)=(single=class'KFMod.FlareRevolverPickup',dual=class'KFMod.DualFlareRevolverPickup') |
||||
} |
@ -0,0 +1,51 @@
|
||||
/** |
||||
* Overloaded broadcast events listener to catch the moment |
||||
* someone becomes alive player / spectator. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class BroadcastListener_FixSpectatorCrash extends BroadcastListenerBase |
||||
abstract; |
||||
|
||||
var private const int becomeAlivePlayerID; |
||||
var private const int becomeSpectatorID; |
||||
|
||||
static function bool HandleLocalized |
||||
( |
||||
Actor sender, |
||||
BroadcastEvents.LocalizedMessage message |
||||
) |
||||
{ |
||||
local FixSpectatorCrash specFix; |
||||
local PlayerController senderController; |
||||
if (sender == none) return true; |
||||
if (sender.level == none || sender.level.game == none) return true; |
||||
if (message.class != sender.level.game.gameMessageClass) return true; |
||||
if ( message.id != default.becomeAlivePlayerID |
||||
&& message.id != default.becomeSpectatorID) return true; |
||||
|
||||
specFix = FixSpectatorCrash(class'FixSpectatorCrash'.static.GetInstance()); |
||||
senderController = GetController(sender); |
||||
specFix.NotifyStatusChange(senderController); |
||||
return (!specFix.IsViolator(senderController)); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
becomeAlivePlayerID = 1 |
||||
becomeSpectatorID = 14 |
||||
} |
@ -0,0 +1,292 @@
|
||||
/** |
||||
* This feature attempts to prevent server crashes caused by someone |
||||
* quickly switching between being spectator and an active player. |
||||
* |
||||
* We do so by disconnecting players who start switching way too fast |
||||
* (more than twice in a short amount of time) and temporarily faking a large |
||||
* amount of players on the server, to prevent such spam from affecting the server. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixSpectatorCrash extends Feature |
||||
dependson(ConnectionService) |
||||
config(AcediaFixes); |
||||
|
||||
/** |
||||
* We use broadcast events to track when someone is switching |
||||
* to active player or spectator and remember such people |
||||
* for a short time (cooldown), defined by ('spectatorChangeTimeout'). |
||||
* If one of the player we've remembered tries to switch again, |
||||
* before the defined cooldown ran out, - we kick him |
||||
* by destroying his controller. |
||||
* One possible problem arises from the fact that controllers aren't |
||||
* immediately destroyed and instead initiate player disconnection, - |
||||
* exploiter might have enough time to cause a lag or even crash the server. |
||||
* We address this issue by temporarily blocking anyone from |
||||
* becoming active player (we do this by setting 'numPlayers' variable in |
||||
* killing floor's game info to a large value). |
||||
* After all malicious players have successfully disconnected, - |
||||
* we remove the block. |
||||
*/ |
||||
|
||||
// This fix will try to kick any player that switches between active player |
||||
// and cooldown faster than time (in seconds) in this value. |
||||
// NOTE: raising this value past default value of '0.25' |
||||
// won't actually improve crash prevention. |
||||
var private config const float spectatorChangeTimeout; |
||||
|
||||
// [ADVANCED] Don't change this setting unless you know what you're doing. |
||||
// Allows you to turn off server blocking. |
||||
// Players that don't respect timeout will still be kicked. |
||||
// This might be needed if this fix conflicts with another mutator |
||||
// that also changes 'numPlayers'. |
||||
// However, it is necessary to block aggressive enough server crash attempts, |
||||
// but can cause compatibility issues with some mutators. |
||||
// It's highly preferred to rewrite such a mutator to be compatible. |
||||
// NOTE: it should be compatible with most faked players-type mutators, |
||||
// since this fix remembers the difference between amount of |
||||
// real players and 'numPlayers'. |
||||
// After unblocking, it sets 'numPlayers' to |
||||
// the current amount of real players + that difference. |
||||
// So 4 players + 3 (=7 numPlayers) after kicking 1 player becomes |
||||
// 3 players + 3 (=6 numPlayers). |
||||
var private config const bool allowServerBlock; |
||||
|
||||
// Stores remaining cooldown value before the next allowed |
||||
// spectator change per player. |
||||
struct CooldownRecord |
||||
{ |
||||
var PlayerController player; |
||||
var float cooldown; |
||||
}; |
||||
|
||||
// Currently active cooldowns |
||||
var private array<CooldownRecord> currentCooldowns; |
||||
|
||||
// Players who were decided to be violators and |
||||
// were marked for disconnecting. |
||||
// We'll be maintaining server block as long as even one |
||||
// of them hasn't yet disconnected. |
||||
var private array<PlayerController> violators; |
||||
|
||||
// Is server currently blocked? |
||||
var private bool becomingActiveBlocked; |
||||
// This value introduced to accommodate mods such as faked player that can |
||||
// change 'numPlayers' to a value that isn't directly tied to the |
||||
// current number of active players. |
||||
// We remember the difference between active players and 'numPlayers' |
||||
/// variable in game type before server block and add it after block is over. |
||||
// If some mod introduces a more complicated relation between amount of |
||||
// active players and 'numPlayers', then it must take care of |
||||
// compatibility on it's own. |
||||
var private int recordedNumPlayersMod; |
||||
|
||||
// If given 'PlayerController' is registered in our cooldown records, - |
||||
// returns it's index. |
||||
// If it doesn't exists (or 'none' value was passes), - returns '-1'. |
||||
private final function int GetCooldownIndex(PlayerController player) |
||||
{ |
||||
local int i; |
||||
if (player == none) return -1; |
||||
|
||||
for (i = 0; i < currentCooldowns.length; i += 1) |
||||
{ |
||||
if (currentCooldowns[i].player == player) |
||||
{ |
||||
return i; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
// Checks if given 'PlayerController' is registered as a violator. |
||||
// 'none' value isn't a violator. |
||||
public final function bool IsViolator(PlayerController player) |
||||
{ |
||||
local int i; |
||||
if (player == none) return false; |
||||
|
||||
for (i = 0; i < violators.length; i += 1) |
||||
{ |
||||
if (violators[i] == player) |
||||
{ |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
// This function is to notify our fix that some player just changed status |
||||
// of active player / spectator. |
||||
// If passes value isn't 'none', it puts given player on cooldown or kicks him. |
||||
public final function NotifyStatusChange(PlayerController player) |
||||
{ |
||||
local int index; |
||||
local CooldownRecord newRecord; |
||||
if (player == none) return; |
||||
|
||||
index = GetCooldownIndex(player); |
||||
// Players already on cool down must be kicked and marked as violators |
||||
if (index >= 0) |
||||
{ |
||||
player.Destroy(); |
||||
currentCooldowns.Remove(index, 1); |
||||
violators[violators.length] = player; |
||||
if (allowServerBlock) |
||||
{ |
||||
SetBlock(true); |
||||
} |
||||
} |
||||
// Players that aren't on cooldown are |
||||
// either violators (do nothing, just wait for their disconnect) |
||||
// or didn't recently change their status (put them on cooldown). |
||||
else if (!IsViolator(player)) |
||||
{ |
||||
newRecord.player = player; |
||||
newRecord.cooldown = spectatorChangeTimeout; |
||||
currentCooldowns[currentCooldowns.length] = newRecord; |
||||
} |
||||
} |
||||
|
||||
// Pass 'true' to block server, 'false' to unblock. |
||||
// Only works if 'allowServerBlock' is set to 'true'. |
||||
private final function SetBlock(bool activateBlock) |
||||
{ |
||||
local KFGameType kfGameType; |
||||
// Do we even need to do anything? |
||||
if (!allowServerBlock) return; |
||||
if (activateBlock == becomingActiveBlocked) return; |
||||
// Only works with 'KFGameType' and it's children. |
||||
if (level != none) kfGameType = KFGameType(level.game); |
||||
if (kfGameType == none) return; |
||||
|
||||
// Actually block/unblock |
||||
becomingActiveBlocked = activateBlock; |
||||
if (activateBlock) |
||||
{ |
||||
recordedNumPlayersMod = GetNumPlayersMod(); |
||||
// This value both can't realistically fall below |
||||
// 'kfGameType.maxPlayer' and won't overflow from random increase |
||||
// in vanilla code. |
||||
kfGameType.numPlayers = maxInt / 2; |
||||
} |
||||
else |
||||
{ |
||||
// Adding 'recordedNumPlayersMod' should prevent |
||||
// faked players from breaking. |
||||
kfGameType.numPlayers = GetRealPlayers() + recordedNumPlayersMod; |
||||
} |
||||
} |
||||
|
||||
// Performs server blocking if violators have disconnected. |
||||
private final function TryUnblocking() |
||||
{ |
||||
local int i; |
||||
if (!allowServerBlock) return; |
||||
if (!becomingActiveBlocked) return; |
||||
|
||||
for (i = 0; i < violators.length; i += 1) |
||||
{ |
||||
if (violators[i] != none) |
||||
{ |
||||
return; |
||||
} |
||||
} |
||||
SetBlock(false); |
||||
} |
||||
|
||||
// Counts current amount of "real" active players |
||||
// (connected to the server and not spectators). |
||||
// Need 'ConnectionService' to be running, otherwise return '-1'. |
||||
private final function int GetRealPlayers() |
||||
{ |
||||
// Auxiliary variables |
||||
local int i; |
||||
local int realPlayersAmount; |
||||
local PlayerController player; |
||||
// Information extraction |
||||
local ConnectionService service; |
||||
local array<ConnectionService.Connection> connections; |
||||
service = ConnectionService(class'ConnectionService'.static.GetInstance()); |
||||
if (service == none) return -1; |
||||
|
||||
// Count non-spectators |
||||
connections = service.GetActiveConnections(); |
||||
realPlayersAmount = 0; |
||||
for (i = 0; i < connections.length; i += 1) |
||||
{ |
||||
player = connections[i].controllerReference; |
||||
if (player == none) continue; |
||||
if (player.playerReplicationInfo == none) continue; |
||||
if (!player.playerReplicationInfo.bOnlySpectator) |
||||
{ |
||||
realPlayersAmount += 1; |
||||
} |
||||
} |
||||
return realPlayersAmount; |
||||
} |
||||
|
||||
// Calculates difference between current amount of "real" active players |
||||
// and 'numPlayers' from 'KFGameType'. |
||||
// Most typically this difference will be non-zero when using |
||||
// faked players-type mutators |
||||
// (difference will be equal to the amount of faked players). |
||||
private final function int GetNumPlayersMod() |
||||
{ |
||||
local KFGameType kfGameType; |
||||
if (level != none) kfGameType = KFGameType(level.game); |
||||
if (kfGameType == none) return 0; |
||||
return kfGameType.numPlayers - GetRealPlayers(); |
||||
} |
||||
|
||||
private final function ReduceCooldowns(float timePassed) |
||||
{ |
||||
local int i; |
||||
i = 0; |
||||
while (i < currentCooldowns.length) |
||||
{ |
||||
currentCooldowns[i].cooldown -= timePassed; |
||||
if ( currentCooldowns[i].player != none |
||||
&& currentCooldowns[i].cooldown > 0.0) |
||||
{ |
||||
i += 1; |
||||
} |
||||
else |
||||
{ |
||||
currentCooldowns.Remove(i, 1); |
||||
} |
||||
} |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
local float trueTimePassed; |
||||
trueTimePassed = delta * (1.1 / level.timeDilation); |
||||
TryUnblocking(); |
||||
ReduceCooldowns(trueTimePassed); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
// Configurable variables |
||||
spectatorChangeTimeout = 0.25 |
||||
allowServerBlock = true |
||||
// Inner variables |
||||
becomingActiveBlocked = false |
||||
// Listeners |
||||
requiredListeners(0) = class'BroadcastListener_FixSpectatorCrash' |
||||
} |
@ -0,0 +1,189 @@
|
||||
/** |
||||
* This feature fixes lags caused by a zed time that can occur |
||||
* on some maps when a lot of zeds are present at once. |
||||
* As a side effect it also fixes an issue where during zed time speed up |
||||
* 'zedTimeSlomoScale' was assumed to be default value of '0.2'. |
||||
* Now zed time will behave correctly with mods that |
||||
* change 'zedTimeSlomoScale'. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixZedTimeLags extends Feature |
||||
dependson(ConnectionService) |
||||
config(AcediaFixes); |
||||
|
||||
/** |
||||
* When zed time activates, game speed is immediately set to |
||||
* 'zedTimeSlomoScale' (0.2 by default), defined, like all other variables, |
||||
* in 'KFGameType'. Zed time lasts 'zedTimeDuration' seconds (3.0 by default), |
||||
* but during last 'zedTimeDuration * 0.166' seconds (by default 0.498) |
||||
* it starts to speed back up, causing game speed to update every tick. |
||||
* This makes animations look more smooth when exiting zed-time; |
||||
* however, updating speed every tick for that purpose seems like |
||||
* an overkill and, combined with things like |
||||
* increased tick rate, certain maps and raised zed limit, |
||||
* it can lead to noticeable lags at the end of zed time. |
||||
* To fix this issue we disable 'Tick' event in |
||||
* 'KFGameType' and then repeat that functionality in our own 'Tick' event, |
||||
* but only perform game speed updates occasionally, |
||||
* to make sure that overall amount of updates won't go over a limit, |
||||
* that can be configured via 'maxGameSpeedUpdatesAmount' |
||||
* Author's test (looking really hard on clots' animations) |
||||
* seem to suggest that there shouldn't be much visible difference if |
||||
* we limit game speed updates to about 2 or 3. |
||||
*/ |
||||
|
||||
// Max amount of game speed updates during speed up phase |
||||
// (actual amount of updates can't be larger than amount of ticks). |
||||
// On servers with default 30 tick rate there's usually |
||||
// about 13 updates total on vanilla game. |
||||
// Values lower than 1 are treated like 1. |
||||
var private config const int maxGameSpeedUpdatesAmount; |
||||
// [ADVANCED] Don't change this setting unless you know what you're doing. |
||||
// Compatibility setting that allows to keep 'GameInfo' 's 'Tick' event |
||||
// from being disabled. |
||||
// Useful when running Acedia along with custom 'GameInfo' |
||||
// (that isn't 'KFGameType') that relies on 'Tick' event. |
||||
// Note, however, that in order to keep this fix working properly, |
||||
// it's on you to make sure 'KFGameType.Tick()' logic isn't executed. |
||||
var private config const bool disableTick; |
||||
// Counts how much time is left until next update |
||||
var private float updateCooldown; |
||||
// Recorded game type, to avoid constant conversions every tick |
||||
var private KFGameType gameType; |
||||
|
||||
protected function OnEnabled() |
||||
{ |
||||
gameType = KFGameType(level.game); |
||||
if (gameType == none) |
||||
{ |
||||
Destroy(); |
||||
} |
||||
else if (disableTick) |
||||
{ |
||||
gameType.Disable('Tick'); |
||||
} |
||||
} |
||||
|
||||
protected function OnDisabled() |
||||
{ |
||||
gameType = KFGameType(level.game); |
||||
if (gameType != none && disableTick) |
||||
{ |
||||
gameType.Enable('Tick'); |
||||
} |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
local float trueTimePassed; |
||||
if (gameType == none) return; |
||||
if (!gameType.bZEDTimeActive) return; |
||||
// Unfortunately we need to keep disabling 'Tick' probe function, |
||||
// because it constantly gets enabled back and I don't know where |
||||
// (maybe native code?); only really matters during zed time. |
||||
if (disableTick) |
||||
{ |
||||
gameType.Disable('Tick'); |
||||
} |
||||
// How much real (not in-game) time has passed |
||||
trueTimePassed = delta * (1.1 / level.timeDilation); |
||||
gameType.currentZEDTimeDuration -= trueTimePassed; |
||||
|
||||
// Handle speeding up phase |
||||
if (gameType.bSpeedingBackUp) |
||||
{ |
||||
DoSpeedBackUp(trueTimePassed); |
||||
} |
||||
else if (gameType.currentZEDTimeDuration < GetSpeedupDuration()) |
||||
{ |
||||
gameType.bSpeedingBackUp = true; |
||||
updateCooldown = GetFullUpdateCooldown(); |
||||
TellClientsZedTimeEnds(); |
||||
DoSpeedBackUp(trueTimePassed); |
||||
} |
||||
// End zed time once it's duration has passed |
||||
if (gameType.currentZEDTimeDuration <= 0) |
||||
{ |
||||
gameType.bZEDTimeActive = false; |
||||
gameType.bSpeedingBackUp = false; |
||||
gameType.zedTimeExtensionsUsed = 0; |
||||
gameType.SetGameSpeed(1.0); |
||||
} |
||||
} |
||||
|
||||
private final function TellClientsZedTimeEnds() |
||||
{ |
||||
local int i; |
||||
local KFPlayerController player; |
||||
local ConnectionService service; |
||||
local array<ConnectionService.Connection> connections; |
||||
service = ConnectionService(class'ConnectionService'.static.GetInstance()); |
||||
if (service == none) return; |
||||
connections = service.GetActiveConnections(); |
||||
for (i = 0; i < connections.length; i += 1) |
||||
{ |
||||
player = KFPlayerController(connections[i].controllerReference); |
||||
if (player != none) |
||||
{ |
||||
// Play sound of leaving zed time |
||||
player.ClientExitZedTime(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// This function is called every tick during speed up phase and manages |
||||
// gradual game speed increase. |
||||
private final function DoSpeedBackUp(float trueTimePassed) |
||||
{ |
||||
// Game speed will always be updated in our 'Tick' event |
||||
// at the very end of the zed time. |
||||
// The rest of the updates will be uniformly distributed |
||||
// over the speed up duration. |
||||
|
||||
local float newGameSpeed; |
||||
local float slowdownScale; |
||||
if (maxGameSpeedUpdatesAmount <= 1) return; |
||||
if (updateCooldown > 0.0) |
||||
{ |
||||
updateCooldown -= trueTimePassed; |
||||
return; |
||||
} |
||||
else |
||||
{ |
||||
updateCooldown = GetFullUpdateCooldown(); |
||||
} |
||||
slowdownScale = gameType.currentZEDTimeDuration / GetSpeedupDuration(); |
||||
newGameSpeed = Lerp(slowdownScale, 1.0, gameType.zedTimeSlomoScale); |
||||
gameType.SetGameSpeed(newGameSpeed); |
||||
} |
||||
|
||||
private final function float GetSpeedupDuration() |
||||
{ |
||||
return gameType.zedTimeDuration * 0.166; |
||||
} |
||||
|
||||
private final function float GetFullUpdateCooldown() |
||||
{ |
||||
return GetSpeedupDuration() / maxGameSpeedUpdatesAmount; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
maxGameSpeedUpdatesAmount = 3 |
||||
disableTick = true |
||||
} |
@ -0,0 +1,33 @@
|
||||
/** |
||||
* Manifest for AcediaFixes package |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Manifest extends _manifest |
||||
abstract; |
||||
|
||||
defaultproperties |
||||
{ |
||||
features(0) = class'FixZedTimeLags' |
||||
features(1) = class'FixDoshSpam' |
||||
features(2) = class'FixFFHack' |
||||
features(3) = class'FixInfiniteNades' |
||||
features(4) = class'FixAmmoSelling' |
||||
features(5) = class'FixSpectatorCrash' |
||||
features(6) = class'FixDualiesCost' |
||||
features(7) = class'FixInventoryAbuse' |
||||
} |
Loading…
Reference in new issue