Anton Tarasenko
4 years ago
commit
d56eb1db3e
66 changed files with 17979 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,165 @@ |
|||||||
|
[AcediaCore_0_2.ColorAliasSource] |
||||||
|
; System colors |
||||||
|
record=(alias="text_default",value="rgb(255,255,255)") |
||||||
|
record=(alias="text_subtle",value="rgb(128,128,128)") |
||||||
|
record=(alias="text_emphasis",value="rgb(0,128,255)") |
||||||
|
record=(alias="text_ok",value="rgb(0,255,0)") |
||||||
|
record=(alias="text_warning",value="rgb(255,128,0)") |
||||||
|
record=(alias="text_failure",value="rgb(255,0,0)") |
||||||
|
record=(alias="type_number",value="rgb(181,137,0)") |
||||||
|
record=(alias="type_boolean",value="rgb(38,139,210)") |
||||||
|
record=(alias="type_string",value="rgb(211,54,130)") |
||||||
|
record=(alias="type_literal",value="rgb(42,161,152)") |
||||||
|
record=(alias="type_class",value="rgb(108,113,196)") |
||||||
|
; Pink colors |
||||||
|
record=(alias="Pink",value="rgb(255,192,203)") |
||||||
|
record=(alias="LightPink",value="rgb(255,182,193)") |
||||||
|
record=(alias="HotPink",value="rgb(255,105,180)") |
||||||
|
record=(alias="DeepPink",value="rgb(255,20,147)") |
||||||
|
record=(alias="PaleVioletRed",value="rgb(219,112,147)") |
||||||
|
record=(alias="MediumVioletRed",value="rgb(199,21,133)") |
||||||
|
; Red colors |
||||||
|
record=(alias="LightSalmon",value="rgb(255,160,122)") |
||||||
|
record=(alias="Salmon",value="rgb(250,128,114)") |
||||||
|
record=(alias="DarkSalmon",value="rgb(233,150,122)") |
||||||
|
record=(alias="LightCoral",value="rgb(240,128,128)") |
||||||
|
record=(alias="IndianRed",value="rgb(205,92,92)") |
||||||
|
record=(alias="Crimson",value="rgb(220,20,60)") |
||||||
|
record=(alias="Firebrick",value="rgb(178,34,34)") |
||||||
|
record=(alias="DarkRed",value="rgb(139,0,0)") |
||||||
|
record=(alias="Red",value="rgb(255,0,0)") |
||||||
|
; Orange colors |
||||||
|
record=(alias="OrangeRed",value="rgb(255,69,0)") |
||||||
|
record=(alias="Tomato",value="rgb(255,99,71)") |
||||||
|
record=(alias="Coral",value="rgb(255,127,80)") |
||||||
|
record=(alias="DarkOrange",value="rgb(255,140,0)") |
||||||
|
record=(alias="Orange",value="rgb(255,165,0)") |
||||||
|
; Yellow colors |
||||||
|
record=(alias="Yellow",value="rgb(255,255,0)") |
||||||
|
record=(alias="LightYellow",value="rgb(255,255,224)") |
||||||
|
record=(alias="LemonChiffon",value="rgb(255,250,205)") |
||||||
|
record=(alias="LightGoldenrodYellow",value="rgb(250,250,210)") |
||||||
|
record=(alias="PapayaWhip",value="rgb(255,239,213)") |
||||||
|
record=(alias="Moccasin",value="rgb(255,228,181)") |
||||||
|
record=(alias="PeachPuff",value="rgb(255,218,185)") |
||||||
|
record=(alias="PaleGoldenrod",value="rgb(238,232,170)") |
||||||
|
record=(alias="Khaki",value="rgb(240,230,140)") |
||||||
|
record=(alias="DarkKhaki",value="rgb(189,183,107)") |
||||||
|
record=(alias="Gold",value="rgb(255,215,0)") |
||||||
|
; Brown colors |
||||||
|
record=(alias="Cornsilk",value="rgb(255,248,220)") |
||||||
|
record=(alias="BlanchedAlmond",value="rgb(255,235,205)") |
||||||
|
record=(alias="Bisque",value="rgb(255,228,196)") |
||||||
|
record=(alias="NavajoWhite",value="rgb(255,222,173)") |
||||||
|
record=(alias="Wheat",value="rgb(245,222,179)") |
||||||
|
record=(alias="Burlywood",value="rgb(222,184,135)") |
||||||
|
record=(alias="Tan",value="rgb(210,180,140)") |
||||||
|
record=(alias="RosyBrown",value="rgb(188,143,143)") |
||||||
|
record=(alias="SandyBrown",value="rgb(244,164,96)") |
||||||
|
record=(alias="Goldenrod",value="rgb(218,165,32)") |
||||||
|
record=(alias="DarkGoldenrod",value="rgb(184,134,11)") |
||||||
|
record=(alias="Peru",value="rgb(205,133,63)") |
||||||
|
record=(alias="Chocolate",value="rgb(210,105,30)") |
||||||
|
record=(alias="SaddleBrown",value="rgb(139,69,19)") |
||||||
|
record=(alias="Sienna",value="rgb(160,82,45)") |
||||||
|
record=(alias="Brown",value="rgb(165,42,42)") |
||||||
|
record=(alias="Maroon",value="rgb(128,0,0)") |
||||||
|
; Green colors |
||||||
|
record=(alias="DarkOliveGreen",value="rgb(85,107,47)") |
||||||
|
record=(alias="Olive",value="rgb(128,128,0)") |
||||||
|
record=(alias="OliveDrab",value="rgb(107,142,35)") |
||||||
|
record=(alias="YellowGreen",value="rgb(154,205,50)") |
||||||
|
record=(alias="LimeGreen",value="rgb(50,205,50)") |
||||||
|
record=(alias="Lime",value="rgb(0,255,0)") |
||||||
|
record=(alias="LawnGreen",value="rgb(124,252,0)") |
||||||
|
record=(alias="Chartreuse",value="rgb(127,255,0)") |
||||||
|
record=(alias="GreenYellow",value="rgb(173,255,47)") |
||||||
|
record=(alias="SpringGreen",value="rgb(0,255,127)") |
||||||
|
record=(alias="MediumSpringGreen",value="rgb(0,250,154)") |
||||||
|
record=(alias="LightGreen",value="rgb(144,238,144)") |
||||||
|
record=(alias="PaleGreen",value="rgb(152,251,152)") |
||||||
|
record=(alias="DarkSeaGreen",value="rgb(143,188,143)") |
||||||
|
record=(alias="MediumAquamarine",value="rgb(102,205,170)") |
||||||
|
record=(alias="MediumSeaGreen",value="rgb(60,179,113)") |
||||||
|
record=(alias="SeaGreen",value="rgb(46,139,87)") |
||||||
|
record=(alias="ForestGreen",value="rgb(34,139,34)") |
||||||
|
record=(alias="Green",value="rgb(0,128,0)") |
||||||
|
record=(alias="DarkGreen",value="rgb(0,100,0)") |
||||||
|
; Cyan colors |
||||||
|
record=(alias="Aqua",value="rgb(0,255,255)") |
||||||
|
record=(alias="Cyan",value="rgb(0,255,255)") |
||||||
|
record=(alias="LightCyan",value="rgb(224,255,255)") |
||||||
|
record=(alias="PaleTurquoise",value="rgb(175,238,238)") |
||||||
|
record=(alias="Aquamarine",value="rgb(127,255,212)") |
||||||
|
record=(alias="Turquoise",value="rgb(64,224,208)") |
||||||
|
record=(alias="MediumTurquoise",value="rgb(72,209,204)") |
||||||
|
record=(alias="DarkTurquoise",value="rgb(0,206,209)") |
||||||
|
record=(alias="LightSeaGreen",value="rgb(32,178,170)") |
||||||
|
record=(alias="CadetBlue",value="rgb(95,158,160)") |
||||||
|
record=(alias="DarkCyan",value="rgb(0,139,139)") |
||||||
|
record=(alias="Teal",value="rgb(0,128,128)") |
||||||
|
; Blue colors |
||||||
|
record=(alias="LightSteelBlue",value="rgb(176,196,222)") |
||||||
|
record=(alias="PowderBlue",value="rgb(176,224,230)") |
||||||
|
record=(alias="LightBlue",value="rgb(173,216,230)") |
||||||
|
record=(alias="SkyBlue",value="rgb(135,206,235)") |
||||||
|
record=(alias="LightSkyBlue",value="rgb(135,206,250)") |
||||||
|
record=(alias="DeepSkyBlue",value="rgb(0,191,255)") |
||||||
|
record=(alias="DodgerBlue",value="rgb(30,144,255)") |
||||||
|
record=(alias="CornflowerBlue",value="rgb(100,149,237)") |
||||||
|
record=(alias="SteelBlue",value="rgb(70,130,180)") |
||||||
|
record=(alias="RoyalBlue",value="rgb(65,105,225)") |
||||||
|
record=(alias="Blue",value="rgb(0,0,255)") |
||||||
|
record=(alias="MediumBlue",value="rgb(0,0,205)") |
||||||
|
record=(alias="DarkBlue",value="rgb(0,0,139)") |
||||||
|
record=(alias="Navy",value="rgb(0,0,128)") |
||||||
|
record=(alias="MidnightBlue",value="rgb(25,25,112)") |
||||||
|
; Purple, violet, and magenta colors |
||||||
|
record=(alias="Lavender",value="rgb(230,230,250)") |
||||||
|
record=(alias="Thistle",value="rgb(216,191,216)") |
||||||
|
record=(alias="Plum",value="rgb(221,160,221)") |
||||||
|
record=(alias="Violet",value="rgb(238,130,238)") |
||||||
|
record=(alias="Orchid",value="rgb(218,112,214)") |
||||||
|
record=(alias="Fuchsia",value="rgb(255,0,255)") |
||||||
|
record=(alias="Magenta",value="rgb(255,0,255)") |
||||||
|
record=(alias="MediumOrchid",value="rgb(186,85,211)") |
||||||
|
record=(alias="MediumPurple",value="rgb(147,112,219)") |
||||||
|
record=(alias="BlueViolet",value="rgb(138,43,226)") |
||||||
|
record=(alias="DarkViolet",value="rgb(148,0,211)") |
||||||
|
record=(alias="DarkOrchid",value="rgb(153,50,204)") |
||||||
|
record=(alias="DarkMagenta",value="rgb(139,0,139)") |
||||||
|
record=(alias="Purple",value="rgb(128,0,128)") |
||||||
|
record=(alias="Indigo",value="rgb(75,0,130)") |
||||||
|
record=(alias="DarkSlateBlue",value="rgb(72,61,139)") |
||||||
|
record=(alias="SlateBlue",value="rgb(106,90,205)") |
||||||
|
record=(alias="MediumSlateBlue",value="rgb(123,104,238)") |
||||||
|
; White colors |
||||||
|
record=(alias="White",value="rgb(255,255,255)") |
||||||
|
record=(alias="Snow",value="rgb(255,250,250)") |
||||||
|
record=(alias="Honeydew",value="rgb(240,255,240)") |
||||||
|
record=(alias="MintCream",value="rgb(245,255,250)") |
||||||
|
record=(alias="Azure",value="rgb(240,255,255)") |
||||||
|
record=(alias="AliceBlue",value="rgb(240,248,255)") |
||||||
|
record=(alias="GhostWhite",value="rgb(248,248,255)") |
||||||
|
record=(alias="WhiteSmoke",value="rgb(245,245,245)") |
||||||
|
record=(alias="Seashell",value="rgb(255,245,238)") |
||||||
|
record=(alias="Beige",value="rgb(245,245,220)") |
||||||
|
record=(alias="OldLace",value="rgb(253,245,230)") |
||||||
|
record=(alias="FloralWhite",value="rgb(255,250,240)") |
||||||
|
record=(alias="Ivory",value="rgb(255,255,240)") |
||||||
|
record=(alias="AntiqueWhite",value="rgb(250,235,215)") |
||||||
|
record=(alias="Linen",value="rgb(250,240,230)") |
||||||
|
record=(alias="LavenderBlush",value="rgb(255,240,245)") |
||||||
|
record=(alias="MistyRose",value="rgb(255,228,225)") |
||||||
|
; Gray and black colors |
||||||
|
record=(alias="Gainsboro",value="rgb(220,220,220)") |
||||||
|
record=(alias="LightGray",value="rgb(211,211,211)") |
||||||
|
record=(alias="Silver",value="rgb(192,192,192)") |
||||||
|
record=(alias="Gray",value="rgb(169,169,169)") |
||||||
|
record=(alias="DimGray",value="rgb(128,128,128)") |
||||||
|
record=(alias="DarkGray",value="rgb(105,105,105)") |
||||||
|
record=(alias="LightSlateGray",value="rgb(119,136,153)") |
||||||
|
record=(alias="SlateGray",value="rgb(112,128,144)") |
||||||
|
record=(alias="DarkSlateGray",value="rgb(47,79,79)") |
||||||
|
record=(alias="Eigengrau",value="rgb(22,22,29)") |
||||||
|
record=(alias="Black",value="rgb(0,0,0)") |
@ -0,0 +1,17 @@ |
|||||||
|
; For the puposes of testing alias functionality. |
||||||
|
; Changing these can break tests. |
||||||
|
; |
||||||
|
; If you don't plan to run tests or do not know what they are, - |
||||||
|
; feel free to remove this file. |
||||||
|
[AcediaCore_0_2.MockAliasSource] |
||||||
|
record=(alias="global",value="value") |
||||||
|
record=(alias="question",value="response") |
||||||
|
record=(alias="",value="empty") |
||||||
|
record=(alias="also",value="") |
||||||
|
[car MockAliases] |
||||||
|
Alias="Ford" |
||||||
|
Alias="Delorean" |
||||||
|
Alias="Audi" |
||||||
|
[sci:fi MockAliases] |
||||||
|
Alias="Spice" |
||||||
|
Alias="HardToBeAGod" |
@ -0,0 +1,547 @@ |
|||||||
|
[AcediaCore_0_2.WeaponAliasSource] |
||||||
|
; Field Medic weapons |
||||||
|
[KFMod:MP7MMedicGun WeaponAliases] |
||||||
|
Alias="MP7M" |
||||||
|
Alias="MP7" |
||||||
|
[KFMod:MP5MMedicGun WeaponAliases] |
||||||
|
Alias="MP5M" |
||||||
|
Alias="MP5" |
||||||
|
Alias="MP" |
||||||
|
Alias="M5" |
||||||
|
[KFMod:CamoMP5MMedicGun WeaponAliases] |
||||||
|
Alias="CamoMP5M" |
||||||
|
Alias="CamoMP5" |
||||||
|
Alias="CamoMP" |
||||||
|
Alias="CamoM5" |
||||||
|
[KFMod:M7A3MMedicGun WeaponAliases] |
||||||
|
Alias="M7A3" |
||||||
|
Alias="M7A" |
||||||
|
Alias="M7" |
||||||
|
[KFMod:KrissMMedicGun WeaponAliases] |
||||||
|
Alias="Schneidzekk" |
||||||
|
Alias="Schneidzek" |
||||||
|
Alias="Kriss" |
||||||
|
Alias="Kris" |
||||||
|
[KFMod:NeonKrissMMedicGun WeaponAliases] |
||||||
|
Alias="NeonSchneidzekk" |
||||||
|
Alias="NeonSchneidzek" |
||||||
|
Alias="NeonKriss" |
||||||
|
Alias="NeonKris" |
||||||
|
[KFMod:BlowerThrower WeaponAliases] |
||||||
|
Alias="BlowerThrower" |
||||||
|
Alias="Blower" |
||||||
|
Alias="Thrower" |
||||||
|
Alias="BThrower" |
||||||
|
Alias="PoopGun" |
||||||
|
Alias="BileGun" |
||||||
|
Alias="BloatGun" |
||||||
|
|
||||||
|
; Support Specialist weapons |
||||||
|
[KFMod:Shotgun WeaponAliases] |
||||||
|
Alias="Shotgun" |
||||||
|
[KFMod:CamoShotgun WeaponAliases] |
||||||
|
Alias="CamoShotgun" |
||||||
|
[KFMod:BoomStick WeaponAliases] |
||||||
|
Alias="HuntingShotgun" |
||||||
|
Alias="BoomStick" |
||||||
|
Alias="Hunting" |
||||||
|
[KFMod:KSGShotgun WeaponAliases] |
||||||
|
Alias="HSG-1Shotgun" |
||||||
|
Alias="HSG1Shotgun" |
||||||
|
Alias="HSGShotgun" |
||||||
|
Alias="HSG" |
||||||
|
Alias="KSG-1Shotgun" |
||||||
|
Alias="KSG1Shotgun" |
||||||
|
Alias="KSGShotgun" |
||||||
|
Alias="KSG" |
||||||
|
[KFMod:NeonKSGShotgun WeaponAliases] |
||||||
|
Alias="NeonHSG-1Shotgun" |
||||||
|
Alias="NeonHSG1Shotgun" |
||||||
|
Alias="NeonHSGShotgun" |
||||||
|
Alias="NeonHSG" |
||||||
|
Alias="NeonKSG-1Shotgun" |
||||||
|
Alias="NeonKSG1Shotgun" |
||||||
|
Alias="NeonKSGShotgun" |
||||||
|
Alias="NeonKSG" |
||||||
|
[KFMod:NailGun WeaponAliases] |
||||||
|
Alias="VladTheImpaler" |
||||||
|
Alias="VladImpaler" |
||||||
|
Alias="Vlad" |
||||||
|
Alias="Impaler" |
||||||
|
Alias="NailGun" |
||||||
|
Alias="Nails" |
||||||
|
Alias="Nail" |
||||||
|
[KFMod:SPAutoShotgun WeaponAliases] |
||||||
|
Alias="MultichamberZEDThrower" |
||||||
|
Alias="ZEDThrower" |
||||||
|
Alias="ZThrower" |
||||||
|
[KFMod:BenelliShotgun WeaponAliases] |
||||||
|
Alias="CombatShotgun" |
||||||
|
Alias="Combat" |
||||||
|
Alias="CShotgun" |
||||||
|
Alias="BenelliShotgun" |
||||||
|
Alias="BeneliShotgun" |
||||||
|
Alias="Benelli" |
||||||
|
Alias="Beneli" |
||||||
|
[KFMod:GoldenBenelliShotgun WeaponAliases] |
||||||
|
Alias="GoldCombatShotgun" |
||||||
|
Alias="GoldCombat" |
||||||
|
Alias="GoldCShotgun" |
||||||
|
Alias="GoldBenelliShotgun" |
||||||
|
Alias="GoldBeneliShotgun" |
||||||
|
Alias="GoldBenelli" |
||||||
|
Alias="GoldBeneli" |
||||||
|
[KFMod:AA12AutoShotgun WeaponAliases] |
||||||
|
Alias="AA12" |
||||||
|
Alias="AA12AutoShotgun" |
||||||
|
Alias="AA12Shotgun" |
||||||
|
[KFMod:GoldenAA12AutoShotgun WeaponAliases] |
||||||
|
Alias="GoldAA12" |
||||||
|
Alias="GoldAA12AutoShotgun" |
||||||
|
Alias="GoldAA12Shotgun" |
||||||
|
|
||||||
|
; Sharpshooter weapons |
||||||
|
[KFMod:Single WeaponAliases] |
||||||
|
Alias="9mmTactical" |
||||||
|
Alias="9mmTact" |
||||||
|
Alias="9mm" |
||||||
|
Alias="Single" |
||||||
|
Alias="Pistol" |
||||||
|
[KFMod:Dualies WeaponAliases] |
||||||
|
Alias="Dual9mms" |
||||||
|
Alias="Dual9mm" |
||||||
|
Alias="9mmDual" |
||||||
|
Alias="Dualies" |
||||||
|
Alias="Dual" |
||||||
|
[KFMod:Magnum44Pistol WeaponAliases] |
||||||
|
Alias="Magnum44Pistol" |
||||||
|
Alias="Magnum44" |
||||||
|
Alias="44Magnum" |
||||||
|
Alias="Magnum" |
||||||
|
Alias="44" |
||||||
|
[KFMod:Dual44Magnum WeaponAliases] |
||||||
|
Alias="DualMagnum44Pistols" |
||||||
|
Alias="DualMagnum44s" |
||||||
|
Alias="DualMagnums" |
||||||
|
Alias="DualMagnumPistols" |
||||||
|
Alias="Dual44Magnums" |
||||||
|
Alias="Dual44Magnum" |
||||||
|
Alias="DualMagnum" |
||||||
|
Alias="Dual44ss" |
||||||
|
Alias="Dual44" |
||||||
|
[KFMod:MK23Pistol WeaponAliases] |
||||||
|
Alias="MK23" |
||||||
|
Alias="MK" |
||||||
|
Alias="23" |
||||||
|
[KFMod:DualMK23Pistol WeaponAliases] |
||||||
|
Alias="DualMK23s" |
||||||
|
Alias="DualMK23" |
||||||
|
Alias="DualMKs" |
||||||
|
Alias="DualMK" |
||||||
|
Alias="Dual23s" |
||||||
|
Alias="Dual23" |
||||||
|
[KFMod:Deagle WeaponAliases] |
||||||
|
Alias="Handcannon" |
||||||
|
Alias="Deagle" |
||||||
|
Alias="HC" |
||||||
|
[KFMod:DualDeagle WeaponAliases] |
||||||
|
Alias="DualHandcannons" |
||||||
|
Alias="DualHC" |
||||||
|
Alias="DualDeagle" |
||||||
|
[KFMod:GoldenDeagle WeaponAliases] |
||||||
|
Alias="GoldHandcannon" |
||||||
|
Alias="GoldDeagle" |
||||||
|
Alias="GoldHC" |
||||||
|
[KFMod:GoldenDualDeagle WeaponAliases] |
||||||
|
Alias="GoldDualHandcannons" |
||||||
|
Alias="GoldDualHC" |
||||||
|
Alias="GoldDualDeagle" |
||||||
|
[KFMod:Winchester WeaponAliases] |
||||||
|
Alias="Winchester" |
||||||
|
Alias="LeverActionRifle" |
||||||
|
Alias="LAR" |
||||||
|
[KFMod:SPSniperRifle WeaponAliases] |
||||||
|
Alias="SPMusket" |
||||||
|
Alias="Musket" |
||||||
|
Alias="SPSniperRifle" |
||||||
|
Alias="SPRifle" |
||||||
|
Alias="SPSniper" |
||||||
|
Alias="SPMauler" |
||||||
|
Alias="Mauler" |
||||||
|
[KFMod:M14EBRBattleRifle WeaponAliases] |
||||||
|
Alias="M14EBR" |
||||||
|
Alias="M14" |
||||||
|
Alias="EBR" |
||||||
|
Alias="M14EBRRifle" |
||||||
|
Alias="M14EBRBattleRifle" |
||||||
|
[KFMod:Crossbow WeaponAliases] |
||||||
|
Alias="CompoundCrossbow" |
||||||
|
Alias="CCrossbow" |
||||||
|
Alias="Crossbow" |
||||||
|
Alias="XBow" |
||||||
|
[KFMod:M99SniperRifle WeaponAliases] |
||||||
|
Alias="M99AMR" |
||||||
|
Alias="M99" |
||||||
|
Alias="M99SniperRifle" |
||||||
|
Alias="M99Sniper" |
||||||
|
Alias="M99Rifle" |
||||||
|
Alias="M99SR" |
||||||
|
|
||||||
|
; Commando weapons |
||||||
|
[KFMod:Bullpup WeaponAliases] |
||||||
|
Alias="Bullpup" |
||||||
|
Alias="Bulpup" |
||||||
|
[KFMod:ThompsonSMG WeaponAliases] |
||||||
|
Alias="ThompsonSMG" |
||||||
|
Alias="Thompson" |
||||||
|
Alias="Thomp" |
||||||
|
Alias="TommyGun" |
||||||
|
Alias="TomyGun" |
||||||
|
Alias="Tommy" |
||||||
|
Alias="Tomy" |
||||||
|
[KFMod:SPThompsonSMG WeaponAliases] |
||||||
|
Alias="SPThompsonSMG" |
||||||
|
Alias="SPThompson" |
||||||
|
Alias="SPThomp" |
||||||
|
Alias="Dr.T'sLeadDeliverySystem" |
||||||
|
Alias="Dr.TsLeadDeliverySystem" |
||||||
|
Alias="DrT'sLeadDeliverySystem" |
||||||
|
Alias="DrTsLeadDeliverySystem" |
||||||
|
Alias="Dr.T'LeadDeliverySystem" |
||||||
|
Alias="Dr.TLeadDeliverySystem" |
||||||
|
Alias="DrT'LeadDeliverySystem" |
||||||
|
Alias="DrTLeadDeliverySystem" |
||||||
|
Alias="DrTDeliverySystem" |
||||||
|
Alias="DrTLeadSystem" |
||||||
|
Alias="DrTLeadDelivery" |
||||||
|
Alias="DrTDelivery" |
||||||
|
Alias="LeadDelivery" |
||||||
|
Alias="LeadSystem" |
||||||
|
Alias="DeliverySystem" |
||||||
|
Alias="LeadDS" |
||||||
|
Alias="LeadD" |
||||||
|
[KFMod:ThompsonDrumSMG WeaponAliases] |
||||||
|
Alias="ThompsonDrumSMG" |
||||||
|
Alias="ThompsonDrum" |
||||||
|
Alias="ThompDrum" |
||||||
|
Alias="RisingStormTommyGun" |
||||||
|
Alias="RisingStormTommy" |
||||||
|
Alias="RisingStormTomyGun" |
||||||
|
Alias="RisingStormTomy" |
||||||
|
Alias="RSTommyGun" |
||||||
|
Alias="RSTommy" |
||||||
|
Alias="RSTomyGun" |
||||||
|
Alias="RSTomy" |
||||||
|
[KFMod:AK47AssaultRifle WeaponAliases] |
||||||
|
Alias="AK47AssaultRifle" |
||||||
|
Alias="AK47Assault" |
||||||
|
Alias="AK47Rifle" |
||||||
|
Alias="AK47AR" |
||||||
|
Alias="AK47" |
||||||
|
Alias="AK" |
||||||
|
Alias="47" |
||||||
|
[KFMod:GoldenAK47AssaultRifle WeaponAliases] |
||||||
|
Alias="GoldAK47AssaultRifle" |
||||||
|
Alias="GoldAK47Assault" |
||||||
|
Alias="GoldAK47Rifle" |
||||||
|
Alias="GoldAK47AR" |
||||||
|
Alias="GoldAK47" |
||||||
|
Alias="GoldAK" |
||||||
|
Alias="Gold47" |
||||||
|
[KFMod:NeonAK47AssaultRifle WeaponAliases] |
||||||
|
Alias="NeonAK47AssaultRifle" |
||||||
|
Alias="NeonAK47Assault" |
||||||
|
Alias="NeonAK47Rifle" |
||||||
|
Alias="NeonAK47AR" |
||||||
|
Alias="NeonAK47" |
||||||
|
Alias="NeonAK" |
||||||
|
Alias="Neon47" |
||||||
|
[KFMod:M4AssaultRifle WeaponAliases] |
||||||
|
Alias="M4AssaultRifle" |
||||||
|
Alias="M4Assault" |
||||||
|
Alias="M4Rifle" |
||||||
|
Alias="M4" |
||||||
|
[KFMod:CamoM4AssaultRifle WeaponAliases] |
||||||
|
Alias="CamoM4AssaultRifle" |
||||||
|
Alias="CamoM4Assault" |
||||||
|
Alias="CamoM4Rifle" |
||||||
|
Alias="CamoM4" |
||||||
|
[KFMod:MKb42AssaultRifle WeaponAliases] |
||||||
|
Alias="MKb42AssaultRifle" |
||||||
|
Alias="MKb42Assault" |
||||||
|
Alias="MKb42Rifle" |
||||||
|
Alias="MKb42" |
||||||
|
Alias="MK42" |
||||||
|
Alias="MKb" |
||||||
|
[KFMod:SCARMK17AssaultRifle WeaponAliases] |
||||||
|
Alias="SCARMK17AssaultRifle" |
||||||
|
Alias="SCARMK17Assault" |
||||||
|
Alias="SCARMK17Rifle" |
||||||
|
Alias="SCARMKAssaultRifle" |
||||||
|
Alias="SCARMKAssault" |
||||||
|
Alias="SCARMKRifle" |
||||||
|
Alias="SCAR17AssaultRifle" |
||||||
|
Alias="SCAR17Assault" |
||||||
|
Alias="SCAR17Rifle" |
||||||
|
Alias="SCARAssaultRifle" |
||||||
|
Alias="SCARAssault" |
||||||
|
Alias="SCARRifle" |
||||||
|
Alias="SCAR17" |
||||||
|
Alias="SCARMK" |
||||||
|
Alias="SCAR" |
||||||
|
[KFMod:NeonSCARMK17AssaultRifle WeaponAliases] |
||||||
|
Alias="NeonSCARMK17AssaultRifle" |
||||||
|
Alias="NeonSCARMK17Assault" |
||||||
|
Alias="NeonSCARMK17Rifle" |
||||||
|
Alias="NeonSCARMKAssaultRifle" |
||||||
|
Alias="NeonSCARMKAssault" |
||||||
|
Alias="NeonSCARMKRifle" |
||||||
|
Alias="NeonSCAR17AssaultRifle" |
||||||
|
Alias="NeonSCAR17Assault" |
||||||
|
Alias="NeonSCAR17Rifle" |
||||||
|
Alias="NeonSCARAssaultRifle" |
||||||
|
Alias="NeonSCARAssault" |
||||||
|
Alias="NeonSCARRifle" |
||||||
|
Alias="NeonSCAR17" |
||||||
|
Alias="NeonSCARMK" |
||||||
|
Alias="NeonSCAR" |
||||||
|
[KFMod:FNFAL_ACOG_AssaultRifle WeaponAliases]FNFAL ACOG |
||||||
|
Alias="FNFALACOGAssaultRifle" |
||||||
|
Alias="FNFALACOGAssault" |
||||||
|
Alias="FNFALACOGRifle" |
||||||
|
Alias="FNFALAssaultRifle" |
||||||
|
Alias="FNFALAssault" |
||||||
|
Alias="FNFALRifle" |
||||||
|
Alias="FALACOGAssaultRifle" |
||||||
|
Alias="FALACOGAssault" |
||||||
|
Alias="FALACOGRifle" |
||||||
|
Alias="FALAssaultRifle" |
||||||
|
Alias="FALAssault" |
||||||
|
Alias="FALRifle" |
||||||
|
Alias="FNFALACOG" |
||||||
|
Alias="FNFAL" |
||||||
|
Alias="FALACOG" |
||||||
|
Alias="FAL" |
||||||
|
Alias="FN" |
||||||
|
|
||||||
|
; Berserker weapons |
||||||
|
[KFMod:Knife WeaponAliases] |
||||||
|
Alias="Knife" |
||||||
|
[KFMod:Machete WeaponAliases] |
||||||
|
Alias="Machete" |
||||||
|
Alias="Chete" |
||||||
|
[KFMod:Axe WeaponAliases] |
||||||
|
Alias="Axe" |
||||||
|
Alias="FireAxe" |
||||||
|
[KFMod:Katana WeaponAliases] |
||||||
|
Alias="Katana" |
||||||
|
[KFMod:GoldenKatana WeaponAliases] |
||||||
|
Alias="GoldKatana" |
||||||
|
[KFMod:Scythe WeaponAliases] |
||||||
|
Alias="Scythe" |
||||||
|
Alias="Scyte" |
||||||
|
Alias="Sickle" |
||||||
|
Alias="Sickl" |
||||||
|
[KFMod:Chainsaw WeaponAliases] |
||||||
|
Alias="Chainsaw" |
||||||
|
Alias="Saw" |
||||||
|
Alias="Denji" |
||||||
|
Alias="Pochita" |
||||||
|
[KFMod:GoldenChainsaw WeaponAliases] |
||||||
|
Alias="GoldChainsaw" |
||||||
|
Alias="GoldSaw" |
||||||
|
Alias="GoldDenji" |
||||||
|
Alias="GoldPochita" |
||||||
|
[KFMod:DwarfAxe WeaponAliases] |
||||||
|
Alias="DwarfsAxe" |
||||||
|
Alias="DwarfAxe" |
||||||
|
Alias="ShitAxe" |
||||||
|
Alias="CrapAxe" |
||||||
|
Alias="PushAxe" |
||||||
|
Alias="GnomeAxe" |
||||||
|
Alias="TrollAxe" |
||||||
|
Alias="NoobAxe" |
||||||
|
[KFMod:ClaymoreSword WeaponAliases] |
||||||
|
Alias="ClaymoreSword" |
||||||
|
Alias="ClaymoreBlade" |
||||||
|
Alias="Claymore" |
||||||
|
Alias="Claymor" |
||||||
|
Alias="ClaimoreSword" |
||||||
|
Alias="ClaimoreBlade" |
||||||
|
Alias="Claimore" |
||||||
|
Alias="Claimor" |
||||||
|
Alias="Sword" |
||||||
|
Alias="Blade" |
||||||
|
[KFMod:Crossbuzzsaw WeaponAliases] |
||||||
|
Alias="Crossbuzzsaw" |
||||||
|
Alias="Buzzsaw" |
||||||
|
Alias="Buzz" |
||||||
|
Alias="BuzzsawBow" |
||||||
|
Alias="BuzzBow" |
||||||
|
Alias="ZerkBow" |
||||||
|
|
||||||
|
; Firebug weapons |
||||||
|
[KFMod:MAC10MP WeaponAliases] |
||||||
|
Alias="MAC10MP" |
||||||
|
Alias="MAC10" |
||||||
|
Alias="MAC" |
||||||
|
[KFMod:FlareRevolver WeaponAliases] |
||||||
|
Alias="FlareRevolver" |
||||||
|
Alias="FireRevolver" |
||||||
|
Alias="FlareGun" |
||||||
|
Alias="Flares" |
||||||
|
Alias="Flare" |
||||||
|
[KFMod:DualFlareRevolver WeaponAliases] |
||||||
|
Alias="DualFlareRevolvers" |
||||||
|
Alias="DualFlareRevolver" |
||||||
|
Alias="DualFireRevolvers" |
||||||
|
Alias="DualFireRevolver" |
||||||
|
Alias="DualFlareGuns" |
||||||
|
Alias="DualFlareGun" |
||||||
|
Alias="DualFlares" |
||||||
|
Alias="DualFlare" |
||||||
|
[KFMod:FlameThrower WeaponAliases] |
||||||
|
Alias="FlameThrower" |
||||||
|
Alias="FireThrower" |
||||||
|
Alias="FThrower" |
||||||
|
Alias="Flamer" |
||||||
|
Alias="FireSpam" |
||||||
|
[KFMod:GoldenFlamethrower WeaponAliases] |
||||||
|
Alias="GoldFlameThrower" |
||||||
|
Alias="GoldFireThrower" |
||||||
|
Alias="GoldFThrower" |
||||||
|
Alias="GoldFlamer" |
||||||
|
Alias="GoldFireSpam" |
||||||
|
[KFMod:Trenchgun WeaponAliases] |
||||||
|
Alias="DragonsBreathTrenchgun" |
||||||
|
Alias="DragonsBreathGun" |
||||||
|
Alias="DragonsBreath" |
||||||
|
Alias="DragBreathTrenchgun" |
||||||
|
Alias="DragBreathGun" |
||||||
|
Alias="DragBreath" |
||||||
|
Alias="Trenchgun" |
||||||
|
Alias="FireShotgun" |
||||||
|
Alias="Flameshotgun" |
||||||
|
[KFMod:HuskGun WeaponAliases] |
||||||
|
Alias="HuskFireballLauncher" |
||||||
|
Alias="HuskFireball" |
||||||
|
Alias="FireballLauncher" |
||||||
|
Alias="HuskLauncher" |
||||||
|
Alias="HuskFirebalLauncher" |
||||||
|
Alias="HuskFirebal" |
||||||
|
Alias="FirebalLauncher" |
||||||
|
Alias="HuskGun" |
||||||
|
Alias="Husk" |
||||||
|
|
||||||
|
; Demolition weapons |
||||||
|
[KFMod:M79GrenadeLauncher WeaponAliases] |
||||||
|
Alias="M79GrenadeLauncher" |
||||||
|
Alias="M79Grenade" |
||||||
|
Alias="M79Launcher" |
||||||
|
Alias="M79NadeLauncher" |
||||||
|
Alias="M79Nade" |
||||||
|
Alias="M79" |
||||||
|
[KFMod:GoldenM79GrenadeLauncher WeaponAliases] |
||||||
|
Alias="GoldM79GrenadeLauncher" |
||||||
|
Alias="GoldM79Grenade" |
||||||
|
Alias="GoldM79Launcher" |
||||||
|
Alias="GoldM79NadeLauncher" |
||||||
|
Alias="GoldM79Nade" |
||||||
|
Alias="GoldM79" |
||||||
|
[KFMod:SPGrenadeLauncher WeaponAliases] |
||||||
|
Alias="SPGrenadeLauncher" |
||||||
|
Alias="SPNadeLauncher" |
||||||
|
Alias="SPLauncher" |
||||||
|
Alias="SPNade" |
||||||
|
Alias="TheOrcaBombPropeller" |
||||||
|
Alias="TheOrcaBombPropeler" |
||||||
|
Alias="TheOrcaBomb" |
||||||
|
Alias="TheOrca" |
||||||
|
Alias="TheOrcaLauncher" |
||||||
|
Alias="OrcaBombPropeller" |
||||||
|
Alias="OrcaBombPropeler" |
||||||
|
Alias="OrcaBomb" |
||||||
|
Alias="Orca" |
||||||
|
Alias="OrcaLauncher" |
||||||
|
[KFMod:PipeBombExplosive WeaponAliases] |
||||||
|
Alias="PipeBombExplosive" |
||||||
|
Alias="PipeExplosive" |
||||||
|
Alias="PipeBomb" |
||||||
|
Alias="Pipes" |
||||||
|
Alias="Pipe" |
||||||
|
[KFMod:SealSquealHarpoonBomber WeaponAliases] |
||||||
|
Alias="SealSquealHarpoonBomber" |
||||||
|
Alias="SealSquealHarpoon" |
||||||
|
Alias="SealSquealBomber" |
||||||
|
Alias="SealHarpoonBomber" |
||||||
|
Alias="SealHarpoon" |
||||||
|
Alias="SealBomber" |
||||||
|
Alias="SealSqueal" |
||||||
|
Alias="HarpoonBomber" |
||||||
|
Alias="Harpoon" |
||||||
|
Alias="Harp" |
||||||
|
[KFMod:SeekerSixRocketLauncher WeaponAliases] |
||||||
|
Alias="SeekerSixRocketLauncher" |
||||||
|
Alias="SeekerSixLauncher" |
||||||
|
Alias="Seeker6RocketLauncher" |
||||||
|
Alias="Seeker6Launcher" |
||||||
|
Alias="SeekerRocketLauncher" |
||||||
|
Alias="SeekerLauncher" |
||||||
|
Alias="SeekerSix" |
||||||
|
Alias="Seeker6" |
||||||
|
Alias="Seeker" |
||||||
|
Alias="SuckerSix" |
||||||
|
Alias="Sucker6" |
||||||
|
Alias="Sucker" |
||||||
|
[KFMod:M4203AssaultRifle WeaponAliases] |
||||||
|
Alias="M4203Assault" |
||||||
|
Alias="M4203Rifle" |
||||||
|
Alias="M4203" |
||||||
|
Alias="M4200" |
||||||
|
Alias="M420" |
||||||
|
Alias="M42" |
||||||
|
[KFMod:LAW WeaponAliases] |
||||||
|
Alias="LAW" |
||||||
|
[KFMod:M32GrenadeLauncher WeaponAliases] |
||||||
|
Alias="M32GrenadeLauncher" |
||||||
|
Alias="M32Grenade" |
||||||
|
Alias="M32Launcher" |
||||||
|
Alias="M32NadeLauncher" |
||||||
|
Alias="M32Nade" |
||||||
|
Alias="M32" |
||||||
|
[KFMod:CamoM32GrenadeLauncher WeaponAliases] |
||||||
|
Alias="CamoM32GrenadeLauncher" |
||||||
|
Alias="CamoM32Grenade" |
||||||
|
Alias="CamoM32Launcher" |
||||||
|
Alias="CamoM32NadeLauncher" |
||||||
|
Alias="CamoM32Nade" |
||||||
|
Alias="CamoM32" |
||||||
|
|
||||||
|
; Off-perk weapons |
||||||
|
[KFMod:ZEDGun WeaponAliases] |
||||||
|
Alias="ZedEradicationDevice" |
||||||
|
Alias="ZedEradication" |
||||||
|
Alias="ZedDevice" |
||||||
|
Alias="ZEDGun" |
||||||
|
Alias="ZED" |
||||||
|
[KFMod:ZEDMKIIWeapon WeaponAliases] |
||||||
|
Alias="ZedEradicationDeviceMKII" |
||||||
|
Alias="ZedEradicationMKII" |
||||||
|
Alias="ZedDeviceMKII" |
||||||
|
Alias="ZEDGunMKII" |
||||||
|
Alias="ZEDMKII" |
||||||
|
Alias="ZedEradicationDeviceMK2" |
||||||
|
Alias="ZedEradicationMK2" |
||||||
|
Alias="ZedDeviceMK2" |
||||||
|
Alias="ZEDGunMK2" |
||||||
|
Alias="ZEDMK2" |
||||||
|
Alias="ZedEradicationDeviceMK" |
||||||
|
Alias="ZedEradicationMK" |
||||||
|
Alias="ZedDeviceMK" |
||||||
|
Alias="ZEDGunMK" |
||||||
|
Alias="ZEDMK" |
||||||
|
Alias="ZedEradicationDevice2" |
||||||
|
Alias="ZedEradication2" |
||||||
|
Alias="ZedDevice2" |
||||||
|
Alias="ZEDGun2" |
||||||
|
Alias="ZED2" |
@ -0,0 +1,255 @@ |
|||||||
|
[AcediaFixes.FixDualiesCost] |
||||||
|
; 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. |
||||||
|
; |
||||||
|
; 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. |
||||||
|
autoEnable=true |
||||||
|
; Some issues involve possible decrease in pistols' price and |
||||||
|
; don't lead to the 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 players won't even |
||||||
|
; notice these issues. |
||||||
|
; 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. |
||||||
|
allowSellValueIncrease=true |
||||||
|
|
||||||
|
|
||||||
|
[AcediaFixes.FixAmmoSelling] |
||||||
|
; This feature addressed an oversight in vanilla code that |
||||||
|
; allows clients to sell weapon's ammunition. |
||||||
|
; Due to the implementation of ammo selling, this allows cheaters to |
||||||
|
; "print money" by buying and selling ammo over and over again. |
||||||
|
autoEnable=true |
||||||
|
; 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. |
||||||
|
allowNegativeDosh=false |
||||||
|
|
||||||
|
|
||||||
|
[AcediaFixes.FixInventoryAbuse] |
||||||
|
; This feature addressed two issues with the inventory: |
||||||
|
; 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 type of gun, so you shouldn't be able to carry |
||||||
|
; both MK23 and dual MK23 or dual handcannons and golden handcannon. |
||||||
|
; But cheaters do. But not with this fix. |
||||||
|
autoEnable=true |
||||||
|
; How often (in seconds) should we do inventory validation checks? |
||||||
|
; You shouldn't really worry about performance, but there's also no need to |
||||||
|
; do this check too often. |
||||||
|
checkInterval=0.25 |
||||||
|
; 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. |
||||||
|
dualiesClasses=(single=class'KFMod.SinglePickup',dual=class'KFMod.DualiesPickup') |
||||||
|
dualiesClasses=(single=class'KFMod.Magnum44Pickup',dual=class'KFMod.Dual44MagnumPickup') |
||||||
|
dualiesClasses=(single=class'KFMod.MK23Pickup',dual=class'KFMod.DualMK23Pickup') |
||||||
|
dualiesClasses=(single=class'KFMod.DeaglePickup',dual=class'KFMod.DualDeaglePickup') |
||||||
|
dualiesClasses=(single=class'KFMod.GoldenDeaglePickup',dual=class'KFMod.GoldenDualDeaglePickup') |
||||||
|
dualiesClasses=(single=class'KFMod.FlareRevolverPickup',dual=class'KFMod.DualFlareRevolverPickup') |
||||||
|
|
||||||
|
|
||||||
|
[AcediaFixes.FixInfiniteNades] |
||||||
|
; 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. |
||||||
|
autoEnable=true |
||||||
|
; 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'. |
||||||
|
ignoreTossFlags=false |
||||||
|
|
||||||
|
|
||||||
|
[AcediaFixes.FixDoshSpam] |
||||||
|
; 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. |
||||||
|
autoEnable=true |
||||||
|
; 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. |
||||||
|
doshPerSecondLimitMax=50 |
||||||
|
doshPerSecondLimitMin=5 |
||||||
|
; 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. |
||||||
|
criticalDoshAmount=25 |
||||||
|
|
||||||
|
|
||||||
|
[AcediaFixes.FixSpectatorCrash] |
||||||
|
; This feature attempts to prevent server crashes caused by someone |
||||||
|
; quickly switching between being spectator and an active player. |
||||||
|
autoEnable=true |
||||||
|
; 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, but might cause regular players to |
||||||
|
; get accidentally kicked. |
||||||
|
spectatorChangeTimeout=0.25 |
||||||
|
; [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'. |
||||||
|
; This option is necessary to block aggressive enough server crash |
||||||
|
; attempts, but can cause compatibility issues with some mutators. |
||||||
|
; It's highly recommended to rewrite such a mutator to be compatible instead. |
||||||
|
; NOTE: fix should be compatible with most faked players-type mutators, |
||||||
|
; since this it 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). |
||||||
|
allowServerBlock=true |
||||||
|
|
||||||
|
|
||||||
|
[AcediaFixes.FixFFHack] |
||||||
|
; 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 oder 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. |
||||||
|
autoEnable=true |
||||||
|
; Defines a general rule for chosing 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'. |
||||||
|
scaleByDefault=false |
||||||
|
; Damage types, for which we should always reaaply friendly fire scaling. |
||||||
|
alwaysScale=Class'KFMod.DamTypeCrossbuzzsawHeadShot' |
||||||
|
alwaysScale=Class'KFMod.DamTypeCrossbuzzsaw' |
||||||
|
alwaysScale=Class'KFMod.DamTypeFrag' |
||||||
|
alwaysScale=Class'KFMod.DamTypePipeBomb' |
||||||
|
alwaysScale=Class'KFMod.DamTypeM203Grenade' |
||||||
|
alwaysScale=Class'KFMod.DamTypeM79Grenade' |
||||||
|
alwaysScale=Class'KFMod.DamTypeM79GrenadeImpact' |
||||||
|
alwaysScale=Class'KFMod.DamTypeM32Grenade' |
||||||
|
alwaysScale=Class'KFMod.DamTypeLAW' |
||||||
|
alwaysScale=Class'KFMod.DamTypeLawRocketImpact' |
||||||
|
alwaysScale=Class'KFMod.DamTypeFlameNade' |
||||||
|
alwaysScale=Class'KFMod.DamTypeFlareRevolver' |
||||||
|
alwaysScale=Class'KFMod.DamTypeFlareProjectileImpact' |
||||||
|
alwaysScale=Class'KFMod.DamTypeBurned' |
||||||
|
alwaysScale=Class'KFMod.DamTypeTrenchgun' |
||||||
|
alwaysScale=Class'KFMod.DamTypeHuskGun' |
||||||
|
alwaysScale=Class'KFMod.DamTypeCrossbow' |
||||||
|
alwaysScale=Class'KFMod.DamTypeCrossbowHeadShot' |
||||||
|
alwaysScale=Class'KFMod.DamTypeM99SniperRifle' |
||||||
|
alwaysScale=Class'KFMod.DamTypeM99HeadShot' |
||||||
|
alwaysScale=Class'KFMod.DamTypeShotgun' |
||||||
|
alwaysScale=Class'KFMod.DamTypeNailGun' |
||||||
|
alwaysScale=Class'KFMod.DamTypeDBShotgun' |
||||||
|
alwaysScale=Class'KFMod.DamTypeKSGShotgun' |
||||||
|
alwaysScale=Class'KFMod.DamTypeBenelli' |
||||||
|
alwaysScale=Class'KFMod.DamTypeSPGrenade' |
||||||
|
alwaysScale=Class'KFMod.DamTypeSPGrenadeImpact' |
||||||
|
alwaysScale=Class'KFMod.DamTypeSeekerSixRocket' |
||||||
|
alwaysScale=Class'KFMod.DamTypeSeekerRocketImpact' |
||||||
|
alwaysScale=Class'KFMod.DamTypeSealSquealExplosion' |
||||||
|
alwaysScale=Class'KFMod.DamTypeRocketImpact' |
||||||
|
alwaysScale=Class'KFMod.DamTypeBlowerThrower' |
||||||
|
alwaysScale=Class'KFMod.DamTypeSPShotgun' |
||||||
|
alwaysScale=Class'KFMod.DamTypeZEDGun' |
||||||
|
alwaysScale=Class'KFMod.DamTypeZEDGunMKII' |
||||||
|
alwaysScale=Class'KFMod.DamTypeZEDGunMKII' |
||||||
|
; Damage types, for which we should never reaply friendly fire scaling. |
||||||
|
;neverScale=Class'KFMod.???' |
||||||
|
|
||||||
|
|
||||||
|
[AcediaFixes.FixZedTimeLags] |
||||||
|
; 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 open maps and increased zed limit, |
||||||
|
; it can lead to noticable lags at the end of the zed time. |
||||||
|
; This fix limits amount of actual game speed updates, alleviating the issue. |
||||||
|
; |
||||||
|
; 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'. |
||||||
|
autoEnable=true |
||||||
|
; Maximum amount of game speed updates upon leaving zed time. |
||||||
|
; 2 or 3 seem to provide a good enough result that, |
||||||
|
; i.e. it should be hard to notice difference with vanilla game behavior. |
||||||
|
; 1 is a smallest possible value, resulting in effectively removing any |
||||||
|
; smooting via speed up, simply changing speed from |
||||||
|
; the slowest (0.2) to the highest. |
||||||
|
; For the reference: on servers with default 30 tick rate there's usually |
||||||
|
; about 13 updates total (without this fix). |
||||||
|
maxGameSpeedUpdatesAmount=3 |
||||||
|
; [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. |
||||||
|
disableTick=true |
@ -0,0 +1,180 @@ |
|||||||
|
; Every single option in this config should be considered [ADVANCED] |
||||||
|
[AcediaCore_0_2.AliasService] |
||||||
|
; Changing these allows you to change in what sources `AliasesAPI` |
||||||
|
; looks for weapon and color aliases. |
||||||
|
weaponAliasesSource=Class'WeaponAliasSource' |
||||||
|
colorAliasesSource=Class'ColorAliasSource' |
||||||
|
; How often are different alias-storing objects are allowed to record |
||||||
|
; their updated data into a config. |
||||||
|
; Negative or zero values would be reset to `0.05`. |
||||||
|
saveInterval=0.05 |
||||||
|
|
||||||
|
[AcediaCore_0_2.AliasHash] |
||||||
|
; Reasonable lower and upper limits on hash table capacity for |
||||||
|
; aliases' storage, that will be enforced if user requires something outside |
||||||
|
; those bounds. |
||||||
|
MINIMUM_CAPACITY=10 |
||||||
|
MAXIMUM_CAPACITY=100000 |
||||||
|
|
||||||
|
[AcediaCore_0_2.TestingService] |
||||||
|
; Allows you to run tests on server's start up. This option is to help run |
||||||
|
; tests quicker during development and should not be used for servers that are |
||||||
|
; setup for actually playing the game. |
||||||
|
runTestsOnStartUp=false |
||||||
|
; Use these flags to only run tests from particular test cases |
||||||
|
filterTestsByName=false |
||||||
|
filterTestsByGroup=false |
||||||
|
requiredName="" |
||||||
|
requiredGroup="" |
||||||
|
|
||||||
|
[AcediaCore_0_2.ConsoleAPI] |
||||||
|
; These should guarantee decent text output in console even at |
||||||
|
; 640x480 shit resolution |
||||||
|
; (and it look fine at normal resolutions as well) |
||||||
|
maxVisibleLineWidth=80 |
||||||
|
maxTotalLineWidth=108 |
||||||
|
|
||||||
|
[AcediaCore_0_2.ColorAPI] |
||||||
|
; Changing these values will alter color's definitions in `ColorAPI`, |
||||||
|
; changing how Acedia behaves |
||||||
|
Pink=(R=255,G=192,B=203,A=255) |
||||||
|
LightPink=(R=255,G=182,B=193,A=255) |
||||||
|
HotPink=(R=255,G=105,B=180,A=255) |
||||||
|
DeepPink=(R=255,G=20,B=147,A=255) |
||||||
|
PaleVioletRed=(R=219,G=112,B=147,A=255) |
||||||
|
MediumVioletRed=(R=199,G=21,B=133,A=255) |
||||||
|
LightSalmon=(R=255,G=160,B=122,A=255) |
||||||
|
Salmon=(R=250,G=128,B=114,A=255) |
||||||
|
DarkSalmon=(R=233,G=150,B=122,A=255) |
||||||
|
LightCoral=(R=240,G=128,B=128,A=255) |
||||||
|
IndianRed=(R=205,G=92,B=92,A=255) |
||||||
|
Crimson=(R=220,G=20,B=60,A=255) |
||||||
|
Firebrick=(R=178,G=34,B=34,A=255) |
||||||
|
DarkRed=(R=139,G=0,B=0,A=255) |
||||||
|
Red=(R=255,G=0,B=0,A=255) |
||||||
|
OrangeRed=(R=255,G=69,B=0,A=255) |
||||||
|
Tomato=(R=255,G=99,B=71,A=255) |
||||||
|
Coral=(R=255,G=127,B=80,A=255) |
||||||
|
DarkOrange=(R=255,G=140,B=0,A=255) |
||||||
|
Orange=(R=255,G=165,B=0,A=255) |
||||||
|
Yellow=(R=255,G=255,B=0,A=255) |
||||||
|
LightYellow=(R=255,G=255,B=224,A=255) |
||||||
|
LemonChiffon=(R=255,G=250,B=205,A=255) |
||||||
|
LightGoldenrodYellow=(R=250,G=250,B=210,A=255) |
||||||
|
PapayaWhip=(R=255,G=239,B=213,A=255) |
||||||
|
Moccasin=(R=255,G=228,B=181,A=255) |
||||||
|
PeachPuff=(R=255,G=218,B=185,A=255) |
||||||
|
PaleGoldenrod=(R=238,G=232,B=170,A=255) |
||||||
|
Khaki=(R=240,G=230,B=140,A=255) |
||||||
|
DarkKhaki=(R=189,G=183,B=107,A=255) |
||||||
|
Gold=(R=255,G=215,B=0,A=255) |
||||||
|
Cornsilk=(R=255,G=248,B=220,A=255) |
||||||
|
BlanchedAlmond=(R=255,G=235,B=205,A=255) |
||||||
|
Bisque=(R=255,G=228,B=196,A=255) |
||||||
|
NavajoWhite=(R=255,G=222,B=173,A=255) |
||||||
|
Wheat=(R=245,G=222,B=179,A=255) |
||||||
|
Burlywood=(R=222,G=184,B=135,A=255) |
||||||
|
TanColor=(R=210,G=180,B=140,A=255) |
||||||
|
RosyBrown=(R=188,G=143,B=143,A=255) |
||||||
|
SandyBrown=(R=244,G=164,B=96,A=255) |
||||||
|
Goldenrod=(R=218,G=165,B=32,A=255) |
||||||
|
DarkGoldenrod=(R=184,G=134,B=11,A=255) |
||||||
|
Peru=(R=205,G=133,B=63,A=255) |
||||||
|
Chocolate=(R=210,G=105,B=30,A=255) |
||||||
|
SaddleBrown=(R=139,G=69,B=19,A=255) |
||||||
|
Sienna=(R=160,G=82,B=45,A=255) |
||||||
|
Brown=(R=165,G=42,B=42,A=255) |
||||||
|
Maroon=(R=128,G=0,B=0,A=255) |
||||||
|
DarkOliveGreen=(R=85,G=107,B=47,A=255) |
||||||
|
Olive=(R=128,G=128,B=0,A=255) |
||||||
|
OliveDrab=(R=107,G=142,B=35,A=255) |
||||||
|
YellowGreen=(R=154,G=205,B=50,A=255) |
||||||
|
LimeGreen=(R=50,G=205,B=50,A=255) |
||||||
|
Lime=(R=0,G=255,B=0,A=255) |
||||||
|
LawnGreen=(R=124,G=252,B=0,A=255) |
||||||
|
Chartreuse=(R=127,G=255,B=0,A=255) |
||||||
|
GreenYellow=(R=173,G=255,B=47,A=255) |
||||||
|
SpringGreen=(R=0,G=255,B=127,A=255) |
||||||
|
MediumSpringGreen=(R=0,G=250,B=154,A=255) |
||||||
|
LightGreen=(R=144,G=238,B=144,A=255) |
||||||
|
PaleGreen=(R=152,G=251,B=152,A=255) |
||||||
|
DarkSeaGreen=(R=143,G=188,B=143,A=255) |
||||||
|
MediumAquamarine=(R=102,G=205,B=170,A=255) |
||||||
|
MediumSeaGreen=(R=60,G=179,B=113,A=255) |
||||||
|
SeaGreen=(R=46,G=139,B=87,A=255) |
||||||
|
ForestGreen=(R=34,G=139,B=34,A=255) |
||||||
|
Green=(R=0,G=128,B=0,A=255) |
||||||
|
DarkGreen=(R=0,G=100,B=0,A=255) |
||||||
|
Aqua=(R=0,G=255,B=255,A=255) |
||||||
|
Cyan=(R=0,G=255,B=255,A=255) |
||||||
|
LightCyan=(R=224,G=255,B=255,A=255) |
||||||
|
PaleTurquoise=(R=175,G=238,B=238,A=255) |
||||||
|
Aquamarine=(R=127,G=255,B=212,A=255) |
||||||
|
Turquoise=(R=64,G=224,B=208,A=255) |
||||||
|
MediumTurquoise=(R=72,G=209,B=204,A=255) |
||||||
|
DarkTurquoise=(R=0,G=206,B=209,A=255) |
||||||
|
LightSeaGreen=(R=32,G=178,B=170,A=255) |
||||||
|
CadetBlue=(R=95,G=158,B=160,A=255) |
||||||
|
DarkCyan=(R=0,G=139,B=139,A=255) |
||||||
|
Teal=(R=0,G=128,B=128,A=255) |
||||||
|
LightSteelBlue=(R=176,G=196,B=222,A=255) |
||||||
|
PowderBlue=(R=176,G=224,B=230,A=255) |
||||||
|
LightBlue=(R=173,G=216,B=230,A=255) |
||||||
|
SkyBlue=(R=135,G=206,B=235,A=255) |
||||||
|
LightSkyBlue=(R=135,G=206,B=250,A=255) |
||||||
|
DeepSkyBlue=(R=0,G=191,B=255,A=255) |
||||||
|
DodgerBlue=(R=30,G=144,B=255,A=255) |
||||||
|
CornflowerBlue=(R=100,G=149,B=237,A=255) |
||||||
|
SteelBlue=(R=70,G=130,B=180,A=255) |
||||||
|
RoyalBlue=(R=65,G=105,B=225,A=255) |
||||||
|
Blue=(R=0,G=0,B=255,A=255) |
||||||
|
MediumBlue=(R=0,G=0,B=205,A=255) |
||||||
|
DarkBlue=(R=0,G=0,B=139,A=255) |
||||||
|
Navy=(R=0,G=0,B=128,A=255) |
||||||
|
MidnightBlue=(R=25,G=25,B=112,A=255) |
||||||
|
Lavender=(R=230,G=230,B=250,A=255) |
||||||
|
Thistle=(R=216,G=191,B=216,A=255) |
||||||
|
Plum=(R=221,G=160,B=221,A=255) |
||||||
|
Violet=(R=238,G=130,B=238,A=255) |
||||||
|
Orchid=(R=218,G=112,B=214,A=255) |
||||||
|
Fuchsia=(R=255,G=0,B=255,A=255) |
||||||
|
Magenta=(R=255,G=0,B=255,A=255) |
||||||
|
MediumOrchid=(R=186,G=85,B=211,A=255) |
||||||
|
MediumPurple=(R=147,G=112,B=219,A=255) |
||||||
|
BlueViolet=(R=138,G=43,B=226,A=255) |
||||||
|
DarkViolet=(R=148,G=0,B=211,A=255) |
||||||
|
DarkOrchid=(R=153,G=50,B=204,A=255) |
||||||
|
DarkMagenta=(R=139,G=0,B=139,A=255) |
||||||
|
Purple=(R=128,G=0,B=128,A=255) |
||||||
|
Indigo=(R=75,G=0,B=130,A=255) |
||||||
|
DarkSlateBlue=(R=72,G=61,B=139,A=255) |
||||||
|
SlateBlue=(R=106,G=90,B=205,A=255) |
||||||
|
MediumSlateBlue=(R=123,G=104,B=238,A=255) |
||||||
|
White=(R=255,G=255,B=255,A=255) |
||||||
|
Snow=(R=255,G=250,B=250,A=255) |
||||||
|
Honeydew=(R=240,G=255,B=240,A=255) |
||||||
|
MintCream=(R=245,G=255,B=250,A=255) |
||||||
|
Azure=(R=240,G=255,B=255,A=255) |
||||||
|
AliceBlue=(R=240,G=248,B=255,A=255) |
||||||
|
GhostWhite=(R=248,G=248,B=255,A=255) |
||||||
|
WhiteSmoke=(R=245,G=245,B=245,A=255) |
||||||
|
Seashell=(R=255,G=245,B=238,A=255) |
||||||
|
Beige=(R=245,G=245,B=220,A=255) |
||||||
|
OldLace=(R=253,G=245,B=230,A=255) |
||||||
|
FloralWhite=(R=255,G=250,B=240,A=255) |
||||||
|
Ivory=(R=255,G=255,B=240,A=255) |
||||||
|
AntiqueWhite=(R=250,G=235,B=215,A=255) |
||||||
|
Linen=(R=250,G=240,B=230,A=255) |
||||||
|
LavenderBlush=(R=255,G=240,B=245,A=255) |
||||||
|
MistyRose=(R=255,G=228,B=225,A=255) |
||||||
|
Gainsboro=(R=220,G=220,B=220,A=255) |
||||||
|
LightGray=(R=211,G=211,B=211,A=255) |
||||||
|
Silver=(R=192,G=192,B=192,A=255) |
||||||
|
DarkGray=(R=169,G=169,B=169,A=255) |
||||||
|
Gray=(R=128,G=128,B=128,A=255) |
||||||
|
DimGray=(R=105,G=105,B=105,A=255) |
||||||
|
LightSlateGray=(R=119,G=136,B=153,A=255) |
||||||
|
SlateGray=(R=112,G=128,B=144,A=255) |
||||||
|
DarkSlateGray=(R=47,G=79,B=79,A=255) |
||||||
|
Eigengrau=(R=22,G=22,B=29,A=255) |
||||||
|
Black=(R=0,G=0,B=0,A=255) |
@ -0,0 +1,138 @@ |
|||||||
|
# Aliases |
||||||
|
|
||||||
|
Aliases are `string` values that act as human-readable synonyms to some other `string` values. |
||||||
|
|
||||||
|
Often, when using some console commands, users are forced to type into exact class names of objects in **UnrealScript** (e.g., commands to give someone an M14EBR take form similar to `mutate give KFmod.M14EBRBattleRifle`), but such names can be cumbersome to remember and type. |
||||||
|
|
||||||
|
Aliases solve this problem by allowing players to instead type `mutate give $ebr`, where `$` denotes that following word `ebr` is an alias that will be automatically resolved into `KFmod.M14EBRBattleRifle`. |
||||||
|
|
||||||
|
## Alias names |
||||||
|
|
||||||
|
Alias can be any `string` consisting of ASCII character, although for practical reasons it is better to use only letters, digits and `_` character. Otherwise using them might become more difficult, partially defeating their purpose. |
||||||
|
|
||||||
|
Aliases are case-insensitive, so `EBR`, `Ebr` and `ebr` are all considered the same alias. |
||||||
|
|
||||||
|
## Alias sources |
||||||
|
|
||||||
|
Sources essentially act as aliases databases: matching each alias to some value. They can be used to separate aliases that describe different categories of objects: weapons, zeds, colors, etc.. |
||||||
|
|
||||||
|
Inside each source aliases and their values are expected to be in many-to-one relationship: many aliases can mean the same value, but each alias can only mean one value. However, two different sources can each contain the same alias and make it point to different values. So it's important for the game to know what source contains what type of aliases. |
||||||
|
|
||||||
|
In case there are several aliases with the same name in the database, - **Acedia** will warn you about it, but won't actually remove duplicates, instead letting the source use the first it finds. |
||||||
|
|
||||||
|
By default **Acedia** offers 4 different alias sources: |
||||||
|
|
||||||
|
* `WeaponAliasSource` (*AcediaAliases_Weapons.ini*) - source filled with aliases for weapons (by default contains aliases to every vanilla weapon); |
||||||
|
* `ColorAliasSource` (*AcediaAliases_Colors.ini*) - source filled with aliases for colors (by default contains a decent amount of pre-defined colors); |
||||||
|
* `AliasSource` (*AcediaAliases.ini*) - unused source that can, nevertheless, be utilized by server admins or other packages (by default empty); |
||||||
|
* `MockAliasSource` (*AcediaAliases_Tests.ini*) - source that is used for testing whether aliases functionality works correctly, avoid changing it if you intend to run tests for **Acedia**'s functionality. |
||||||
|
|
||||||
|
### [Advanced] Changing meaning of alias sources |
||||||
|
|
||||||
|
Even though some of the above sources have rather specific names, only use of `MockAliasSource` is hardcoded: admins can, in theory, move all aliases into any source they like. They'll just have to tell **Acedia** where to look for them by changing *AcediaSystem.ini*'s section *Acedia.AliasService* to point at appropriate source: |
||||||
|
|
||||||
|
```ini |
||||||
|
weaponAliasesSource=Class'Acedia.WeaponAliasSource' |
||||||
|
colorAliasesSource=Class'Acedia.ColorAliasSource' |
||||||
|
``` |
||||||
|
|
||||||
|
Specifically, you can move all aliases to a single source (for example `AliasSource`) and tell **Acedia** to look for weapon and color aliases there: |
||||||
|
|
||||||
|
```ini |
||||||
|
weaponAliasesSource=Class'Acedia.AliasSource' |
||||||
|
colorAliasesSource=Class'Acedia.AliasSource' |
||||||
|
``` |
||||||
|
|
||||||
|
## How sources are stored |
||||||
|
|
||||||
|
Alias sources are stored in appropriate *ini*-files in two ways that can be mixed with each other however you like. |
||||||
|
|
||||||
|
### 1. Flat array `record` |
||||||
|
|
||||||
|
First way is to define a set alias-value pairs in section of the alias source. Example from the color alias source: |
||||||
|
|
||||||
|
```ini |
||||||
|
[Acedia.ColorAliasSource] |
||||||
|
; Pink colors |
||||||
|
record=(alias="Pink",value="rgb(255,192,203)") |
||||||
|
record=(alias="LightPink",value="rgb(255,182,193)") |
||||||
|
record=(alias="HotPink",value="rgb(255,105,180)") |
||||||
|
record=(alias="DeepPink",value="rgb(255,20,147)") |
||||||
|
record=(alias="PaleVioletRed",value="rgb(219,112,147)") |
||||||
|
record=(alias="MediumVioletRed",value="rgb(199,21,133)") |
||||||
|
``` |
||||||
|
|
||||||
|
If you want several different aliases to point to the same value, just add a record for each of them: |
||||||
|
|
||||||
|
```ini |
||||||
|
record=(alias="Pink",value="rgb(255,192,203)") |
||||||
|
record=(alias="Punk",value="rgb(255,192,203)") |
||||||
|
record=(alias="Bunk",value="rgb(255,192,203)") |
||||||
|
``` |
||||||
|
|
||||||
|
Just avoid having several records for the same alias in one source. |
||||||
|
|
||||||
|
### 2. Per-object-config |
||||||
|
|
||||||
|
If you need to define several aliases for one value it might be better to use per-object-configuration with named objects: each of them stores an array of aliases, while the corresponding value is recorded as object's name. Example from weapons alias source: |
||||||
|
|
||||||
|
```ini |
||||||
|
[KFMod:MP5MMedicGun WeaponAliases] |
||||||
|
Alias="MP5M" |
||||||
|
Alias="MP5" |
||||||
|
Alias="MP" |
||||||
|
Alias="M5" |
||||||
|
``` |
||||||
|
|
||||||
|
Here aliases are defined in every line that starts with `Alias=`. Their value `KFMod:MP5MMedicGun` is defined as a first part of the config section (`:` is going to be translated to `.`, more on that below) and the second part `WeaponAliases` indicates that this is a record for `WeaponAliasSource`. |
||||||
|
|
||||||
|
Each source has it's own identification for per-object-config records: |
||||||
|
|
||||||
|
* For `WeaponAliasSource` it is `WeaponAliases`; |
||||||
|
* For `ColorAliasSource` it is `ColorAliases`; |
||||||
|
* For `MockAliasSource` it is `MockAliases`; |
||||||
|
* For `AliasSource` it is just `Aliases`. |
||||||
|
|
||||||
|
#### Limitations of the second way |
||||||
|
|
||||||
|
Because alias' value must be a part of the *ini*-file section there are certain limitations imposed on what that value can be (for example having `.` or `]` inside value's name will confuse **Unreal Engine**'s config parser, so you can't use them). There is not official, complete list of forbidden characters, but it is suggested you keep them limited to sequence of letters, numbers and `_` character. |
||||||
|
|
||||||
|
If you do need to store some weird string as a value, - first test that it does load correctly and, if not, use the first way to define it's aliases. |
||||||
|
|
||||||
|
But `.` being a forbidden symbol is too harsh of a limitation, since we mainly want to store class names via per-object-configs. Because of that any alias values defined the second way will load `:` as `.` from a config. This change allows us to define classes as values at the cost of preventing the use of `:`. |
||||||
|
|
||||||
|
## [Technical] Defining new alias sources |
||||||
|
|
||||||
|
If you make a module using **Acedia** and want to add another alias source you simply need to decide on the names of your: |
||||||
|
|
||||||
|
* Alias source (suppose it's `NewSource`); |
||||||
|
* Helper class for second way (*per-object-config*) of defining aliases (suppose it's `NewAliases`) |
||||||
|
* Config file, where their data will be stored (suppose it's `MyNewAliases.ini`); |
||||||
|
|
||||||
|
then create two classes, like that: |
||||||
|
|
||||||
|
```java |
||||||
|
class NewSource extends AliasSource |
||||||
|
config(MyNewAliases); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
configName = "MyNewAliases" |
||||||
|
aliasesClass = class'NewAliases' |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
```java |
||||||
|
class NewAliases extends Aliases |
||||||
|
perObjectConfig |
||||||
|
config(MyNewAliases); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
sourceClass = class'NewSource' |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
and put them in your manifest. |
||||||
|
|
||||||
|
For more examples check out source code for `ColorAliasSource`, `WeaponAliasSource`, `MockAliasSource`. |
@ -0,0 +1,16 @@ |
|||||||
|
# Colors |
||||||
|
|
||||||
|
The main, and possibly only, notable thing abotu **Acedia**'s colors is it's support for parsing their text representation. To be precise, **Acedia** understands: |
||||||
|
|
||||||
|
1. Hex color definitions in format of `#ffc0cb`; |
||||||
|
2. RGB color definitions that look like either `rgb(255,192,203)` or `rgb(r=255,g=192,b=203)`; |
||||||
|
3. RGBA color definitions that look like either `rgb(255,192,203,13)` or `rgb(r=255,g=192,b=203,a=13)`; |
||||||
|
4. Alias color definitions that **Acedia** looks up from color-specific alias source and look like any other alias reference: `$pink`. |
||||||
|
|
||||||
|
You should be able to use any form you like while working with **Acedia**. |
||||||
|
|
||||||
|
## [Technical] Color fixing |
||||||
|
|
||||||
|
Killing floor's standard methods of rendering colored `string`s make use of inserting 4-byte sequence into them: first bytes denotes the start of the sequence, 3 following bytes denote rgb color components. Unfortunately these methods also have issues with rendering `string`s if you specify certain values (`0` and `10`) as red-green-blue color components. |
||||||
|
|
||||||
|
You can freely use colors with these components, since **Acedia** automatically should fix them for you (by replacing them with indistinguishably close, but valid color) whenever it matters. |
@ -0,0 +1,50 @@ |
|||||||
|
/** |
||||||
|
* Actor base class to be used to Acedia instead of an `Actor`. |
||||||
|
* The only difference is defined `_` member that provides convenient access to |
||||||
|
* Acedia's API. |
||||||
|
* It isn't guaranteed that `default._` will be defined for `AcediaActor`s. |
||||||
|
* 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 AcediaActor extends Actor |
||||||
|
abstract; |
||||||
|
|
||||||
|
var protected Global _; |
||||||
|
|
||||||
|
public final function Text T(string string) |
||||||
|
{ |
||||||
|
return _.text.FromString(string); |
||||||
|
} |
||||||
|
|
||||||
|
public static final function Global __() |
||||||
|
{ |
||||||
|
return Global(class'Global'.static.GetInstance()); |
||||||
|
} |
||||||
|
|
||||||
|
event PreBeginPlay() |
||||||
|
{ |
||||||
|
super.PreBeginPlay(); |
||||||
|
if (_ == none) |
||||||
|
{ |
||||||
|
_ = Global(class'Global'.static.GetInstance()); |
||||||
|
default._ = _; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
/** |
||||||
|
* Object base class to be used to Acedia instead of an `Object`. |
||||||
|
* The only difference is defined `_` member that provides convenient access to |
||||||
|
* Acedia's API. |
||||||
|
* Since `Global` is an actor, we wish to avoid storing it's instance in |
||||||
|
* the object because it can mess with garbage collection on level change. |
||||||
|
* So we provide an accessor function `_()` instead. |
||||||
|
* 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 AcediaObject extends Object |
||||||
|
abstract; |
||||||
|
|
||||||
|
public final function Text T(string string) |
||||||
|
{ |
||||||
|
return _().text.FromString(string); |
||||||
|
} |
||||||
|
|
||||||
|
public static final function Global _() |
||||||
|
{ |
||||||
|
return Global(class'Global'.static.GetInstance()); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
/** |
||||||
|
* Facilitates some core replicated functions between client and server. |
||||||
|
* 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 AcediaReplicationInfo extends ReplicationInfo; |
||||||
|
|
||||||
|
var public PlayerController linkOwner; |
||||||
|
|
||||||
|
replication |
||||||
|
{ |
||||||
|
reliable if (role == ROLE_Authority) |
||||||
|
linkOwner; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,218 @@ |
|||||||
|
/** |
||||||
|
* A class, implementing a hash-table-based dictionary for quick access to |
||||||
|
* aliases' values. |
||||||
|
* It does not support dynamic hash table capacity change and |
||||||
|
* requires to set the size upfront. |
||||||
|
* 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 AliasHash extends AcediaObject |
||||||
|
dependson(AliasSource) |
||||||
|
config(AcediaSystem); |
||||||
|
|
||||||
|
// Reasonable lower and upper limits on hash table capacity, |
||||||
|
// that will be enforced if user requires something outside those bounds |
||||||
|
var private config const int MINIMUM_CAPACITY; |
||||||
|
var private config const int MAXIMUM_CAPACITY; |
||||||
|
|
||||||
|
// Bucket of alias-value pairs, with the same alias hash. |
||||||
|
struct PairBucket |
||||||
|
{ |
||||||
|
var array<AliasSource.AliasValuePair> pairs; |
||||||
|
}; |
||||||
|
var private array<PairBucket> hashTable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes caller `AliasHash`. |
||||||
|
* |
||||||
|
* Calling this function again will clear all existing data and will create |
||||||
|
* a brand new hash table. |
||||||
|
* |
||||||
|
* @param desiredCapacity Desired capacity of the underlying hash table. |
||||||
|
* Will be clamped between `MINIMUM_CAPACITY` and `MAXIMUM_CAPACITY`. |
||||||
|
* Not specifying anything as this parameter creates a hash table of |
||||||
|
* size `MINIMUM_CAPACITY`. |
||||||
|
* @return A reference to a caller object to allow for function chaining. |
||||||
|
*/ |
||||||
|
public final function AliasHash Initialize(optional int desiredCapacity) |
||||||
|
{ |
||||||
|
desiredCapacity = Clamp(desiredCapacity, MINIMUM_CAPACITY, |
||||||
|
MAXIMUM_CAPACITY); |
||||||
|
hashTable.length = 0; |
||||||
|
hashTable.length = desiredCapacity; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
// Helper method that is needed as a replacement for `%`, since it is |
||||||
|
// an operation on `float`s in UnrealScript and does not have enough precision |
||||||
|
// to work with hashes. |
||||||
|
// Assumes positive input. |
||||||
|
private function int Remainder(int number, int divisor) |
||||||
|
{ |
||||||
|
local int quotient; |
||||||
|
quotient = number / divisor; |
||||||
|
return (number - quotient * divisor); |
||||||
|
} |
||||||
|
|
||||||
|
// Finds indices for: |
||||||
|
// 1. Bucked that contains specified alias (`bucketIndex`); |
||||||
|
// 2. Pair for specified alias in the bucket's collection (`pairIndex`). |
||||||
|
// `bucketIndex` is always found, |
||||||
|
// `pairIndex` is valid iff method returns `true`. |
||||||
|
private final function bool FindPairIndices( |
||||||
|
string alias, |
||||||
|
out int bucketIndex, |
||||||
|
out int pairIndex) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<AliasSource.AliasValuePair> bucketPairs; |
||||||
|
// `Locs()` is used because aliases are case-insensitive. |
||||||
|
bucketIndex = _().text.GetHash(Locs(alias)); |
||||||
|
if (bucketIndex < 0) { |
||||||
|
bucketIndex *= -1; |
||||||
|
} |
||||||
|
bucketIndex = Remainder(bucketIndex, hashTable.length); |
||||||
|
// Check if bucket actually has given alias. |
||||||
|
bucketPairs = hashTable[bucketIndex].pairs; |
||||||
|
for (i = 0; i < bucketPairs.length; i += 1) |
||||||
|
{ |
||||||
|
if (bucketPairs[i].alias ~= alias) |
||||||
|
{ |
||||||
|
pairIndex = i; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Finds a value for a given alias. |
||||||
|
* |
||||||
|
* @param alias Alias for which we need to find a value. |
||||||
|
* Aliases are case-insensitive. |
||||||
|
* @param value If given alias is present in caller `AliasHash`, - |
||||||
|
* it's value will be written in this variable. |
||||||
|
* Otherwise value is undefined. |
||||||
|
* @return `true` if we found value, `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool Find(string alias, out string value) |
||||||
|
{ |
||||||
|
local int bucketIndex; |
||||||
|
local int pairIndex; |
||||||
|
if (FindPairIndices(alias, bucketIndex, pairIndex)) |
||||||
|
{ |
||||||
|
value = hashTable[bucketIndex].pairs[pairIndex].value; |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if caller `AliasHash` contains given alias. |
||||||
|
* |
||||||
|
* @param alias Alias to check for belonging to caller `AliasHash`. |
||||||
|
* Aliases are case-insensitive. |
||||||
|
* @return `true` if caller `AliasHash` contains the value for a given alias |
||||||
|
* and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool Contains(string alias) |
||||||
|
{ |
||||||
|
local int bucketIndex; |
||||||
|
local int pairIndex; |
||||||
|
return FindPairIndices(alias, bucketIndex, pairIndex); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Inserts new record for alias `alias` for value of `value`. |
||||||
|
* |
||||||
|
* If there is already a value for a given `alias` - it will be overwritten. |
||||||
|
* |
||||||
|
* @param alias Alias to insert. Aliases are case-insensitive. |
||||||
|
* @param value Value for a given alias to store. |
||||||
|
* @return A reference to a caller object to allow for function chaining. |
||||||
|
*/ |
||||||
|
public final function AliasHash Insert(string alias, string value) |
||||||
|
{ |
||||||
|
local int bucketIndex; |
||||||
|
local int pairIndex; |
||||||
|
local AliasSource.AliasValuePair newRecord; |
||||||
|
newRecord.value = value; |
||||||
|
newRecord.alias = alias; |
||||||
|
if (!FindPairIndices(alias, bucketIndex, pairIndex)) { |
||||||
|
pairIndex = hashTable[bucketIndex].pairs.length; |
||||||
|
} |
||||||
|
hashTable[bucketIndex].pairs[pairIndex] = newRecord; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Inserts new record for alias `alias` for value of `value`. |
||||||
|
* |
||||||
|
* If there is already a value for a given `alias`, - new value will be |
||||||
|
* discarded and `AliasHash` will not be changed. |
||||||
|
* |
||||||
|
* @param alias Alias to insert. Aliases are case-insensitive. |
||||||
|
* @param value Value for a given alias to store. |
||||||
|
* @param existingValue Value that will correspond to a given alias after |
||||||
|
* this method's execution. If insertion was successful - given `value`, |
||||||
|
* otherwise (if there already was a record for an `alias`) |
||||||
|
* it will return value that already existed in caller `AliasHash`. |
||||||
|
* @return `true` if given alias-value pair was inserted and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool InsertIfMissing( |
||||||
|
string alias, |
||||||
|
string value, |
||||||
|
out string existingValue) |
||||||
|
{ |
||||||
|
local int bucketIndex; |
||||||
|
local int pairIndex; |
||||||
|
local AliasSource.AliasValuePair newRecord; |
||||||
|
newRecord.value = value; |
||||||
|
newRecord.alias = alias; |
||||||
|
existingValue = value; |
||||||
|
if (FindPairIndices(alias, bucketIndex, pairIndex)) { |
||||||
|
existingValue = hashTable[bucketIndex].pairs[pairIndex].value; |
||||||
|
return false; |
||||||
|
} |
||||||
|
pairIndex = hashTable[bucketIndex].pairs.length; |
||||||
|
hashTable[bucketIndex].pairs[pairIndex] = newRecord; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes record, corresponding to a given alias `alias`. |
||||||
|
* |
||||||
|
* @param alias Alias for which all records must be removed. |
||||||
|
* @return `true` if record was removed, `false` if id did not |
||||||
|
* (can only happen when `AliasHash` did not have any records for `alias`). |
||||||
|
*/ |
||||||
|
public final function bool Remove(string alias) |
||||||
|
{ |
||||||
|
local int bucketIndex; |
||||||
|
local int pairIndex; |
||||||
|
if (FindPairIndices(alias, bucketIndex, pairIndex)) { |
||||||
|
hashTable[bucketIndex].pairs.Remove(pairIndex, 1); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
MINIMUM_CAPACITY = 10 |
||||||
|
MAXIMUM_CAPACITY = 100000 |
||||||
|
} |
@ -0,0 +1,135 @@ |
|||||||
|
/** |
||||||
|
* Service that handles pending saving of aliases data into configs. |
||||||
|
* Adding aliases into `AliasSource`s causes corresponding configs to update. |
||||||
|
* This service allows to delay and spread config rewrites over time, |
||||||
|
* which should help in case someone dynamically adds a lot of |
||||||
|
* different aliases. |
||||||
|
* 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 AliasService extends Service |
||||||
|
config(AcediaSystem); |
||||||
|
|
||||||
|
// Objects for which we are yet to write configs |
||||||
|
var private array<AliasSource> sourcesPendingToSave; |
||||||
|
var private array<Aliases> aliasesPendingToSave; |
||||||
|
// How often should we do it. |
||||||
|
// Negative or zero values would be reset to `0.05`. |
||||||
|
var public config const float saveInterval; |
||||||
|
|
||||||
|
// To avoid creating yet another object for aliases system we will |
||||||
|
// keep config variable pointing to weapon, color, etc. `AliasSource` |
||||||
|
// subclasses here. It's not the best regarding separation of responsibility, |
||||||
|
// but should make config files less fragmented. |
||||||
|
// Changing these allows you to change in what sources `AliasesAPI` |
||||||
|
// looks for weapon and color aliases. |
||||||
|
var public config const class<AliasSource> weaponAliasesSource; |
||||||
|
var public config const class<AliasSource> colorAliasesSource; |
||||||
|
|
||||||
|
protected function OnLaunch() |
||||||
|
{ |
||||||
|
local float actualInterval; |
||||||
|
actualInterval = saveInterval; |
||||||
|
if (actualInterval <= 0) |
||||||
|
{ |
||||||
|
actualInterval = 0.05; |
||||||
|
} |
||||||
|
SetTimer(actualInterval, true); |
||||||
|
} |
||||||
|
|
||||||
|
protected function OnShutdown() |
||||||
|
{ |
||||||
|
SaveAllPendingObjects(); |
||||||
|
} |
||||||
|
|
||||||
|
public final function PendingSaveSource(AliasSource sourceToSave) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (sourceToSave == none) return; |
||||||
|
// Starting searching from the end of an array will make situations when |
||||||
|
// we add several aliases to a single source in a row more efficient. |
||||||
|
for (i = sourcesPendingToSave.length - 1;i >= 0; i -= 1) { |
||||||
|
if (sourcesPendingToSave[i] == sourceToSave) return; |
||||||
|
} |
||||||
|
sourcesPendingToSave[sourcesPendingToSave.length] = sourceToSave; |
||||||
|
} |
||||||
|
|
||||||
|
public final function PendingSaveObject(Aliases objectToSave) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (objectToSave == none) return; |
||||||
|
// Starting searching from the end of an array will make situations when |
||||||
|
// we add several aliases to a single `Aliases` object in a row |
||||||
|
// more efficient. |
||||||
|
for (i = aliasesPendingToSave.length - 1;i >= 0; i -= 1) { |
||||||
|
if (aliasesPendingToSave[i] == objectToSave) return; |
||||||
|
} |
||||||
|
aliasesPendingToSave[aliasesPendingToSave.length] = objectToSave; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Forces saving of the next object (either `AliasSource` or `Aliases`) |
||||||
|
* in queue to the config file. |
||||||
|
* |
||||||
|
* Does not reset the timer until next saving. |
||||||
|
*/ |
||||||
|
private final function DoSaveNextPendingObject() |
||||||
|
{ |
||||||
|
if (sourcesPendingToSave.length > 0) |
||||||
|
{ |
||||||
|
if (sourcesPendingToSave[0] != none) { |
||||||
|
sourcesPendingToSave[0].SaveConfig(); |
||||||
|
} |
||||||
|
sourcesPendingToSave.Remove(0, 1); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (aliasesPendingToSave.length > 0) |
||||||
|
{ |
||||||
|
aliasesPendingToSave[0].SaveOrClear(); |
||||||
|
aliasesPendingToSave.Remove(0, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Forces saving of all objects (both `AliasSource`s or `Aliases`s) in queue |
||||||
|
* to their config files. |
||||||
|
*/ |
||||||
|
private final function SaveAllPendingObjects() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < sourcesPendingToSave.length; i += 1) { |
||||||
|
if (sourcesPendingToSave[i] == none) continue; |
||||||
|
sourcesPendingToSave[i].SaveConfig(); |
||||||
|
} |
||||||
|
for (i = 0; i < aliasesPendingToSave.length; i += 1) { |
||||||
|
aliasesPendingToSave[i].SaveOrClear(); |
||||||
|
} |
||||||
|
sourcesPendingToSave.length = 0; |
||||||
|
aliasesPendingToSave.length = 0; |
||||||
|
} |
||||||
|
|
||||||
|
event Timer() |
||||||
|
{ |
||||||
|
DoSaveNextPendingObject(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
saveInterval = 0.05 |
||||||
|
weaponAliasesSource = class'WeaponAliasSource' |
||||||
|
colorAliasesSource = class'ColorAliasSource' |
||||||
|
} |
@ -0,0 +1,379 @@ |
|||||||
|
/** |
||||||
|
* Aliases allow users to define human-readable and easier to use |
||||||
|
* "synonyms" to some symbol sequences (mainly names of UnrealScript classes). |
||||||
|
* This class implements an alias database that stores aliases inside |
||||||
|
* standard config ini-files. |
||||||
|
* Several `AliasSource`s are supposed to exist separately, each storing |
||||||
|
* aliases of particular kind: for weapon, zeds, colors, etc.. |
||||||
|
* 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 AliasSource extends Singleton |
||||||
|
config(AcediaAliases); |
||||||
|
|
||||||
|
// Name of the configurational file (without extension) where |
||||||
|
// this `AliasSource`'s data will be stored. |
||||||
|
var private const string configName; |
||||||
|
|
||||||
|
// (Sub-)class of `Aliases` objects that this `AliasSource` uses to store |
||||||
|
// aliases in per-object-config manner. |
||||||
|
// Leaving this variable `none` will produce an `AliasSource` that can |
||||||
|
// only store aliases in form of `record=(alias="...",value="...")`. |
||||||
|
var public const class<Aliases> aliasesClass; |
||||||
|
// Storage for all objects of `aliasesClass` class in the config. |
||||||
|
// Exists after `OnCreated()` event and is maintained up-to-date at all times. |
||||||
|
var private array<Aliases> loadedAliasObjects; |
||||||
|
|
||||||
|
// Links alias to a value. |
||||||
|
// An array of these structures (without duplicate `alias` records) defines |
||||||
|
// a function from the space of aliases to the space of values. |
||||||
|
struct AliasValuePair |
||||||
|
{ |
||||||
|
var string alias; |
||||||
|
var string value; |
||||||
|
}; |
||||||
|
// Aliases data for saving and loading on a disk (ini-file). |
||||||
|
// Name is chosen to make configurational files more readable. |
||||||
|
var private config array<AliasValuePair> record; |
||||||
|
// Hash table for a faster access to value by alias' name. |
||||||
|
// It contains same records as `record` array + aliases from |
||||||
|
// `loadedAliasObjects` objects when there are no duplicate aliases. |
||||||
|
// Otherwise only stores first loaded alias. |
||||||
|
var private AliasHash hash; |
||||||
|
|
||||||
|
|
||||||
|
// How many times bigger capacity of `hash` should be, compared to amount of |
||||||
|
// initially loaded data from a config. |
||||||
|
var private const float HASH_TABLE_SCALE; |
||||||
|
|
||||||
|
// Load and hash all the data `AliasSource` creation. |
||||||
|
protected function OnCreated() |
||||||
|
{ |
||||||
|
local int entriesAmount; |
||||||
|
if (!AssertAliasesClassIsOwnedByMe()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// Load and hash |
||||||
|
entriesAmount = LoadData(); |
||||||
|
hash = AliasHash(_.memory.Allocate(class'AliasHash')); |
||||||
|
hash.Initialize(int(entriesAmount * HASH_TABLE_SCALE)); |
||||||
|
HashValidAliases(); |
||||||
|
} |
||||||
|
|
||||||
|
// Ensures invariant of our `Aliases` class only belonging to us by |
||||||
|
// itself ourselves otherwise. |
||||||
|
private final function bool AssertAliasesClassIsOwnedByMe() |
||||||
|
{ |
||||||
|
if (aliasesClass == none) return true; |
||||||
|
if (aliasesClass.default.sourceClass == class) return true; |
||||||
|
_.logger.Failure("`AliasSource`-`Aliases` class pair is incorrectly" |
||||||
|
@ "setup for source `" $ string(class) $ "`. Omitting it."); |
||||||
|
Destroy(); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// This method loads all the defined aliases from the config file and |
||||||
|
// returns how many entries are there are total. |
||||||
|
// Does not change data, including fixing duplicates. |
||||||
|
private final function int LoadData() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int entriesAmount; |
||||||
|
local array<string> objectNames; |
||||||
|
entriesAmount = record.length; |
||||||
|
if (aliasesClass == none) { |
||||||
|
return entriesAmount; |
||||||
|
} |
||||||
|
objectNames = |
||||||
|
GetPerObjectNames(configName, string(aliasesClass.name), MaxInt); |
||||||
|
loadedAliasObjects.length = objectNames.length; |
||||||
|
for (i = 0; i < objectNames.length; i += 1) |
||||||
|
{ |
||||||
|
loadedAliasObjects[i] = new(none, objectNames[i]) aliasesClass; |
||||||
|
entriesAmount += loadedAliasObjects[i].GetAliases().length; |
||||||
|
} |
||||||
|
return entriesAmount; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Simply checks if given alias is present in caller `AliasSource`. |
||||||
|
* |
||||||
|
* @param alias Alias to check, case-insensitive. |
||||||
|
* @return `true` if present, `false` otherwise. |
||||||
|
*/ |
||||||
|
public function bool ContainsAlias(string alias) |
||||||
|
{ |
||||||
|
return hash.Contains(alias); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tries to look up a value, stored for given alias in caller `AliasSource` and |
||||||
|
* reports error upon failure. |
||||||
|
* |
||||||
|
* Also see `Try()` method. |
||||||
|
* |
||||||
|
* @param alias Alias, for which method will attempt to look up a value. |
||||||
|
* Case-insensitive. |
||||||
|
* @param value If passed `alias` was recorded in caller `AliasSource`, |
||||||
|
* it's corresponding value will be written in this variable. |
||||||
|
* Otherwise value is undefined. |
||||||
|
* @return `true` if lookup was successful (alias present in 'AliasSource`) |
||||||
|
* and correct value was written into `value`, `false` otherwise. |
||||||
|
*/ |
||||||
|
public function bool Resolve(string alias, out string value) |
||||||
|
{ |
||||||
|
return hash.Find(alias, value); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tries to look up a value, stored for given alias in caller `AliasSource` and |
||||||
|
* silently returns given `alias` value upon failure. |
||||||
|
* |
||||||
|
* Also see `Resolve()` method. |
||||||
|
* |
||||||
|
* @param alias Alias, for which method will attempt to look up a value. |
||||||
|
* Case-insensitive. |
||||||
|
* @return Value corresponding to a given alias, if it was present in |
||||||
|
* caller `AliasSource` and value of `alias` parameter instead. |
||||||
|
*/ |
||||||
|
public function string Try(string alias) |
||||||
|
{ |
||||||
|
local string result; |
||||||
|
if (hash.Find(alias, result)) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
return alias; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds another alias to the caller `AliasSource`. |
||||||
|
* If alias with the same name as `aliasToAdd` already exists, - |
||||||
|
* method overwrites it. |
||||||
|
* |
||||||
|
* Can fail iff `aliasToAdd` is an invalid alias. |
||||||
|
* |
||||||
|
* When adding alias to an object (`saveInObject == true`) alias `aliasToAdd` |
||||||
|
* will be altered by changing any ':' inside it into a '.'. |
||||||
|
* This is a necessary measure to allow storing class names in |
||||||
|
* config files via per-object-config. |
||||||
|
* |
||||||
|
* NOTE: This call will cause update of an ini-file. That update can be |
||||||
|
* slightly delayed, so do not make assumptions about it's immediacy. |
||||||
|
* |
||||||
|
* NOTE #2: Removing alias would require this method to go through the |
||||||
|
* whole `AliasSource` to remove possible duplicates. |
||||||
|
* This means that unless you can guarantee that there is no duplicates, - |
||||||
|
* performing a lot of alias additions during run-time can be costly. |
||||||
|
* |
||||||
|
* @param aliasToAdd Alias that you want to add to caller source. |
||||||
|
* Alias names are case-insensitive. |
||||||
|
* @param aliasValue Intended value of this alias. |
||||||
|
* @param saveInObject Setting this to `true` will make `AliasSource` save |
||||||
|
* given alias in per-object-config storage, while keeping it at default |
||||||
|
* `false` will just add alias to the `record=` storage. |
||||||
|
* If caller `AliasSource` does not support per-object-config storage, - |
||||||
|
* this flag will be ignores. |
||||||
|
* @return `true` if alias was added and `false` otherwise (alias was invalid). |
||||||
|
*/ |
||||||
|
public final function bool AddAlias( |
||||||
|
string aliasToAdd, |
||||||
|
string aliasValue, |
||||||
|
optional bool saveInObject) |
||||||
|
{ |
||||||
|
local AliasValuePair newPair; |
||||||
|
if (_.alias.IsAliasValid(aliasToAdd)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (hash.Contains(aliasToAdd)) { |
||||||
|
RemoveAlias(aliasToAdd); |
||||||
|
} |
||||||
|
// We might not be able to use per-object-config storage |
||||||
|
if (saveInObject && aliasesClass == none) { |
||||||
|
saveInObject = false; |
||||||
|
_.logger.Warning("Cannot save alias in object for source `" |
||||||
|
$ string(class) |
||||||
|
$ "`, because it does not have appropriate `Aliases` class setup."); |
||||||
|
} |
||||||
|
// Save |
||||||
|
if (saveInObject) { |
||||||
|
GetAliasesObjectWithValue(aliasValue).AddAlias(aliasToAdd); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
newPair.alias = aliasToAdd; |
||||||
|
newPair.value = aliasValue; |
||||||
|
record[record.length] = newPair; |
||||||
|
} |
||||||
|
hash.Insert(aliasToAdd, aliasValue); |
||||||
|
AliasService(class'AliasService'.static.Require()).PendingSaveSource(self); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes alias (all records with it, in case of duplicates) from |
||||||
|
* the caller `AliasSource`. |
||||||
|
* |
||||||
|
* Cannot fail. |
||||||
|
* |
||||||
|
* NOTE: This call will cause update of an ini-file. That update can be |
||||||
|
* slightly delayed, so do not make assumptions about it's immediacy. |
||||||
|
* |
||||||
|
* NOTE #2: removing alias requires this method to go through the |
||||||
|
* whole `AliasSource` to remove possible duplicates, which can make |
||||||
|
* performing a lot of alias removal during run-time costly. |
||||||
|
* |
||||||
|
* @param aliasToRemove Alias that you want to remove from caller source. |
||||||
|
*/ |
||||||
|
public final function RemoveAlias(string aliasToRemove) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool removedAliasFromRecord; |
||||||
|
hash.Remove(aliasToRemove); |
||||||
|
while (i < record.length) |
||||||
|
{ |
||||||
|
if (record[i].alias ~= aliasToRemove) |
||||||
|
{ |
||||||
|
record.Remove(i, 1); |
||||||
|
removedAliasFromRecord = true; |
||||||
|
} |
||||||
|
else { |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < loadedAliasObjects.length; i += 1) { |
||||||
|
loadedAliasObjects[i].RemoveAlias(aliasToRemove); |
||||||
|
} |
||||||
|
if (removedAliasFromRecord) |
||||||
|
{ |
||||||
|
AliasService(class'AliasService'.static.Require()) |
||||||
|
.PendingSaveSource(self); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Performs initial hashing of every record with valid alias. |
||||||
|
// In case of duplicate or invalid aliases - method will skip them |
||||||
|
// and log warnings. |
||||||
|
private final function HashValidAliases() |
||||||
|
{ |
||||||
|
if (hash == none) { |
||||||
|
_.logger.Warning("Alias source `" $ string(class) $ "` called" |
||||||
|
$ "`HashValidAliases()` function without creating an `AliasHasher`" |
||||||
|
$ "instance first. This should not have happened."); |
||||||
|
return; |
||||||
|
} |
||||||
|
HashValidAliasesFromRecord(); |
||||||
|
HashValidAliasesFromPerObjectConfig(); |
||||||
|
} |
||||||
|
|
||||||
|
private final function LogDuplicateAliasWarning( |
||||||
|
string alias, |
||||||
|
string existingValue) |
||||||
|
{ |
||||||
|
_.logger.Warning("Alias source `" $ string(class) |
||||||
|
$ "` has duplicate record for alias \"" $ alias |
||||||
|
$ "\". This is likely due to an erroneous config. \"" $ existingValue |
||||||
|
$ "\" value will be used."); |
||||||
|
} |
||||||
|
|
||||||
|
private final function LogInvalidAliasWarning(string invalidAlias) |
||||||
|
{ |
||||||
|
_.logger.Warning("Alias source `" $ string(class) |
||||||
|
$ "` contains invalid alias name \"" $ invalidAlias |
||||||
|
$ "\". This alias will not be loaded."); |
||||||
|
} |
||||||
|
|
||||||
|
private final function HashValidAliasesFromRecord() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool isDuplicate; |
||||||
|
local string existingValue; |
||||||
|
for (i = 0; i < record.length; i += 1) |
||||||
|
{ |
||||||
|
if (!_.alias.IsAliasValid(record[i].alias)) |
||||||
|
{ |
||||||
|
LogInvalidAliasWarning(record[i].alias); |
||||||
|
continue; |
||||||
|
} |
||||||
|
isDuplicate = !hash.InsertIfMissing(record[i].alias, record[i].value, |
||||||
|
existingValue); |
||||||
|
if (isDuplicate) { |
||||||
|
LogDuplicateAliasWarning(record[i].alias, existingValue); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private final function HashValidAliasesFromPerObjectConfig() |
||||||
|
{ |
||||||
|
local int i, j; |
||||||
|
local bool isDuplicate; |
||||||
|
local string existingValue; |
||||||
|
local string objectValue; |
||||||
|
local array<string> objectAliases; |
||||||
|
for (i = 0; i < loadedAliasObjects.length; i += 1) |
||||||
|
{ |
||||||
|
objectValue = loadedAliasObjects[i].GetValue(); |
||||||
|
objectAliases = loadedAliasObjects[i].GetAliases(); |
||||||
|
for (j = 0; j < objectAliases.length; j += 1) |
||||||
|
{ |
||||||
|
if (!_.alias.IsAliasValid(objectAliases[j])) |
||||||
|
{ |
||||||
|
LogInvalidAliasWarning(objectAliases[j]); |
||||||
|
continue; |
||||||
|
} |
||||||
|
isDuplicate = !hash.InsertIfMissing(objectAliases[j], objectValue, |
||||||
|
existingValue); |
||||||
|
if (isDuplicate) { |
||||||
|
LogDuplicateAliasWarning(objectAliases[j], existingValue); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tries to find a loaded `Aliases` config object that stores aliases for |
||||||
|
// the given value. If such object does not exists - creates a new one. |
||||||
|
private final function Aliases GetAliasesObjectWithValue(string value) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Aliases newAliasesObject; |
||||||
|
// This method only makes sense if this `AliasSource` supports |
||||||
|
// per-object-config storage. |
||||||
|
if (aliasesClass == none) |
||||||
|
{ |
||||||
|
_.logger.Warning("`GetAliasesObjectForValue()` function was called for " |
||||||
|
$ "alias source with `aliasesClass == none`." |
||||||
|
$ "This should not happen."); |
||||||
|
return none; |
||||||
|
} |
||||||
|
for (i = 0; i < loadedAliasObjects.length; i += 1) |
||||||
|
{ |
||||||
|
if (loadedAliasObjects[i].GetValue() ~= value) { |
||||||
|
return loadedAliasObjects[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
newAliasesObject = new(none, value) aliasesClass; |
||||||
|
loadedAliasObjects[loadedAliasObjects.length] = newAliasesObject; |
||||||
|
return newAliasesObject; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
// Source main parameters |
||||||
|
configName = "AcediaAliases" |
||||||
|
aliasesClass = class'Aliases' |
||||||
|
// HashTable twice the size of data entries should do it |
||||||
|
HASH_TABLE_SCALE = 2.0 |
||||||
|
} |
@ -0,0 +1,142 @@ |
|||||||
|
/** |
||||||
|
* This is a simple helper object for `AliasSource` that can store |
||||||
|
* an array of aliases in config files in a per-object-config manner. |
||||||
|
* One `Aliases` object can store several aliases for a single value. |
||||||
|
* It is recommended that you do not try to access these objects directly. |
||||||
|
* Class name `Aliases` is chosen to make configuration files |
||||||
|
* more readable. |
||||||
|
* It's only interesting function is storing '.'s as ':' in it's config, |
||||||
|
* which is necessary to allow storing aliases for class names via |
||||||
|
* these objects (since UnrealScript's cannot handle '.'s in object's names |
||||||
|
* in it's configs). |
||||||
|
* 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 Aliases extends AcediaObject |
||||||
|
perObjectConfig |
||||||
|
config(AcediaAliases); |
||||||
|
|
||||||
|
// Link to the `AliasSource` that uses `Aliases` objects of this class. |
||||||
|
// To ensure that any `Aliases` sub-class only belongs to one `AliasSource`. |
||||||
|
var public const class<AliasSource> sourceClass; |
||||||
|
|
||||||
|
// Aliases, recorded by this `Aliases` object that all mean the same value, |
||||||
|
// defined by this object's name `string(self.name)`. |
||||||
|
var protected config array<string> alias; |
||||||
|
|
||||||
|
// Since '.'s in values are converted into ':' for storage purposes, |
||||||
|
// we need methods to convert between "storage" and "actual" value version. |
||||||
|
// `ToStorageVersion()` and `ToActualVersion()` do that. |
||||||
|
private final function string ToStorageVersion(string actualValue) |
||||||
|
{ |
||||||
|
return Repl(actualValue, ".", ":"); |
||||||
|
} |
||||||
|
|
||||||
|
private final function string ToActualVersion(string storageValue) |
||||||
|
{ |
||||||
|
return Repl(storageValue, ":", "."); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns value that caller's `Aliases` object's aliases point to. |
||||||
|
* |
||||||
|
* @return Value, stored by this object. |
||||||
|
*/ |
||||||
|
public final function string GetValue() |
||||||
|
{ |
||||||
|
return ToActualVersion(string(self.name)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns array of aliases that caller `Aliases` tells us point to it's value. |
||||||
|
* |
||||||
|
* @return Array of all aliases, stored by caller `Aliases` object. |
||||||
|
*/ |
||||||
|
public final function array<string> GetAliases() |
||||||
|
{ |
||||||
|
return alias; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* [For inner use by `AliasSource`] Adds new alias to this object. |
||||||
|
* |
||||||
|
* Does no duplicates checks through for it's `AliasSource` and |
||||||
|
* neither it updates relevant `AliasHash`, |
||||||
|
* but will prevent adding duplicate records inside it's own storage. |
||||||
|
* |
||||||
|
* @param aliasToAdd Alias to add to caller `Aliases` object. |
||||||
|
*/ |
||||||
|
public final function AddAlias(string aliasToAdd) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < alias.length; i += 1) { |
||||||
|
if (alias[i] ~= aliasToAdd) return; |
||||||
|
} |
||||||
|
alias[alias.length] = ToStorageVersion(aliasToAdd); |
||||||
|
AliasService(class'AliasService'.static.Require()) |
||||||
|
.PendingSaveObject(self); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* [For inner use by `AliasSource`] Removes alias from this object. |
||||||
|
* |
||||||
|
* Does not update relevant `AliasHash`. |
||||||
|
* |
||||||
|
* Will prevent adding duplicate records inside it's own storage. |
||||||
|
* |
||||||
|
* @param aliasToRemove Alias to remove from caller `Aliases` object. |
||||||
|
*/ |
||||||
|
public final function RemoveAlias(string aliasToRemove) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool removedAlias; |
||||||
|
while (i < alias.length) |
||||||
|
{ |
||||||
|
if (alias[i] ~= aliasToRemove) |
||||||
|
{ |
||||||
|
alias.Remove(i, 1); |
||||||
|
removedAlias = true; |
||||||
|
} |
||||||
|
else { |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
if (removedAlias) |
||||||
|
{ |
||||||
|
AliasService(class'AliasService'.static.Require()) |
||||||
|
.PendingSaveObject(self); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* If this object still has any alias records, - forces a rewrite of it's data |
||||||
|
* into the config file, otherwise - removes it's record entirely. |
||||||
|
*/ |
||||||
|
public final function SaveOrClear() |
||||||
|
{ |
||||||
|
if (alias.length <= 0) { |
||||||
|
ClearConfig(); |
||||||
|
} |
||||||
|
else { |
||||||
|
SaveConfig(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
sourceClass = class'AliasSource' |
||||||
|
} |
@ -0,0 +1,233 @@ |
|||||||
|
/** |
||||||
|
* Provides convenient access to Aliases-related functions. |
||||||
|
* 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 AliasesAPI extends Singleton; |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks that passed value is a valid alias name. |
||||||
|
* |
||||||
|
* A valid name is any name consisting out of 128 ASCII symbols. |
||||||
|
* |
||||||
|
* @param aliasToCheck Alias to check for validity. |
||||||
|
* @return `true` if `aliasToCheck` is a valid alias and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool IsAliasValid(string aliasToCheck) |
||||||
|
{ |
||||||
|
return _.text.IsASCIIString(aliasToCheck); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Provides an easier access to the instance of the `AliasSource` of |
||||||
|
* the given class. |
||||||
|
* |
||||||
|
* Can fail if `customSourceClass` is incorrectly defined. |
||||||
|
* |
||||||
|
* @param customSourceClass Class of the source we want. |
||||||
|
* @return Instance of the requested `AliasSource`, |
||||||
|
* `none` if `customSourceClass` is incorrectly defined. |
||||||
|
*/ |
||||||
|
public final function AliasSource GetCustomSource( |
||||||
|
class<AliasSource> customSourceClass) |
||||||
|
{ |
||||||
|
return AliasSource(customSourceClass.static.GetInstance(true)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns `AliasSource` that is designated in configuration files as |
||||||
|
* a source for weapon aliases. |
||||||
|
* |
||||||
|
* NOTE: while by default weapon aliases source will contain only weapon |
||||||
|
* aliases, you should not assume that. Acedia allows admins to store all |
||||||
|
* the aliases in the same config. |
||||||
|
* |
||||||
|
* @return Reference to the `AliasSource` that contains weapon aliases. |
||||||
|
* Can return `none` if no source for weapons was configured or |
||||||
|
* the configured source is incorrectly defined. |
||||||
|
*/ |
||||||
|
public final function AliasSource GetWeaponSource() |
||||||
|
{ |
||||||
|
local AliasSource weaponSource; |
||||||
|
local class<AliasSource> sourceClass; |
||||||
|
sourceClass = class'AliasService'.default.weaponAliasesSource; |
||||||
|
if (sourceClass == none) { |
||||||
|
_.logger.Failure("No weapon aliases source configured for Acedia's" |
||||||
|
@ "alias API. Error is most likely cause by erroneous config."); |
||||||
|
return none; |
||||||
|
} |
||||||
|
weaponSource = AliasSource(sourceClass.static.GetInstance(true)); |
||||||
|
if (weaponSource == none) { |
||||||
|
_.logger.Failure("`AliasSource` class `" $ string(sourceClass) $ "` is" |
||||||
|
@ "configured to store weapon aliases, but it seems to be invalid." |
||||||
|
@ "This is a bug and not configuration file problem, but issue" |
||||||
|
@ "might be avoided by using a different `AliasSource`."); |
||||||
|
return none; |
||||||
|
} |
||||||
|
return weaponSource; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns `AliasSource` that is designated in configuration files as |
||||||
|
* a source for color aliases. |
||||||
|
* |
||||||
|
* NOTE: while by default color aliases source will contain only color aliases, |
||||||
|
* you should not assume that. Acedia allows admins to store all the aliases |
||||||
|
* in the same config. |
||||||
|
* |
||||||
|
* @return Reference to the `AliasSource` that contains color aliases. |
||||||
|
* Can return `none` if no source for colors was configured or |
||||||
|
* the configured source is incorrectly defined. |
||||||
|
*/ |
||||||
|
public final function AliasSource GetColorSource() |
||||||
|
{ |
||||||
|
local AliasSource colorSource; |
||||||
|
local class<AliasSource> sourceClass; |
||||||
|
sourceClass = class'AliasService'.default.colorAliasesSource; |
||||||
|
if (sourceClass == none) { |
||||||
|
_.logger.Failure("No color aliases source configured for Acedia's" |
||||||
|
@ "alias API. Error is most likely cause by erroneous config."); |
||||||
|
return none; |
||||||
|
} |
||||||
|
colorSource = AliasSource(sourceClass.static.GetInstance(true)); |
||||||
|
if (colorSource == none) { |
||||||
|
_.logger.Failure("`AliasSource` class `" $ string(sourceClass) $ "` is" |
||||||
|
@ "configured to store color aliases, but it seems to be invalid." |
||||||
|
@ "This is a bug and not configuration file problem, but issue" |
||||||
|
@ "might be avoided by using a different `AliasSource`."); |
||||||
|
return none; |
||||||
|
} |
||||||
|
return colorSource; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tries to look up a value, stored for given alias in an `AliasSource` |
||||||
|
* configured to store weapon aliases. Reports error on failure. |
||||||
|
* |
||||||
|
* Lookup of alias can fail if either alias does not exist in weapon alias |
||||||
|
* source or weapon alias source itself does not exist |
||||||
|
* (due to either faulty configuration or incorrect definition). |
||||||
|
* To determine if weapon alias source exists you can check |
||||||
|
* `_.alias.GetWeaponSource()` value. |
||||||
|
* |
||||||
|
* Also see `TryWeapon()` method. |
||||||
|
* |
||||||
|
* @param alias Alias, for which method will attempt to look up a value. |
||||||
|
* Case-insensitive. |
||||||
|
* @param value If passed `alias` was recorded as a weapon alias, |
||||||
|
* it's corresponding value will be written in this variable. |
||||||
|
* Otherwise value is undefined. |
||||||
|
* @return `true` if lookup was successful and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool ResolveWeapon(string alias, out string result) |
||||||
|
{ |
||||||
|
local AliasSource source; |
||||||
|
source = GetWeaponSource(); |
||||||
|
if (source != none) { |
||||||
|
return source.Resolve(alias, result); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tries to look up a value, stored for given alias in an `AliasSource` |
||||||
|
* configured to store weapon aliases and silently returns given `alias` |
||||||
|
* value upon failure. |
||||||
|
* |
||||||
|
* Lookup of alias can fail if either alias does not exist in weapon alias |
||||||
|
* source or weapon alias source itself does not exist |
||||||
|
* (due to either faulty configuration or incorrect definition). |
||||||
|
* To determine if weapon alias source exists you can check |
||||||
|
* `_.alias.GetWeaponSource()` value. |
||||||
|
* |
||||||
|
* Also see `ResolveWeapon()` method. |
||||||
|
* |
||||||
|
* @param alias Alias, for which method will attempt to look up a value. |
||||||
|
* Case-insensitive. |
||||||
|
* @return Weapon value corresponding to a given alias, if it was present in |
||||||
|
* the weapon alias source and value of `alias` parameter instead. |
||||||
|
*/ |
||||||
|
public function string TryWeapon(string alias) |
||||||
|
{ |
||||||
|
local AliasSource source; |
||||||
|
source = GetWeaponSource(); |
||||||
|
if (source != none) { |
||||||
|
return source.Try(alias); |
||||||
|
} |
||||||
|
return alias; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tries to look up a value, stored for given alias in an `AliasSource` |
||||||
|
* configured to store color aliases. Reports error on failure. |
||||||
|
* |
||||||
|
* Lookup of alias can fail if either alias does not exist in color alias |
||||||
|
* source or color alias source itself does not exist |
||||||
|
* (due to either faulty configuration or incorrect definition). |
||||||
|
* To determine if color alias source exists you can check |
||||||
|
* `_.alias.GetColorSource()` value. |
||||||
|
* |
||||||
|
* Also see `TryColor()` method. |
||||||
|
* |
||||||
|
* @param alias Alias, for which method will attempt to look up a value. |
||||||
|
* Case-insensitive. |
||||||
|
* @param value If passed `alias` was recorded as a color alias, |
||||||
|
* it's corresponding value will be written in this variable. |
||||||
|
* Otherwise value is undefined. |
||||||
|
* @return `true` if lookup was successful and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool ResolveColor(string alias, out string result) |
||||||
|
{ |
||||||
|
local AliasSource source; |
||||||
|
source = GetColorSource(); |
||||||
|
if (source != none) { |
||||||
|
return source.Resolve(alias, result); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tries to look up a value, stored for given alias in an `AliasSource` |
||||||
|
* configured to store color aliases and silently returns given `alias` |
||||||
|
* value upon failure. |
||||||
|
* |
||||||
|
* Lookup of alias can fail if either alias does not exist in color alias |
||||||
|
* source or color alias source itself does not exist |
||||||
|
* (due to either faulty configuration or incorrect definition). |
||||||
|
* To determine if color alias source exists you can check |
||||||
|
* `_.alias.GetColorSource()` value. |
||||||
|
* |
||||||
|
* Also see `ResolveColor()` method. |
||||||
|
* |
||||||
|
* @param alias Alias, for which method will attempt to look up a value. |
||||||
|
* Case-insensitive. |
||||||
|
* @return Color value corresponding to a given alias, if it was present in |
||||||
|
* the color alias source and value of `alias` parameter instead. |
||||||
|
*/ |
||||||
|
public function string TryColor(string alias) |
||||||
|
{ |
||||||
|
local AliasSource source; |
||||||
|
source = GetColorSource(); |
||||||
|
if (source != none) { |
||||||
|
return source.Try(alias); |
||||||
|
} |
||||||
|
return alias; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/** |
||||||
|
* Source intended for color aliases. |
||||||
|
* 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 ColorAliasSource extends AliasSource |
||||||
|
config(AcediaAliases_Colors); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
configName = "AcediaAliases_Colors" |
||||||
|
aliasesClass = class'ColorAliases' |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/** |
||||||
|
* Per-object-configuration intended for color aliases. |
||||||
|
* 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 ColorAliases extends Aliases |
||||||
|
perObjectConfig |
||||||
|
config(AcediaAliases_Colors); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
sourceClass = class'ColorAliasSource' |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/** |
||||||
|
* Source intended for weapon aliases. |
||||||
|
* 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 WeaponAliasSource extends AliasSource |
||||||
|
config(AcediaAliases_Weapons); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
configName = "AcediaAliases_Weapons" |
||||||
|
aliasesClass = class'WeaponAliases' |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/** |
||||||
|
* Per-object-configuration intended for weapon aliases. |
||||||
|
* 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 WeaponAliases extends Aliases |
||||||
|
perObjectConfig |
||||||
|
config(AcediaAliases_Weapons); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
sourceClass = class'WeaponAliasSource' |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/** |
||||||
|
* Source intended for testing aliases. |
||||||
|
* 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 MockAliasSource extends AliasSource |
||||||
|
config(AcediaAliases_Tests); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
configName = "AcediaAliases_Tests" |
||||||
|
aliasesClass = class'MockAliases' |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/** |
||||||
|
* Per-object-configuration intended for testing aliases. |
||||||
|
* 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 MockAliases extends Aliases |
||||||
|
perObjectConfig |
||||||
|
config(AcediaAliases_Tests); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
sourceClass = class'MockAliasSource' |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
/** |
||||||
|
* Set of tests for Aliases system. |
||||||
|
* 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 TEST_Aliases extends TestCase |
||||||
|
abstract; |
||||||
|
|
||||||
|
protected static function TESTS() |
||||||
|
{ |
||||||
|
Test_AliasHash(); |
||||||
|
Test_AliasLoading(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_AliasLoading() |
||||||
|
{ |
||||||
|
Context("Testing loading aliases from a mock object `MockAliasSource`."); |
||||||
|
SubTest_AliasLoadingCorrect(); |
||||||
|
SubTest_AliasLoadingIncorrect(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_AliasLoadingCorrect() |
||||||
|
{ |
||||||
|
local AliasSource source; |
||||||
|
local string outValue; |
||||||
|
|
||||||
|
Issue("`Resolve()` fails to return alias that should be loaded."); |
||||||
|
source = _().alias.GetCustomSource(class'MockAliasSource'); |
||||||
|
TEST_ExpectTrue(source.Resolve("Global", outValue)); |
||||||
|
TEST_ExpectTrue(outValue == "value"); |
||||||
|
TEST_ExpectTrue(source.Resolve("ford", outValue)); |
||||||
|
TEST_ExpectTrue(outValue == "car"); |
||||||
|
|
||||||
|
Issue("`Try()` fails to return alias that should be loaded."); |
||||||
|
TEST_ExpectTrue(source.Try("question") == "response"); |
||||||
|
TEST_ExpectTrue(source.Try("delorean") == "car"); |
||||||
|
|
||||||
|
Issue("`ContainsAlias()` reports alias, that should be present," |
||||||
|
@ "as missing."); |
||||||
|
TEST_ExpectTrue(source.ContainsAlias("Global")); |
||||||
|
TEST_ExpectTrue(source.ContainsAlias("audi")); |
||||||
|
|
||||||
|
Issue("Aliases in per-object-configs incorrectly handle ':'."); |
||||||
|
TEST_ExpectTrue(source.Try("HardToBeAGod") == "sci.fi"); |
||||||
|
|
||||||
|
Issue("Aliases with empty values in alias name or their value are handled" |
||||||
|
@ "incorrectly."); |
||||||
|
TEST_ExpectTrue(source.Try("") == "empty"); |
||||||
|
TEST_ExpectTrue(source.Try("also") == ""); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_AliasLoadingIncorrect() |
||||||
|
{ |
||||||
|
local AliasSource source; |
||||||
|
local string outValue; |
||||||
|
Context("Testing loading aliases from a mock object `MockAliasSource`."); |
||||||
|
Issue("`AliasAPI` cannot return value custom source."); |
||||||
|
source = _().alias.GetCustomSource(class'MockAliasSource'); |
||||||
|
TEST_ExpectNotNone(source); |
||||||
|
|
||||||
|
Issue("`Resolve()` reports success of finding inexistent alias."); |
||||||
|
source = _().alias.GetCustomSource(class'MockAliasSource'); |
||||||
|
TEST_ExpectFalse(source.Resolve("noSuchThing", outValue)); |
||||||
|
|
||||||
|
Issue("`Try()` does not return given value for non-existent alias."); |
||||||
|
TEST_ExpectTrue(source.Try("TheHellIsThis") == "TheHellIsThis"); |
||||||
|
|
||||||
|
Issue("`ContainsAlias()` reports inexistent alias as present."); |
||||||
|
TEST_ExpectFalse(source.ContainsAlias("FordК")); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_AliasHash() |
||||||
|
{ |
||||||
|
Context("Testing `AliasHasher`."); |
||||||
|
SubTest_AliasHashInsertingRemoval(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_AliasHashInsertingRemoval() |
||||||
|
{ |
||||||
|
local AliasHash hasher; |
||||||
|
local string outValue; |
||||||
|
hasher = new class'AliasHash'; |
||||||
|
hasher.Initialize(); |
||||||
|
Issue("`AliasHash` cannot properly store added aliases."); |
||||||
|
hasher.Insert("alias", "value").Insert("one", "more"); |
||||||
|
TEST_ExpectTrue(hasher.Contains("alias")); |
||||||
|
TEST_ExpectTrue(hasher.Contains("one")); |
||||||
|
TEST_ExpectTrue(hasher.Find("alias", outValue)); |
||||||
|
TEST_ExpectTrue(outValue == "value"); |
||||||
|
TEST_ExpectTrue(hasher.Find("one", outValue)); |
||||||
|
TEST_ExpectTrue(outValue == "more"); |
||||||
|
|
||||||
|
Issue("`AliasHash` reports hashing aliases that never were hashed."); |
||||||
|
TEST_ExpectFalse(hasher.Contains("alia")); |
||||||
|
|
||||||
|
Issue("`AliasHash` cannot properly remove stored aliases."); |
||||||
|
hasher.Remove("alias"); |
||||||
|
TEST_ExpectFalse(hasher.Contains("alias")); |
||||||
|
TEST_ExpectTrue(hasher.Contains("one")); |
||||||
|
TEST_ExpectFalse(hasher.Find("alias", outValue)); |
||||||
|
outValue = "wrong"; |
||||||
|
TEST_ExpectTrue(hasher.Find("one", outValue)); |
||||||
|
TEST_ExpectTrue(outValue == "more"); |
||||||
|
|
||||||
|
Issue("`InsertIfMissing()` function cannot properly store added aliases."); |
||||||
|
TEST_ExpectTrue(hasher.InsertIfMissing("another", "var", outValue)); |
||||||
|
TEST_ExpectTrue(hasher.Find("another", outValue)); |
||||||
|
TEST_ExpectTrue(outValue == "var"); |
||||||
|
|
||||||
|
Issue("`InsertIfMissing()` function incorrectly resolves a conflict with" |
||||||
|
@ "an existing value."); |
||||||
|
TEST_ExpectFalse(hasher.InsertIfMissing("one", "something", outValue)); |
||||||
|
TEST_ExpectTrue(outValue == "more"); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
caseName = "Aliases" |
||||||
|
} |
@ -0,0 +1,812 @@ |
|||||||
|
/** |
||||||
|
* API that provides functions for working with color. |
||||||
|
* It has a wide range of pre-defined colors and some functions that |
||||||
|
* allow to quickly assemble color from rgb(a) values, parse it from |
||||||
|
* a `Text`/string or load it from an alias. |
||||||
|
* 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 ColorAPI extends Singleton |
||||||
|
dependson(Parser) |
||||||
|
config(AcediaSystem); |
||||||
|
|
||||||
|
/** |
||||||
|
* Enumeration for ways to represent `Color` as a `string`. |
||||||
|
*/ |
||||||
|
enum ColorDisplayType |
||||||
|
{ |
||||||
|
// Hex format; for pink: #ffc0cb |
||||||
|
CLRDISPLAY_HEX, |
||||||
|
// RGB format; for pink: rgb(255,192,203) |
||||||
|
CLRDISPLAY_RGB, |
||||||
|
// RGBA format; for opaque pink: rgb(255,192,203,255) |
||||||
|
CLRDISPLAY_RGBA, |
||||||
|
// RGB format with tags; for pink: rgb(r=255,g=192,b=203) |
||||||
|
CLRDISPLAY_RGB_TAG, |
||||||
|
// RGBA format with tags; for pink: rgb(r=255,g=192,b=203,a=255) |
||||||
|
CLRDISPLAY_RGBA_TAG |
||||||
|
}; |
||||||
|
|
||||||
|
// Some useful predefined color values. |
||||||
|
// They are marked as `config` to allow server admins to mess about with |
||||||
|
// colors if they want to. |
||||||
|
// Pink colors |
||||||
|
var public config const Color Pink; |
||||||
|
var public config const Color LightPink; |
||||||
|
var public config const Color HotPink; |
||||||
|
var public config const Color DeepPink; |
||||||
|
var public config const Color PaleVioletRed; |
||||||
|
var public config const Color MediumVioletRed; |
||||||
|
// Red colors |
||||||
|
var public config const Color LightSalmon; |
||||||
|
var public config const Color Salmon; |
||||||
|
var public config const Color DarkSalmon; |
||||||
|
var public config const Color LightCoral; |
||||||
|
var public config const Color IndianRed; |
||||||
|
var public config const Color Crimson; |
||||||
|
var public config const Color Firebrick; |
||||||
|
var public config const Color DarkRed; |
||||||
|
var public config const Color Red; |
||||||
|
// Orange colors |
||||||
|
var public config const Color OrangeRed; |
||||||
|
var public config const Color Tomato; |
||||||
|
var public config const Color Coral; |
||||||
|
var public config const Color DarkOrange; |
||||||
|
var public config const Color Orange; |
||||||
|
// Yellow colors |
||||||
|
var public config const Color Yellow; |
||||||
|
var public config const Color LightYellow; |
||||||
|
var public config const Color LemonChiffon; |
||||||
|
var public config const Color LightGoldenrodYellow; |
||||||
|
var public config const Color PapayaWhip; |
||||||
|
var public config const Color Moccasin; |
||||||
|
var public config const Color PeachPuff; |
||||||
|
var public config const Color PaleGoldenrod; |
||||||
|
var public config const Color Khaki; |
||||||
|
var public config const Color DarkKhaki; |
||||||
|
var public config const Color Gold; |
||||||
|
// Brown colors |
||||||
|
var public config const Color Cornsilk; |
||||||
|
var public config const Color BlanchedAlmond; |
||||||
|
var public config const Color Bisque; |
||||||
|
var public config const Color NavajoWhite; |
||||||
|
var public config const Color Wheat; |
||||||
|
var public config const Color Burlywood; |
||||||
|
var public config const Color TanColor; // `Tan()` already taken by a function |
||||||
|
var public config const Color RosyBrown; |
||||||
|
var public config const Color SandyBrown; |
||||||
|
var public config const Color Goldenrod; |
||||||
|
var public config const Color DarkGoldenrod; |
||||||
|
var public config const Color Peru; |
||||||
|
var public config const Color Chocolate; |
||||||
|
var public config const Color SaddleBrown; |
||||||
|
var public config const Color Sienna; |
||||||
|
var public config const Color Brown; |
||||||
|
var public config const Color Maroon; |
||||||
|
// Green colors |
||||||
|
var public config const Color DarkOliveGreen; |
||||||
|
var public config const Color Olive; |
||||||
|
var public config const Color OliveDrab; |
||||||
|
var public config const Color YellowGreen; |
||||||
|
var public config const Color LimeGreen; |
||||||
|
var public config const Color Lime; |
||||||
|
var public config const Color LawnGreen; |
||||||
|
var public config const Color Chartreuse; |
||||||
|
var public config const Color GreenYellow; |
||||||
|
var public config const Color SpringGreen; |
||||||
|
var public config const Color MediumSpringGreen; |
||||||
|
var public config const Color LightGreen; |
||||||
|
var public config const Color PaleGreen; |
||||||
|
var public config const Color DarkSeaGreen; |
||||||
|
var public config const Color MediumAquamarine; |
||||||
|
var public config const Color MediumSeaGreen; |
||||||
|
var public config const Color SeaGreen; |
||||||
|
var public config const Color ForestGreen; |
||||||
|
var public config const Color Green; |
||||||
|
var public config const Color DarkGreen; |
||||||
|
// Cyan colors |
||||||
|
var public config const Color Aqua; |
||||||
|
var public config const Color Cyan; |
||||||
|
var public config const Color LightCyan; |
||||||
|
var public config const Color PaleTurquoise; |
||||||
|
var public config const Color Aquamarine; |
||||||
|
var public config const Color Turquoise; |
||||||
|
var public config const Color MediumTurquoise; |
||||||
|
var public config const Color DarkTurquoise; |
||||||
|
var public config const Color LightSeaGreen; |
||||||
|
var public config const Color CadetBlue; |
||||||
|
var public config const Color DarkCyan; |
||||||
|
var public config const Color Teal; |
||||||
|
// Blue colors |
||||||
|
var public config const Color LightSteelBlue; |
||||||
|
var public config const Color PowderBlue; |
||||||
|
var public config const Color LightBlue; |
||||||
|
var public config const Color SkyBlue; |
||||||
|
var public config const Color LightSkyBlue; |
||||||
|
var public config const Color DeepSkyBlue; |
||||||
|
var public config const Color DodgerBlue; |
||||||
|
var public config const Color CornflowerBlue; |
||||||
|
var public config const Color SteelBlue; |
||||||
|
var public config const Color RoyalBlue; |
||||||
|
var public config const Color Blue; |
||||||
|
var public config const Color MediumBlue; |
||||||
|
var public config const Color DarkBlue; |
||||||
|
var public config const Color Navy; |
||||||
|
var public config const Color MidnightBlue; |
||||||
|
// Purple, violet, and magenta colors |
||||||
|
var public config const Color Lavender; |
||||||
|
var public config const Color Thistle; |
||||||
|
var public config const Color Plum; |
||||||
|
var public config const Color Violet; |
||||||
|
var public config const Color Orchid; |
||||||
|
var public config const Color Fuchsia; |
||||||
|
var public config const Color Magenta; |
||||||
|
var public config const Color MediumOrchid; |
||||||
|
var public config const Color MediumPurple; |
||||||
|
var public config const Color BlueViolet; |
||||||
|
var public config const Color DarkViolet; |
||||||
|
var public config const Color DarkOrchid; |
||||||
|
var public config const Color DarkMagenta; |
||||||
|
var public config const Color Purple; |
||||||
|
var public config const Color Indigo; |
||||||
|
var public config const Color DarkSlateBlue; |
||||||
|
var public config const Color SlateBlue; |
||||||
|
var public config const Color MediumSlateBlue; |
||||||
|
// White colors |
||||||
|
var public config const Color White; |
||||||
|
var public config const Color Snow; |
||||||
|
var public config const Color Honeydew; |
||||||
|
var public config const Color MintCream; |
||||||
|
var public config const Color Azure; |
||||||
|
var public config const Color AliceBlue; |
||||||
|
var public config const Color GhostWhite; |
||||||
|
var public config const Color WhiteSmoke; |
||||||
|
var public config const Color Seashell; |
||||||
|
var public config const Color Beige; |
||||||
|
var public config const Color OldLace; |
||||||
|
var public config const Color FloralWhite; |
||||||
|
var public config const Color Ivory; |
||||||
|
var public config const Color AntiqueWhite; |
||||||
|
var public config const Color Linen; |
||||||
|
var public config const Color LavenderBlush; |
||||||
|
var public config const Color MistyRose; |
||||||
|
// Gray and black colors |
||||||
|
var public config const Color Gainsboro; |
||||||
|
var public config const Color LightGray; |
||||||
|
var public config const Color Silver; |
||||||
|
var public config const Color DarkGray; |
||||||
|
var public config const Color Gray; |
||||||
|
var public config const Color DimGray; |
||||||
|
var public config const Color LightSlateGray; |
||||||
|
var public config const Color SlateGray; |
||||||
|
var public config const Color DarkSlateGray; |
||||||
|
var public config const Color Eigengrau; |
||||||
|
var public config const Color Black; |
||||||
|
|
||||||
|
// Escape code point is used to change output's color and is used in |
||||||
|
// Unreal Engine's `string`s. |
||||||
|
var private const int CODEPOINT_ESCAPE; |
||||||
|
var private const int CODEPOINT_SMALL_A; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates opaque color from (red, green, blue) triplet. |
||||||
|
* |
||||||
|
* @param red Red component, range from 0 to 255. |
||||||
|
* @param green Green component, range from 0 to 255. |
||||||
|
* @param blue Blue component, range from 0 to 255. |
||||||
|
* @return `Color` with specified red, green and blue component and |
||||||
|
* alpha component of `255`. |
||||||
|
*/ |
||||||
|
public final function Color RGB(byte red, byte green, byte blue) |
||||||
|
{ |
||||||
|
local Color result; |
||||||
|
result.r = red; |
||||||
|
result.g = green; |
||||||
|
result.b = blue; |
||||||
|
result.a = 255; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates color from (red, green, blue, alpha) quadruplet. |
||||||
|
* |
||||||
|
* @param red Red component, range from 0 to 255. |
||||||
|
* @param green Green component, range from 0 to 255. |
||||||
|
* @param blue Blue component, range from 0 to 255. |
||||||
|
* @param alpha Alpha component, range from 0 to 255. |
||||||
|
* @return `Color` with specified red, green, blue and alpha component. |
||||||
|
*/ |
||||||
|
public final function Color RGBA(byte red, byte green, byte blue, byte alpha) |
||||||
|
{ |
||||||
|
local Color result; |
||||||
|
result.r = red; |
||||||
|
result.g = green; |
||||||
|
result.b = blue; |
||||||
|
result.a = alpha; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Compares two colors for exact equality of red, green and blue components. |
||||||
|
* Alpha component is ignored. |
||||||
|
* |
||||||
|
* @param color1 Color to compare |
||||||
|
* @param color2 Color to compare |
||||||
|
* @return `true` if colors' red, green and blue components are equal |
||||||
|
* and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool AreEqual(Color color1, Color color2, optional bool fixColors) |
||||||
|
{ |
||||||
|
if (fixColors) { |
||||||
|
color1 = FixColor(color1); |
||||||
|
color2 = FixColor(color2); |
||||||
|
} |
||||||
|
if (color1.r != color2.r) return false; |
||||||
|
if (color1.g != color2.g) return false; |
||||||
|
if (color1.b != color2.b) return false; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Compares two colors for exact equality of red, green, blue |
||||||
|
* and alpha components. |
||||||
|
* |
||||||
|
* @param color1 Color to compare |
||||||
|
* @param color2 Color to compare |
||||||
|
* @return `true` if colors' red, green, blue and alpha components are equal |
||||||
|
* and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool AreEqualWithAlpha(Color color1, Color color2, optional bool fixColors) |
||||||
|
{ |
||||||
|
if (fixColors) { |
||||||
|
color1 = FixColor(color1); |
||||||
|
color2 = FixColor(color2); |
||||||
|
} |
||||||
|
if (color1.r != color2.r) return false; |
||||||
|
if (color1.g != color2.g) return false; |
||||||
|
if (color1.b != color2.b) return false; |
||||||
|
if (color1.a != color2.a) return false; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Killing floor's standard methods of rendering colored `string`s |
||||||
|
* make use of inserting 4-byte sequence into them: first bytes denotes |
||||||
|
* the start of the sequence, 3 following bytes denote rgb color components. |
||||||
|
* Unfortunately these methods also have issues with rendering `string`s |
||||||
|
* if you specify certain values (`0` and `10`) of rgb color components. |
||||||
|
* |
||||||
|
* This function "fixes" components by replacing them with close and valid |
||||||
|
* color component values (adds `1` to the component). |
||||||
|
*/ |
||||||
|
public final function byte FixColorComponent(byte colorComponent) |
||||||
|
{ |
||||||
|
if (colorComponent == 0 || colorComponent == 10) |
||||||
|
{ |
||||||
|
return colorComponent + 1; |
||||||
|
} |
||||||
|
return colorComponent; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Killing floor's standard methods of rendering colored `string`s |
||||||
|
* make use of inserting 4-byte sequence into them: first bytes denotes |
||||||
|
* the start of the sequence, 3 following bytes denote rgb color components. |
||||||
|
* Unfortunately these methods also have issues with rendering `string`s |
||||||
|
* if you specify certain values (`0` and `10`) as rgb color components. |
||||||
|
* |
||||||
|
* This function "fixes" given `Color`'s components by replacing them with |
||||||
|
* close and valid color values (using `FixColorComponent()` method), |
||||||
|
* resulting in a `Color` that looks almost the same, but is suitable to be |
||||||
|
* included into 4-byte color change sequence. |
||||||
|
* |
||||||
|
* Since alpha component is never used in color-change sequences, |
||||||
|
* it is never affected. |
||||||
|
*/ |
||||||
|
public final function Color FixColor(Color colorToFix) |
||||||
|
{ |
||||||
|
colorToFix.r = FixColorComponent(colorToFix.r); |
||||||
|
colorToFix.g = FixColorComponent(colorToFix.g); |
||||||
|
colorToFix.b = FixColorComponent(colorToFix.b); |
||||||
|
return colorToFix; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns 4-gyte sequence for color change to a given color. |
||||||
|
* |
||||||
|
* To make returned tag work in most sequences, the value of given color is |
||||||
|
* auto "fixed" (see `FixColor()` for details). |
||||||
|
* There is an option to skip color fixing, but method will still change |
||||||
|
* `0` components to `1`, since they cannot otherwise be used in a tag at all. |
||||||
|
* |
||||||
|
* Also see `GetColorTagRGB()`. |
||||||
|
* |
||||||
|
* @param colorToUse Color to which tag must change the text. |
||||||
|
* It's alpha value (`colorToUse.a`) is discarded. |
||||||
|
* @param doNotFixComponents Minimizes changes to color components |
||||||
|
* (only allows to change `0` components to `1` before creating a tag). |
||||||
|
* @return `string` containing 4-byte sequence that will swap text's color to |
||||||
|
* a given one in standard Unreal Engine's UI. |
||||||
|
*/ |
||||||
|
public final function string GetColorTag( |
||||||
|
Color colorToUse, |
||||||
|
optional bool doNotFixComponents) |
||||||
|
{ |
||||||
|
if (!doNotFixComponents) { |
||||||
|
colorToUse = FixColor(colorToUse); |
||||||
|
} |
||||||
|
colorToUse.r = Max(1, colorToUse.r); |
||||||
|
colorToUse.g = Max(1, colorToUse.g); |
||||||
|
colorToUse.b = Max(1, colorToUse.b); |
||||||
|
return Chr(CODEPOINT_ESCAPE) |
||||||
|
$ Chr(colorToUse.r) |
||||||
|
$ Chr(colorToUse.g) |
||||||
|
$ Chr(colorToUse.b); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns 4-gyte sequence for color change to a given color. |
||||||
|
* |
||||||
|
* To make returned tag work in most sequences, the value of given color is |
||||||
|
* auto "fixed" (see `FixColor()` for details). |
||||||
|
* There is an option to skip color fixing, but method will still change |
||||||
|
* `0` components to `1`, since they cannot otherwise be used in a tag at all. |
||||||
|
* |
||||||
|
* Also see `GetColorTag()`. |
||||||
|
* |
||||||
|
* @param red Red component of color to which tag must |
||||||
|
* change the text. |
||||||
|
* @param green Green component of color to which tag must |
||||||
|
* change the text. |
||||||
|
* @param blue Blue component of color to which tag must |
||||||
|
* change the text. |
||||||
|
* @param doNotFixComponents Minimizes changes to color components |
||||||
|
* (only allows to change `0` components to `1` before creating a tag). |
||||||
|
* @return `string` containing 4-byte sequence that will swap text's color to |
||||||
|
* a given one in standard Unreal Engine's UI. |
||||||
|
*/ |
||||||
|
public final function string GetColorTagRGB( |
||||||
|
int red, |
||||||
|
int green, |
||||||
|
int blue, |
||||||
|
optional bool doNotFixComponents) |
||||||
|
{ |
||||||
|
if (!doNotFixComponents) |
||||||
|
{ |
||||||
|
red = FixColorComponent(red); |
||||||
|
green = FixColorComponent(green); |
||||||
|
blue = FixColorComponent(blue); |
||||||
|
} |
||||||
|
red = Max(1, red); |
||||||
|
green = Max(1, green); |
||||||
|
blue = Max(1, blue); |
||||||
|
return Chr(CODEPOINT_ESCAPE) $ Chr(red) $ Chr(green) $ Chr(blue); |
||||||
|
} |
||||||
|
|
||||||
|
// Helper function that converts `byte` with values between 0 and 15 into |
||||||
|
// a corresponding hex letter |
||||||
|
private final function string ByteToHexCharacter(byte component) |
||||||
|
{ |
||||||
|
component = Clamp(component, 0, 15); |
||||||
|
if (component < 10) { |
||||||
|
return string(component); |
||||||
|
} |
||||||
|
return Chr(component - 10 + CODEPOINT_SMALL_A); |
||||||
|
} |
||||||
|
|
||||||
|
// `byte` to `string` in hex |
||||||
|
private final function string ComponentToHex(byte component) |
||||||
|
{ |
||||||
|
local byte high4Bits, low4Bits; |
||||||
|
low4Bits = component % 16; |
||||||
|
if (component >= 16) { |
||||||
|
high4Bits = (component - low4Bits) / 16; |
||||||
|
} |
||||||
|
else { |
||||||
|
high4Bits = 0; |
||||||
|
} |
||||||
|
return ByteToHexCharacter(high4Bits) $ ByteToHexCharacter(low4Bits); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Displays given color as a string in a given style |
||||||
|
* (hex color representation by default). |
||||||
|
* |
||||||
|
* @param colorToConvert Color to display as a `string`. |
||||||
|
* @param displayType `enum` value, describing how should color |
||||||
|
* be displayed. |
||||||
|
* @return `string` representation of a given color in a given style. |
||||||
|
*/ |
||||||
|
public final function string ToStringType( |
||||||
|
Color colorToConvert, |
||||||
|
optional ColorDisplayType displayType) |
||||||
|
{ |
||||||
|
if (displayType == CLRDISPLAY_HEX) { |
||||||
|
return "#" $ ComponentToHex(colorToConvert.r) |
||||||
|
$ ComponentToHex(colorToConvert.g) |
||||||
|
$ ComponentToHex(colorToConvert.b); |
||||||
|
} |
||||||
|
else if (displayType == CLRDISPLAY_RGB) |
||||||
|
{ |
||||||
|
return "rgb(" $ string(colorToConvert.r) $ "," |
||||||
|
$ string(colorToConvert.g) $ "," |
||||||
|
$ string(colorToConvert.b) $ ")"; |
||||||
|
} |
||||||
|
else if (displayType == CLRDISPLAY_RGBA) |
||||||
|
{ |
||||||
|
return "rgba(" $ string(colorToConvert.r) $ "," |
||||||
|
$ string(colorToConvert.g) $ "," |
||||||
|
$ string(colorToConvert.b) $ "," |
||||||
|
$ string(colorToConvert.a) $ ")"; |
||||||
|
} |
||||||
|
else if (displayType == CLRDISPLAY_RGB_TAG) |
||||||
|
{ |
||||||
|
return "rgb(r=" $ string(colorToConvert.r) $ "," |
||||||
|
$ "g=" $ string(colorToConvert.g) $ "," |
||||||
|
$ "b=" $ string(colorToConvert.b) $ ")"; |
||||||
|
} |
||||||
|
//else if (displayType == CLRDISPLAY_RGBA_TAG) |
||||||
|
return "rgba(r=" $ string(colorToConvert.r) $ "," |
||||||
|
$ "g=" $ string(colorToConvert.g) $ "," |
||||||
|
$ "b=" $ string(colorToConvert.b) $ "," |
||||||
|
$ "a=" $ string(colorToConvert.a) $ ")"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Displays given color as a string in RGB or RGBA format, depending on |
||||||
|
* whether color is opaque. |
||||||
|
* |
||||||
|
* @param colorToConvert Color to display as a `string` in `CLRDISPLAY_RGB` |
||||||
|
* style if `colorToConvert.a == 255` and `CLRDISPLAY_RGBA` otherwise. |
||||||
|
* @return `string` representation of a given color in a given style. |
||||||
|
*/ |
||||||
|
public final function string ToString(Color colorToConvert) |
||||||
|
{ |
||||||
|
if (colorToConvert.a < 255) { |
||||||
|
return ToStringType(colorToConvert, CLRDISPLAY_RGBA); |
||||||
|
} |
||||||
|
return ToStringType(colorToConvert, CLRDISPLAY_RGB); |
||||||
|
} |
||||||
|
|
||||||
|
// Parses color in `CLRDISPLAY_RGB` and `CLRDISPLAY_RGB_TAG` representations. |
||||||
|
private final function Color ParseRGB(Parser parser) |
||||||
|
{ |
||||||
|
local int redComponent; |
||||||
|
local int greenComponent; |
||||||
|
local int blueComponent; |
||||||
|
local Parser.ParserState initialParserState; |
||||||
|
initialParserState = parser.GetCurrentState(); |
||||||
|
parser.Match("rgb(", true) |
||||||
|
.MInteger(redComponent).Match(",") |
||||||
|
.MInteger(greenComponent).Match(",") |
||||||
|
.MInteger(blueComponent).Match(")"); |
||||||
|
if (!parser.Ok()) |
||||||
|
{ |
||||||
|
parser.RestoreState(initialParserState).Match("rgb(", true) |
||||||
|
.Match("r=", true).MInteger(redComponent).Match(",") |
||||||
|
.Match("g=", true).MInteger(greenComponent).Match(",") |
||||||
|
.Match("b=", true).MInteger(blueComponent).Match(")"); |
||||||
|
} |
||||||
|
return RGB(redComponent, greenComponent, blueComponent); |
||||||
|
} |
||||||
|
|
||||||
|
// Parses color in `CLRDISPLAY_RGBA` and `CLRDISPLAY_RGBA_TAG` representations. |
||||||
|
private final function Color ParseRGBA(Parser parser) |
||||||
|
{ |
||||||
|
local int redComponent; |
||||||
|
local int greenComponent; |
||||||
|
local int blueComponent; |
||||||
|
local int alphaComponent; |
||||||
|
local Parser.ParserState initialParserState; |
||||||
|
initialParserState = parser.GetCurrentState(); |
||||||
|
parser.Match("rgba(", true) |
||||||
|
.MInteger(redComponent).Match(",") |
||||||
|
.MInteger(greenComponent).Match(",") |
||||||
|
.MInteger(blueComponent).Match(",") |
||||||
|
.MInteger(alphaComponent).Match(")"); |
||||||
|
if (!parser.Ok()) |
||||||
|
{ |
||||||
|
parser.RestoreState(initialParserState).Match("rgba(", true) |
||||||
|
.Match("r=", true).MInteger(redComponent).Match(",") |
||||||
|
.Match("g=", true).MInteger(greenComponent).Match(",") |
||||||
|
.Match("b=", true).MInteger(blueComponent).Match(",") |
||||||
|
.Match("a=", true).MInteger(alphaComponent).Match(")"); |
||||||
|
} |
||||||
|
return RGBA(redComponent, greenComponent, blueComponent, alphaComponent); |
||||||
|
} |
||||||
|
|
||||||
|
// Parses color in `CLRDISPLAY_HEX` representation. |
||||||
|
private final function Color ParseHexColor(Parser parser) |
||||||
|
{ |
||||||
|
local int redComponent; |
||||||
|
local int greenComponent; |
||||||
|
local int blueComponent; |
||||||
|
parser.Match("#") |
||||||
|
.MUnsignedInteger(redComponent, 16, 2) |
||||||
|
.MUnsignedInteger(greenComponent, 16, 2) |
||||||
|
.MUnsignedInteger(blueComponent, 16, 2); |
||||||
|
return RGB(redComponent, greenComponent, blueComponent); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Uses given parser to try and parse a color in any of the |
||||||
|
* `ColorDisplayType` representations. |
||||||
|
* |
||||||
|
* @param parser Parser that method would use to parse color from |
||||||
|
* wherever it left. It's confirmed state will not be changed. |
||||||
|
* Do not treat `parser` bein in a non-failed state as a confirmation of |
||||||
|
* successful parsing: color parsing might fail regardless. |
||||||
|
* Check return value for that. |
||||||
|
* @param resultingColor Parsed color will be written here if parsing is |
||||||
|
* successful, otherwise value is undefined. |
||||||
|
* If parsed color did not specify alpha component - 255 will be used. |
||||||
|
* @return `true` if parsing was successful and false otherwise. |
||||||
|
*/ |
||||||
|
public final function bool ParseWith(Parser parser, out Color resultingColor) |
||||||
|
{ |
||||||
|
local bool successfullyParsed; |
||||||
|
local string colorAlias; |
||||||
|
local Parser colorParser; |
||||||
|
local Parser.ParserState initialParserState; |
||||||
|
if (parser == none) return false; |
||||||
|
resultingColor.a = 0xff; |
||||||
|
colorParser = parser; |
||||||
|
initialParserState = parser.GetCurrentState(); |
||||||
|
if (parser.Match("$").MUntil(colorAlias,, true).Ok()) |
||||||
|
{ |
||||||
|
colorParser = _.text.ParseString(_.alias.TryColor(colorAlias)); |
||||||
|
initialParserState = colorParser.GetCurrentState(); |
||||||
|
} |
||||||
|
else { |
||||||
|
parser.RestoreState(initialParserState); |
||||||
|
} |
||||||
|
resultingColor = ParseRGB(colorParser); |
||||||
|
if (!colorParser.Ok()) |
||||||
|
{ |
||||||
|
colorParser.RestoreState(initialParserState); |
||||||
|
resultingColor = ParseRGBA(colorParser); |
||||||
|
} |
||||||
|
if (!colorParser.Ok()) |
||||||
|
{ |
||||||
|
colorParser.RestoreState(initialParserState); |
||||||
|
resultingColor = ParseHexColor(colorParser); |
||||||
|
} |
||||||
|
successfullyParsed = colorParser.Ok(); |
||||||
|
if (colorParser != parser) { |
||||||
|
_.memory.Free(colorParser); |
||||||
|
} |
||||||
|
return successfullyParsed; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses a color in any of the `ColorDisplayType` representations from the |
||||||
|
* beginning of a given `string`. |
||||||
|
* |
||||||
|
* @param stringWithColor String, that contains color definition at |
||||||
|
* the beginning. Anything after color definition is not used. |
||||||
|
* @param resultingColor Parsed color will be written here if parsing is |
||||||
|
* successful, otherwise value is undefined. |
||||||
|
* If parsed color did not specify alpha component - 255 will be used. |
||||||
|
* @param stringType How to treat given `string`, |
||||||
|
* see `StringType` for more details. |
||||||
|
* @return `true` if parsing was successful and false otherwise. |
||||||
|
*/ |
||||||
|
public final function bool ParseString( |
||||||
|
string stringWithColor, |
||||||
|
out Color resultingColor, |
||||||
|
optional Text.StringType stringType) |
||||||
|
{ |
||||||
|
local bool successfullyParsed; |
||||||
|
local Parser colorParser; |
||||||
|
colorParser = _.text.ParseString(stringWithColor, stringType); |
||||||
|
successfullyParsed = ParseWith(colorParser, resultingColor); |
||||||
|
_.memory.Free(colorParser); |
||||||
|
return successfullyParsed; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses a color in any of the `ColorDisplayType` representations from the |
||||||
|
* beginning of a given `Text`. |
||||||
|
* |
||||||
|
* @param textWithColor `Text`, that contains color definition at |
||||||
|
* the beginning. Anything after color definition is not used. |
||||||
|
* @param resultingColor Parsed color will be written here if parsing is |
||||||
|
* successful, otherwise value is undefined. |
||||||
|
* If parsed color did not specify alpha component - 255 will be used. |
||||||
|
* @return `true` if parsing was successful and false otherwise. |
||||||
|
*/ |
||||||
|
public final function bool ParseText( |
||||||
|
Text textWithColor, |
||||||
|
out Color resultingColor) |
||||||
|
{ |
||||||
|
local bool successfullyParsed; |
||||||
|
local Parser colorParser; |
||||||
|
colorParser = _.text.Parse(textWithColor); |
||||||
|
successfullyParsed = ParseWith(colorParser, resultingColor); |
||||||
|
_.memory.Free(colorParser); |
||||||
|
return successfullyParsed; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses a color in any of the `ColorDisplayType` representations from the |
||||||
|
* beginning of a given raw data. |
||||||
|
* |
||||||
|
* @param rawDataWithColor Raw data, that contains color definition at |
||||||
|
* the beginning. Anything after color definition is not used. |
||||||
|
* @param resultingColor Parsed color will be written here if parsing is |
||||||
|
* successful, otherwise value is undefined. |
||||||
|
* If parsed color did not specify alpha component - 255 will be used. |
||||||
|
* @return `true` if parsing was successful and false otherwise. |
||||||
|
*/ |
||||||
|
public final function bool ParseRaw( |
||||||
|
array<Text.Character> rawDataWithColor, |
||||||
|
out Color resultingColor) |
||||||
|
{ |
||||||
|
local bool successfullyParsed; |
||||||
|
local Parser colorParser; |
||||||
|
colorParser = _.text.ParseRaw(rawDataWithColor); |
||||||
|
successfullyParsed = ParseWith(colorParser, resultingColor); |
||||||
|
_.memory.Free(colorParser); |
||||||
|
return successfullyParsed; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
Pink=(R=255,G=192,B=203,A=255) |
||||||
|
LightPink=(R=255,G=182,B=193,A=255) |
||||||
|
HotPink=(R=255,G=105,B=180,A=255) |
||||||
|
DeepPink=(R=255,G=20,B=147,A=255) |
||||||
|
PaleVioletRed=(R=219,G=112,B=147,A=255) |
||||||
|
MediumVioletRed=(R=199,G=21,B=133,A=255) |
||||||
|
LightSalmon=(R=255,G=160,B=122,A=255) |
||||||
|
Salmon=(R=250,G=128,B=114,A=255) |
||||||
|
DarkSalmon=(R=233,G=150,B=122,A=255) |
||||||
|
LightCoral=(R=240,G=128,B=128,A=255) |
||||||
|
IndianRed=(R=205,G=92,B=92,A=255) |
||||||
|
Crimson=(R=220,G=20,B=60,A=255) |
||||||
|
Firebrick=(R=178,G=34,B=34,A=255) |
||||||
|
DarkRed=(R=139,G=0,B=0,A=255) |
||||||
|
Red=(R=255,G=0,B=0,A=255) |
||||||
|
OrangeRed=(R=255,G=69,B=0,A=255) |
||||||
|
Tomato=(R=255,G=99,B=71,A=255) |
||||||
|
Coral=(R=255,G=127,B=80,A=255) |
||||||
|
DarkOrange=(R=255,G=140,B=0,A=255) |
||||||
|
Orange=(R=255,G=165,B=0,A=255) |
||||||
|
Yellow=(R=255,G=255,B=0,A=255) |
||||||
|
LightYellow=(R=255,G=255,B=224,A=255) |
||||||
|
LemonChiffon=(R=255,G=250,B=205,A=255) |
||||||
|
LightGoldenrodYellow=(R=250,G=250,B=210,A=255) |
||||||
|
PapayaWhip=(R=255,G=239,B=213,A=255) |
||||||
|
Moccasin=(R=255,G=228,B=181,A=255) |
||||||
|
PeachPuff=(R=255,G=218,B=185,A=255) |
||||||
|
PaleGoldenrod=(R=238,G=232,B=170,A=255) |
||||||
|
Khaki=(R=240,G=230,B=140,A=255) |
||||||
|
DarkKhaki=(R=189,G=183,B=107,A=255) |
||||||
|
Gold=(R=255,G=215,B=0,A=255) |
||||||
|
Cornsilk=(R=255,G=248,B=220,A=255) |
||||||
|
BlanchedAlmond=(R=255,G=235,B=205,A=255) |
||||||
|
Bisque=(R=255,G=228,B=196,A=255) |
||||||
|
NavajoWhite=(R=255,G=222,B=173,A=255) |
||||||
|
Wheat=(R=245,G=222,B=179,A=255) |
||||||
|
Burlywood=(R=222,G=184,B=135,A=255) |
||||||
|
TanColor=(R=210,G=180,B=140,A=255) |
||||||
|
RosyBrown=(R=188,G=143,B=143,A=255) |
||||||
|
SandyBrown=(R=244,G=164,B=96,A=255) |
||||||
|
Goldenrod=(R=218,G=165,B=32,A=255) |
||||||
|
DarkGoldenrod=(R=184,G=134,B=11,A=255) |
||||||
|
Peru=(R=205,G=133,B=63,A=255) |
||||||
|
Chocolate=(R=210,G=105,B=30,A=255) |
||||||
|
SaddleBrown=(R=139,G=69,B=19,A=255) |
||||||
|
Sienna=(R=160,G=82,B=45,A=255) |
||||||
|
Brown=(R=165,G=42,B=42,A=255) |
||||||
|
Maroon=(R=128,G=0,B=0,A=255) |
||||||
|
DarkOliveGreen=(R=85,G=107,B=47,A=255) |
||||||
|
Olive=(R=128,G=128,B=0,A=255) |
||||||
|
OliveDrab=(R=107,G=142,B=35,A=255) |
||||||
|
YellowGreen=(R=154,G=205,B=50,A=255) |
||||||
|
LimeGreen=(R=50,G=205,B=50,A=255) |
||||||
|
Lime=(R=0,G=255,B=0,A=255) |
||||||
|
LawnGreen=(R=124,G=252,B=0,A=255) |
||||||
|
Chartreuse=(R=127,G=255,B=0,A=255) |
||||||
|
GreenYellow=(R=173,G=255,B=47,A=255) |
||||||
|
SpringGreen=(R=0,G=255,B=127,A=255) |
||||||
|
MediumSpringGreen=(R=0,G=250,B=154,A=255) |
||||||
|
LightGreen=(R=144,G=238,B=144,A=255) |
||||||
|
PaleGreen=(R=152,G=251,B=152,A=255) |
||||||
|
DarkSeaGreen=(R=143,G=188,B=143,A=255) |
||||||
|
MediumAquamarine=(R=102,G=205,B=170,A=255) |
||||||
|
MediumSeaGreen=(R=60,G=179,B=113,A=255) |
||||||
|
SeaGreen=(R=46,G=139,B=87,A=255) |
||||||
|
ForestGreen=(R=34,G=139,B=34,A=255) |
||||||
|
Green=(R=0,G=128,B=0,A=255) |
||||||
|
DarkGreen=(R=0,G=100,B=0,A=255) |
||||||
|
Aqua=(R=0,G=255,B=255,A=255) |
||||||
|
Cyan=(R=0,G=255,B=255,A=255) |
||||||
|
LightCyan=(R=224,G=255,B=255,A=255) |
||||||
|
PaleTurquoise=(R=175,G=238,B=238,A=255) |
||||||
|
Aquamarine=(R=127,G=255,B=212,A=255) |
||||||
|
Turquoise=(R=64,G=224,B=208,A=255) |
||||||
|
MediumTurquoise=(R=72,G=209,B=204,A=255) |
||||||
|
DarkTurquoise=(R=0,G=206,B=209,A=255) |
||||||
|
LightSeaGreen=(R=32,G=178,B=170,A=255) |
||||||
|
CadetBlue=(R=95,G=158,B=160,A=255) |
||||||
|
DarkCyan=(R=0,G=139,B=139,A=255) |
||||||
|
Teal=(R=0,G=128,B=128,A=255) |
||||||
|
LightSteelBlue=(R=176,G=196,B=222,A=255) |
||||||
|
PowderBlue=(R=176,G=224,B=230,A=255) |
||||||
|
LightBlue=(R=173,G=216,B=230,A=255) |
||||||
|
SkyBlue=(R=135,G=206,B=235,A=255) |
||||||
|
LightSkyBlue=(R=135,G=206,B=250,A=255) |
||||||
|
DeepSkyBlue=(R=0,G=191,B=255,A=255) |
||||||
|
DodgerBlue=(R=30,G=144,B=255,A=255) |
||||||
|
CornflowerBlue=(R=100,G=149,B=237,A=255) |
||||||
|
SteelBlue=(R=70,G=130,B=180,A=255) |
||||||
|
RoyalBlue=(R=65,G=105,B=225,A=255) |
||||||
|
Blue=(R=0,G=0,B=255,A=255) |
||||||
|
MediumBlue=(R=0,G=0,B=205,A=255) |
||||||
|
DarkBlue=(R=0,G=0,B=139,A=255) |
||||||
|
Navy=(R=0,G=0,B=128,A=255) |
||||||
|
MidnightBlue=(R=25,G=25,B=112,A=255) |
||||||
|
Lavender=(R=230,G=230,B=250,A=255) |
||||||
|
Thistle=(R=216,G=191,B=216,A=255) |
||||||
|
Plum=(R=221,G=160,B=221,A=255) |
||||||
|
Violet=(R=238,G=130,B=238,A=255) |
||||||
|
Orchid=(R=218,G=112,B=214,A=255) |
||||||
|
Fuchsia=(R=255,G=0,B=255,A=255) |
||||||
|
Magenta=(R=255,G=0,B=255,A=255) |
||||||
|
MediumOrchid=(R=186,G=85,B=211,A=255) |
||||||
|
MediumPurple=(R=147,G=112,B=219,A=255) |
||||||
|
BlueViolet=(R=138,G=43,B=226,A=255) |
||||||
|
DarkViolet=(R=148,G=0,B=211,A=255) |
||||||
|
DarkOrchid=(R=153,G=50,B=204,A=255) |
||||||
|
DarkMagenta=(R=139,G=0,B=139,A=255) |
||||||
|
Purple=(R=128,G=0,B=128,A=255) |
||||||
|
Indigo=(R=75,G=0,B=130,A=255) |
||||||
|
DarkSlateBlue=(R=72,G=61,B=139,A=255) |
||||||
|
SlateBlue=(R=106,G=90,B=205,A=255) |
||||||
|
MediumSlateBlue=(R=123,G=104,B=238,A=255) |
||||||
|
White=(R=255,G=255,B=255,A=255) |
||||||
|
Snow=(R=255,G=250,B=250,A=255) |
||||||
|
Honeydew=(R=240,G=255,B=240,A=255) |
||||||
|
MintCream=(R=245,G=255,B=250,A=255) |
||||||
|
Azure=(R=240,G=255,B=255,A=255) |
||||||
|
AliceBlue=(R=240,G=248,B=255,A=255) |
||||||
|
GhostWhite=(R=248,G=248,B=255,A=255) |
||||||
|
WhiteSmoke=(R=245,G=245,B=245,A=255) |
||||||
|
Seashell=(R=255,G=245,B=238,A=255) |
||||||
|
Beige=(R=245,G=245,B=220,A=255) |
||||||
|
OldLace=(R=253,G=245,B=230,A=255) |
||||||
|
FloralWhite=(R=255,G=250,B=240,A=255) |
||||||
|
Ivory=(R=255,G=255,B=240,A=255) |
||||||
|
AntiqueWhite=(R=250,G=235,B=215,A=255) |
||||||
|
Linen=(R=250,G=240,B=230,A=255) |
||||||
|
LavenderBlush=(R=255,G=240,B=245,A=255) |
||||||
|
MistyRose=(R=255,G=228,B=225,A=255) |
||||||
|
Gainsboro=(R=220,G=220,B=220,A=255) |
||||||
|
LightGray=(R=211,G=211,B=211,A=255) |
||||||
|
Silver=(R=192,G=192,B=192,A=255) |
||||||
|
Gray=(R=169,G=169,B=169,A=255) |
||||||
|
DimGray=(R=128,G=128,B=128,A=255) |
||||||
|
DarkGray=(R=105,G=105,B=105,A=255) |
||||||
|
LightSlateGray=(R=119,G=136,B=153,A=255) |
||||||
|
SlateGray=(R=112,G=128,B=144,A=255) |
||||||
|
DarkSlateGray=(R=47,G=79,B=79,A=255) |
||||||
|
Eigengrau=(R=22,G=22,B=29,A=255) |
||||||
|
Black=(R=0,G=0,B=0,A=255) |
||||||
|
CODEPOINT_SMALL_A = 97 |
||||||
|
CODEPOINT_ESCAPE = 27 |
||||||
|
} |
@ -0,0 +1,507 @@ |
|||||||
|
/** |
||||||
|
* Set of tests for Color API. |
||||||
|
* 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 TEST_ColorAPI extends TestCase |
||||||
|
abstract; |
||||||
|
|
||||||
|
protected static function TESTS() |
||||||
|
{ |
||||||
|
Test_ColorCreation(); |
||||||
|
Test_EqualityCheck(); |
||||||
|
Test_ColorFixing(); |
||||||
|
Test_ToString(); |
||||||
|
Test_Parse(); |
||||||
|
Test_GetTag(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_ColorCreation() |
||||||
|
{ |
||||||
|
Context("Testing `ColorAPI`'s functions for creating color structures."); |
||||||
|
SubTest_ColorCreationRGB(); |
||||||
|
SubTest_ColorCreationRGBA(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ColorCreationRGB() |
||||||
|
{ |
||||||
|
local Color createdColor; |
||||||
|
Issue("`RGB() function does not set red, green and blue components" |
||||||
|
@ "correctly."); |
||||||
|
createdColor = _().color.RGB(145, 67, 237); |
||||||
|
TEST_ExpectTrue(createdColor.r == 145); |
||||||
|
TEST_ExpectTrue(createdColor.g == 67); |
||||||
|
TEST_ExpectTrue(createdColor.b == 237); |
||||||
|
|
||||||
|
Issue("`RGB() function does not set alpha component to 255."); |
||||||
|
TEST_ExpectTrue(createdColor.a == 255); |
||||||
|
|
||||||
|
Issue("`RGB() function does not set special values (border values" |
||||||
|
@ "`0`, `255` and value `10`, incorrect for coloring a `string`) for" |
||||||
|
@"red, green and blue components correctly."); |
||||||
|
createdColor = _().color.RGB(0, 10, 255); |
||||||
|
TEST_ExpectTrue(createdColor.r == 0); |
||||||
|
TEST_ExpectTrue(createdColor.g == 10); |
||||||
|
TEST_ExpectTrue(createdColor.b == 255); |
||||||
|
|
||||||
|
Issue("`RGB() function does not set alpha value to 255."); |
||||||
|
TEST_ExpectTrue(createdColor.a == 255); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ColorCreationRGBA() |
||||||
|
{ |
||||||
|
local Color createdColor; |
||||||
|
Issue("`RGBA() function does not set red, green, blue, alpha" |
||||||
|
@ "components correctly."); |
||||||
|
createdColor = _().color.RGBA(93, 245, 1, 67); |
||||||
|
TEST_ExpectTrue(createdColor.r == 93); |
||||||
|
TEST_ExpectTrue(createdColor.g == 245); |
||||||
|
TEST_ExpectTrue(createdColor.b == 1); |
||||||
|
TEST_ExpectTrue(createdColor.a == 67); |
||||||
|
|
||||||
|
Issue("`RGBA() function does not set special values (border values" |
||||||
|
@ "`0`, `255` and value `10`, incorrect for coloring a `string`) for" |
||||||
|
@"red, green, blue components correctly."); |
||||||
|
createdColor = _().color.RGBA(0, 10, 10, 255); |
||||||
|
TEST_ExpectTrue(createdColor.r == 0); |
||||||
|
TEST_ExpectTrue(createdColor.g == 10); |
||||||
|
TEST_ExpectTrue(createdColor.b == 10); |
||||||
|
TEST_ExpectTrue(createdColor.a == 255); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_EqualityCheck() |
||||||
|
{ |
||||||
|
Context("Testing `ColorAPI`'s functions for color equality check."); |
||||||
|
SubTest_EqualityCheckNotFixed(); |
||||||
|
SubTest_EqualityCheckFixed(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_EqualityCheckNotFixed() |
||||||
|
{ |
||||||
|
local Color color1, color2, color3; |
||||||
|
color1 = _().color.RGB(45, 10, 19); |
||||||
|
color2 = _().color.RGB(45, 11, 19); |
||||||
|
color3 = _().color.RGBA(45, 10, 19, 178); |
||||||
|
Issue("`AreEqual()` does not recognized equal colors as such."); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(color1, color1)); |
||||||
|
|
||||||
|
Issue("`AreEqual()` does not recognized colors that differ only in alpha" |
||||||
|
@ "channel as equal."); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(color1, color3)); |
||||||
|
|
||||||
|
Issue("`AreEqual()` does not recognized different colors as such."); |
||||||
|
TEST_ExpectFalse(_().color.AreEqual(color1, color2)); |
||||||
|
|
||||||
|
Issue("`AreEqualWithAlpha()` does not recognized equal colors as such."); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(color1, color1)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(color3, color3)); |
||||||
|
|
||||||
|
Issue("`AreEqualWithAlpha()` does not recognized different colors" |
||||||
|
@ "as such."); |
||||||
|
TEST_ExpectFalse(_().color.AreEqualWithAlpha(color1, color2)); |
||||||
|
TEST_ExpectFalse(_().color.AreEqualWithAlpha(color1, color3)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_EqualityCheckFixed() |
||||||
|
{ |
||||||
|
local Color color1, color2, color3; |
||||||
|
color1 = _().color.RGB(45, 10, 0); |
||||||
|
color2 = _().color.RGB(45, 239, 19); |
||||||
|
color3 = _().color.RGBA(45, 11, 1, 178); |
||||||
|
Issue("`AreEqual()` does not recognized equal colors as such (with color" |
||||||
|
@ "auto-fix)."); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(color1, color1, true)); |
||||||
|
|
||||||
|
Issue("`AreEqual()` does not recognized colors that differ only in alpha" |
||||||
|
@ "channel as equal (with color auto-fix)."); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(color1, color3, true)); |
||||||
|
|
||||||
|
Issue("`AreEqual()` does not recognized different colors as such" |
||||||
|
@ "(with color auto-fix)."); |
||||||
|
TEST_ExpectFalse(_().color.AreEqual(color1, color2, true)); |
||||||
|
|
||||||
|
Issue("`AreEqualWithAlpha()` does not recognized equal colors as such" |
||||||
|
@ "(with color auto-fix)."); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(color1, color1, true)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(color3, color3, true)); |
||||||
|
|
||||||
|
Issue("`AreEqualWithAlpha()` does not recognized different colors as such" |
||||||
|
@ "(with color auto-fix)."); |
||||||
|
TEST_ExpectFalse(_().color.AreEqualWithAlpha(color1, color2, true)); |
||||||
|
TEST_ExpectFalse(_().color.AreEqualWithAlpha(color1, color3, true)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_ColorFixing() |
||||||
|
{ |
||||||
|
local Color validColor, brokenColor; |
||||||
|
validColor = _().color.RGB(23, 179, 244); |
||||||
|
brokenColor = _().color.RGB(10, 35, 0); |
||||||
|
Context("Testing `ColorAPI`'s functions for fixing color components for" |
||||||
|
@ "game's native render functions."); |
||||||
|
Issue("`FixColorComponent()` does not \"fix\" values it is expected to," |
||||||
|
@ "the way it is expected to."); |
||||||
|
TEST_ExpectTrue(_().color.FixColorComponent(0) == 1); |
||||||
|
TEST_ExpectTrue(_().color.FixColorComponent(10) == 11); |
||||||
|
|
||||||
|
Issue("`FixColorComponent()` changes values it should not."); |
||||||
|
TEST_ExpectTrue(_().color.FixColorComponent(9) == 9); |
||||||
|
TEST_ExpectTrue(_().color.FixColorComponent(255) == 255); |
||||||
|
TEST_ExpectTrue(_().color.FixColorComponent(87) == 87); |
||||||
|
|
||||||
|
Issue("`FixColor()` changes colors it should not."); |
||||||
|
TEST_ExpectTrue( |
||||||
|
_().color.AreEqualWithAlpha(validColor, |
||||||
|
_().color.FixColor(validColor))); |
||||||
|
|
||||||
|
Issue("`FixColor()` doesn't fix color it should fix in an expected way."); |
||||||
|
TEST_ExpectTrue( |
||||||
|
_().color.AreEqualWithAlpha(_().color.RGB(11, 35, 1), |
||||||
|
_().color.FixColor(brokenColor))); |
||||||
|
|
||||||
|
Issue("`FixColor()` affects alpha channel."); |
||||||
|
TEST_ExpectTrue(_().color.FixColor(validColor).a == 255); |
||||||
|
validColor.a = 0; |
||||||
|
TEST_ExpectTrue(_().color.FixColor(validColor).a == 0); |
||||||
|
validColor.a = 10; |
||||||
|
TEST_ExpectTrue(_().color.FixColor(validColor).a == 10); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_ToString() |
||||||
|
{ |
||||||
|
Context("Testing `ColorAPI`'s `ToString()` function."); |
||||||
|
SubTest_ToStringType(); |
||||||
|
SubTest_ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ToStringType() |
||||||
|
{ |
||||||
|
local Color normalColor, borderValueColor; |
||||||
|
normalColor = _().color.RGBA(24, 232, 187, 34); |
||||||
|
borderValueColor = _().color.RGBA(0, 255, 255, 0); |
||||||
|
Issue("`ToStringType()` improperly works with `CLRDISPLAY_HEX` option."); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(normalColor) ~= "#18e8bb"); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(borderValueColor) ~= "#00ffff"); |
||||||
|
|
||||||
|
Issue("`ToStringType()` improperly works with `CLRDISPLAY_RGB` option."); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(normalColor, CLRDISPLAY_RGB) |
||||||
|
~= "rgb(24,232,187)"); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(borderValueColor, CLRDISPLAY_RGB) |
||||||
|
~= "rgb(0,255,255)"); |
||||||
|
|
||||||
|
Issue("`ToStringType()` improperly works with `CLRDISPLAY_RGBA` option."); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(normalColor, CLRDISPLAY_RGBA) |
||||||
|
~= "rgba(24,232,187,34)"); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(borderValueColor, CLRDISPLAY_RGBA) |
||||||
|
~= "rgba(0,255,255,0)"); |
||||||
|
|
||||||
|
Issue("`ToStringType()` improperly works with `CLRDISPLAY_RGB_TAG`" |
||||||
|
@ "option."); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(normalColor, CLRDISPLAY_RGB_TAG) |
||||||
|
~= "rgb(r=24,g=232,b=187)"); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(borderValueColor, CLRDISPLAY_RGB_TAG) |
||||||
|
~= "rgb(r=0,g=255,b=255)"); |
||||||
|
|
||||||
|
Issue("`ToStringType()` improperly works with `CLRDISPLAY_RGBA_TAG`" |
||||||
|
@ "option."); |
||||||
|
TEST_ExpectTrue(_().color.ToStringType(normalColor, CLRDISPLAY_RGBA_TAG) |
||||||
|
~= "rgba(r=24,g=232,b=187,a=34)"); |
||||||
|
TEST_ExpectTrue( |
||||||
|
_().color.ToStringType(borderValueColor, CLRDISPLAY_RGBA_TAG) |
||||||
|
~= "rgba(r=0,g=255,b=255,a=0)"); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ToString() |
||||||
|
{ |
||||||
|
local Color opaqueColor, transparentColor; |
||||||
|
opaqueColor = _().color.RGBA(143, 211, 43, 255); |
||||||
|
transparentColor = _().color.RGBA(234, 32, 145, 13); |
||||||
|
Issue("`ToString()` improperly converts color with opaque color."); |
||||||
|
TEST_ExpectTrue(_().color.ToString(opaqueColor) ~= "rgb(143,211,43)"); |
||||||
|
Issue("`ToString()` improperly converts color with transparent color."); |
||||||
|
TEST_ExpectTrue(_().color.ToString(transparentColor) |
||||||
|
~= "rgba(234,32,145,13)"); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_GetTag() |
||||||
|
{ |
||||||
|
Context("Testing `ColorAPI`'s functionality of creating 4-byte color" |
||||||
|
@ "change sequences."); |
||||||
|
SubTest_GetTagColor(); |
||||||
|
SubTest_GetTagRGB(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_GetTagColor() |
||||||
|
{ |
||||||
|
local Color normalColor, borderColor; |
||||||
|
normalColor = _().color.RGB(143, 211, 43); |
||||||
|
borderColor = _().color.RGB(10, 0, 255); |
||||||
|
Issue("`GetColorTag()` does not properly convert colors."); |
||||||
|
TEST_ExpectTrue(_().color.GetColorTag(normalColor) |
||||||
|
== (Chr(27) $ Chr(143) $ Chr(211) $ Chr(43))); |
||||||
|
TEST_ExpectTrue(_().color.GetColorTag(borderColor) |
||||||
|
== (Chr(27) $ Chr(11) $ Chr(1) $ Chr(255))); |
||||||
|
|
||||||
|
Issue("`GetColorTag()` does not properly convert colors when asked not to" |
||||||
|
@ "fix components."); |
||||||
|
TEST_ExpectTrue(_().color.GetColorTag(normalColor, true) |
||||||
|
== (Chr(27) $ Chr(143) $ Chr(211) $ Chr(43))); |
||||||
|
TEST_ExpectTrue(_().color.GetColorTag(borderColor, true) |
||||||
|
== (Chr(27) $ Chr(10) $ Chr(1) $ Chr(255))); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_GetTagRGB() |
||||||
|
{ |
||||||
|
Issue("`GetColorTagRGB()` does not properly convert colors."); |
||||||
|
TEST_ExpectTrue(_().color.GetColorTagRGB(143, 211, 43) |
||||||
|
== (Chr(27) $ Chr(143) $ Chr(211) $ Chr(43))); |
||||||
|
TEST_ExpectTrue(_().color.GetColorTagRGB(10, 0, 255) |
||||||
|
== (Chr(27) $ Chr(11) $ Chr(1) $ Chr(255))); |
||||||
|
|
||||||
|
Issue("`GetColorTagRGB()` does not properly convert colors when asked" |
||||||
|
@ "not to fix components."); |
||||||
|
TEST_ExpectTrue(_().color.GetColorTagRGB(143, 211, 43, true) |
||||||
|
== (Chr(27) $ Chr(143) $ Chr(211) $ Chr(43))); |
||||||
|
TEST_ExpectTrue(_().color.GetColorTagRGB(10, 0, 255, true) |
||||||
|
== (Chr(27) $ Chr(10) $ Chr(1) $ Chr(255))); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_Parse() |
||||||
|
{ |
||||||
|
Context("Testing `ColorAPI`'s parsing functionality."); |
||||||
|
SubTest_ParseWithParser(); |
||||||
|
SubTest_ParseStringPlain(); |
||||||
|
SubTest_ParseStringColored(); |
||||||
|
SubTest_ParseStringFormatted(); |
||||||
|
SubTest_ParseText(); |
||||||
|
SubTest_ParseRaw(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ParseWithParser() |
||||||
|
{ |
||||||
|
local Color expectedColor, resultColor; |
||||||
|
expectedColor = _().color.RGBA(154, 255, 0, 187); |
||||||
|
Issue("`ParseWith()` cannot parse hex colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseWith(_().text.ParseString("#9aff00"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseWith()` cannot parse rgb colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseWith(_().text.ParseString("rgb(154,255,0)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseWith()` cannot parse rgba colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseWith( |
||||||
|
_().text.ParseString("rgba(154,255,0,187)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseWith()` cannot parse rgb colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseWith( |
||||||
|
_().text.ParseString("rgb(r=154,g=255,b=0)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseWith()` cannot parse rgba colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseWith( |
||||||
|
_().text.ParseString("rgba(r=154,g=255,b=0,a=187)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseWith()` reports success when parsing invalid color string."); |
||||||
|
TEST_ExpectFalse(_().color.ParseWith( _().text.ParseString("#9aff0g"), |
||||||
|
resultColor)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ParseStringPlain() |
||||||
|
{ |
||||||
|
local Color expectedColor, resultColor; |
||||||
|
expectedColor = _().color.RGBA(154, 255, 0, 187); |
||||||
|
Issue("`ParseString()` cannot parse hex colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString("#9aff00", resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString()` cannot parse rgb colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString("rgb(154,255,0)", resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString()` cannot parse rgba colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString("rgba(154,255,0,187)", resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString()` cannot parse rgb colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString("rgb(r=154,g=255,b=0)", resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString()` cannot parse rgba colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( "rgba(r=154,g=255,b=0,a=187)", |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString()` reports success when parsing invalid color string."); |
||||||
|
TEST_ExpectFalse(_().color.ParseString("#9aff0g", resultColor)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ParseStringColored() |
||||||
|
{ |
||||||
|
local Color expectedColor, resultColor; |
||||||
|
expectedColor = _().color.RGBA(154, 255, 0, 187); |
||||||
|
Issue("`ParseString(STRING_Colored)` cannot parse hex colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"#9af" $ Chr(27) $ Chr(45) $ Chr(234) $ Chr(24) $ "f00", |
||||||
|
resultColor, STRING_Colored)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString(STRING_Colored)` cannot parse rgb colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"rgb(154,2" $ Chr(27) $ Chr(23) $ Chr(32) $ Chr(53) $ "55,0)", |
||||||
|
resultColor, STRING_Colored)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString(STRING_Colored)` cannot parse rgba colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"rgba(154,255,0,187" $ Chr(27) $ Chr(133) $ Chr(234) $ Chr(10) $ ")", |
||||||
|
resultColor, STRING_Colored)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString(STRING_Colored)` cannot parse rgb colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"rg" $ Chr(27) $ Chr(26) $ Chr(234) $ Chr(125) $ "b(r=154,g=255,b=0)", |
||||||
|
resultColor, STRING_Colored)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString(STRING_Colored)` cannot parse rgba colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"rgba(r=154,g=255,b" $ Chr(27) $ Chr(1) $ Chr(4) $ Chr(7) $ "=0,a=187)", |
||||||
|
resultColor, STRING_Colored)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ParseStringFormatted() |
||||||
|
{ |
||||||
|
local Color expectedColor, resultColor; |
||||||
|
expectedColor = _().color.RGBA(154, 255, 0, 187); |
||||||
|
Issue("`ParseString(STRING_Formatted)` cannot parse hex colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"#9a{#4753d5 ff0}0", |
||||||
|
resultColor, STRING_Formatted)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString(STRING_Formatted)` cannot parse rgb colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"rg{rgb(45,67,123) b(154,25}5,0)", |
||||||
|
resultColor, STRING_Formatted)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString(STRING_Formatted)` cannot parse rgba colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"rgba(154,2{#34d1a7 }55,0,187)", |
||||||
|
resultColor, STRING_Formatted)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString(STRING_Formatted)` cannot parse rgb colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"rgb(r{#34d1a7 }=154,g=255,b=0)", |
||||||
|
resultColor, STRING_Formatted)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseString(STRING_Formatted)` cannot parse rgba colors with" |
||||||
|
@ "tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseString( |
||||||
|
"r{rgb(12,12,253) gba(r=154,g=255,b=0,a=187)}", |
||||||
|
resultColor, STRING_Formatted)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ParseText() |
||||||
|
{ |
||||||
|
local Color expectedColor, resultColor; |
||||||
|
expectedColor = _().color.RGBA(154, 255, 0, 187); |
||||||
|
Issue("`ParseText()` cannot parse hex colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseText(_().text.FromString("#9aff00"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseText()` cannot parse rgb colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseText(_().text.FromString("rgb(154,255,0)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseText()` cannot parse rgba colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseText( |
||||||
|
_().text.FromString("rgba(154,255,0,187)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseText()` cannot parse rgb colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseText( |
||||||
|
_().text.FromString("rgb(r=154,g=255,b=0)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseText()` cannot parse rgba colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseText( |
||||||
|
_().text.FromString("rgba(r=154,g=255,b=0,a=187)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseText()` reports success when parsing invalid color string."); |
||||||
|
TEST_ExpectFalse(_().color.ParseText( _().text.FromString("#9aff0g"), |
||||||
|
resultColor)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ParseRaw() |
||||||
|
{ |
||||||
|
local Color expectedColor, resultColor; |
||||||
|
expectedColor = _().color.RGBA(154, 255, 0, 187); |
||||||
|
Issue("`ParseRaw()` cannot parse hex colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseRaw( _().text.StringToRaw("#9aff00"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseRaw()` cannot parse rgb colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseRaw( _().text.StringToRaw("rgb(154,255,0)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseRaw()` cannot parse rgba colors."); |
||||||
|
TEST_ExpectTrue(_().color.ParseRaw( |
||||||
|
_().text.StringToRaw("rgba(154,255,0,187)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseRaw()` cannot parse rgb colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseRaw( |
||||||
|
_().text.StringToRaw("rgb(r=154,g=255,b=0)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqual(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseRaw()` cannot parse rgba colors with tags."); |
||||||
|
TEST_ExpectTrue(_().color.ParseRaw( |
||||||
|
_().text.StringToRaw("rgba(r=154,g=255,b=0,a=187)"), |
||||||
|
resultColor)); |
||||||
|
TEST_ExpectTrue(_().color.AreEqualWithAlpha(resultColor, expectedColor)); |
||||||
|
|
||||||
|
Issue("`ParseRaw()` reports success when parsing invalid color string."); |
||||||
|
TEST_ExpectFalse(_().color.ParseRaw(_().text.StringToRaw("#9aff0g"), |
||||||
|
resultColor)); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
caseName = "Colors" |
||||||
|
} |
@ -0,0 +1,280 @@ |
|||||||
|
/** |
||||||
|
* API that provides functions for outputting text in |
||||||
|
* Killing Floor's console. It takes care of coloring output and breaking up |
||||||
|
* long lines (since allowing game to handle line breaking completely |
||||||
|
* messes up console output). |
||||||
|
* |
||||||
|
* Actual output is taken care of by `ConsoleWriter` objects that this |
||||||
|
* API generates. |
||||||
|
* 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 ConsoleAPI extends Singleton |
||||||
|
config(AcediaSystem); |
||||||
|
|
||||||
|
/** |
||||||
|
* Main issue with console output in Killing Floor is |
||||||
|
* automatic line breaking of long enough messages: |
||||||
|
* it breaks formatting and can lead to an ugly text overlapping. |
||||||
|
* To fix this we will try to break up user's output into lines ourselves, |
||||||
|
* before game does it for us. |
||||||
|
* |
||||||
|
* We are not 100% sure how Killing Floor decides when to break the line, |
||||||
|
* but it seems to calculate how much text can actually fit in a certain |
||||||
|
* area on screen. |
||||||
|
* There are two issues: |
||||||
|
* 1. We do not know for sure what this limit value is. |
||||||
|
* Even if we knew how to compute it, we cannot do that in server mode, |
||||||
|
* since it depends on a screen resolution and font, which |
||||||
|
* can vary for different players. |
||||||
|
* 2. Even invisible characters, such as color change sequences, |
||||||
|
* that do not take any space on the screen, contribute towards |
||||||
|
* that limit. So for a heavily colored text we will have to |
||||||
|
* break line much sooner than for the plain text. |
||||||
|
* Both issues are solved by introducing two limits that users themselves |
||||||
|
* are allowed to change: visible character limit and total character limit. |
||||||
|
* ~ Total character limit will be a hard limit on a character amount in |
||||||
|
* a line (including hidden ones used for color change sequences) that |
||||||
|
* will be used to prevent Killing Floor's native line breaks. |
||||||
|
* ~ Visible character limit will be a lower limit on amount of actually |
||||||
|
* visible character. It introduction basically reserves some space that can be |
||||||
|
* used only for color change sequences. Without this limit lines with |
||||||
|
* colored lines will appear to be shorter that mono-colored ones. |
||||||
|
* Visible limit will help to alleviate this problem. |
||||||
|
* |
||||||
|
* For example, if we set total limit to `120` and visible limit to `80`: |
||||||
|
* 1. Line not formatted with color will all break at |
||||||
|
* around length of `80`. |
||||||
|
* 2. Since color change sequence consists of 4 characters: |
||||||
|
* we can fit up to `(120 - 80) / 4 = 10` color swaps into each line, |
||||||
|
* while still breaking them at a around the same length of `80`. |
||||||
|
* ~ To differentiate our line breaks from line breaks intended by |
||||||
|
* the user, we will also add 2 symbols worth of padding in front of all our |
||||||
|
* output: |
||||||
|
* 1. Before intended new line they will be just two spaces. |
||||||
|
* 2. After our line break we will replace first space with "|" to indicate |
||||||
|
* that we had to break a long line. |
||||||
|
* |
||||||
|
* Described measures are not perfect: |
||||||
|
* 1. Since Killing Floor's console doe not use monospaced font, |
||||||
|
* the same amount of characters on the line does not mean lines of |
||||||
|
* visually the same length; |
||||||
|
* 2. Heavily enough colored lines are still going to be shorter; |
||||||
|
* 3. Depending on a resolution, default limits may appear to either use |
||||||
|
* too little space (for high resolutions) or, on the contrary, |
||||||
|
* not prevent native line breaks (low resolutions). |
||||||
|
* In these cases user might be required to manually set limits; |
||||||
|
* 4. There are probably more. |
||||||
|
* But if seems to provide good enough results for the average use case. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Configures how text will be rendered in target console(s). |
||||||
|
*/ |
||||||
|
struct ConsoleDisplaySettings |
||||||
|
{ |
||||||
|
// What color to use for text by default |
||||||
|
var Color defaultColor; |
||||||
|
// How many visible characters in be displayed in a line? |
||||||
|
var int maxVisibleLineWidth; |
||||||
|
// How many total characters can be output at once? |
||||||
|
var int maxTotalLineWidth; |
||||||
|
}; |
||||||
|
// We will store data for `ConsoleDisplaySettings` separately for the ease of |
||||||
|
// configuration. |
||||||
|
var private config Color defaultColor; |
||||||
|
var private config int maxVisibleLineWidth; |
||||||
|
var private config int maxTotalLineWidth; |
||||||
|
|
||||||
|
/** |
||||||
|
* Return current global visible limit that describes how many (at most) |
||||||
|
* visible characters can be output in the console line. |
||||||
|
* |
||||||
|
* Instances of `ConsoleWriter` are initialized with this value, |
||||||
|
* but can later change this value independently. |
||||||
|
* Changes to global values do not affect already created `ConsoleWriters`. |
||||||
|
* |
||||||
|
* @return Current global visible limit. |
||||||
|
*/ |
||||||
|
public final function int GetVisibleLineLength() |
||||||
|
{ |
||||||
|
return maxVisibleLineWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets current global visible limit that describes how many (at most) visible |
||||||
|
* characters can be output in the console line. |
||||||
|
* |
||||||
|
* Instances of `ConsoleWriter` are initialized with this value, |
||||||
|
* but can later change this value independently. |
||||||
|
* Changes to global values do not affect already created `ConsoleWriters`. |
||||||
|
* |
||||||
|
* @param newMaxVisibleLineWidth New global visible character limit. |
||||||
|
*/ |
||||||
|
public final function SetVisibleLineLength(int newMaxVisibleLineWidth) |
||||||
|
{ |
||||||
|
maxVisibleLineWidth = newMaxVisibleLineWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return current global total limit that describes how many (at most) |
||||||
|
* characters can be output in the console line. |
||||||
|
* |
||||||
|
* Instances of `ConsoleWriter` are initialized with this value, |
||||||
|
* but can later change this value independently. |
||||||
|
* Changes to global values do not affect already created `ConsoleWriters`. |
||||||
|
* |
||||||
|
* @return Current global total limit. |
||||||
|
*/ |
||||||
|
public final function int GetTotalLineLength() |
||||||
|
{ |
||||||
|
return maxTotalLineWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets current global total limit that describes how many (at most) |
||||||
|
* characters can be output in the console line, counting both visible symbols |
||||||
|
* and color change sequences. |
||||||
|
* |
||||||
|
* Instances of `ConsoleWriter` are initialized with this value, |
||||||
|
* but can later change this value independently. |
||||||
|
* Changes to global values do not affect already created `ConsoleWriters`. |
||||||
|
* |
||||||
|
* @param newMaxTotalLineWidth New global total character limit. |
||||||
|
*/ |
||||||
|
public final function SetTotalLineLength(int newMaxTotalLineWidth) |
||||||
|
{ |
||||||
|
maxTotalLineWidth = newMaxTotalLineWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return current global total limit that describes how many (at most) |
||||||
|
* characters can be output in the console line. |
||||||
|
* |
||||||
|
* Instances of `ConsoleWriter` are initialized with this value, |
||||||
|
* but can later change this value independently. |
||||||
|
* Changes to global values do not affect already created `ConsoleWriters`. |
||||||
|
* |
||||||
|
* @return Current default output color. |
||||||
|
*/ |
||||||
|
public final function Color GetDefaultColor(int newMaxTotalLineWidth) |
||||||
|
{ |
||||||
|
return defaultColor; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets current global default color for console output., |
||||||
|
* |
||||||
|
* Instances of `ConsoleWriter` are initialized with this value, |
||||||
|
* but can later change this value independently. |
||||||
|
* Changes to global values do not affect already created `ConsoleWriters`. |
||||||
|
* |
||||||
|
* @param newMaxTotalLineWidth New global default output color. |
||||||
|
*/ |
||||||
|
public final function SetDefaultColor(Color newDefaultColor) |
||||||
|
{ |
||||||
|
defaultColor = newDefaultColor; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns borrowed `ConsoleWriter` instance that will write into |
||||||
|
* consoles of all players. |
||||||
|
* |
||||||
|
* @return ConsoleWriter Borrowed `ConsoleWriter` instance, configured to |
||||||
|
* write into consoles of all players. |
||||||
|
* Never `none`. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter ForAll() |
||||||
|
{ |
||||||
|
local ConsoleDisplaySettings globalSettings; |
||||||
|
globalSettings.defaultColor = defaultColor; |
||||||
|
globalSettings.maxTotalLineWidth = maxTotalLineWidth; |
||||||
|
globalSettings.maxVisibleLineWidth = maxVisibleLineWidth; |
||||||
|
return ConsoleWriter(_.memory.Claim(class'ConsoleWriter')) |
||||||
|
.Initialize(globalSettings).ForAll(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns borrowed `ConsoleWriter` instance that will write into |
||||||
|
* console of the player with a given controller. |
||||||
|
* |
||||||
|
* @param targetController Player, to whom console we want to write. |
||||||
|
* If `none` - returned `ConsoleWriter` would be configured to |
||||||
|
* throw messages away. |
||||||
|
* @return Borrowed `ConsoleWriter` instance, configured to |
||||||
|
* write into consoles of all players. |
||||||
|
* Never `none`. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter For(PlayerController targetController) |
||||||
|
{ |
||||||
|
local ConsoleDisplaySettings globalSettings; |
||||||
|
globalSettings.defaultColor = defaultColor; |
||||||
|
globalSettings.maxTotalLineWidth = maxTotalLineWidth; |
||||||
|
globalSettings.maxVisibleLineWidth = maxVisibleLineWidth; |
||||||
|
return ConsoleWriter(_.memory.Claim(class'ConsoleWriter')) |
||||||
|
.Initialize(globalSettings).ForController(targetController); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns new `ConsoleWriter` instance that will write into |
||||||
|
* consoles of all players. |
||||||
|
* Should be freed after use. |
||||||
|
* |
||||||
|
* @return ConsoleWriter New `ConsoleWriter` instance, configured to |
||||||
|
* write into consoles of all players. |
||||||
|
* Never `none`. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter MakeForAll() |
||||||
|
{ |
||||||
|
local ConsoleDisplaySettings globalSettings; |
||||||
|
globalSettings.defaultColor = defaultColor; |
||||||
|
globalSettings.maxTotalLineWidth = maxTotalLineWidth; |
||||||
|
globalSettings.maxVisibleLineWidth = maxVisibleLineWidth; |
||||||
|
return ConsoleWriter(_.memory.Allocate(class'ConsoleWriter')) |
||||||
|
.Initialize(globalSettings).ForAll(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns new `ConsoleWriter` instance that will write into |
||||||
|
* console of the player with a given controller. |
||||||
|
* Should be freed after use. |
||||||
|
* |
||||||
|
* @param targetController Player, to whom console we want to write. |
||||||
|
* If `none` - returned `ConsoleWriter` would be configured to |
||||||
|
* throw messages away. |
||||||
|
* @return New `ConsoleWriter` instance, configured to |
||||||
|
* write into consoles of all players. |
||||||
|
* Never `none`. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter MakeFor(PlayerController targetController) |
||||||
|
{ |
||||||
|
local ConsoleDisplaySettings globalSettings; |
||||||
|
globalSettings.defaultColor = defaultColor; |
||||||
|
globalSettings.maxTotalLineWidth = maxTotalLineWidth; |
||||||
|
globalSettings.maxVisibleLineWidth = maxVisibleLineWidth; |
||||||
|
return ConsoleWriter(_.memory.Allocate(class'ConsoleWriter')) |
||||||
|
.Initialize(globalSettings).ForController(targetController); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
defaultColor = (R=255,G=255,B=255,A=255) |
||||||
|
// These should guarantee decent text output even at |
||||||
|
// 640x480 shit resolution |
||||||
|
maxVisibleLineWidth = 80 |
||||||
|
maxTotalLineWidth = 108 |
||||||
|
} |
@ -0,0 +1,393 @@ |
|||||||
|
/** |
||||||
|
* Object that provides a buffer functionality for Killing Floor's (in-game) |
||||||
|
* console output: it accepts content that user want to output and breaks it |
||||||
|
* into lines that will be well-rendered according to the given |
||||||
|
* `ConsoleDisplaySettings`. |
||||||
|
* 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 ConsoleBuffer extends AcediaObject |
||||||
|
dependson(Text) |
||||||
|
dependson(ConsoleAPI); |
||||||
|
|
||||||
|
/** |
||||||
|
* `ConsoleBuffer` works by breaking it's input into words, counting how much |
||||||
|
* space they take up and only then deciding to which line to append them |
||||||
|
* (new or the next, new one). |
||||||
|
*/ |
||||||
|
|
||||||
|
var private int CODEPOINT_ESCAPE; |
||||||
|
var private int CODEPOINT_NEWLINE; |
||||||
|
var private int COLOR_SEQUENCE_LENGTH; |
||||||
|
|
||||||
|
// Display settings according to which to format our output |
||||||
|
var private ConsoleAPI.ConsoleDisplaySettings displaySettings; |
||||||
|
|
||||||
|
/** |
||||||
|
* This structure is used to both share results of our work and for tracking |
||||||
|
* information about the line we are currently filling. |
||||||
|
*/ |
||||||
|
struct LineRecord |
||||||
|
{ |
||||||
|
// Contents of the line, in `STRING_Colored` format |
||||||
|
var string contents; |
||||||
|
// Is this a wrapped line? |
||||||
|
// `true` means that this line was supposed to be part part of another, |
||||||
|
// singular line of text, that had to be broken into smaller pieces. |
||||||
|
// Such lines will start with "|" in front of them in Acedia's |
||||||
|
// `ConsoleWriter`. |
||||||
|
var bool wrappedLine; |
||||||
|
// Information variables that describe how many visible and total symbols |
||||||
|
// (visible + color change sequences) are stored int the `line` |
||||||
|
var int visibleSymbolsStored; |
||||||
|
var int totalSymbolsStored; |
||||||
|
// Does `contents` contain a color change sequence? |
||||||
|
// Non-empty line can have no such sequence if they consist of whitespaces. |
||||||
|
var private bool colorInserted; |
||||||
|
// If `colorInserted == true`, stores the last inserted color. |
||||||
|
var private Color endColor; |
||||||
|
}; |
||||||
|
// Lines that are ready to be output to the console |
||||||
|
var private array<LineRecord> completedLines; |
||||||
|
|
||||||
|
// Line we are currently building |
||||||
|
var private LineRecord currentLine; |
||||||
|
// Word we are currently building, colors of it's characters will be |
||||||
|
// automatically converted into `STRCOLOR_Struct`, according to the default |
||||||
|
// color setting at the time of their addition. |
||||||
|
var private array<Text.Character> wordBuffer; |
||||||
|
// Amount of color swaps inside `wordBuffer` |
||||||
|
var private int colorSwapsInWordBuffer; |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns current setting used by this buffer to break up it's input into |
||||||
|
* lines fit to be output in console. |
||||||
|
* |
||||||
|
* @return Currently used `ConsoleDisplaySettings`. |
||||||
|
*/ |
||||||
|
public final function ConsoleAPI.ConsoleDisplaySettings GetSettings() |
||||||
|
{ |
||||||
|
return displaySettings; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets new setting to be used by this buffer to break up it's input into |
||||||
|
* lines fit to be output in console. |
||||||
|
* |
||||||
|
* It is recommended (although not required) to call `Flush()` before |
||||||
|
* changing settings. Not doing so would not lead to any errors or warnings, |
||||||
|
* but can lead to some wonky results and is considered an undefined behavior. |
||||||
|
* |
||||||
|
* @param newSettings New `ConsoleDisplaySettings` to be used. |
||||||
|
* @return Returns caller `ConsoleBuffer` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleBuffer SetSettings( |
||||||
|
ConsoleAPI.ConsoleDisplaySettings newSettings) |
||||||
|
{ |
||||||
|
displaySettings = newSettings; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Does caller `ConsoleBuffer` has any completed lines that can be output? |
||||||
|
* |
||||||
|
* "Completed line" means that nothing else will be added to it. |
||||||
|
* So negative (`false`) response does not mean that the buffer is empty, - |
||||||
|
* it can still contain an uncompleted and non-empty line that can still be |
||||||
|
* expanded with `InsertString()`. If you want to completely empty the buffer - |
||||||
|
* call the `Flush()` method. |
||||||
|
* Also see `IsEmpty()`. |
||||||
|
* |
||||||
|
* @return `true` if caller `ConsoleBuffer` has no completed lines and |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool HasCompletedLines() |
||||||
|
{ |
||||||
|
return (completedLines.length > 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Does caller `ConsoleBuffer` has any unprocessed input? |
||||||
|
* |
||||||
|
* Note that `ConsoleBuffer` can be non-empty, but no completed line if it |
||||||
|
* currently builds one. |
||||||
|
* See `Flush()` and `HasCompletedLines()` methods. |
||||||
|
* |
||||||
|
* @return `true` if `ConsoleBuffer` is completely empty |
||||||
|
* (either did not receive or already returned all processed input) and |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool IsEmpty() |
||||||
|
{ |
||||||
|
if (HasCompletedLines()) return false; |
||||||
|
if (currentLine.totalSymbolsStored > 0) return false; |
||||||
|
if (wordBuffer.length > 0) return false; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Clears the buffer of all data, but leaving current settings intact. |
||||||
|
* After this calling method `IsEmpty()` should return `true`. |
||||||
|
* |
||||||
|
* @return Returns caller `ConsoleBuffer` to allow method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleBuffer Clear() |
||||||
|
{ |
||||||
|
local LineRecord newLineRecord; |
||||||
|
currentLine = newLineRecord; |
||||||
|
completedLines.length = 0; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Inserts a string into the buffer. This method does not automatically break |
||||||
|
* the line after the `input`, call `Flush()` or add line feed symbol "\n" |
||||||
|
* at the end of the `input` if you want that. |
||||||
|
* |
||||||
|
* @param input `string` to be added to the current line in caller |
||||||
|
* `ConsoleBuffer`. |
||||||
|
* @param inputType How to treat given `string` regarding coloring. |
||||||
|
* @return Returns caller `ConsoleBuffer` to allow method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleBuffer InsertString( |
||||||
|
string input, |
||||||
|
Text.StringType inputType) |
||||||
|
{ |
||||||
|
local int inputConsumed; |
||||||
|
local array<Text.Character> rawInput; |
||||||
|
rawInput = _().text.StringToRaw(input, inputType); |
||||||
|
while (rawInput.length > 0) |
||||||
|
{ |
||||||
|
// Fill word buffer, remove consumed input from `rawInput` |
||||||
|
inputConsumed = 0; |
||||||
|
while (inputConsumed < rawInput.length) |
||||||
|
{ |
||||||
|
if (_().text.IsWhitespace(rawInput[inputConsumed])) break; |
||||||
|
InsertIntoWordBuffer(rawInput[inputConsumed]); |
||||||
|
inputConsumed += 1; |
||||||
|
} |
||||||
|
rawInput.Remove(0, inputConsumed); |
||||||
|
// If we didn't encounter any whitespace symbols - bail |
||||||
|
if (rawInput.length <= 0) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
FlushWordBuffer(); |
||||||
|
// Dump whitespaces into lines |
||||||
|
inputConsumed = 0; |
||||||
|
while (inputConsumed < rawInput.length) |
||||||
|
{ |
||||||
|
if (!_().text.IsWhitespace(rawInput[inputConsumed])) break; |
||||||
|
AppendWhitespaceToCurrentLine(rawInput[inputConsumed]); |
||||||
|
inputConsumed += 1; |
||||||
|
} |
||||||
|
rawInput.Remove(0, inputConsumed); |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns (and makes caller `ConsoleBuffer` forget) next completed line that |
||||||
|
* can be output to console in `STRING_Colored` format. |
||||||
|
* |
||||||
|
* If there are no completed line to return - returns an empty one. |
||||||
|
* |
||||||
|
* @return Next completed line that can be output, in `STRING_Colored` format. |
||||||
|
*/ |
||||||
|
public final function LineRecord PopNextLine() |
||||||
|
{ |
||||||
|
local LineRecord result; |
||||||
|
if (completedLines.length <= 0) return result; |
||||||
|
result = completedLines[0]; |
||||||
|
completedLines.Remove(0, 1); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Forces all buffered data into "completed line" array, making it retrievable |
||||||
|
* by `PopNextLine()`. |
||||||
|
* |
||||||
|
* @return Next completed line that can be output, in `STRING_Colored` format. |
||||||
|
*/ |
||||||
|
public final function ConsoleBuffer Flush() |
||||||
|
{ |
||||||
|
FlushWordBuffer(); |
||||||
|
BreakLine(false); |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
// It is assumed that passed characters are not whitespace, - |
||||||
|
// responsibility to check is on the one calling this method. |
||||||
|
private final function InsertIntoWordBuffer(Text.Character newCharacter) |
||||||
|
{ |
||||||
|
local int newCharacterIndex; |
||||||
|
local Color oldColor, newColor; |
||||||
|
newCharacterIndex = wordBuffer.length; |
||||||
|
// Fix text color in the buffer to remember default color, if we use it. |
||||||
|
newCharacter.color = |
||||||
|
_().text.GetCharacterColor(newCharacter, displaySettings.defaultColor); |
||||||
|
newCharacter.colorType = STRCOLOR_Struct; |
||||||
|
wordBuffer[newCharacterIndex] = newCharacter; |
||||||
|
if (newCharacterIndex <= 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
oldColor = wordBuffer[newCharacterIndex].color; |
||||||
|
newColor = wordBuffer[newCharacterIndex - 1].color; |
||||||
|
if (!_().color.AreEqual(oldColor, newColor, true)) { |
||||||
|
colorSwapsInWordBuffer += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Pushes whole `wordBuffer` into lines |
||||||
|
private final function FlushWordBuffer() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Color newColor; |
||||||
|
if (!WordCanFitInCurrentLine() && WordCanFitInNewLine()) { |
||||||
|
BreakLine(true); |
||||||
|
} |
||||||
|
for (i = 0; i < wordBuffer.length; i += 1) |
||||||
|
{ |
||||||
|
if (!CanAppendNonWhitespaceIntoLine(wordBuffer[i])) { |
||||||
|
BreakLine(true); |
||||||
|
} |
||||||
|
newColor = wordBuffer[i].color; |
||||||
|
if (MustSwapColorsFor(newColor)) |
||||||
|
{ |
||||||
|
currentLine.contents $= _().color.GetColorTag(newColor); |
||||||
|
currentLine.totalSymbolsStored += COLOR_SEQUENCE_LENGTH; |
||||||
|
currentLine.colorInserted = true; |
||||||
|
currentLine.endColor = newColor; |
||||||
|
} |
||||||
|
currentLine.contents $= Chr(wordBuffer[i].codePoint); |
||||||
|
currentLine.totalSymbolsStored += 1; |
||||||
|
currentLine.visibleSymbolsStored += 1; |
||||||
|
} |
||||||
|
wordBuffer.length = 0; |
||||||
|
colorSwapsInWordBuffer = 0; |
||||||
|
} |
||||||
|
|
||||||
|
private final function BreakLine(bool makeWrapped) |
||||||
|
{ |
||||||
|
local LineRecord newLineRecord; |
||||||
|
if (currentLine.visibleSymbolsStored > 0) { |
||||||
|
completedLines[completedLines.length] = currentLine; |
||||||
|
} |
||||||
|
currentLine = newLineRecord; |
||||||
|
currentLine.wrappedLine = makeWrapped; |
||||||
|
} |
||||||
|
|
||||||
|
private final function bool MustSwapColorsFor(Color newColor) |
||||||
|
{ |
||||||
|
if (!currentLine.colorInserted) return true; |
||||||
|
return !_().color.AreEqual(currentLine.endColor, newColor, true); |
||||||
|
} |
||||||
|
|
||||||
|
private final function bool CanAppendWhitespaceIntoLine() |
||||||
|
{ |
||||||
|
// We always allow to append at least something into empty line, |
||||||
|
// otherwise we can never insert it anywhere |
||||||
|
if (currentLine.totalSymbolsStored <= 0) return true; |
||||||
|
if (currentLine.totalSymbolsStored >= displaySettings.maxTotalLineWidth) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (currentLine.visibleSymbolsStored >= displaySettings.maxVisibleLineWidth) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private final function bool CanAppendNonWhitespaceIntoLine( |
||||||
|
Text.Character nextCharacter) |
||||||
|
{ |
||||||
|
// We always allow to insert at least something into empty line, |
||||||
|
// otherwise we can never insert it anywhere |
||||||
|
if (currentLine.totalSymbolsStored <= 0) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
// Check if we can fit a single character by fitting a whitespace symbol. |
||||||
|
if (!CanAppendWhitespaceIntoLine()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (!MustSwapColorsFor(nextCharacter.color)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
// Can we fit character + color swap sequence? |
||||||
|
return ( currentLine.totalSymbolsStored + COLOR_SEQUENCE_LENGTH + 1 |
||||||
|
<= displaySettings.maxTotalLineWidth); |
||||||
|
} |
||||||
|
|
||||||
|
// For performance reasons assumes that passed character is a whitespace, |
||||||
|
// the burden of checking is on the caller. |
||||||
|
private final function AppendWhitespaceToCurrentLine(Text.Character whitespace) |
||||||
|
{ |
||||||
|
if (_().text.IsCodePoint(whitespace, CODEPOINT_NEWLINE)) { |
||||||
|
BreakLine(true); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!CanAppendWhitespaceIntoLine()) { |
||||||
|
BreakLine(true); |
||||||
|
} |
||||||
|
currentLine.contents $= Chr(whitespace.codePoint); |
||||||
|
currentLine.totalSymbolsStored += 1; |
||||||
|
currentLine.visibleSymbolsStored += 1; |
||||||
|
} |
||||||
|
|
||||||
|
private final function bool WordCanFitInNewLine() |
||||||
|
{ |
||||||
|
local int totalCharactersInWord; |
||||||
|
if (wordBuffer.length <= 0) return true; |
||||||
|
if (wordBuffer.length > displaySettings.maxVisibleLineWidth) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// `(colorSwapsInWordBuffer + 1)` counts how many times we must |
||||||
|
// switch color inside a word + 1 for setting initial color |
||||||
|
totalCharactersInWord = wordBuffer.length |
||||||
|
+ (colorSwapsInWordBuffer + 1) * COLOR_SEQUENCE_LENGTH; |
||||||
|
return (totalCharactersInWord <= displaySettings.maxTotalLineWidth); |
||||||
|
} |
||||||
|
|
||||||
|
private final function bool WordCanFitInCurrentLine() |
||||||
|
{ |
||||||
|
local int totalLimit, visibleLimit; |
||||||
|
local int totalCharactersInWord; |
||||||
|
if (wordBuffer.length <= 0) return true; |
||||||
|
totalLimit = |
||||||
|
displaySettings.maxTotalLineWidth - currentLine.totalSymbolsStored; |
||||||
|
visibleLimit = |
||||||
|
displaySettings.maxVisibleLineWidth - currentLine.visibleSymbolsStored; |
||||||
|
// Visible symbols check |
||||||
|
if (wordBuffer.length > visibleLimit) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// Total symbols check |
||||||
|
totalCharactersInWord = wordBuffer.length |
||||||
|
+ colorSwapsInWordBuffer * COLOR_SEQUENCE_LENGTH; |
||||||
|
if (MustSwapColorsFor(wordBuffer[0].color)) { |
||||||
|
totalCharactersInWord += COLOR_SEQUENCE_LENGTH; |
||||||
|
} |
||||||
|
return (totalCharactersInWord <= totalLimit); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
CODEPOINT_ESCAPE = 27 |
||||||
|
CODEPOINT_NEWLINE = 10 |
||||||
|
// CODEPOINT_ESCAPE + <redByte> + <greenByte> + <blueByte> |
||||||
|
COLOR_SEQUENCE_LENGTH = 4 |
||||||
|
} |
@ -0,0 +1,373 @@ |
|||||||
|
/** |
||||||
|
* Object that provides simple access to console output. |
||||||
|
* Can either write to a certain player's console or to all consoles at once. |
||||||
|
* Supports "fancy" and "raw" output (for more details @see `ConsoleAPI`). |
||||||
|
* 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 ConsoleWriter extends AcediaObject |
||||||
|
dependson(ConsoleAPI) |
||||||
|
dependson(ConnectionService); |
||||||
|
|
||||||
|
// Prefixes we output before every line to signify whether they were broken |
||||||
|
// or not |
||||||
|
var private string NEWLINE_PREFIX; |
||||||
|
var private string BROKENLINE_PREFIX; |
||||||
|
|
||||||
|
/** |
||||||
|
* Describes current output target of the `ConsoleWriter`. |
||||||
|
*/ |
||||||
|
enum ConsoleWriterTarget |
||||||
|
{ |
||||||
|
// No one. Can happed if our target disconnects. |
||||||
|
CWTARGET_None, |
||||||
|
// A certain player. |
||||||
|
CWTARGET_Player, |
||||||
|
// All players. |
||||||
|
CWTARGET_All |
||||||
|
}; |
||||||
|
var private ConsoleWriterTarget targetType; |
||||||
|
// Controller of the player that will receive output passed |
||||||
|
// to this `ConsoleWriter`. |
||||||
|
// Only used when `targetType == CWTARGET_Player` |
||||||
|
var private PlayerController outputTarget; |
||||||
|
var private ConsoleBuffer outputBuffer; |
||||||
|
|
||||||
|
var private ConsoleAPI.ConsoleDisplaySettings displaySettings; |
||||||
|
|
||||||
|
public final function ConsoleWriter Initialize( |
||||||
|
ConsoleAPI.ConsoleDisplaySettings newDisplaySettings) |
||||||
|
{ |
||||||
|
displaySettings = newDisplaySettings; |
||||||
|
if (outputBuffer == none) { |
||||||
|
outputBuffer = ConsoleBuffer(_().memory.Allocate(class'ConsoleBuffer')); |
||||||
|
} |
||||||
|
else { |
||||||
|
outputBuffer.Clear(); |
||||||
|
} |
||||||
|
outputBuffer.SetSettings(displaySettings); |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return current default color for caller `ConsoleWriter`. |
||||||
|
* |
||||||
|
* This method returns default color, i.e. color that will be used if no other |
||||||
|
* is specified by text you're outputting. |
||||||
|
* If color is specified, this value will be ignored. |
||||||
|
* |
||||||
|
* This value is not synchronized with the global value from `ConsoleAPI` |
||||||
|
* (or such value from any other `ConsoleWriter`) and affects only |
||||||
|
* output produced by this `ConsoleWriter`. |
||||||
|
* |
||||||
|
* @return Current default color. |
||||||
|
*/ |
||||||
|
public final function Color GetColor() |
||||||
|
{ |
||||||
|
return displaySettings.defaultColor; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets default color for caller 'ConsoleWriter`'s output. |
||||||
|
* |
||||||
|
* This only changes default color, i.e. color that will be used if no other is |
||||||
|
* specified by text you're outputting. |
||||||
|
* If color is specified, this value will be ignored. |
||||||
|
* |
||||||
|
* This value is not synchronized with the global value from `ConsoleAPI` |
||||||
|
* (or such value from any other `ConsoleWriter`) and affects only |
||||||
|
* output produced by this `ConsoleWriter`. |
||||||
|
* |
||||||
|
* @param newDefaultColor New color to use when none specified by text itself. |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter SetColor(Color newDefaultColor) |
||||||
|
{ |
||||||
|
displaySettings.defaultColor = newDefaultColor; |
||||||
|
if (outputBuffer != none) { |
||||||
|
outputBuffer.SetSettings(displaySettings); |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return current visible limit that describes how many (at most) |
||||||
|
* visible characters can be output in the console line. |
||||||
|
* |
||||||
|
* This value is not synchronized with the global value from `ConsoleAPI` |
||||||
|
* (or such value from any other `ConsoleWriter`) and affects only |
||||||
|
* output produced by this `ConsoleWriter`. |
||||||
|
* |
||||||
|
* @return Current global visible limit. |
||||||
|
*/ |
||||||
|
public final function int GetVisibleLineLength() |
||||||
|
{ |
||||||
|
return displaySettings.maxVisibleLineWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets current visible limit that describes how many (at most) visible |
||||||
|
* characters can be output in the console line. |
||||||
|
* |
||||||
|
* This value is not synchronized with the global value from `ConsoleAPI` |
||||||
|
* (or such value from any other `ConsoleWriter`) and affects only |
||||||
|
* output produced by this `ConsoleWriter`. |
||||||
|
* |
||||||
|
* @param newVisibleLimit New global visible limit. |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter SetVisibleLineLength( |
||||||
|
int newMaxVisibleLineWidth |
||||||
|
) |
||||||
|
{ |
||||||
|
displaySettings.maxVisibleLineWidth = newMaxVisibleLineWidth; |
||||||
|
if (outputBuffer != none) { |
||||||
|
outputBuffer.SetSettings(displaySettings); |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return current total limit that describes how many (at most) |
||||||
|
* characters can be output in the console line. |
||||||
|
* |
||||||
|
* This value is not synchronized with the global value from `ConsoleAPI` |
||||||
|
* (or such value from any other `ConsoleWriter`) and affects only |
||||||
|
* output produced by this `ConsoleWriter`. |
||||||
|
* |
||||||
|
* @return Current global total limit. |
||||||
|
*/ |
||||||
|
public final function int GetTotalLineLength() |
||||||
|
{ |
||||||
|
return displaySettings.maxTotalLineWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets current total limit that describes how many (at most) |
||||||
|
* characters can be output in the console line. |
||||||
|
* |
||||||
|
* This value is not synchronized with the global value from `ConsoleAPI` |
||||||
|
* (or such value from any other `ConsoleWriter`) and affects only |
||||||
|
* output produced by this `ConsoleWriter`. |
||||||
|
* |
||||||
|
* @param newTotalLimit New global total limit. |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter SetTotalLineLength(int newMaxTotalLineWidth) |
||||||
|
{ |
||||||
|
displaySettings.maxTotalLineWidth = newMaxTotalLineWidth; |
||||||
|
if (outputBuffer != none) { |
||||||
|
outputBuffer.SetSettings(displaySettings); |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Configures caller `ConsoleWriter` to output to all players. |
||||||
|
* `Flush()` will be automatically called between target change. |
||||||
|
* |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter ForAll() |
||||||
|
{ |
||||||
|
Flush(); |
||||||
|
targetType = CWTARGET_All; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Configures caller `ConsoleWriter` to output only to a player, |
||||||
|
* given by a passed `PlayerController`. |
||||||
|
* `Flush()` will be automatically called between target change. |
||||||
|
* |
||||||
|
* @param targetController Player, to whom console we want to write. |
||||||
|
* If `none` - caller `ConsoleWriter` would be configured to |
||||||
|
* throw messages away. |
||||||
|
* @return ConsoleWriter Returns caller `ConsoleWriter` to allow for |
||||||
|
* method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter ForController( |
||||||
|
PlayerController targetController |
||||||
|
) |
||||||
|
{ |
||||||
|
Flush(); |
||||||
|
if (targetController != none) |
||||||
|
{ |
||||||
|
targetType = CWTARGET_Player; |
||||||
|
outputTarget = targetController; |
||||||
|
} |
||||||
|
else { |
||||||
|
targetType = CWTARGET_None; |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns type of current target for the caller `ConsoleWriter`. |
||||||
|
* |
||||||
|
* @return `ConsoleWriterTarget` value, describing current target of |
||||||
|
* the caller `ConsoleWriter`. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriterTarget CurrentTarget() |
||||||
|
{ |
||||||
|
if (targetType == CWTARGET_Player && outputTarget == none) { |
||||||
|
targetType = CWTARGET_None; |
||||||
|
} |
||||||
|
return targetType; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns `PlayerController` of the player to whom console caller |
||||||
|
* `ConsoleWriter` is outputting messages. |
||||||
|
* |
||||||
|
* @return `PlayerController` of the player to whom console caller |
||||||
|
* `ConsoleWriter` is outputting messages. |
||||||
|
* Returns `none` iff it currently outputs to every player or to no one. |
||||||
|
*/ |
||||||
|
public final function PlayerController GetTargetPlayerController() |
||||||
|
{ |
||||||
|
if (targetType == CWTARGET_All) return none; |
||||||
|
return outputTarget; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Outputs all buffered input and moves further output onto a new line. |
||||||
|
* |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter Flush() |
||||||
|
{ |
||||||
|
outputBuffer.Flush(); |
||||||
|
SendBuffer(); |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes a formatted string into console. |
||||||
|
* |
||||||
|
* Does not trigger console output, for that use `WriteLine()` or `Flush()`. |
||||||
|
* |
||||||
|
* To output a different type of string into a console, use `WriteT()`. |
||||||
|
* |
||||||
|
* @param message Formatted string to output. |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter Write(string message) |
||||||
|
{ |
||||||
|
outputBuffer.InsertString(message, STRING_Formatted); |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes a formatted string into console. |
||||||
|
* Result will be output immediately, starts a new line. |
||||||
|
* |
||||||
|
* To output a different type of string into a console, use `WriteLineT()`. |
||||||
|
* |
||||||
|
* @param message Formatted string to output. |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter WriteLine(string message) |
||||||
|
{ |
||||||
|
outputBuffer.InsertString(message, STRING_Formatted); |
||||||
|
Flush(); |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes a `string` of specified type into console. |
||||||
|
* |
||||||
|
* Does not trigger console output, for that use `WriteLineT()` or `Flush()`. |
||||||
|
* |
||||||
|
* To output a formatted string you might want to simply use `Write()`. |
||||||
|
* |
||||||
|
* @param message String of a given type to output. |
||||||
|
* @param inputType Type of the string method should output. |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter WriteT( |
||||||
|
string message, |
||||||
|
Text.StringType inputType) |
||||||
|
{ |
||||||
|
outputBuffer.InsertString(message, inputType); |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes a `string` of specified type into console. |
||||||
|
* Result will be output immediately, starts a new line. |
||||||
|
* |
||||||
|
* To output a formatted string you might want to simply use `WriteLine()`. |
||||||
|
* |
||||||
|
* @param message String of a given type to output. |
||||||
|
* @param inputType Type of the string method should output. |
||||||
|
* @return Returns caller `ConsoleWriter` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function ConsoleWriter WriteLineT( |
||||||
|
string message, |
||||||
|
Text.StringType inputType) |
||||||
|
{ |
||||||
|
outputBuffer.InsertString(message, inputType); |
||||||
|
Flush(); |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
// Send all completed lines from an `outputBuffer` |
||||||
|
private final function SendBuffer() |
||||||
|
{ |
||||||
|
local string prefix; |
||||||
|
local ConnectionService service; |
||||||
|
local ConsoleBuffer.LineRecord nextLineRecord; |
||||||
|
while (outputBuffer.HasCompletedLines()) |
||||||
|
{ |
||||||
|
nextLineRecord = outputBuffer.PopNextLine(); |
||||||
|
if (nextLineRecord.wrappedLine) { |
||||||
|
prefix = NEWLINE_PREFIX; |
||||||
|
} |
||||||
|
else { |
||||||
|
prefix = BROKENLINE_PREFIX; |
||||||
|
} |
||||||
|
service = ConnectionService(class'ConnectionService'.static.Require()); |
||||||
|
SendConsoleMessage(service, prefix $ nextLineRecord.contents); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Assumes `service != none`, caller function must ensure that. |
||||||
|
private final function SendConsoleMessage( |
||||||
|
ConnectionService service, |
||||||
|
string message) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<ConnectionService.Connection> connections; |
||||||
|
if (targetType != CWTARGET_All) |
||||||
|
{ |
||||||
|
if (outputTarget != none) { |
||||||
|
outputTarget.ClientMessage(message); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
connections = service.GetActiveConnections(); |
||||||
|
for (i = 0; i < connections.length; i += 1) { |
||||||
|
connections[i].controllerReference.ClientMessage(message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
NEWLINE_PREFIX = "| " |
||||||
|
BROKENLINE_PREFIX = " " |
||||||
|
} |
@ -0,0 +1,351 @@ |
|||||||
|
/** |
||||||
|
* This class implements JSON array storage capabilities. |
||||||
|
* Array stores ordered JSON values that can be referred by their index. |
||||||
|
* It can contain any mix of JSON value types and cannot have any gaps, |
||||||
|
* i.e. in array of length N, there must be a valid value for all indices |
||||||
|
* from 0 to N-1. |
||||||
|
* 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 JArray extends JSON; |
||||||
|
|
||||||
|
// Data will simply be stored as an array of JSON values |
||||||
|
var private array<JStorageAtom> data; |
||||||
|
|
||||||
|
// Return type of value stored at a given index. |
||||||
|
// Returns `JSON_Undefined` if and only if given index is out of bounds. |
||||||
|
public final function JType GetTypeOf(int index) |
||||||
|
{ |
||||||
|
if (index < 0) return JSON_Undefined; |
||||||
|
if (index >= data.length) return JSON_Undefined; |
||||||
|
|
||||||
|
return data[index].type; |
||||||
|
} |
||||||
|
|
||||||
|
// Returns current length of this array. |
||||||
|
public final function int GetLength() |
||||||
|
{ |
||||||
|
return data.length; |
||||||
|
} |
||||||
|
|
||||||
|
// Changes length of this array. |
||||||
|
// In case of the increase - fills new indices with `null` values. |
||||||
|
public final function SetLength(int newLength) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int oldLength; |
||||||
|
oldLength = data.length; |
||||||
|
data.length = newLength; |
||||||
|
if (oldLength >= newLength) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
i = oldLength; |
||||||
|
while (i < newLength) |
||||||
|
{ |
||||||
|
SetNull(i); |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Following functions are getters for various types of variables. |
||||||
|
// Getter for null value simply checks if it's null |
||||||
|
// and returns true/false as a result. |
||||||
|
// Getters for simple types (number, string, boolean) can have optional |
||||||
|
// default value specified, that will be returned if requested variable |
||||||
|
// doesn't exist or has a different type. |
||||||
|
// Getters for object and array types don't take default values and |
||||||
|
// will simply return `none`. |
||||||
|
public final function float GetNumber(int index, optional float defaultValue) |
||||||
|
{ |
||||||
|
if (index < 0) return defaultValue; |
||||||
|
if (index >= data.length) return defaultValue; |
||||||
|
if (data[index].type != JSON_Number) return defaultValue; |
||||||
|
|
||||||
|
return data[index].numberValue; |
||||||
|
} |
||||||
|
|
||||||
|
public final function string GetString(int index, optional string defaultValue) |
||||||
|
{ |
||||||
|
if (index < 0) return defaultValue; |
||||||
|
if (index >= data.length) return defaultValue; |
||||||
|
if (data[index].type != JSON_String) return defaultValue; |
||||||
|
|
||||||
|
return data[index].stringValue; |
||||||
|
} |
||||||
|
|
||||||
|
public final function bool GetBoolean(int index, optional bool defaultValue) |
||||||
|
{ |
||||||
|
if (index < 0) return defaultValue; |
||||||
|
if (index >= data.length) return defaultValue; |
||||||
|
if (data[index].type != JSON_Boolean) return defaultValue; |
||||||
|
|
||||||
|
return data[index].booleanValue; |
||||||
|
} |
||||||
|
|
||||||
|
public final function bool IsNull(int index) |
||||||
|
{ |
||||||
|
if (index < 0) return false; |
||||||
|
if (index >= data.length) return false; |
||||||
|
|
||||||
|
return (data[index].type == JSON_Null); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray GetArray(int index) |
||||||
|
{ |
||||||
|
if (index < 0) return none; |
||||||
|
if (index >= data.length) return none; |
||||||
|
if (data[index].type != JSON_Array) return none; |
||||||
|
|
||||||
|
return JArray(data[index].complexValue); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JObject GetObject(int index) |
||||||
|
{ |
||||||
|
if (index < 0) return none; |
||||||
|
if (index >= data.length) return none; |
||||||
|
if (data[index].type != JSON_Object) return none; |
||||||
|
|
||||||
|
return JObject(data[index].complexValue); |
||||||
|
} |
||||||
|
|
||||||
|
// Following functions provide simple setters for boolean, string, number |
||||||
|
// and null values. |
||||||
|
// If passed index is negative - does nothing. |
||||||
|
// If index lies beyond array length (`>= GetLength()`), - |
||||||
|
// these functions will expand array in the same way as `GetLength()` function. |
||||||
|
// This can be prevented by setting optional parameter `preventExpansion` to |
||||||
|
// `false` (nothing will be done in this case). |
||||||
|
// They return object itself, allowing user to chain calls like this: |
||||||
|
// `array.SetNumber("num1", 1).SetNumber("num2", 2);`. |
||||||
|
public final function JArray SetNumber |
||||||
|
( |
||||||
|
int index, |
||||||
|
float value, |
||||||
|
optional bool preventExpansion |
||||||
|
) |
||||||
|
{ |
||||||
|
local JStorageAtom newStorageValue; |
||||||
|
if (index < 0) return self; |
||||||
|
|
||||||
|
if (index >= data.length) |
||||||
|
{ |
||||||
|
if (preventExpansion) |
||||||
|
{ |
||||||
|
return self; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
SetLength(index + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
newStorageValue.type = JSON_Number; |
||||||
|
newStorageValue.numberValue = value; |
||||||
|
data[index] = newStorageValue; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray SetString |
||||||
|
( |
||||||
|
int index, |
||||||
|
string value, |
||||||
|
optional bool preventExpansion |
||||||
|
) |
||||||
|
{ |
||||||
|
local JStorageAtom newStorageValue; |
||||||
|
if (index < 0) return self; |
||||||
|
|
||||||
|
if (index >= data.length) |
||||||
|
{ |
||||||
|
if (preventExpansion) |
||||||
|
{ |
||||||
|
return self; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
SetLength(index + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
newStorageValue.type = JSON_String; |
||||||
|
newStorageValue.stringValue = value; |
||||||
|
data[index] = newStorageValue; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray SetBoolean |
||||||
|
( |
||||||
|
int index, |
||||||
|
bool value, |
||||||
|
optional bool preventExpansion |
||||||
|
) |
||||||
|
{ |
||||||
|
local JStorageAtom newStorageValue; |
||||||
|
if (index < 0) return self; |
||||||
|
|
||||||
|
if (index >= data.length) |
||||||
|
{ |
||||||
|
if (preventExpansion) |
||||||
|
{ |
||||||
|
return self; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
SetLength(index + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
newStorageValue.type = JSON_Boolean; |
||||||
|
newStorageValue.booleanValue = value; |
||||||
|
data[index] = newStorageValue; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray SetNull |
||||||
|
( |
||||||
|
int index, |
||||||
|
optional bool preventExpansion |
||||||
|
) |
||||||
|
{ |
||||||
|
local JStorageAtom newStorageValue; |
||||||
|
if (index < 0) return self; |
||||||
|
|
||||||
|
if (index >= data.length) |
||||||
|
{ |
||||||
|
if (preventExpansion) |
||||||
|
{ |
||||||
|
return self; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
SetLength(index + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
newStorageValue.type = JSON_Null; |
||||||
|
data[index] = newStorageValue; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
// JSON array and object types don't have setters, but instead have |
||||||
|
// functions to create a new, empty array/object under a certain name. |
||||||
|
// If passed index is negative - does nothing. |
||||||
|
// If index lies beyond array length (`>= GetLength()`), - |
||||||
|
// these functions will expand array in the same way as `GetLength()` function. |
||||||
|
// This can be prevented by setting optional parameter `preventExpansion` to |
||||||
|
// `false` (nothing will be done in this case). |
||||||
|
// They return object itself, allowing user to chain calls like this: |
||||||
|
// `array.CreateObject("sub object").CreateArray("sub array");`. |
||||||
|
public final function JArray CreateArray |
||||||
|
( |
||||||
|
int index, |
||||||
|
optional bool preventExpansion |
||||||
|
) |
||||||
|
{ |
||||||
|
local JStorageAtom newStorageValue; |
||||||
|
if (index < 0) return self; |
||||||
|
|
||||||
|
if (index >= data.length) |
||||||
|
{ |
||||||
|
if (preventExpansion) |
||||||
|
{ |
||||||
|
return self; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
SetLength(index + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
newStorageValue.type = JSON_Array; |
||||||
|
newStorageValue.complexValue = _.json.newArray(); |
||||||
|
data[index] = newStorageValue; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray CreateObject |
||||||
|
( |
||||||
|
int index, |
||||||
|
optional bool preventExpansion |
||||||
|
) |
||||||
|
{ |
||||||
|
local JStorageAtom newStorageValue; |
||||||
|
if (index < 0) return self; |
||||||
|
|
||||||
|
if (index >= data.length) |
||||||
|
{ |
||||||
|
if (preventExpansion) |
||||||
|
{ |
||||||
|
return self; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
SetLength(index + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
newStorageValue.type = JSON_Object; |
||||||
|
newStorageValue.complexValue = _.json.newObject(); |
||||||
|
data[index] = newStorageValue; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
// Wrappers for setter functions that don't take index or |
||||||
|
// `preventExpansion` parameters and add/create value at the end of the array. |
||||||
|
public final function JArray AddNumber(float value) |
||||||
|
{ |
||||||
|
return SetNumber(data.length, value); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray AddString(string value) |
||||||
|
{ |
||||||
|
return SetString(data.length, value); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray AddBoolean(bool value) |
||||||
|
{ |
||||||
|
return SetBoolean(data.length, value); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray AddNull() |
||||||
|
{ |
||||||
|
return SetNull(data.length); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray AddArray() |
||||||
|
{ |
||||||
|
return CreateArray(data.length); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray AddObject() |
||||||
|
{ |
||||||
|
return CreateObject(data.length); |
||||||
|
} |
||||||
|
|
||||||
|
// Removes up to `amount` (minimum of `1`) of values, starting from |
||||||
|
// a given index. |
||||||
|
// If `index` falls outside array boundaries - nothing will be done. |
||||||
|
// Returns `true` if value was actually removed and `false` if it didn't exist. |
||||||
|
public final function bool RemoveValue(int index, optional int amount) |
||||||
|
{ |
||||||
|
if (index < 0) return false; |
||||||
|
if (index >= data.length) return false; |
||||||
|
|
||||||
|
amount = Max(amount, 1); |
||||||
|
amount = Min(amount, data.length - index); |
||||||
|
data.Remove(index, amount); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,265 @@ |
|||||||
|
/** |
||||||
|
* This class implements JSON object storage capabilities. |
||||||
|
* Whenever one wants to store JSON data, they need to define such object. |
||||||
|
* It stores name-value pairs, where names are strings and values can be: |
||||||
|
* ~ Boolean, string, null or number (float in this implementation) data; |
||||||
|
* ~ Other JSON objects; |
||||||
|
* ~ JSON Arrays (see `JArray` class). |
||||||
|
* |
||||||
|
* This implementation provides getters and setters for boolean, string, |
||||||
|
* null or number types that allow to freely set and fetch their values |
||||||
|
* by name. |
||||||
|
* JSON objects and arrays can be fetched by getters, but you cannot |
||||||
|
* add existing object or array to another object. Instead one has to create |
||||||
|
* a new, empty object with a certain name and then fill it with data. |
||||||
|
* This allows to avoid loop situations, where object is contained in itself. |
||||||
|
* Functions to remove existing values are also provided and are applicable |
||||||
|
* to all variable types. |
||||||
|
* Setters can also be used to overwrite any value by a different value, |
||||||
|
* even of a different type. |
||||||
|
* 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 JObject extends JSON; |
||||||
|
|
||||||
|
// We will store all our properties as a simple array of name-value pairs. |
||||||
|
struct JProperty |
||||||
|
{ |
||||||
|
var string name; |
||||||
|
var JStorageAtom value; |
||||||
|
}; |
||||||
|
var private array<JProperty> properties; |
||||||
|
|
||||||
|
// Returns index of name-value pair in `properties` for a given name. |
||||||
|
// Returns `-1` if such a pair does not exist. |
||||||
|
private final function int GetPropertyIndex(string name) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < properties.length; i += 1) |
||||||
|
{ |
||||||
|
if (name == properties[i].name) |
||||||
|
{ |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// Returns `JType` of a variable with a given name in our properties. |
||||||
|
// This function can be used to check if certain variable exists |
||||||
|
// in this object, since if such variable does not exist - |
||||||
|
// function will return `JSON_Undefined`. |
||||||
|
public final function JType GetTypeOf(string name) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) return JSON_Undefined; |
||||||
|
|
||||||
|
return properties[index].value.type; |
||||||
|
} |
||||||
|
|
||||||
|
// Following functions are getters for various types of variables. |
||||||
|
// Getter for null value simply checks if it's null |
||||||
|
// and returns true/false as a result. |
||||||
|
// Getters for simple types (number, string, boolean) can have optional |
||||||
|
// default value specified, that will be returned if requested variable |
||||||
|
// doesn't exist or has a different type. |
||||||
|
// Getters for object and array types don't take default values and |
||||||
|
// will simply return `none`. |
||||||
|
public final function float GetNumber(string name, optional float defaultValue) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) return defaultValue; |
||||||
|
if (properties[index].value.type != JSON_Number) return defaultValue; |
||||||
|
|
||||||
|
return properties[index].value.numberValue; |
||||||
|
} |
||||||
|
|
||||||
|
public final function string GetString |
||||||
|
( |
||||||
|
string name, |
||||||
|
optional string defaultValue |
||||||
|
) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) return defaultValue; |
||||||
|
if (properties[index].value.type != JSON_String) return defaultValue; |
||||||
|
|
||||||
|
return properties[index].value.stringValue; |
||||||
|
} |
||||||
|
|
||||||
|
public final function bool GetBoolean(string name, optional bool defaultValue) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) return defaultValue; |
||||||
|
if (properties[index].value.type != JSON_Boolean) return defaultValue; |
||||||
|
|
||||||
|
return properties[index].value.booleanValue; |
||||||
|
} |
||||||
|
|
||||||
|
public final function bool IsNull(string name) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) return false; |
||||||
|
if (properties[index].value.type != JSON_Null) return false; |
||||||
|
|
||||||
|
return (properties[index].value.type == JSON_Null); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JArray GetArray(string name) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) return none; |
||||||
|
if (properties[index].value.type != JSON_Array) return none; |
||||||
|
|
||||||
|
return JArray(properties[index].value.complexValue); |
||||||
|
} |
||||||
|
|
||||||
|
public final function JObject GetObject(string name) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) return none; |
||||||
|
if (properties[index].value.type != JSON_Object) return none; |
||||||
|
|
||||||
|
return JObject(properties[index].value.complexValue); |
||||||
|
} |
||||||
|
|
||||||
|
// Following functions provide simple setters for boolean, string, number |
||||||
|
// and null values. |
||||||
|
// They return object itself, allowing user to chain calls like this: |
||||||
|
// `object.SetNumber("num1", 1).SetNumber("num2", 2);`. |
||||||
|
public final function JObject SetNumber(string name, float value) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
local JProperty newProperty; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) |
||||||
|
{ |
||||||
|
index = properties.length; |
||||||
|
} |
||||||
|
|
||||||
|
newProperty.name = name; |
||||||
|
newProperty.value.type = JSON_Number; |
||||||
|
newProperty.value.numberValue = value; |
||||||
|
properties[index] = newProperty; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function JObject SetString(string name, string value) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
local JProperty newProperty; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) |
||||||
|
{ |
||||||
|
index = properties.length; |
||||||
|
} |
||||||
|
newProperty.name = name; |
||||||
|
newProperty.value.type = JSON_String; |
||||||
|
newProperty.value.stringValue = value; |
||||||
|
properties[index] = newProperty; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function JObject SetBoolean(string name, bool value) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
local JProperty newProperty; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) |
||||||
|
{ |
||||||
|
index = properties.length; |
||||||
|
} |
||||||
|
newProperty.name = name; |
||||||
|
newProperty.value.type = JSON_Boolean; |
||||||
|
newProperty.value.booleanValue = value; |
||||||
|
properties[index] = newProperty; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function JObject SetNull(string name) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
local JProperty newProperty; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) |
||||||
|
{ |
||||||
|
index = properties.length; |
||||||
|
} |
||||||
|
newProperty.name = name; |
||||||
|
newProperty.value.type = JSON_Null; |
||||||
|
properties[index] = newProperty; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
// JSON array and object types don't have setters, but instead have |
||||||
|
// functions to create a new, empty array/object under a certain name. |
||||||
|
// They return object itself, allowing user to chain calls like this: |
||||||
|
// `object.CreateObject("folded object").CreateArray("names list");`. |
||||||
|
public final function JObject CreateArray(string name) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
local JProperty newProperty; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) |
||||||
|
{ |
||||||
|
index = properties.length; |
||||||
|
} |
||||||
|
newProperty.name = name; |
||||||
|
newProperty.value.type = JSON_Array; |
||||||
|
newProperty.value.complexValue = _.json.newArray(); |
||||||
|
properties[index] = newProperty; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function JObject CreateObject(string name) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
local JProperty newProperty; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) |
||||||
|
{ |
||||||
|
index = properties.length; |
||||||
|
} |
||||||
|
newProperty.name = name; |
||||||
|
newProperty.value.type = JSON_Object; |
||||||
|
newProperty.value.complexValue = _.json.newObject(); |
||||||
|
properties[index] = newProperty; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
// Removes values with a given name. |
||||||
|
// Returns `true` if value was actually removed and `false` if it didn't exist. |
||||||
|
public final function bool RemoveValue(string name) |
||||||
|
{ |
||||||
|
local int index; |
||||||
|
index = GetPropertyIndex(name); |
||||||
|
if (index < 0) return false; |
||||||
|
|
||||||
|
properties.Remove(index, 1); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
/** |
||||||
|
* JSON is an open standard file format, and data interchange format, |
||||||
|
* that uses human-readable text to store and transmit data objects |
||||||
|
* consisting of name–value pairs and array data types. |
||||||
|
* For more information refer to https://en.wikipedia.org/wiki/JSON |
||||||
|
* This is a base class for implementation of JSON data storage for Acedia. |
||||||
|
* It does not implement parsing and printing from/into human-readable |
||||||
|
* text representation, just provides means to store such information. |
||||||
|
* |
||||||
|
* JSON data is stored as an object (represented via `JSONObject`) that |
||||||
|
* contains a set of name-value pairs, where value can be |
||||||
|
* a number, string, boolean value, another object or |
||||||
|
* an array (represented by `JSONArray`). |
||||||
|
* 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 JSON extends AcediaActor |
||||||
|
abstract; |
||||||
|
|
||||||
|
// Enumeration for possible types of JSON values. |
||||||
|
enum JType |
||||||
|
{ |
||||||
|
// Technical type, used to indicate that requested value is missing. |
||||||
|
// Undefined values are not part of JSON format. |
||||||
|
JSON_Undefined, |
||||||
|
// An empty value, in teste representation defined by a single word "null". |
||||||
|
JSON_Null, |
||||||
|
// A number, recorded as a float. |
||||||
|
// JSON itself doesn't specify whether number is an integer or float. |
||||||
|
JSON_Number, |
||||||
|
// A string. |
||||||
|
JSON_String, |
||||||
|
// A bool value. |
||||||
|
JSON_Boolean, |
||||||
|
// Array of other JSON values, stored without names; |
||||||
|
// Single array can contain any mix of value types. |
||||||
|
JSON_Array, |
||||||
|
// Another JSON object, i.e. associative array of name-value pairs |
||||||
|
JSON_Object |
||||||
|
}; |
||||||
|
|
||||||
|
// Stores a single JSON value |
||||||
|
struct JStorageAtom |
||||||
|
{ |
||||||
|
// What type is stored exactly? |
||||||
|
// Depending on that, uses one of the other fields as a storage. |
||||||
|
var protected JType type; |
||||||
|
var protected float numberValue; |
||||||
|
var protected string stringValue; |
||||||
|
var protected bool booleanValue; |
||||||
|
// Used for storing both JSON objects and arrays. |
||||||
|
var protected JSON complexValue; |
||||||
|
}; |
||||||
|
|
||||||
|
// TODO: Rewrite JSON object to use more efficient storage data structures |
||||||
|
// that will support subtypes: |
||||||
|
// ~ Number: byte, int, float |
||||||
|
// ~ String: string, class |
||||||
|
// (maybe move to auto generated code?). |
||||||
|
// TODO: Add cleanup queue to efficiently and without crashes clean up |
||||||
|
// removed objects. |
||||||
|
// TODO: Add `JValue` - a reference type for number / string / boolean / null |
||||||
|
// TODO: Add accessors for last values. |
||||||
|
// TODO: Add path-getters. |
||||||
|
// TODO: Add iterators. |
||||||
|
// TODO: Add parsing/printing. |
||||||
|
// TODO: Add functions for deep copy. |
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/** |
||||||
|
* Provides convenient access to JSON-related functions. |
||||||
|
* 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 JSONAPI extends Singleton; |
||||||
|
|
||||||
|
public function JObject newObject() |
||||||
|
{ |
||||||
|
local JObject newObject; |
||||||
|
newObject = Spawn(class'JObject'); |
||||||
|
return newObject; |
||||||
|
} |
||||||
|
|
||||||
|
public function JArray newArray() |
||||||
|
{ |
||||||
|
local JArray newArray; |
||||||
|
newArray = Spawn(class'JArray'); |
||||||
|
return newArray; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,711 @@ |
|||||||
|
/** |
||||||
|
* Set of tests for JSON data storage, implemented via |
||||||
|
* `JObject` and `JArray`. |
||||||
|
* 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 TEST_JSON extends TestCase |
||||||
|
abstract; |
||||||
|
|
||||||
|
protected static function TESTS() |
||||||
|
{ |
||||||
|
local JObject jsonData; |
||||||
|
jsonData = _().json.newObject(); |
||||||
|
Test_ObjectGetSetRemove(); |
||||||
|
Test_ArrayGetSetRemove(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_ObjectGetSetRemove() |
||||||
|
{ |
||||||
|
SubTest_Undefined(); |
||||||
|
SubTest_StringGetSetRemove(); |
||||||
|
SubTest_BooleanGetSetRemove(); |
||||||
|
SubTest_NumberGetSetRemove(); |
||||||
|
SubTest_NullGetSetRemove(); |
||||||
|
SubTest_MultipleVariablesGetSet(); |
||||||
|
SubTest_Object(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_ArrayGetSetRemove() |
||||||
|
{ |
||||||
|
Context("Testing get/set/remove functions for JSON arrays"); |
||||||
|
SubTest_ArrayUndefined(); |
||||||
|
SubTest_ArrayStringGetSetRemove(); |
||||||
|
SubTest_ArrayBooleanGetSetRemove(); |
||||||
|
SubTest_ArrayNumberGetSetRemove(); |
||||||
|
SubTest_ArrayNullGetSetRemove(); |
||||||
|
SubTest_ArrayMultipleVariablesStorage(); |
||||||
|
SubTest_ArrayMultipleVariablesRemoval(); |
||||||
|
SubTest_ArrayRemovingMultipleVariablesAtOnce(); |
||||||
|
SubTest_ArrayExpansions(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_Undefined() |
||||||
|
{ |
||||||
|
local JObject testJSON; |
||||||
|
testJSON = _().json.newObject(); |
||||||
|
|
||||||
|
Context("Testing how `JObject` handles undefined values"); |
||||||
|
Issue("Undefined variable doesn't have proper type."); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Undefined); |
||||||
|
|
||||||
|
Issue("There is a variable in an empty object after `GetTypeOf` call."); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Undefined); |
||||||
|
|
||||||
|
Issue("Getters don't return default values for undefined variables."); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber("some_var", 0) == 0); |
||||||
|
TEST_ExpectTrue(testJSON.GetString("some_var", "") == ""); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean("some_var", false) == false); |
||||||
|
TEST_ExpectNone(testJSON.GetObject("some_var")); |
||||||
|
TEST_ExpectNone(testJSON.GetArray("some_var")); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_BooleanGetSetRemove() |
||||||
|
{ |
||||||
|
local JObject testJSON; |
||||||
|
testJSON = _().json.newObject(); |
||||||
|
testJSON.SetBoolean("some_boolean", true); |
||||||
|
|
||||||
|
Context("Testing `JObject`'s get/set/remove functions for" @ |
||||||
|
"boolean variables"); |
||||||
|
Issue("Boolean type isn't properly set by `SetBoolean`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_boolean") == JSON_Boolean); |
||||||
|
|
||||||
|
Issue("Variable value is incorrectly assigned by `SetBoolean`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean") == true); |
||||||
|
|
||||||
|
Issue("Variable value isn't correctly reassigned by `SetBoolean`"); |
||||||
|
testJSON.SetBoolean("some_boolean", false); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean") == false); |
||||||
|
|
||||||
|
Issue( "Getting boolean variable as a wrong type" @ |
||||||
|
"doesn't yield default value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber("some_boolean", 7) == 7); |
||||||
|
|
||||||
|
Issue("Boolean variable isn't being properly removed"); |
||||||
|
testJSON.RemoveValue("some_boolean"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_boolean") == JSON_Undefined); |
||||||
|
|
||||||
|
Issue( "Getters don't return default value for missing key that" @ |
||||||
|
"previously stored boolean value, that got removed"); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean", true) == true); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_StringGetSetRemove() |
||||||
|
{ |
||||||
|
local JObject testJSON; |
||||||
|
testJSON = _().json.newObject(); |
||||||
|
testJSON.SetString("some_string", "first string"); |
||||||
|
|
||||||
|
Context("Testing `JObject`'s get/set/remove functions for" @ |
||||||
|
"string variables"); |
||||||
|
Issue("String type isn't properly set by `SetString`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_string") == JSON_String); |
||||||
|
|
||||||
|
Issue("Value is incorrectly assigned by `SetString`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetString("some_string") == "first string"); |
||||||
|
|
||||||
|
Issue( "Providing default variable value makes 'GetString'" @ |
||||||
|
"return wrong value"); |
||||||
|
TEST_ExpectTrue( testJSON.GetString("some_string", "alternative") |
||||||
|
== "first string"); |
||||||
|
|
||||||
|
Issue("Variable value isn't correctly reassigned by `SetString`"); |
||||||
|
testJSON.SetString("some_string", "new string!~"); |
||||||
|
TEST_ExpectTrue(testJSON.GetString("some_string") == "new string!~"); |
||||||
|
|
||||||
|
Issue( "Getting string variable as a wrong type" @ |
||||||
|
"doesn't yield default value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean("some_string", true) == true); |
||||||
|
|
||||||
|
Issue("String variable isn't being properly removed"); |
||||||
|
testJSON.RemoveValue("some_string"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_string") == JSON_Undefined); |
||||||
|
|
||||||
|
Issue( "Getters don't return default value for missing key that" @ |
||||||
|
"previously stored string value, but got removed"); |
||||||
|
TEST_ExpectTrue(testJSON.GetString("some_string", "other") == "other"); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_NumberGetSetRemove() |
||||||
|
{ |
||||||
|
local JObject testJSON; |
||||||
|
testJSON = _().json.newObject(); |
||||||
|
testJSON.SetNumber("some_number", 3.5); |
||||||
|
|
||||||
|
Context("Testing `JObject`'s get/set/remove functions for" @ |
||||||
|
"number variables"); |
||||||
|
Issue("Number type isn't properly set by `SetNumber`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Number); |
||||||
|
|
||||||
|
Issue("Value is incorrectly assigned by `SetNumber`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber("some_number") == 3.5); |
||||||
|
|
||||||
|
Issue( "Providing default variable value makes 'GetNumber'" @ |
||||||
|
"return wrong value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber("some_number", 5) == 3.5); |
||||||
|
|
||||||
|
Issue("Variable value isn't correctly reassigned by `SetNumber`"); |
||||||
|
testJSON.SetNumber("some_number", 7); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber("some_number") == 7); |
||||||
|
|
||||||
|
Issue( "Getting number variable as a wrong type" @ |
||||||
|
"doesn't yield default value."); |
||||||
|
TEST_ExpectTrue(testJSON.GetString("some_number", "default") == "default"); |
||||||
|
|
||||||
|
Issue("Number type isn't being properly removed"); |
||||||
|
testJSON.RemoveValue("some_number"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Undefined); |
||||||
|
|
||||||
|
Issue( "Getters don't return default value for missing key that" @ |
||||||
|
"previously stored number value, that got removed"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber("some_number", 13) == 13); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_NullGetSetRemove() |
||||||
|
{ |
||||||
|
local JObject testJSON; |
||||||
|
testJSON = _().json.newObject(); |
||||||
|
|
||||||
|
Context("Testing `JObject`'s get/set/remove functions for" @ |
||||||
|
"null values"); |
||||||
|
Issue("Undefined variable is incorrectly considered `null`"); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull("some_var")); |
||||||
|
|
||||||
|
Issue("Number variable is incorrectly considered `null`"); |
||||||
|
testJSON.SetNumber("some_var", 4); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull("some_var")); |
||||||
|
|
||||||
|
Issue("Boolean variable is incorrectly considered `null`"); |
||||||
|
testJSON.SetBoolean("some_var", true); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull("some_var")); |
||||||
|
|
||||||
|
Issue("String variable is incorrectly considered `null`"); |
||||||
|
testJSON.SetString("some_var", "string"); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull("some_var")); |
||||||
|
|
||||||
|
Issue("Null value is incorrectly assigned"); |
||||||
|
testJSON.SetNull("some_var"); |
||||||
|
TEST_ExpectTrue(testJSON.IsNull("some_var")); |
||||||
|
|
||||||
|
Issue("Null type isn't properly set by `SetNumber`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Null); |
||||||
|
|
||||||
|
Issue("Null value isn't being properly removed."); |
||||||
|
testJSON.RemoveValue("some_var"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Undefined); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_MultipleVariablesGetSet() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool correctValue, allValuesCorrect; |
||||||
|
local JObject testJSON; |
||||||
|
testJSON = _().json.newObject(); |
||||||
|
Context("Testing how `JObject` handles addition, change and removal" @ |
||||||
|
"of relatively large (hundreds) number of variables"); |
||||||
|
for (i = 0; i < 2000; i += 1) |
||||||
|
{ |
||||||
|
testJSON.SetNumber("num" $ string(i), 4 * i*i - 2.6 * i + 0.75); |
||||||
|
} |
||||||
|
for (i = 0; i < 500; i += 1) |
||||||
|
{ |
||||||
|
testJSON.SetString("num" $ string(i), "str" $ string(Sin(i))); |
||||||
|
} |
||||||
|
for (i = 1500; i < 2000; i += 1) |
||||||
|
{ |
||||||
|
testJSON.RemoveValue("num" $ string(i)); |
||||||
|
} |
||||||
|
allValuesCorrect = true; |
||||||
|
for (i = 0; i < 200; i += 1) |
||||||
|
{ |
||||||
|
if (i < 500) |
||||||
|
{ |
||||||
|
correctValue = ( testJSON.GetString("num" $ string(i)) |
||||||
|
== ("str" $ string(Sin(i))) ); |
||||||
|
Issue("Variables are incorrectly overwritten"); |
||||||
|
} |
||||||
|
else if(i < 1500) |
||||||
|
{ |
||||||
|
correctValue = ( testJSON.GetNumber("num" $ string(i)) |
||||||
|
== 4 * i*i - 2.6 * i + 0.75); |
||||||
|
Issue("Variables are lost"); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
correctValue = ( testJSON.GetTypeOf("num" $ string(i)) |
||||||
|
== JSON_Undefined); |
||||||
|
Issue("Variables aren't removed"); |
||||||
|
} |
||||||
|
if (!correctValue) |
||||||
|
{ |
||||||
|
allValuesCorrect = false; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
TEST_ExpectTrue(allValuesCorrect); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_Object() |
||||||
|
{ |
||||||
|
local JObject testObject; |
||||||
|
Context("Testing setters and getters for folded objects"); |
||||||
|
testObject = _().json.newObject(); |
||||||
|
testObject.CreateObject("folded"); |
||||||
|
testObject.GetObject("folded").CreateObject("folded"); |
||||||
|
testObject.SetString("out", "string outside"); |
||||||
|
testObject.GetObject("folded").SetNumber("mid", 8); |
||||||
|
testObject.GetObject("folded") |
||||||
|
.GetObject("folded") |
||||||
|
.SetString("in", "string inside"); |
||||||
|
|
||||||
|
Issue("Addressing variables in root object doesn't work"); |
||||||
|
TEST_ExpectTrue(testObject.GetString("out", "default") == "string outside"); |
||||||
|
|
||||||
|
Issue("Addressing variables in folded object doesn't work"); |
||||||
|
TEST_ExpectTrue(testObject.GetObject("folded").GetNumber("mid", 1) == 8); |
||||||
|
|
||||||
|
Issue("Addressing plain variables in folded (twice) object doesn't work"); |
||||||
|
TEST_ExpectTrue(testObject.GetObject("folded").GetObject("folded") |
||||||
|
.GetString("in", "default") == "string inside"); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayUndefined() |
||||||
|
{ |
||||||
|
local JArray testJSON; |
||||||
|
testJSON = _().json.newArray(); |
||||||
|
Context("Testing how `JArray` handles undefined values"); |
||||||
|
Issue("Undefined variable doesn't have `JSON_Undefined` type"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); |
||||||
|
|
||||||
|
Issue("There is a variable in an empty object after `GetTypeOf` call"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); |
||||||
|
|
||||||
|
Issue("Negative index refers to a defined value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(-1) == JSON_Undefined); |
||||||
|
|
||||||
|
Issue("Getters don't return default values for undefined variables"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber(0, 0) == 0); |
||||||
|
TEST_ExpectTrue(testJSON.GetString(0, "") == ""); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean(0, false) == false); |
||||||
|
TEST_ExpectNone(testJSON.GetObject(0)); |
||||||
|
TEST_ExpectNone(testJSON.GetArray(0)); |
||||||
|
|
||||||
|
Issue( "Getters don't return user-defined default values for" @ |
||||||
|
"undefined variables"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber(0, 10) == 10); |
||||||
|
TEST_ExpectTrue(testJSON.GetString(0, "test") == "test"); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayBooleanGetSetRemove() |
||||||
|
{ |
||||||
|
local JArray testJSON; |
||||||
|
testJSON = _().json.newArray(); |
||||||
|
testJSON.SetBoolean(0, true); |
||||||
|
|
||||||
|
Context("Testing `JArray`'s get/set/remove functions for" @ |
||||||
|
"boolean variables"); |
||||||
|
Issue("Boolean type isn't properly set by `SetBoolean`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Boolean); |
||||||
|
|
||||||
|
Issue("Value is incorrectly assigned by `SetBoolean`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean(0) == true); |
||||||
|
testJSON.SetBoolean(0, false); |
||||||
|
|
||||||
|
Issue("Variable value isn't correctly reassigned by `SetBoolean`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean(0) == false); |
||||||
|
|
||||||
|
Issue( "Getting boolean variable as a wrong type" @ |
||||||
|
"doesn't yield default value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber(0, 7) == 7); |
||||||
|
|
||||||
|
Issue("Boolean variable isn't being properly removed"); |
||||||
|
testJSON.RemoveValue(0); |
||||||
|
TEST_ExpectTrue( testJSON.GetTypeOf(0) == JSON_Undefined); |
||||||
|
|
||||||
|
Issue( "Getters don't return default value for missing key that" @ |
||||||
|
"previously stored boolean value, but got removed"); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayStringGetSetRemove() |
||||||
|
{ |
||||||
|
local JArray testJSON; |
||||||
|
testJSON = _().json.newArray(); |
||||||
|
testJSON.SetString(0, "first string"); |
||||||
|
|
||||||
|
Context("Testing `JArray`'s get/set/remove functions for" @ |
||||||
|
"string variables"); |
||||||
|
Issue("String type isn't properly set by `SetString`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_String); |
||||||
|
|
||||||
|
Issue("Value is incorrectly assigned by `SetString`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetString(0) == "first string"); |
||||||
|
|
||||||
|
Issue( "Providing default variable value makes 'GetString'" @ |
||||||
|
"return incorrect value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetString(0, "alternative") == "first string"); |
||||||
|
|
||||||
|
Issue("Variable value isn't correctly reassigned by `SetString`"); |
||||||
|
testJSON.SetString(0, "new string!~"); |
||||||
|
TEST_ExpectTrue(testJSON.GetString(0) == "new string!~"); |
||||||
|
|
||||||
|
Issue( "Getting string variable as a wrong type" @ |
||||||
|
"doesn't yield default value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true); |
||||||
|
|
||||||
|
Issue("Boolean variable isn't being properly removed"); |
||||||
|
testJSON.RemoveValue(0); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); |
||||||
|
|
||||||
|
Issue( "Getters don't return default value for missing key that" @ |
||||||
|
"previously stored string value, but got removed"); |
||||||
|
TEST_ExpectTrue(testJSON.GetString(0, "other") == "other"); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayNumberGetSetRemove() |
||||||
|
{ |
||||||
|
local JArray testJSON; |
||||||
|
testJSON = _().json.newArray(); |
||||||
|
testJSON.SetNumber(0, 3.5); |
||||||
|
|
||||||
|
Context("Testing `JArray`'s get/set/remove functions for" @ |
||||||
|
"number variables"); |
||||||
|
Issue("Number type isn't properly set by `SetNumber`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Number); |
||||||
|
|
||||||
|
Issue("Value is incorrectly assigned by `SetNumber`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber(0) == 3.5); |
||||||
|
|
||||||
|
Issue( "Providing default variable value makes 'GetNumber'" @ |
||||||
|
"return incorrect value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber(0, 5) == 3.5); |
||||||
|
|
||||||
|
Issue("Variable value isn't correctly reassigned by `SetNumber`"); |
||||||
|
testJSON.SetNumber(0, 7); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber(0) == 7); |
||||||
|
|
||||||
|
Issue( "Getting number variable as a wrong type" @ |
||||||
|
"doesn't yield default value"); |
||||||
|
TEST_ExpectTrue(testJSON.GetString(0, "default") == "default"); |
||||||
|
|
||||||
|
Issue("Number type isn't being properly removed"); |
||||||
|
testJSON.RemoveValue(0); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); |
||||||
|
|
||||||
|
Issue( "Getters don't return default value for missing key that" @ |
||||||
|
"previously stored number value, but got removed"); |
||||||
|
TEST_ExpectTrue(testJSON.GetNumber(0, 13) == 13); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayNullGetSetRemove() |
||||||
|
{ |
||||||
|
local JArray testJSON; |
||||||
|
testJSON = _().json.newArray(); |
||||||
|
|
||||||
|
Context("Testing `JArray`'s get/set/remove functions for" @ |
||||||
|
"null values"); |
||||||
|
|
||||||
|
Issue("Undefined variable is incorrectly considered `null`"); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull(0)); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull(2)); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull(-1)); |
||||||
|
|
||||||
|
Issue("Number variable is incorrectly considered `null`"); |
||||||
|
testJSON.SetNumber(0, 4); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull(0)); |
||||||
|
|
||||||
|
Issue("Boolean variable is incorrectly considered `null`"); |
||||||
|
testJSON.SetBoolean(0, true); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull(0)); |
||||||
|
|
||||||
|
Issue("String variable is incorrectly considered `null`"); |
||||||
|
testJSON.SetString(0, "string"); |
||||||
|
TEST_ExpectFalse(testJSON.IsNull(0)); |
||||||
|
|
||||||
|
Issue("Null value is incorrectly assigned"); |
||||||
|
testJSON.SetNull(0); |
||||||
|
TEST_ExpectTrue(testJSON.IsNull(0)); |
||||||
|
|
||||||
|
Issue("Null type isn't properly set by `SetNumber`"); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Null); |
||||||
|
|
||||||
|
Issue("Null value isn't being properly removed"); |
||||||
|
testJSON.RemoveValue(0); |
||||||
|
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); |
||||||
|
} |
||||||
|
|
||||||
|
// Returns following array: |
||||||
|
// [10.0, "test string", "another string", true, 0.0, {"var": 7.0}] |
||||||
|
protected static function JArray Prepare_Array() |
||||||
|
{ |
||||||
|
local JArray testArray; |
||||||
|
testArray = _().json.newArray(); |
||||||
|
testArray.AddNumber(10.0f) |
||||||
|
.AddString("test string") |
||||||
|
.AddString("another string") |
||||||
|
.AddBoolean(true) |
||||||
|
.AddNumber(0.0f) |
||||||
|
.AddObject(); |
||||||
|
testArray.GetObject(5).SetNumber("var", 7); |
||||||
|
return testArray; |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayMultipleVariablesStorage() |
||||||
|
{ |
||||||
|
local JArray testArray; |
||||||
|
testArray = Prepare_Array(); |
||||||
|
|
||||||
|
Context("Testing how `JArray` handles adding and" @ |
||||||
|
"changing several variables"); |
||||||
|
Issue("Stored values are compromised."); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0f); |
||||||
|
TEST_ExpectTrue(testArray.GetString(1) == "test string"); |
||||||
|
TEST_ExpectTrue(testArray.GetString(2) == "another string"); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(3) == true); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(4) == 0.0f); |
||||||
|
TEST_ExpectTrue(testArray.GetObject(5).GetNumber("var") == 7); |
||||||
|
|
||||||
|
Issue("Values incorrectly change their values."); |
||||||
|
testArray.SetString(3, "new string"); |
||||||
|
TEST_ExpectTrue(testArray.GetString(3) == "new string"); |
||||||
|
|
||||||
|
Issue( "After overwriting boolean value with a different type," @ |
||||||
|
"attempting go get it as a boolean gives old value," @ |
||||||
|
"instead of default"); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(3, false) == false); |
||||||
|
|
||||||
|
Issue("Type of the variable is incorrectly changed."); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_String); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayMultipleVariablesRemoval() |
||||||
|
{ |
||||||
|
local JArray testArray; |
||||||
|
testArray = Prepare_Array(); |
||||||
|
// Test removing variables |
||||||
|
// After `Prepare_Array`, our array should be: |
||||||
|
// [10.0, "test string", "another string", true, 0.0, {"var": 7.0}] |
||||||
|
|
||||||
|
Context("Testing how `JArray` handles adding and" @ |
||||||
|
"removing several variables"); |
||||||
|
Issue("Values are incorrectly removed"); |
||||||
|
testArray.RemoveValue(2); |
||||||
|
// [10.0, "test string", true, 0.0, {"var": 7.0}] |
||||||
|
Issue("Values are incorrectly removed"); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0); |
||||||
|
TEST_ExpectTrue(testArray.GetString(1) == "test string"); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(2) == true); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(3) == 0.0f); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(4) == JSON_Object); |
||||||
|
|
||||||
|
Issue("First element incorrectly removed"); |
||||||
|
testArray.RemoveValue(0); |
||||||
|
// ["test string", true, 0.0, {"var": 7.0}] |
||||||
|
TEST_ExpectTrue(testArray.GetString(0) == "test string"); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(1) == true); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(2) == 0.0f); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Object); |
||||||
|
TEST_ExpectTrue(testArray.GetObject(3).GetNumber("var") == 7.0); |
||||||
|
|
||||||
|
Issue("Last element incorrectly removed"); |
||||||
|
testArray.RemoveValue(3); |
||||||
|
// ["test string", true, 0.0] |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 3); |
||||||
|
TEST_ExpectTrue(testArray.GetString(0) == "test string"); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(1) == true); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(2) == 0.0f); |
||||||
|
|
||||||
|
Issue("Removing all elements is handled incorrectly"); |
||||||
|
testArray.RemoveValue(0); |
||||||
|
testArray.RemoveValue(0); |
||||||
|
testArray.RemoveValue(0); |
||||||
|
TEST_ExpectTrue(testArray.Getlength() == 0); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Undefined); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayRemovingMultipleVariablesAtOnce() |
||||||
|
{ |
||||||
|
local JArray testArray; |
||||||
|
testArray = _().json.newArray(); |
||||||
|
testArray.AddNumber(10.0f) |
||||||
|
.AddString("test string") |
||||||
|
.AddString("another string") |
||||||
|
.AddNumber(7.0); |
||||||
|
|
||||||
|
Context("Testing how `JArray`' handles removing" @ |
||||||
|
"multiple elements at once"); |
||||||
|
Issue("Multiple values are incorrectly removed"); |
||||||
|
testArray.RemoveValue(1, 2); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 2); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(1) == 7.0); |
||||||
|
|
||||||
|
testArray.AddNumber(4.0f) |
||||||
|
.AddString("test string") |
||||||
|
.AddString("another string") |
||||||
|
.AddNumber(8.0); |
||||||
|
|
||||||
|
// Current array: |
||||||
|
// [10.0, 7.0, 4.0, "test string", "another string", 8.0] |
||||||
|
Issue("Last value is incorrectly removed"); |
||||||
|
testArray.RemoveValue(5, 1); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 5); |
||||||
|
TEST_ExpectTrue(testArray.GetString(4) == "another string"); |
||||||
|
|
||||||
|
// Current array: |
||||||
|
// [10.0, 7.0, 4.0, "test string", "another string"] |
||||||
|
Issue("Tail elements are incorrectly removed"); |
||||||
|
testArray.RemoveValue(3, 4); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 3); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(2) == 4.0); |
||||||
|
|
||||||
|
Issue("Array empties incorrectly"); |
||||||
|
testArray.RemoveValue(0, testArray.GetLength()); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 0); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Undefined); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Undefined); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ArrayExpansions() |
||||||
|
{ |
||||||
|
local JArray testArray; |
||||||
|
testArray = _().json.newArray(); |
||||||
|
|
||||||
|
Context("Testing how `JArray`' handles expansions/shrinking " @ |
||||||
|
"via `SetLength()`"); |
||||||
|
Issue("`SetLength()` doesn't properly expand empty array"); |
||||||
|
testArray.SetLength(2); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 2); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null); |
||||||
|
|
||||||
|
Issue("`SetLength()` doesn't properly expand non-empty array"); |
||||||
|
testArray.AddNumber(1); |
||||||
|
testArray.SetLength(4); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 4); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Number); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(2) == 1); |
||||||
|
SubSubTest_ArraySetNumberExpansions(); |
||||||
|
SubSubTest_ArraySetStringExpansions(); |
||||||
|
SubSubTest_ArraySetBooleanExpansions(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubSubTest_ArraySetNumberExpansions() |
||||||
|
{ |
||||||
|
local JArray testArray; |
||||||
|
testArray = _().json.newArray(); |
||||||
|
|
||||||
|
Context("Testing how `JArray`' handles expansions via" @ |
||||||
|
"`SetNumber()` function"); |
||||||
|
Issue("Setters don't create correct first element"); |
||||||
|
testArray.SetNumber(0, 1); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 1); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(0) == 1); |
||||||
|
|
||||||
|
Issue( "`SetNumber()` doesn't properly define array when setting" @ |
||||||
|
"value out-of-bounds"); |
||||||
|
testArray = _().json.newArray(); |
||||||
|
testArray.AddNumber(1); |
||||||
|
testArray.SetNumber(4, 2); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 5); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(0) == 1); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(4) == 2); |
||||||
|
|
||||||
|
Issue("`SetNumber()` expands array even when it told not to"); |
||||||
|
testArray.SetNumber(6, 7, true); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 5); |
||||||
|
TEST_ExpectTrue(testArray.GetNumber(6) == 0); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubSubTest_ArraySetStringExpansions() |
||||||
|
{ |
||||||
|
local JArray testArray; |
||||||
|
testArray = _().json.newArray(); |
||||||
|
|
||||||
|
Context("Testing how `JArray`' handles expansions via" @ |
||||||
|
"`SetString()` function"); |
||||||
|
Issue("Setters don't create correct first element"); |
||||||
|
testArray.SetString(0, "str"); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 1); |
||||||
|
TEST_ExpectTrue(testArray.GetString(0) == "str"); |
||||||
|
|
||||||
|
Issue( "`SetString()` doesn't properly define array when setting" @ |
||||||
|
"value out-of-bounds"); |
||||||
|
testArray = _().json.newArray(); |
||||||
|
testArray.AddString("str"); |
||||||
|
testArray.SetString(4, "str2"); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 5); |
||||||
|
TEST_ExpectTrue(testArray.GetString(0) == "str"); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetString(4) == "str2"); |
||||||
|
|
||||||
|
Issue("`SetString()` expands array even when it told not to"); |
||||||
|
testArray.SetString(6, "new string", true); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 5); |
||||||
|
TEST_ExpectTrue(testArray.GetString(6) == ""); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubSubTest_ArraySetBooleanExpansions() |
||||||
|
{ |
||||||
|
local JArray testArray; |
||||||
|
testArray = _().json.newArray(); |
||||||
|
|
||||||
|
Context("Testing how `JArray`' handles expansions via" @ |
||||||
|
"`SetBoolean()` function"); |
||||||
|
Issue("Setters don't create correct first element"); |
||||||
|
testArray.SetBoolean(0, false); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 1); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(0) == false); |
||||||
|
|
||||||
|
Issue( "`SetBoolean()` doesn't properly define array when setting" @ |
||||||
|
"value out-of-bounds"); |
||||||
|
testArray = _().json.newArray(); |
||||||
|
testArray.AddBoolean(true); |
||||||
|
testArray.SetBoolean(4, true); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 5); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(0) == true); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(4) == true); |
||||||
|
|
||||||
|
Issue("`SetBoolean()` expands array even when it told not to"); |
||||||
|
testArray.SetBoolean(6, true, true); |
||||||
|
TEST_ExpectTrue(testArray.GetLength() == 5); |
||||||
|
TEST_ExpectTrue(testArray.GetBoolean(6) == false); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined); |
||||||
|
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
caseName = "JSON" |
||||||
|
} |
@ -0,0 +1,142 @@ |
|||||||
|
/** |
||||||
|
* Event generator for events, related to broadcasting messages |
||||||
|
* through standard Unreal Script means: |
||||||
|
* 1. text messages, typed by a player; |
||||||
|
* 2. localized messages, identified by a LocalMessage class and id. |
||||||
|
* Allows to make decisions whether or not to propagate certain messages. |
||||||
|
* 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 BroadcastEvents extends Events |
||||||
|
abstract; |
||||||
|
|
||||||
|
struct LocalizedMessage |
||||||
|
{ |
||||||
|
// Every localized message is described by a class and id. |
||||||
|
// For example, consider 'KFMod.WaitingMessage': |
||||||
|
// if passed 'id' is '1', |
||||||
|
// then it's supposed to be a message about new wave, |
||||||
|
// but if passed 'id' is '2', |
||||||
|
// then it's about completing the wave. |
||||||
|
var class<LocalMessage> class; |
||||||
|
var int id; |
||||||
|
// Localized messages in unreal script can be passed along with |
||||||
|
// optional arguments, described by variables below. |
||||||
|
var PlayerReplicationInfo relatedPRI1; |
||||||
|
var PlayerReplicationInfo relatedPRI2; |
||||||
|
var Object relatedObject; |
||||||
|
}; |
||||||
|
|
||||||
|
static function bool CallCanBroadcast(Actor broadcaster, int recentSentTextSize) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool result; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0;i < listeners.length;i += 1) |
||||||
|
{ |
||||||
|
result = class<BroadcastListenerBase>(listeners[i]) |
||||||
|
.static.CanBroadcast(broadcaster, recentSentTextSize); |
||||||
|
if (!result) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static function bool CallHandleText |
||||||
|
( |
||||||
|
Actor sender, |
||||||
|
out string message, |
||||||
|
name messageType |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool result; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0;i < listeners.length;i += 1) |
||||||
|
{ |
||||||
|
result = class<BroadcastListenerBase>(listeners[i]) |
||||||
|
.static.HandleText(sender, message, messageType); |
||||||
|
if (!result) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static function bool CallHandleTextFor |
||||||
|
( |
||||||
|
PlayerController receiver, |
||||||
|
Actor sender, |
||||||
|
out string message, |
||||||
|
name messageType |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool result; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0;i < listeners.length;i += 1) |
||||||
|
{ |
||||||
|
result = class<BroadcastListenerBase>(listeners[i]) |
||||||
|
.static.HandleTextFor(receiver, sender, message, messageType); |
||||||
|
if (!result) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static function bool CallHandleLocalized |
||||||
|
( |
||||||
|
Actor sender, |
||||||
|
LocalizedMessage message |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool result; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0;i < listeners.length;i += 1) |
||||||
|
{ |
||||||
|
result = class<BroadcastListenerBase>(listeners[i]) |
||||||
|
.static.HandleLocalized(sender, message); |
||||||
|
if (!result) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static function bool CallHandleLocalizedFor |
||||||
|
( |
||||||
|
PlayerController receiver, |
||||||
|
Actor sender, |
||||||
|
LocalizedMessage message |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool result; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0;i < listeners.length;i += 1) |
||||||
|
{ |
||||||
|
result = class<BroadcastListenerBase>(listeners[i]) |
||||||
|
.static.HandleLocalizedFor(receiver, sender, message); |
||||||
|
if (!result) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedListener = class'BroadcastListenerBase' |
||||||
|
} |
@ -0,0 +1,197 @@ |
|||||||
|
/** |
||||||
|
* 'BroadcastHandler' class that used by Acedia to catch |
||||||
|
* broadcasting events. For Acedia to work properly it needs to be added to |
||||||
|
* the very beginning of the broadcast handlers' chain. |
||||||
|
* 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/>. |
||||||
|
*/ |
||||||
|
// TODO: make it work from any place in the chain. |
||||||
|
class BroadcastHandler extends Engine.BroadcastHandler |
||||||
|
dependson(BroadcastEvents); |
||||||
|
|
||||||
|
// The way vanilla 'BroadcastHandler' works - it can check if broadcast is |
||||||
|
// possible for any actor, but for actually sending the text messages it will |
||||||
|
// try to extract player's data from it |
||||||
|
// and will simply pass 'none' if it can't. |
||||||
|
// We remember senders in this array in order to pass real ones to our events. |
||||||
|
// Array instead of variable is to account for folded calls |
||||||
|
// (when handling of broadcast events leads to another message generation). |
||||||
|
var private array<Actor> storedSenders; |
||||||
|
|
||||||
|
// We want to insert our code in some of the functions between |
||||||
|
// 'AllowsBroadcast' check and actual broadcasting, |
||||||
|
// so we can't just use a 'super.AllowsBroadcast()' call. |
||||||
|
// Instead we first manually do this check, then perform our logic and then |
||||||
|
// make a super call, but with 'blockAllowsBroadcast' flag set to 'true', |
||||||
|
// which causes overloaded 'AllowsBroadcast()' to omit actual checks. |
||||||
|
var private bool blockAllowsBroadcast; |
||||||
|
|
||||||
|
// Functions below simply reroute vanilla's broadcast events to |
||||||
|
// Acedia's 'BroadcastEvents', while keeping original senders |
||||||
|
// and blocking 'AllowsBroadcast()' as described in comments for |
||||||
|
// 'storedSenders' and 'blockAllowsBroadcast'. |
||||||
|
|
||||||
|
public function bool HandlerAllowsBroadcast(Actor broadcaster, int sentTextNum) |
||||||
|
{ |
||||||
|
local bool canBroadcast; |
||||||
|
// Check listeners |
||||||
|
canBroadcast = class'BroadcastEvents'.static |
||||||
|
.CallCanBroadcast(broadcaster, sentTextNum); |
||||||
|
// Check other broadcast handlers (if present) |
||||||
|
if (canBroadcast && nextBroadcastHandler != none) |
||||||
|
{ |
||||||
|
canBroadcast = nextBroadcastHandler |
||||||
|
.HandlerAllowsBroadcast(broadcaster, sentTextNum); |
||||||
|
} |
||||||
|
return canBroadcast; |
||||||
|
} |
||||||
|
|
||||||
|
function Broadcast(Actor sender, coerce string message, optional name type) |
||||||
|
{ |
||||||
|
local bool canTryToBroadcast; |
||||||
|
if (!AllowsBroadcast(sender, Len(message))) |
||||||
|
return; |
||||||
|
canTryToBroadcast = class'BroadcastEvents'.static |
||||||
|
.CallHandleText(sender, message, type); |
||||||
|
if (canTryToBroadcast) |
||||||
|
{ |
||||||
|
storedSenders[storedSenders.length] = sender; |
||||||
|
blockAllowsBroadcast = true; |
||||||
|
super.Broadcast(sender, message, type); |
||||||
|
blockAllowsBroadcast = false; |
||||||
|
storedSenders.length = storedSenders.length - 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function BroadcastTeam |
||||||
|
( |
||||||
|
Controller sender, |
||||||
|
coerce string message, |
||||||
|
optional name type |
||||||
|
) |
||||||
|
{ |
||||||
|
local bool canTryToBroadcast; |
||||||
|
if (!AllowsBroadcast(sender, Len(message))) |
||||||
|
return; |
||||||
|
canTryToBroadcast = class'BroadcastEvents'.static |
||||||
|
.CallHandleText(sender, message, type); |
||||||
|
if (canTryToBroadcast) |
||||||
|
{ |
||||||
|
storedSenders[storedSenders.length] = sender; |
||||||
|
blockAllowsBroadcast = true; |
||||||
|
super.BroadcastTeam(sender, message, type); |
||||||
|
blockAllowsBroadcast = false; |
||||||
|
storedSenders.length = storedSenders.length - 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
event AllowBroadcastLocalized |
||||||
|
( |
||||||
|
Actor sender, |
||||||
|
class<LocalMessage> message, |
||||||
|
optional int switch, |
||||||
|
optional PlayerReplicationInfo relatedPRI1, |
||||||
|
optional PlayerReplicationInfo relatedPRI2, |
||||||
|
optional Object optionalObject |
||||||
|
) |
||||||
|
{ |
||||||
|
local bool canTryToBroadcast; |
||||||
|
local BroadcastEvents.LocalizedMessage packedMessage; |
||||||
|
if (!AllowsBroadcast(sender, Len(message))) |
||||||
|
return; |
||||||
|
packedMessage.class = message; |
||||||
|
packedMessage.id = switch; |
||||||
|
packedMessage.relatedPRI1 = relatedPRI1; |
||||||
|
packedMessage.relatedPRI2 = relatedPRI2; |
||||||
|
packedMessage.relatedObject = optionalObject; |
||||||
|
canTryToBroadcast = class'BroadcastEvents'.static |
||||||
|
.CallHandleLocalized(sender, packedMessage); |
||||||
|
if (canTryToBroadcast) |
||||||
|
{ |
||||||
|
super.AllowBroadcastLocalized( sender, message, switch, |
||||||
|
relatedPRI1, relatedPRI2, |
||||||
|
optionalObject); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function bool AllowsBroadcast(actor broadcaster, int len) |
||||||
|
{ |
||||||
|
if (blockAllowsBroadcast) |
||||||
|
return true; |
||||||
|
return super.AllowsBroadcast(broadcaster, len); |
||||||
|
} |
||||||
|
|
||||||
|
function bool AcceptBroadcastText |
||||||
|
( |
||||||
|
PlayerController receiver, |
||||||
|
PlayerReplicationInfo senderPRI, |
||||||
|
out string message, |
||||||
|
optional name type |
||||||
|
) |
||||||
|
{ |
||||||
|
local bool canBroadcast; |
||||||
|
local Actor sender; |
||||||
|
if (senderPRI != none) |
||||||
|
{ |
||||||
|
sender = PlayerController(senderPRI.owner); |
||||||
|
} |
||||||
|
if (sender == none && storedSenders.length > 0) |
||||||
|
{ |
||||||
|
sender = storedSenders[storedSenders.length - 1]; |
||||||
|
} |
||||||
|
canBroadcast = class'BroadcastEvents'.static |
||||||
|
.CallHandleTextFor(receiver, sender, message, type); |
||||||
|
if (!canBroadcast) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
return super.AcceptBroadcastText(receiver, senderPRI, message, type); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function bool AcceptBroadcastLocalized |
||||||
|
( |
||||||
|
PlayerController receiver, |
||||||
|
Actor sender, |
||||||
|
class<LocalMessage> message, |
||||||
|
optional int switch, |
||||||
|
optional PlayerReplicationInfo relatedPRI1, |
||||||
|
optional PlayerReplicationInfo relatedPRI2, |
||||||
|
optional Object obj |
||||||
|
) |
||||||
|
{ |
||||||
|
local bool canBroadcast; |
||||||
|
local BroadcastEvents.LocalizedMessage packedMessage; |
||||||
|
packedMessage.class = message; |
||||||
|
packedMessage.id = switch; |
||||||
|
packedMessage.relatedPRI1 = relatedPRI1; |
||||||
|
packedMessage.relatedPRI2 = relatedPRI2; |
||||||
|
packedMessage.relatedObject = obj; |
||||||
|
canBroadcast = class'BroadcastEvents'.static |
||||||
|
.CallHandleLocalizedFor(receiver, sender, packedMessage); |
||||||
|
if (!canBroadcast) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
return super.AcceptBroadcastLocalized( receiver, sender, message, switch, |
||||||
|
relatedPRI1, relatedPRI2, obj); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
blockAllowsBroadcast = false |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
/** |
||||||
|
* Listener for events, related to broadcasting messages |
||||||
|
* through standard Unreal Script means: |
||||||
|
* 1. text messages, typed by a player; |
||||||
|
* 2. localized messages, identified by a LocalMessage class and id. |
||||||
|
* Allows to make decisions whether or not to propagate certain messages. |
||||||
|
* 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 BroadcastListenerBase extends Listener |
||||||
|
abstract; |
||||||
|
|
||||||
|
static final function PlayerController GetController(Actor sender) |
||||||
|
{ |
||||||
|
local Pawn senderPawn; |
||||||
|
senderPawn = Pawn(sender); |
||||||
|
if (senderPawn != none) return PlayerController(senderPawn.controller); |
||||||
|
return PlayerController(sender); |
||||||
|
} |
||||||
|
|
||||||
|
// This event is called whenever registered broadcast handlers are asked if |
||||||
|
// they'd allow given actor ('broadcaster') to broadcast a text message, |
||||||
|
// given that none so far rejected it and he recently already broadcasted |
||||||
|
// or tried to broadcast 'recentSentTextSize' symbols of text |
||||||
|
// (that value is periodically reset in 'GameInfo', |
||||||
|
// by default should be each second). |
||||||
|
// NOTE: this function is ONLY called when someone tries to |
||||||
|
// broadcast TEXT messages. |
||||||
|
// If one of the listeners returns 'false', - |
||||||
|
// it will be treated just like one of broadcasters returning 'false' |
||||||
|
// in 'AllowsBroadcast' and this method won't be called for remaining |
||||||
|
// active listeners. |
||||||
|
static function bool CanBroadcast(Actor broadcaster, int recentSentTextSize) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// This event is called whenever a someone is trying to broadcast |
||||||
|
// a text message (typically the typed by a player). |
||||||
|
// This function is called once per message and allows you to change it |
||||||
|
// (by changing 'message' argument) before any of the players receive it. |
||||||
|
// Return 'true' to allow the message through. |
||||||
|
// If one of the listeners returns 'false', - |
||||||
|
// it will be treated just like one of broadcasters returning 'false' |
||||||
|
// in 'AcceptBroadcastText' and this method won't be called for remaining |
||||||
|
// active listeners. |
||||||
|
static function bool HandleText |
||||||
|
( |
||||||
|
Actor sender, |
||||||
|
out string message, |
||||||
|
optional name messageType |
||||||
|
) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// This event is similar to 'HandleText', but is called for every player |
||||||
|
// the message is sent to. |
||||||
|
// If allows you to alter the message, but the changes are accumulated |
||||||
|
// as events go through the players. |
||||||
|
static function bool HandleTextFor |
||||||
|
( |
||||||
|
PlayerController receiver, |
||||||
|
Actor sender, |
||||||
|
out string message, |
||||||
|
optional name messageType |
||||||
|
) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// This event is called whenever a localized message is trying to |
||||||
|
// get broadcasted to a certain player ('receiver'). |
||||||
|
// Return 'true' to allow the message through. |
||||||
|
// If one of the listeners returns 'false', - |
||||||
|
// it will be treated just like one of broadcasters returning 'false' |
||||||
|
// in 'AcceptBroadcastText' and this method won't be called for remaining |
||||||
|
// active listeners. |
||||||
|
static function bool HandleLocalized |
||||||
|
( |
||||||
|
Actor sender, |
||||||
|
BroadcastEvents.LocalizedMessage message |
||||||
|
) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// This event is similar to 'HandleLocalized', but is called for |
||||||
|
// every player the message is sent to. |
||||||
|
static function bool HandleLocalizedFor |
||||||
|
( |
||||||
|
PlayerController receiver, |
||||||
|
Actor sender, |
||||||
|
BroadcastEvents.LocalizedMessage message |
||||||
|
) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'BroadcastEvents' |
||||||
|
} |
||||||
|
|
||||||
|
// Text messages can (optionally) have their type specified. |
||||||
|
// Examples of it are names 'Say' and 'CriticalEvent'. |
@ -0,0 +1,159 @@ |
|||||||
|
/** |
||||||
|
* One of the two classes that make up a core of event system in Acedia. |
||||||
|
* |
||||||
|
* 'Events' (or it's child) class shouldn't be instantiated. |
||||||
|
* Usually module would provide '...Events' class that defines |
||||||
|
* certain set of static functions that can generate event calls to |
||||||
|
* all it's active listeners. |
||||||
|
* If you're simply using modules someone made, - |
||||||
|
* you don't need to bother yourself with further specifics. |
||||||
|
* If you wish to create your own event generator, |
||||||
|
* then first create a '...ListenerBase' object |
||||||
|
* (more about it in the description of 'Listener' class) |
||||||
|
* and set 'relatedListener' variable to point to it's class. |
||||||
|
* Then for each event create a caller function in your 'Event' class, |
||||||
|
* following this template: |
||||||
|
* ____________________________________________________________________________ |
||||||
|
* | static function CallEVENT_NAME(<ARGUMENTS>) |
||||||
|
* | { |
||||||
|
* | local int i; |
||||||
|
* | local array< class<Listener> > listeners; |
||||||
|
* | listeners = GetListeners(); |
||||||
|
* | for (i = 0; i < listeners.length; i += 1) |
||||||
|
* | { |
||||||
|
* | class<...ListenerBase>(listeners[i]) |
||||||
|
* | .static.EVENT_NAME(<ARGUMENTS>); |
||||||
|
* | } |
||||||
|
* | } |
||||||
|
* |___________________________________________________________________________ |
||||||
|
* If each listener must indicate whether it gives it's permission for |
||||||
|
* something to happen, then use this template: |
||||||
|
* ____________________________________________________________________________ |
||||||
|
* | static function CallEVENT_NAME(<ARGUMENTS>) |
||||||
|
* | { |
||||||
|
* | local int i; |
||||||
|
* | local bool result; |
||||||
|
* | local array< class<Listener> > listeners; |
||||||
|
* | listeners = GetListeners(); |
||||||
|
* | for (i = 0; i < listeners.length; i += 1) |
||||||
|
* | { |
||||||
|
* | result = class<...ListenerBase>(listeners[i]) |
||||||
|
* | .static.EVENT_NAME(<ARGUMENTS>); |
||||||
|
* | if (!result) return false; |
||||||
|
* | } |
||||||
|
* | return true; |
||||||
|
* | } |
||||||
|
* |___________________________________________________________________________ |
||||||
|
* For concrete example look at |
||||||
|
* 'MutatorEvents' and 'MutatorListenerBase'. |
||||||
|
* 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 Events extends AcediaObject |
||||||
|
abstract; |
||||||
|
|
||||||
|
var private array< class<Listener> > listeners; |
||||||
|
|
||||||
|
var public const class<Listener> relatedListener; |
||||||
|
|
||||||
|
// Even class can also auto-spawn a `Service`, |
||||||
|
// in case it's require to generate events |
||||||
|
var public const class<Service> connectedServiceClass; |
||||||
|
// Set this to `true`if you want `connectedServiceClass` service to also |
||||||
|
// auto-shutdown whenever no-one listens to the events. |
||||||
|
var public const bool shutDownServiceWithoutListeners; |
||||||
|
|
||||||
|
static public final function array< class<Listener> > GetListeners() |
||||||
|
{ |
||||||
|
return default.listeners; |
||||||
|
} |
||||||
|
|
||||||
|
// Make given listener active. |
||||||
|
// If listener was already activated also returns 'false'. |
||||||
|
static public final function bool ActivateListener(class<Listener> newListener) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (newListener == none) return false; |
||||||
|
if (!ClassIsChildOf(newListener, default.relatedListener)) return false; |
||||||
|
|
||||||
|
// Spawn service, if absent |
||||||
|
if ( default.listeners.length == 0 |
||||||
|
&& default.connectedServiceClass != none) { |
||||||
|
default.connectedServiceClass.static.Require(); |
||||||
|
} |
||||||
|
// Add listener |
||||||
|
for (i = 0;i < default.listeners.length;i += 1) |
||||||
|
{ |
||||||
|
if (default.listeners[i] == newListener) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
default.listeners[default.listeners.length] = newListener; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// Make given listener inactive. |
||||||
|
// If listener wasn't active returns 'false'. |
||||||
|
static public final function bool DeactivateListener(class<Listener> listener) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool removedListener; |
||||||
|
local Service service; |
||||||
|
if (listener == none) return false; |
||||||
|
|
||||||
|
// Remove listener |
||||||
|
for (i = 0; i < default.listeners.length; i += 1) |
||||||
|
{ |
||||||
|
if (default.listeners[i] == listener) |
||||||
|
{ |
||||||
|
default.listeners.Remove(i, 1); |
||||||
|
removedListener = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
// Remove unneeded service |
||||||
|
if ( default.shutDownServiceWithoutListeners |
||||||
|
&& default.listeners.length == 0 |
||||||
|
&& default.connectedServiceClass != none) |
||||||
|
{ |
||||||
|
service = Service(default.connectedServiceClass.static.GetInstance()); |
||||||
|
if (service != none) { |
||||||
|
service.Destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
return removedListener; |
||||||
|
} |
||||||
|
|
||||||
|
static public final function bool IsActiveListener(class<Listener> listener) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (listener == none) return false; |
||||||
|
|
||||||
|
for (i = 0; i < default.listeners.length; i += 1) |
||||||
|
{ |
||||||
|
if (default.listeners[i] == listener) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedListener = class'Listener' |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
/** |
||||||
|
* One of the two classes that make up a core of event system in Acedia. |
||||||
|
* |
||||||
|
* 'Listener' (or it's child) class shouldn't be instantiated. |
||||||
|
* Usually module would provide '...ListenerBase' class that defines |
||||||
|
* certain set of static functions, corresponding to events it can listen to. |
||||||
|
* In order to handle those events you must create it's child class and |
||||||
|
* override said functions. But they will only be called if |
||||||
|
* 'SetActive(true)' is called for that child class. |
||||||
|
* To create you own '...ListenerBase' class you need to define |
||||||
|
* a static function for each event you wish it to catch and |
||||||
|
* set 'relatedEvents' variable to point at the 'Events' class |
||||||
|
* that will generate your events. |
||||||
|
* For concrete example look at |
||||||
|
* 'ConnectionEvents' and 'ConnectionListenerBase'. |
||||||
|
* 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 Listener extends AcediaObject |
||||||
|
abstract; |
||||||
|
|
||||||
|
var public const class<Events> relatedEvents; |
||||||
|
|
||||||
|
|
||||||
|
static public final function SetActive(bool active) |
||||||
|
{ |
||||||
|
if (active) |
||||||
|
{ |
||||||
|
default.relatedEvents.static.ActivateListener(default.class); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
default.relatedEvents.static.DeactivateListener(default.class); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static public final function IsActive(bool active) |
||||||
|
{ |
||||||
|
default.relatedEvents.static.IsActiveListener(default.class); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'Events' |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
/** |
||||||
|
* Event generator that repeats events of a mutator. |
||||||
|
* 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 MutatorEvents extends Events |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function bool CallCheckReplacement(Actor other, out byte isSuperRelevant) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool result; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
result = class<MutatorListenerBase>(listeners[i]) |
||||||
|
.static.CheckReplacement(other, isSuperRelevant); |
||||||
|
if (!result) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static function bool CallMutate(string command, PlayerController sendingPlayer) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool result; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length;i += 1) |
||||||
|
{ |
||||||
|
result = class<MutatorListenerBase>(listeners[i]) |
||||||
|
.static.Mutate(command, sendingPlayer); |
||||||
|
if (!result) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedListener = class'MutatorListenerBase' |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
/** |
||||||
|
* Listener for events, normally propagated by mutators. |
||||||
|
* 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 MutatorListenerBase extends Listener |
||||||
|
abstract; |
||||||
|
|
||||||
|
// This event is called whenever 'CheckReplacement' |
||||||
|
// check is propagated through mutators. |
||||||
|
// If one of the listeners returns 'false', - |
||||||
|
// it will be treated just like a mutator returning 'false' |
||||||
|
// in 'CheckReplacement' and |
||||||
|
// this method won't be called for remaining active listeners. |
||||||
|
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// This event is called whenever 'Mutate' is propagated through mutators. |
||||||
|
// If one of the listeners returns 'false', - |
||||||
|
// this method won't be called for remaining active listeners or mutators. |
||||||
|
// If all listeners return 'true', - |
||||||
|
// mutate command will be further propagated to the rest of the mutators. |
||||||
|
static function bool Mutate(string command, PlayerController sendingPlayer) |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'MutatorEvents' |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
/** |
||||||
|
* Feature represents a certain subset of Acedia's functionality that |
||||||
|
* can be enabled or disabled, according to server owner's wishes. |
||||||
|
* In the current version of Acedia enabling or disabling a feature requires |
||||||
|
* manually editing configuration file and restarting a server. |
||||||
|
* Factually feature is just a collection of settings with one universal |
||||||
|
* 'isActive' setting that tells Acedia whether or not to load a feature. |
||||||
|
* 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 Feature extends Singleton |
||||||
|
abstract; |
||||||
|
|
||||||
|
// Setting that tells Acedia whether or not to enable this feature |
||||||
|
// during initialization. |
||||||
|
// Only it's default value is ever used. |
||||||
|
var private config bool autoEnable; |
||||||
|
|
||||||
|
// Listeners listed here will be automatically activated. |
||||||
|
var public const array< class<Listener> > requiredListeners; |
||||||
|
|
||||||
|
// Sets whether to enable this feature by default. |
||||||
|
public static final function SetAutoEnable(bool doEnable) |
||||||
|
{ |
||||||
|
default.autoEnable = doEnable; |
||||||
|
StaticSaveConfig(); |
||||||
|
} |
||||||
|
|
||||||
|
public static final function bool IsAutoEnabled() |
||||||
|
{ |
||||||
|
return default.autoEnable; |
||||||
|
} |
||||||
|
|
||||||
|
// Whether feature is enabled is determined by |
||||||
|
public static final function bool IsEnabled() |
||||||
|
{ |
||||||
|
return (GetInstance() != none); |
||||||
|
} |
||||||
|
|
||||||
|
// Enables feature of given class. |
||||||
|
public static final function Feature EnableMe() |
||||||
|
{ |
||||||
|
local Feature newInstance; |
||||||
|
if (IsEnabled()) |
||||||
|
{ |
||||||
|
return Feature(GetInstance()); |
||||||
|
} |
||||||
|
default.blockSpawning = false; |
||||||
|
// TODO: code duplication with `Service`? |
||||||
|
newInstance = __().Spawn(default.class); |
||||||
|
default.blockSpawning = true; |
||||||
|
return newInstance; |
||||||
|
} |
||||||
|
|
||||||
|
public static final function bool DisableMe() |
||||||
|
{ |
||||||
|
local Feature myself; |
||||||
|
myself = Feature(GetInstance()); |
||||||
|
if (myself != none) |
||||||
|
{ |
||||||
|
myself.Destroy(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// Event functions that are called when |
||||||
|
protected function OnEnabled(){} |
||||||
|
protected function OnDisabled(){} |
||||||
|
|
||||||
|
// Set listeners' status |
||||||
|
private static function SetListenersActiveSatus(bool newStatus) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < default.requiredListeners.length; i += 1) |
||||||
|
{ |
||||||
|
if (default.requiredListeners[i] == none) continue; |
||||||
|
default.requiredListeners[i].static.SetActive(newStatus); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function OnCreated() |
||||||
|
{ |
||||||
|
default.blockSpawning = true; |
||||||
|
SetListenersActiveSatus(true); |
||||||
|
OnEnabled(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function OnDestroyed() |
||||||
|
{ |
||||||
|
SetListenersActiveSatus(false); |
||||||
|
OnDisabled(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
autoEnable = false |
||||||
|
DrawType = DT_None |
||||||
|
// Prevent spawning this feature by any other means than 'EnableMe()'. |
||||||
|
blockSpawning = true |
||||||
|
// Features are server-only actors |
||||||
|
remoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/** |
||||||
|
* Class for an object that will provide an access to a Acedia's functionality |
||||||
|
* by giving a reference to this actor to all Acedia's objects and actors, |
||||||
|
* emulating a global API namespace. |
||||||
|
* 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 Global extends Singleton; |
||||||
|
|
||||||
|
var public LoggerAPI logger; |
||||||
|
var public JSONAPI json; |
||||||
|
var public AliasesAPI alias; |
||||||
|
var public TextAPI text; |
||||||
|
var public MemoryAPI memory; |
||||||
|
var public ConsoleAPI console; |
||||||
|
var public ColorAPI color; |
||||||
|
|
||||||
|
// TODO: APIs must be `remoteRole = ROLE_None` |
||||||
|
protected function OnCreated() |
||||||
|
{ |
||||||
|
Spawn(class'LoggerAPI'); |
||||||
|
logger = LoggerAPI(class'LoggerAPI'.static.GetInstance()); |
||||||
|
Spawn(class'JSONAPI'); |
||||||
|
json = JSONAPI(class'JSONAPI'.static.GetInstance()); |
||||||
|
Spawn(class'AliasesAPI'); |
||||||
|
alias = AliasesAPI(class'AliasesAPI'.static.GetInstance()); |
||||||
|
Spawn(class'TextAPI'); |
||||||
|
text = TextAPI(class'TextAPI'.static.GetInstance()); |
||||||
|
Spawn(class'MemoryAPI'); |
||||||
|
memory = MemoryAPI(class'MemoryAPI'.static.GetInstance()); |
||||||
|
Spawn(class'ConsoleAPI'); |
||||||
|
console = ConsoleAPI(class'ConsoleAPI'.static.GetInstance()); |
||||||
|
Spawn(class'ColorAPI'); |
||||||
|
color = ColorAPI(class'ColorAPI'.static.GetInstance()); |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
/** |
||||||
|
* API that provides functions quick access to Acedia's |
||||||
|
* logging functionality. |
||||||
|
* 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 LoggerAPI extends Singleton; |
||||||
|
|
||||||
|
var private LoggerService logService; |
||||||
|
|
||||||
|
protected function OnCreated() |
||||||
|
{ |
||||||
|
logService = LoggerService(class'LoggerService'.static.Require()); |
||||||
|
} |
||||||
|
|
||||||
|
public final function Track(string message) |
||||||
|
{ |
||||||
|
if (logService == none) |
||||||
|
{ |
||||||
|
class'LoggerService'.static.LogMessageToKFLog(LOG_Track, message); |
||||||
|
return; |
||||||
|
} |
||||||
|
logService.LogMessage(LOG_Track, message); |
||||||
|
} |
||||||
|
|
||||||
|
public final function Debug(string message) |
||||||
|
{ |
||||||
|
if (logService == none) |
||||||
|
{ |
||||||
|
class'LoggerService'.static.LogMessageToKFLog(LOG_Debug, message); |
||||||
|
return; |
||||||
|
} |
||||||
|
logService.LogMessage(LOG_Debug, message); |
||||||
|
} |
||||||
|
|
||||||
|
public final function Info(string message) |
||||||
|
{ |
||||||
|
if (logService == none) |
||||||
|
{ |
||||||
|
class'LoggerService'.static.LogMessageToKFLog(LOG_Info, message); |
||||||
|
return; |
||||||
|
} |
||||||
|
logService.LogMessage(LOG_Info, message); |
||||||
|
} |
||||||
|
|
||||||
|
public final function Warning(string message) |
||||||
|
{ |
||||||
|
if (logService == none) |
||||||
|
{ |
||||||
|
class'LoggerService'.static.LogMessageToKFLog(LOG_Warning, message); |
||||||
|
return; |
||||||
|
} |
||||||
|
logService.LogMessage(LOG_Warning, message); |
||||||
|
} |
||||||
|
|
||||||
|
public final function Failure(string message) |
||||||
|
{ |
||||||
|
if (logService == none) |
||||||
|
{ |
||||||
|
class'LoggerService'.static.LogMessageToKFLog(LOG_Failure, message); |
||||||
|
return; |
||||||
|
} |
||||||
|
logService.LogMessage(LOG_Failure, message); |
||||||
|
} |
||||||
|
|
||||||
|
public final function Fatal(string message) |
||||||
|
{ |
||||||
|
if (logService == none) |
||||||
|
{ |
||||||
|
class'LoggerService'.static.LogMessageToKFLog(LOG_Fatal, message); |
||||||
|
return; |
||||||
|
} |
||||||
|
logService.LogMessage(LOG_Fatal, message); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,166 @@ |
|||||||
|
/** |
||||||
|
* Logger that allows to separate log messages into several levels of |
||||||
|
* significance and lets users and admins to access only the ones they want |
||||||
|
* and/or receive notifications when they happen. |
||||||
|
* 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 LoggerService extends Service |
||||||
|
config(AcediaLogger); |
||||||
|
|
||||||
|
// Log levels, available in Acedia. |
||||||
|
enum LogLevel |
||||||
|
{ |
||||||
|
// For the purposes of "tracing" the code, when trying to figure out |
||||||
|
// where exactly problems occurred. |
||||||
|
// Should not be used in any released version of |
||||||
|
// your packages/mutators. |
||||||
|
LOG_Track, |
||||||
|
// Information that can be used to track down errors that occur on |
||||||
|
// other people's systems, that developer cannot otherwise pinpoint. |
||||||
|
// Should be used with purpose of tracking a certain issue and |
||||||
|
// not "just in case". |
||||||
|
LOG_Debug, |
||||||
|
// Information about important events that should be occurring under |
||||||
|
// normal conditions, such as initializations/shutdowns, |
||||||
|
// successful completion of significant events, configuration assumptions. |
||||||
|
// Should not occur too often. |
||||||
|
LOG_Info, |
||||||
|
// For recoverable issues, anything that might cause errors or |
||||||
|
// oddities in behavior. |
||||||
|
// Should be used sparingly, i.e. player disconnecting might cause |
||||||
|
// interruption in some logic, but should not cause a warning, |
||||||
|
// since it is something expected to happen normally. |
||||||
|
LOG_Warning, |
||||||
|
// Use this for errors, - events that some operation cannot recover from, |
||||||
|
// but still does not require your module to shut down. |
||||||
|
LOG_Failure, |
||||||
|
// Anything that does not allow your module or game to function, |
||||||
|
// completely irrecoverable failure state. |
||||||
|
LOG_Fatal |
||||||
|
}; |
||||||
|
|
||||||
|
var private const string kfLogPrefix; |
||||||
|
var private const string traceLevelName; |
||||||
|
var private const string DebugLevelName; |
||||||
|
var private const string infoLevelName; |
||||||
|
var private const string warningLevelName; |
||||||
|
var private const string errorLevelName; |
||||||
|
var private const string fatalLevelName; |
||||||
|
|
||||||
|
var private config array< class<Manifest> > registeredManifests; |
||||||
|
var private config bool logTraceInKFLog; |
||||||
|
var private config bool logDebugInKFLog; |
||||||
|
var private config bool logInfoInKFLog; |
||||||
|
var private config bool logWarningInKFLog; |
||||||
|
var private config bool logErrorInKFLog; |
||||||
|
var private config bool logFatalInKFLog; |
||||||
|
|
||||||
|
var private array<string> traceMessages; |
||||||
|
var private array<string> debugMessages; |
||||||
|
var private array<string> infoMessages; |
||||||
|
var private array<string> warningMessages; |
||||||
|
var private array<string> errorMessages; |
||||||
|
var private array<string> fatalMessages; |
||||||
|
|
||||||
|
public final function bool ShouldAddToKFLog(LogLevel messageLevel) |
||||||
|
{ |
||||||
|
if (messageLevel == LOG_Track && logTraceInKFLog) return true; |
||||||
|
if (messageLevel == LOG_Debug && logDebugInKFLog) return true; |
||||||
|
if (messageLevel == LOG_Info && logInfoInKFLog) return true; |
||||||
|
if (messageLevel == LOG_Warning && logWarningInKFLog) return true; |
||||||
|
if (messageLevel == LOG_Failure && logErrorInKFLog) return true; |
||||||
|
if (messageLevel == LOG_Fatal && logFatalInKFLog) return true; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public final static function LogMessageToKFLog |
||||||
|
( |
||||||
|
LogLevel messageLevel, |
||||||
|
string message |
||||||
|
) |
||||||
|
{ |
||||||
|
local string levelPrefix; |
||||||
|
levelPrefix = default.kfLogPrefix; |
||||||
|
switch (messageLevel) |
||||||
|
{ |
||||||
|
case LOG_Track: |
||||||
|
levelPrefix = levelPrefix $ default.traceLevelName; |
||||||
|
break; |
||||||
|
case LOG_Debug: |
||||||
|
levelPrefix = levelPrefix $ default.debugLevelName; |
||||||
|
break; |
||||||
|
case LOG_Info: |
||||||
|
levelPrefix = levelPrefix $ default.infoLevelName; |
||||||
|
break; |
||||||
|
case LOG_Warning: |
||||||
|
levelPrefix = levelPrefix $ default.warningLevelName; |
||||||
|
break; |
||||||
|
case LOG_Failure: |
||||||
|
levelPrefix = levelPrefix $ default.errorLevelName; |
||||||
|
break; |
||||||
|
case LOG_Fatal: |
||||||
|
levelPrefix = levelPrefix $ default.fatalLevelName; |
||||||
|
break; |
||||||
|
default: |
||||||
|
} |
||||||
|
Log(levelPrefix @ message); |
||||||
|
} |
||||||
|
|
||||||
|
public final function LogMessage(LogLevel messageLevel, string message) |
||||||
|
{ |
||||||
|
switch (messageLevel) |
||||||
|
{ |
||||||
|
case LOG_Track: |
||||||
|
traceMessages[traceMessages.length] = message; |
||||||
|
case LOG_Debug: |
||||||
|
debugMessages[debugMessages.length] = message; |
||||||
|
case LOG_Info: |
||||||
|
infoMessages[infoMessages.length] = message; |
||||||
|
case LOG_Warning: |
||||||
|
warningMessages[warningMessages.length] = message; |
||||||
|
case LOG_Failure: |
||||||
|
errorMessages[errorMessages.length] = message; |
||||||
|
case LOG_Fatal: |
||||||
|
fatalMessages[fatalMessages.length] = message; |
||||||
|
default: |
||||||
|
} |
||||||
|
if (ShouldAddToKFLog(messageLevel)) |
||||||
|
{ |
||||||
|
LogMessageToKFLog(messageLevel, message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
// Log everything by default, if someone does not like it - |
||||||
|
// he/she can disable it themselves. |
||||||
|
logTraceInKFLog = true |
||||||
|
logDebugInKFLog = true |
||||||
|
logInfoInKFLog = true |
||||||
|
logWarningInKFLog = true |
||||||
|
logErrorInKFLog = true |
||||||
|
logFatalInKFLog = true |
||||||
|
// Parts of the prefix for our log messages, redirected into kf log file. |
||||||
|
kfLogPrefix = "Acedia:" |
||||||
|
traceLevelName = "Trace" |
||||||
|
debugLevelName = "Debug" |
||||||
|
infoLevelName = "Info" |
||||||
|
warningLevelName = "Warning" |
||||||
|
errorLevelName = "Error" |
||||||
|
fatalLevelName = "Fatal" |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
/** |
||||||
|
* Manifest is meant to describe contents of the Acedia's 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 |
||||||
|
{ |
||||||
|
aliasSources(0) = class'AliasSource' |
||||||
|
aliasSources(1) = class'WeaponAliasSource' |
||||||
|
aliasSources(2) = class'ColorAliasSource' |
||||||
|
testCases(0) = class'TEST_Aliases' |
||||||
|
testCases(1) = class'TEST_ColorAPI' |
||||||
|
testCases(2) = class'TEST_JSON' |
||||||
|
testCases(3) = class'TEST_Text' |
||||||
|
testCases(4) = class'TEST_TextAPI' |
||||||
|
testCases(5) = class'TEST_Parser' |
||||||
|
} |
@ -0,0 +1,290 @@ |
|||||||
|
/** |
||||||
|
* API that provides functions for managing objects and actors by providing |
||||||
|
* easy and general means to create and destroy them, that allow to make use of |
||||||
|
* temporary `Object`s in a more efficient way. |
||||||
|
* This is a low-level API that most users of Acedia, most likely, |
||||||
|
* would not have to use, since creation of most objects would use their own |
||||||
|
* wrapper functions around this API. |
||||||
|
* 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 MemoryAPI extends Singleton; |
||||||
|
|
||||||
|
// This variable counts ticks and should be different each new tick. |
||||||
|
var private int currentTick; |
||||||
|
|
||||||
|
// Stores instance of an `Object` that can be borrowed from the pool. |
||||||
|
struct BorrowableRecord |
||||||
|
{ |
||||||
|
// Borrowable instance |
||||||
|
var Object instance; |
||||||
|
// Was this object borrowed? |
||||||
|
// This flag will persist unless object was explicitly freed, |
||||||
|
// even if borrowed reference timed out. |
||||||
|
var bool borrowed; |
||||||
|
// When was this object borrowed? |
||||||
|
// Used to automatically free borrowed objects after the tick has passed. |
||||||
|
var int borrowTick; |
||||||
|
}; |
||||||
|
|
||||||
|
// Available object pools |
||||||
|
var private array<BorrowableRecord> borrowPool; |
||||||
|
|
||||||
|
// Checks if instance in the given `record` is borrowed. |
||||||
|
private final function bool IsBorrowed(BorrowableRecord record) |
||||||
|
{ |
||||||
|
// `record.borrowed` means instance was borrowed, |
||||||
|
// but not explicitly freed; |
||||||
|
// `record.borrowTick >= currentTick` means that rights to the borrowed |
||||||
|
// instance hasn't yet ran out. |
||||||
|
return (record.borrowed && record.borrowTick >= currentTick); |
||||||
|
} |
||||||
|
|
||||||
|
// Loads a reference to class instance from it's string representation. |
||||||
|
private final function class<Object> LoadClass(string classReference) |
||||||
|
{ |
||||||
|
return class<Object>(DynamicLoadObject(classReference, class'Class', true)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new `Object` / `Actor` of a given class. |
||||||
|
* |
||||||
|
* If uses a proper spawning mechanism for both objects (`new`) |
||||||
|
* and actors (`Spawn`). |
||||||
|
* |
||||||
|
* @param classToAllocate Class of the `Object` / `Actor` that this method |
||||||
|
* must create. |
||||||
|
* @return Newly created object, might be `none` if creation has failed. |
||||||
|
*/ |
||||||
|
public final function Object Allocate(class<Object> classToAllocate) |
||||||
|
{ |
||||||
|
local class<Actor> actorClassToSpawn; |
||||||
|
if (classToAllocate == none) return none; |
||||||
|
|
||||||
|
actorClassToSpawn = class<Actor>(classToAllocate); |
||||||
|
if (actorClassToSpawn != none) |
||||||
|
{ |
||||||
|
return Spawn(actorClassToSpawn); |
||||||
|
} |
||||||
|
return (new classToAllocate); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new `Object` / `Actor` of a given class. |
||||||
|
* |
||||||
|
* If uses a proper spawning mechanism for both objects (`new`) |
||||||
|
* and actors (`Spawn`). |
||||||
|
* |
||||||
|
* @param classToAllocate Text representation (name) of the class of the |
||||||
|
* `Object` / `Actor` that this method must create. |
||||||
|
* Should contain full package-path. |
||||||
|
* @return Newly created object, might be `none` if creation has failed. |
||||||
|
*/ |
||||||
|
public final function Object AllocateByReference(string refToClassToAllocate) |
||||||
|
{ |
||||||
|
return Allocate(LoadClass(refToClassToAllocate)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Borrows an instance of an `Object` / `Actor` of the given class |
||||||
|
* from the pool. |
||||||
|
* Borrowed instance will be auto-freed during next tick. |
||||||
|
* |
||||||
|
* @param classToBorrow Class of an `Object` / `Actor` we want to borrow. |
||||||
|
* @return Borrowed object, might be `none` if borrow pool is empty and |
||||||
|
* creation of a new `Object` / `Actor` has failed. |
||||||
|
*/ |
||||||
|
public final function Object Borrow(class<Object> classToBorrow) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local BorrowableRecord newRecord; |
||||||
|
for (i = 0; i < borrowPool.length; i += 1) |
||||||
|
{ |
||||||
|
if (IsBorrowed(borrowPool[i])) continue; |
||||||
|
if (borrowPool[i].instance == none) continue; |
||||||
|
if (borrowPool[i].instance.class != classToBorrow) continue; |
||||||
|
|
||||||
|
borrowPool[i].borrowed = true; |
||||||
|
borrowPool[i].borrowTick = currentTick; |
||||||
|
return borrowPool[i].instance; |
||||||
|
} |
||||||
|
// Create a new instance to borrow, if there isn't any available for |
||||||
|
// the given class. |
||||||
|
newRecord.borrowed = false; |
||||||
|
newRecord.instance = Allocate(classToBorrow); |
||||||
|
if (newRecord.instance != none) |
||||||
|
{ |
||||||
|
borrowPool[borrowPool.length] = newRecord; |
||||||
|
} |
||||||
|
return newRecord.instance; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Borrows an instance of an `Object` / `Actor` of the given class |
||||||
|
* from the pool. |
||||||
|
* Borrowed instance will be auto-freed during next tick. |
||||||
|
* |
||||||
|
* @param classToBorrow Text representation (name) of the class of |
||||||
|
* an `Object` / `Actor` we want to borrow. |
||||||
|
* @return Borrowed object, might be `none` if borrow pool is empty and |
||||||
|
* creation of a new `Object` / `Actor` has failed. |
||||||
|
*/ |
||||||
|
public final function Object BorrowByReference(string refToClassToBorrow) |
||||||
|
{ |
||||||
|
return Borrow(LoadClass(refToClassToBorrow)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Claims an instance of an `Object` / `Actor` of the given class |
||||||
|
* from the pool. |
||||||
|
* Claimed instances are removed from the borrow pool and |
||||||
|
* will not be automatically freed. |
||||||
|
* |
||||||
|
* @param classToClaim Class of an `Object` / `Actor` we wish to borrow. |
||||||
|
* @return Borrowed object, might be `none` if borrow pool is empty and |
||||||
|
* creation of a new `Object` / `Actor` has failed. |
||||||
|
*/ |
||||||
|
public final function Object Claim(class<Object> classToClaim) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Object instance; |
||||||
|
for (i = 0; i < borrowPool.length; i += 1) |
||||||
|
{ |
||||||
|
if (IsBorrowed(borrowPool[i])) continue; |
||||||
|
if (borrowPool[i].instance == none) continue; |
||||||
|
if (borrowPool[i].instance.class != classToClaim) continue; |
||||||
|
|
||||||
|
instance = borrowPool[i].instance; |
||||||
|
borrowPool.Remove(i, 1); |
||||||
|
return instance; |
||||||
|
} |
||||||
|
// Create a new instance to borrow, if there isn't any available for |
||||||
|
// the given class. |
||||||
|
return Allocate(classToClaim); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Claims an instance of an `Object` / `Actor` of the given class |
||||||
|
* from the pool. |
||||||
|
* Claimed instances are removed from the borrow pool and |
||||||
|
* will not be automatically freed. |
||||||
|
* |
||||||
|
* @param classToClaim Text representation (name) of the class of |
||||||
|
* an `Object` / `Actor` we wish to claim. |
||||||
|
* @return Borrowed object, might be `none` if borrow pool is empty and |
||||||
|
* creation of a new `Object` / `Actor` has failed. |
||||||
|
*/ |
||||||
|
public final function Object ClaimByReference(string refToClassToClaim) |
||||||
|
{ |
||||||
|
return Claim(LoadClass(refToClassToClaim)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Frees given `Object` / `Actor` resource. |
||||||
|
* |
||||||
|
* By default `Actor`s are destroyed. |
||||||
|
* Due to limitations of the engine objects cannot be outright destroyed. |
||||||
|
* Instead, they are put into a "borrow pool", from where they can later be |
||||||
|
* taken for a reuse. |
||||||
|
* |
||||||
|
* @param objectToDelete `Object` / `Actor` that must be freed. |
||||||
|
* @param forceMakeBorrowable Only has an effect if `objectToDelete` |
||||||
|
* is an `Actor`, in which case it forces it to be added |
||||||
|
* to the borrow pool, instead of being destroyed. |
||||||
|
*/ |
||||||
|
public final function Free |
||||||
|
( |
||||||
|
Object objectToDelete, |
||||||
|
optional bool forceMakeBorrowable |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Actor actorToDelete; |
||||||
|
local BorrowableRecord newRecord; |
||||||
|
if (objectToDelete == none) return; |
||||||
|
|
||||||
|
actorToDelete = Actor(objectToDelete); |
||||||
|
if (actorToDelete != none && !forceMakeBorrowable) |
||||||
|
{ |
||||||
|
actorToDelete.Destroy(); |
||||||
|
return; |
||||||
|
} |
||||||
|
// Check if `objectToDelete` is already in our records. |
||||||
|
for (i = 0; i < borrowPool.length; i += 1) |
||||||
|
{ |
||||||
|
if (borrowPool[i].instance == objectToDelete) |
||||||
|
{ |
||||||
|
borrowPool[i].borrowed = false; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
// If not - add it |
||||||
|
newRecord.instance = objectToDelete; |
||||||
|
newRecord.borrowed = false; |
||||||
|
borrowPool[borrowPool.length] = newRecord; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Forces Unreal Engine to do garbage collection. |
||||||
|
* By default also cleans up all the objects in the borrow object pool. |
||||||
|
* |
||||||
|
* Process of garbage collection causes significant lag spike during the game |
||||||
|
* and should be used carefully. |
||||||
|
* |
||||||
|
* NOTE: method does not guarantee that borrow pool will be empty after |
||||||
|
* this call (even with `keepBorrowedObjectPool = true`), |
||||||
|
* since some of the borrowable objects might be currently in use and, |
||||||
|
* therefore, cannot be garbage collected. |
||||||
|
* |
||||||
|
* @param keepBorrowedObjectPool Set this to `true` to NOT garbage collect |
||||||
|
* objects in a borrow pool. Otherwise keep it `false`. |
||||||
|
*/ |
||||||
|
public final function CollectGarbage(optional bool keepBorrowedObjectPool) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (!keepBorrowedObjectPool) |
||||||
|
{ |
||||||
|
// Dereference all non-borrowed objects from borrow pool, |
||||||
|
// so that they can be garbage collected. |
||||||
|
i = 0; |
||||||
|
while (i < borrowPool.length) |
||||||
|
{ |
||||||
|
if ( borrowPool[i].instance == none |
||||||
|
|| !IsBorrowed(borrowPool[i]) ) |
||||||
|
{ |
||||||
|
borrowPool.Remove(i, 1); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// This makes Unreal Engine do garbage collection |
||||||
|
ConsoleCommand("obj garbage"); |
||||||
|
} |
||||||
|
|
||||||
|
event Tick(float delta) |
||||||
|
{ |
||||||
|
currentTick += 1; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: add cleaning on cooldown |
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
currentTick = 0 |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
/** |
||||||
|
* Parent class for all services used in Acedia. |
||||||
|
* Currently simply makes itself server-only. |
||||||
|
* 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 Service extends Singleton |
||||||
|
abstract; |
||||||
|
|
||||||
|
// Listeners listed here will be automatically activated. |
||||||
|
var public const array< class<Listener> > requiredListeners; |
||||||
|
|
||||||
|
// Enables feature of given class. |
||||||
|
public static final function Service Require() |
||||||
|
{ |
||||||
|
local Service newInstance; |
||||||
|
if (IsRunning()) |
||||||
|
{ |
||||||
|
return Service(GetInstance()); |
||||||
|
} |
||||||
|
default.blockSpawning = false; |
||||||
|
newInstance = __().Spawn(default.class); |
||||||
|
default.blockSpawning = true; |
||||||
|
return newInstance; |
||||||
|
} |
||||||
|
|
||||||
|
// Whether service is currently running is determined by |
||||||
|
public static final function bool IsRunning() |
||||||
|
{ |
||||||
|
return (GetInstance() != none); |
||||||
|
} |
||||||
|
|
||||||
|
protected function OnLaunch(){} |
||||||
|
protected function OnShutdown(){} |
||||||
|
|
||||||
|
protected function OnCreated() |
||||||
|
{ |
||||||
|
default.blockSpawning = true; |
||||||
|
SetListenersActiveSatus(true); |
||||||
|
OnLaunch(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function OnDestroyed() |
||||||
|
{ |
||||||
|
SetListenersActiveSatus(false); |
||||||
|
OnShutdown(); |
||||||
|
} |
||||||
|
|
||||||
|
// Set listeners' status |
||||||
|
private static function SetListenersActiveSatus(bool newStatus) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < default.requiredListeners.length; i += 1) |
||||||
|
{ |
||||||
|
if (default.requiredListeners[i] == none) continue; |
||||||
|
default.requiredListeners[i].static.SetActive(newStatus); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
DrawType = DT_None |
||||||
|
// Prevent spawning this feature by any other means than 'Launch()'. |
||||||
|
blockSpawning = true |
||||||
|
// Features are server-only actors |
||||||
|
remoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
/** |
||||||
|
* Event generator for 'ConnectionService'. |
||||||
|
* 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 ConnectionEvents extends Events |
||||||
|
dependson(ConnectionService) |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function CallPlayerConnected(ConnectionService.Connection connection) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
class<ConnectionListenerBase>(listeners[i]) |
||||||
|
.static.PlayerConnected(connection); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static function CallPlayerDisconnected(ConnectionService.Connection connection) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
class<ConnectionListenerBase>(listeners[i]) |
||||||
|
.static.PlayerDisconnected(connection); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedListener = class'ConnectionListenerBase' |
||||||
|
connectedServiceClass = class'ConnectionService' |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
/** |
||||||
|
* Listener for events generated by 'ConnectionService'. |
||||||
|
* 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 ConnectionListenerBase extends Listener |
||||||
|
dependson(ConnectionService) |
||||||
|
abstract; |
||||||
|
|
||||||
|
// 'PlayerConnected' is called the moment we detect a new player on a server. |
||||||
|
static function PlayerConnected(ConnectionService.Connection connection); |
||||||
|
|
||||||
|
// 'PlayerDisconnected' is called the moment we |
||||||
|
// detect a player leaving the server. |
||||||
|
static function PlayerDisconnected(ConnectionService.Connection connection); |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'ConnectionEvents' |
||||||
|
} |
@ -0,0 +1,144 @@ |
|||||||
|
/** |
||||||
|
* This service tracks current connections to the server |
||||||
|
* as well as their basic information, |
||||||
|
* like IP or steam ID of connecting player. |
||||||
|
* 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 ConnectionService extends Service; |
||||||
|
|
||||||
|
// Stores basic information about a connection |
||||||
|
struct Connection |
||||||
|
{ |
||||||
|
var public string networkAddress; |
||||||
|
var public string steamID; |
||||||
|
var public PlayerController controllerReference; |
||||||
|
// Reference to 'AcediaReplicationInfo' for this client, |
||||||
|
// in case it was created. |
||||||
|
var private AcediaReplicationInfo acediaRI; |
||||||
|
}; |
||||||
|
|
||||||
|
var private array<Connection> activeConnections; |
||||||
|
|
||||||
|
// Shortcut to 'ConnectionEvents', so that we don't have to write |
||||||
|
// class'ConnectionEvents' every time. |
||||||
|
var const class<ConnectionEvents> events; |
||||||
|
|
||||||
|
// Returning 'true' guarantees that 'controllerToCheck != none' |
||||||
|
// and either 'controllerToCheck.playerReplicationInfo != none' |
||||||
|
// or 'auxiliaryRepInfo != none'. |
||||||
|
private function bool IsHumanController(PlayerController controllerToCheck) |
||||||
|
{ |
||||||
|
local PlayerReplicationInfo replicationInfo; |
||||||
|
if (controllerToCheck == none) return false; |
||||||
|
if (!controllerToCheck.bIsPlayer) return false; |
||||||
|
// Is this a WebAdmin that didn't yet set 'bIsPlayer = false' |
||||||
|
if (MessagingSpectator(controllerToCheck) != none) return false; |
||||||
|
// Check replication info |
||||||
|
replicationInfo = controllerToCheck.playerReplicationInfo; |
||||||
|
if (replicationInfo == none) return false; |
||||||
|
if (replicationInfo.bBot) return false; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// 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 function int GetConnectionIndex(PlayerController controllerToCheck) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (controllerToCheck == none) return -1; |
||||||
|
for (i = 0; i < activeConnections.length; i += 1) |
||||||
|
{ |
||||||
|
if (activeConnections[i].controllerReference == controllerToCheck) |
||||||
|
{ |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// Remove connections with now invalid ('none') player controller reference. |
||||||
|
private function RemoveBrokenConnections() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
i = 0; |
||||||
|
while (i < activeConnections.length) |
||||||
|
{ |
||||||
|
if (activeConnections[i].controllerReference == none) |
||||||
|
{ |
||||||
|
if (activeConnections[i].acediaRI != none) |
||||||
|
{ |
||||||
|
activeConnections[i].acediaRI.Destroy(); |
||||||
|
} |
||||||
|
events.static.CallPlayerDisconnected(activeConnections[i]); |
||||||
|
activeConnections.Remove(i, 1); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Return connection, corresponding to a given player controller. |
||||||
|
public final function Connection GetConnection(PlayerController player) |
||||||
|
{ |
||||||
|
local int connectionIndex; |
||||||
|
local Connection emptyConnection; |
||||||
|
connectionIndex = GetConnectionIndex(player); |
||||||
|
if (connectionIndex < 0) return emptyConnection; |
||||||
|
return activeConnections[connectionIndex]; |
||||||
|
} |
||||||
|
|
||||||
|
// Attempts to register a connection for this player controller. |
||||||
|
// Shouldn't be used outside of 'ConnectionService' module. |
||||||
|
// Returns 'true' if connection is registered (even if it was already added). |
||||||
|
public final function bool RegisterConnection(PlayerController player) |
||||||
|
{ |
||||||
|
local Connection newConnection; |
||||||
|
if (!IsHumanController(player)) return false; |
||||||
|
if (GetConnectionIndex(player) >= 0) return true; |
||||||
|
newConnection.controllerReference = player; |
||||||
|
// TODO: move this check to AcediaCore |
||||||
|
/*if (!class'Acedia'.static.GetInstance().IsServerOnly()) |
||||||
|
{ |
||||||
|
newConnection.acediaRI = Spawn(class'AcediaReplicationInfo', player); |
||||||
|
newConnection.acediaRI.linkOwner = player; |
||||||
|
}*/ |
||||||
|
newConnection.networkAddress = player.GetPlayerNetworkAddress(); |
||||||
|
newConnection.steamID = player.GetPlayerIDHash(); |
||||||
|
activeConnections[activeConnections.length] = newConnection; |
||||||
|
events.static.CallPlayerConnected(newConnection); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public final function array<Connection> GetActiveConnections() |
||||||
|
{ |
||||||
|
return activeConnections; |
||||||
|
} |
||||||
|
|
||||||
|
event Tick(float delta) |
||||||
|
{ |
||||||
|
RemoveBrokenConnections(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
events = class'ConnectionEvents' |
||||||
|
requiredListeners(0) = class'MutatorListener_Connection' |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
/** |
||||||
|
* Overloaded mutator events listener to catch connecting players. |
||||||
|
* 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_Connection extends MutatorListenerBase |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||||
|
{ |
||||||
|
local KFSteamStatsAndAchievements playerSteamStatsAndAchievements; |
||||||
|
local PlayerController player; |
||||||
|
local ConnectionService service; |
||||||
|
// We are looking for 'KFSteamStatsAndAchievements' instead of |
||||||
|
// 'PlayerController' because, by the time they it's created, |
||||||
|
// controller should have a valid reference to 'PlayerReplicationInfo', |
||||||
|
// as well as valid network address and IDHash (steam id). |
||||||
|
// However, neither of those are properly initialized at the point when |
||||||
|
// 'CheckReplacement' is called for 'PlayerController'. |
||||||
|
// |
||||||
|
// Since 'KFSteamStatsAndAchievements' |
||||||
|
// is created soon after (at the same tick) |
||||||
|
// for each new `PlayerController`, |
||||||
|
// we'll be detecting new users right after server |
||||||
|
// detected and properly initialized them. |
||||||
|
playerSteamStatsAndAchievements = KFSteamStatsAndAchievements(other); |
||||||
|
if (playerSteamStatsAndAchievements == none) return true; |
||||||
|
service = ConnectionService(class'ConnectionService'.static.GetInstance()); |
||||||
|
if (service == none) return true; |
||||||
|
|
||||||
|
player = PlayerController(playerSteamStatsAndAchievements.owner); |
||||||
|
service.RegisterConnection(player); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'MutatorEvents' |
||||||
|
} |
@ -0,0 +1,104 @@ |
|||||||
|
/** |
||||||
|
* Singleton is an auxiliary class, meant to be used as a base for others, |
||||||
|
* that allows for only one instance of it to exist. |
||||||
|
* To make sure your child class properly works, either don't overload |
||||||
|
* 'PreBeginPlay' or make sure to call it's parent's version. |
||||||
|
* 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 Singleton extends AcediaActor |
||||||
|
abstract; |
||||||
|
|
||||||
|
// Default value of this variable will store one and only existing version |
||||||
|
// of actor of this class. |
||||||
|
var private Singleton activeInstance; |
||||||
|
|
||||||
|
// Setting default value of this variable to 'true' prevents creation of |
||||||
|
// a singleton, even if no instances of it exist. |
||||||
|
// Only a default value is ever used. |
||||||
|
var protected bool blockSpawning; |
||||||
|
|
||||||
|
public final static function Singleton GetInstance(optional bool spawnIfMissing) |
||||||
|
{ |
||||||
|
local bool instanceExists; |
||||||
|
instanceExists = default.activeInstance != none |
||||||
|
&& !default.activeInstance.bPendingDelete; |
||||||
|
if (instanceExists) { |
||||||
|
return default.activeInstance; |
||||||
|
} |
||||||
|
if (spawnIfMissing) { |
||||||
|
return __().Spawn(default.class); |
||||||
|
} |
||||||
|
return none; |
||||||
|
} |
||||||
|
|
||||||
|
public final static function bool IsSingletonCreationBlocked() |
||||||
|
{ |
||||||
|
return default.blockSpawning; |
||||||
|
} |
||||||
|
|
||||||
|
protected function OnCreated(){} |
||||||
|
protected function OnDestroyed(){} |
||||||
|
|
||||||
|
// Make sure only one instance of 'Singleton' exists at any point in time. |
||||||
|
// Instead of overloading this function we suggest you overload a special |
||||||
|
// event function `OnCreated()` that is called whenever a valid `Singleton` |
||||||
|
// instance is spawned. |
||||||
|
// If you absolutely must overload this function in any child class - |
||||||
|
// first call this version of the method and then check if |
||||||
|
// you are about to be deleted 'bDeleteMe == true': |
||||||
|
// ____________________________________________________________________________ |
||||||
|
// | super.PreBeginPlay(); |
||||||
|
// | // ^^^ If singleton wasn't already created, - only after that call |
||||||
|
// | // will instance, returned by 'GetInstance()', be set. |
||||||
|
// | if (bDeleteMe) |
||||||
|
// | return; |
||||||
|
// |___________________________________________________________________________ |
||||||
|
event PreBeginPlay() |
||||||
|
{ |
||||||
|
super.PreBeginPlay(); |
||||||
|
if (default.blockSpawning || GetInstance() != none) |
||||||
|
{ |
||||||
|
Destroy(); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
default.activeInstance = self; |
||||||
|
OnCreated(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Make sure only one instance of 'Singleton' exists at any point in time. |
||||||
|
// Instead of overloading this function we suggest you overload a special |
||||||
|
// event function `OnDestroyed()` that is called whenever a valid `Singleton` |
||||||
|
// instance is destroyed. |
||||||
|
// If you absolutely must overload this function in any child class - |
||||||
|
// first call this version of the method. |
||||||
|
event Destroyed() |
||||||
|
{ |
||||||
|
super.Destroyed(); |
||||||
|
if (self == default.activeInstance) |
||||||
|
{ |
||||||
|
OnDestroyed(); |
||||||
|
default.activeInstance = none; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
blockSpawning = false |
||||||
|
} |
@ -0,0 +1,294 @@ |
|||||||
|
/** |
||||||
|
* Class for storing and processing the information about how well testing |
||||||
|
* against a certain issue went. |
||||||
|
* 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 IssueSummary extends AcediaObject; |
||||||
|
|
||||||
|
// Each issue is uniquely identified by these values. |
||||||
|
var private class<TestCase> ownerCase; |
||||||
|
var private string context; |
||||||
|
var private string description; |
||||||
|
|
||||||
|
// Records, in chronological order, results of the tests that were |
||||||
|
// run to test this issue. |
||||||
|
var private array<byte> successRecords; |
||||||
|
|
||||||
|
private final function byte BoolToByte(bool boolToConvert) |
||||||
|
{ |
||||||
|
if (boolToConvert) return 1; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets `TestCase`, context and description for the issue, |
||||||
|
* tracked in this summary. |
||||||
|
* |
||||||
|
* Can only be successfully called once, but will fail if passed a `none` |
||||||
|
* class reference to `TestCase`. |
||||||
|
* |
||||||
|
* @param targetCase `TestCase`, in which issue, |
||||||
|
* relevant to this summary, is defined. |
||||||
|
* @param targetContext Context, in which this issue, |
||||||
|
* relevant to this summary, is defined. |
||||||
|
* @param targetDescription Description of the issue relevant to |
||||||
|
* this summary. |
||||||
|
* @return `true` if `TestCase`, context and description were successfully set, |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool SetIssue( |
||||||
|
class<TestCase> targetCase, |
||||||
|
string targetContext, |
||||||
|
string targetDescription |
||||||
|
) |
||||||
|
{ |
||||||
|
if (ownerCase != none) return false; |
||||||
|
if (targetCase == none) return false; |
||||||
|
ownerCase = targetCase; |
||||||
|
context = targetContext; |
||||||
|
description = targetDescription; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns context for the issue in question. |
||||||
|
* |
||||||
|
* `TestCase` can be important for both displaying information about testing to |
||||||
|
* the user and distinguishing between two different issues with the same |
||||||
|
* description and context. |
||||||
|
* @see `TestCase` for more information. |
||||||
|
* |
||||||
|
* @return Test case that tested for relevant issue. |
||||||
|
*/ |
||||||
|
public final function class<TestCase> GetTestCase() |
||||||
|
{ |
||||||
|
return ownerCase; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns context for the issue in question. |
||||||
|
* |
||||||
|
* Context can be important for both displaying information about testing to |
||||||
|
* the user and distinguishing between two different issues with |
||||||
|
* the same description and in the same `TestCase`. |
||||||
|
* @see `TestCase` for more information. |
||||||
|
* |
||||||
|
* @return Context for relevant issue. |
||||||
|
*/ |
||||||
|
public final function string GetContext() |
||||||
|
{ |
||||||
|
if (ownerCase == none) return ""; |
||||||
|
return context; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns description for the issue in question. |
||||||
|
* |
||||||
|
* Description of an issue is the main way to distinguish between |
||||||
|
* different possibly arising problems. |
||||||
|
* Two different issues can have the same description if they are defined |
||||||
|
* in different `TestCase`s and/or in different context. |
||||||
|
* @see `TestCase` for more information. |
||||||
|
* |
||||||
|
* @return Description for the issue in question. |
||||||
|
*/ |
||||||
|
public final function string GetDescription() |
||||||
|
{ |
||||||
|
if (ownerCase == none) return ""; |
||||||
|
return description; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds result of another test (success or not) to the records of this summary. |
||||||
|
* |
||||||
|
* @param success `true` if test was successful and had passed, |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function AddTestResult(bool success) |
||||||
|
{ |
||||||
|
successRecords[successRecords.length] = BoolToByte(success); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns total amount of test results recorded in caller summary. |
||||||
|
* Never a negative value. |
||||||
|
* |
||||||
|
* @return Amount of tests that were run. |
||||||
|
*/ |
||||||
|
public final function int GetTotalTestsAmount() |
||||||
|
{ |
||||||
|
return successRecords.length; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns total amount of recorded successful test results in caller summary. |
||||||
|
* Never a negative value. |
||||||
|
* |
||||||
|
* @return Amount of recorded successfully performed tests for |
||||||
|
* the relevant issue. |
||||||
|
*/ |
||||||
|
public final function int GetSuccessfulTestsAmount() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int counter; |
||||||
|
counter = 0; |
||||||
|
for (i = 0; i < successRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (successRecords[i] > 0) { |
||||||
|
counter += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return counter; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns total amount of recorded failed test results in caller summary. |
||||||
|
* Never a negative value. |
||||||
|
* |
||||||
|
* @return Amount of recorded failed tests for the relevant issue. |
||||||
|
*/ |
||||||
|
public final function int GetFailedTestsAmount() |
||||||
|
{ |
||||||
|
return GetTotalTestsAmount() - GetSuccessfulTestsAmount(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns total success rate ("amount of successes" / "total amount of tests") |
||||||
|
* of recorded test results for relevant issue |
||||||
|
* (value between 0 and 1, including boundaries). |
||||||
|
* |
||||||
|
* If there are no test results recorded - returns `-1`. |
||||||
|
* |
||||||
|
* @return Success rate of recorded test results for the relevant issue |
||||||
|
* Returns values outside [0; 1] segment (specifically, negative values) |
||||||
|
* iff no test results at all were recorded. |
||||||
|
*/ |
||||||
|
public final function float GetSuccessRate() |
||||||
|
{ |
||||||
|
local int totalTestsAmount; |
||||||
|
totalTestsAmount = GetTotalTestsAmount(); |
||||||
|
if (totalTestsAmount <= 0) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
return GetSuccessfulTestsAmount() / totalTestsAmount; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether all tests recorded in this summary have passed. |
||||||
|
* |
||||||
|
* @return `true` if all tests for relevant issue have passed, |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool HasPassedAllTests() |
||||||
|
{ |
||||||
|
return (GetFailedTestsAmount() <= 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns boolean array of test results: each element recording whether test |
||||||
|
* was a success (`>0`) or a failure (`0`). |
||||||
|
* |
||||||
|
* All results in the array are in a chronological order of arrival. |
||||||
|
* |
||||||
|
* @return Returns copy of boolean array of recorded test results. |
||||||
|
*/ |
||||||
|
public final function array<byte> GetTestRecords() |
||||||
|
{ |
||||||
|
return successRecords; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns index numbers (starting from 1, not 0) of tests that ended in |
||||||
|
* a success, while performed for the same test case, context and issue. |
||||||
|
* So if tests went: [success, success, failure, success, failure], |
||||||
|
* method will return: [1, 2, 4]. |
||||||
|
* |
||||||
|
* All results in the array are in a chronological order of arrival. |
||||||
|
* |
||||||
|
* @return index numbers of successful tests. |
||||||
|
*/ |
||||||
|
public final function array<int> GetSuccessfulTests() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<int> result; |
||||||
|
for (i = 0; i < successRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (successRecords[i] > 0) { |
||||||
|
result[result.length] = i + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns index numbers (starting from 1, not 0) of tests that ended in |
||||||
|
* a failure, while performed for the same test case, context and issue. |
||||||
|
* So if tests went: [success, success, failure, success, failure], |
||||||
|
* method will return: [3, 5]. |
||||||
|
* |
||||||
|
* All results in the array are in a chronological order of arrival. |
||||||
|
* |
||||||
|
* @return index numbers of successful tests. |
||||||
|
*/ |
||||||
|
public final function array<int> GetFailedTests() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<int> result; |
||||||
|
for (i = 0; i < successRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (successRecords[i] == 0) { |
||||||
|
result[result.length] = i + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a formatted text representation of the caller `IssueSummary` |
||||||
|
* in a following format: |
||||||
|
* "{$text_default <issue_description>} {$text_subtle [<failed_test_numbers>]}" |
||||||
|
* |
||||||
|
* @return Formatted string with text representation of the |
||||||
|
* caller `IssueSummary`. |
||||||
|
*/ |
||||||
|
public final function string ToString() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local string result; |
||||||
|
local array<int> failedTests; |
||||||
|
result = "{$text_default" @ GetDescription() $ "}"; |
||||||
|
if (GetFailedTestsAmount() <= 0) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
result @= "{$text_subtle ["; |
||||||
|
failedTests = GetFailedTests(); |
||||||
|
for (i = 0; i < failedTests.length; i += 1) |
||||||
|
{ |
||||||
|
if (i < failedTests.length - 1) { |
||||||
|
result $= string(failedTests[i]) $ ", "; |
||||||
|
} |
||||||
|
else { |
||||||
|
result $= string(failedTests[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
return (result $ "]"); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
/** |
||||||
|
* Event generator for events related to testing. |
||||||
|
* 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 TestingEvents extends Events |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function CallTestingBegan(array< class<TestCase> > testQueue) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
class<TestingListenerBase>(listeners[i]) |
||||||
|
.static.TestingBegan(testQueue); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static function CallCaseTested( |
||||||
|
class<TestCase> testedCase, |
||||||
|
TestCaseSummary result) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
class<TestingListenerBase>(listeners[i]) |
||||||
|
.static.CaseTested(testedCase, result); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static function CallTestingEnded( |
||||||
|
array< class<TestCase> > testQueue, |
||||||
|
array<TestCaseSummary> results) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
class<TestingListenerBase>(listeners[i]) |
||||||
|
.static.TestingEnded(testQueue, results); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedListener = class'TestingListenerBase' |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
/** |
||||||
|
* Listener for events related to testing. |
||||||
|
* 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 TestingListenerBase extends Listener |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function TestingBegan(array< class<TestCase> > testQueue) {} |
||||||
|
|
||||||
|
static function CaseTested(class<TestCase> testCase, TestCaseSummary result) {} |
||||||
|
|
||||||
|
static function TestingEnded( |
||||||
|
array< class<TestCase> > testQueue, |
||||||
|
array<TestCaseSummary> results) {} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'TestingEvents' |
||||||
|
} |
@ -0,0 +1,253 @@ |
|||||||
|
/** |
||||||
|
* This service allows to separate running separate `TestCase`s in separate |
||||||
|
* ticks, which helps to avoid hang ups or false infinite loop detection. |
||||||
|
* 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 TestingService extends Service |
||||||
|
config(AcediaSystem); |
||||||
|
|
||||||
|
// All test cases, loaded from all available packages. |
||||||
|
// Always use `default` copy of this array. |
||||||
|
var private array< class<TestCase> > registeredTestCases; |
||||||
|
|
||||||
|
// Will be `true` if we have yet more tests to run |
||||||
|
// (either during current or following ticks) |
||||||
|
var private bool runningTests; |
||||||
|
// Queue with all test cases for the current/next testing |
||||||
|
var private array< class<TestCase> > testCasesToRun; |
||||||
|
// Track which test case we need to execute during next tick |
||||||
|
var private int nextTestCase; |
||||||
|
|
||||||
|
// Record test results during the last test run here. |
||||||
|
// After testing has finished - copy them into it's default value |
||||||
|
// `default.summarizedResults` to be available even after `TestingService` |
||||||
|
// shuts down. |
||||||
|
var private array<TestCaseSummary> summarizedResults; |
||||||
|
|
||||||
|
// Configuration variables that tell Acedia what tests to run |
||||||
|
// (and whether to run any at all) on start up. |
||||||
|
var public config const bool runTestsOnStartUp; |
||||||
|
var public config const bool filterTestsByName; |
||||||
|
var public config const bool filterTestsByGroup; |
||||||
|
var public config const string requiredName; |
||||||
|
var public config const string requiredGroup; |
||||||
|
|
||||||
|
// Shortcut to `TestingEvents`, so that we don't have to write |
||||||
|
// class'TestingEvents' every time. |
||||||
|
var const class<TestingEvents> events; |
||||||
|
|
||||||
|
/** |
||||||
|
* Registers another `TestCase` class for later testing. |
||||||
|
* |
||||||
|
* @return `true` if registration was successful. |
||||||
|
*/ |
||||||
|
public final static function bool RegisterTestCase(class<TestCase> newTestCase) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (newTestCase == none) return false; |
||||||
|
|
||||||
|
for (i = 0; i < default.registeredTestCases.length; i += 1) |
||||||
|
{ |
||||||
|
if (default.registeredTestCases[i] == newTestCase) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// Warn if there are test cases with the same name and group |
||||||
|
if ( !(default.registeredTestCases[i].static.GetGroup() |
||||||
|
~= newTestCase.static.GetGroup())) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ( !(default.registeredTestCases[i].static.GetName() |
||||||
|
~= newTestCase.static.GetName())) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
default._.logger.Warning("Two different test cases with name \"" |
||||||
|
$ newTestCase.static.GetName() $ "\" in the same group \"" |
||||||
|
$ newTestCase.static.GetGroup() $ "\"have been registered:" |
||||||
|
@ "\"" $ string(newTestCase) $ "\" and \"" |
||||||
|
$ string(default.registeredTestCases[i]) |
||||||
|
$ "\". This can lead to issues and it is not something you can fix," |
||||||
|
@ "- contact developers of the relevant packages."); |
||||||
|
} |
||||||
|
default.registeredTestCases[default.registeredTestCases.length] = |
||||||
|
newTestCase; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether service is still in the process of running tests. |
||||||
|
* |
||||||
|
* @return `true` if there are still some tests that are scheduled, but |
||||||
|
* were not yet ran and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final static function bool IsRunningTests() |
||||||
|
{ |
||||||
|
local TestingService myInstance; |
||||||
|
myInstance = TestingService(class'TestingService'.static.GetInstance()); |
||||||
|
if (myInstance == none) return false; |
||||||
|
|
||||||
|
return myInstance.runningTests; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the results of the last tests run. |
||||||
|
* |
||||||
|
* If no tests were run - returns an empty array. |
||||||
|
* |
||||||
|
* @return Results of the last tests run. |
||||||
|
*/ |
||||||
|
public final static function array<TestCaseSummary> GetLastResults() |
||||||
|
{ |
||||||
|
return default.summarizedResults; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds all tests to the testing queue. |
||||||
|
* |
||||||
|
* To actually run them use `Run()`. |
||||||
|
* To only run certain tests, - filter them by `FilterByName()` |
||||||
|
* and `FilterByGroup()` |
||||||
|
* |
||||||
|
* Will do nothing if service is already in the process of testing |
||||||
|
* (`IsRunningTests() == true`). |
||||||
|
* |
||||||
|
* @return Caller `TestService` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function TestingService PrepareTests() |
||||||
|
{ |
||||||
|
if (runningTests) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
testCasesToRun = default.registeredTestCases; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Filters tests in current queue to only those that have a specific name. |
||||||
|
* Should be used after `PrepareTests()` call, but before `Run()`. |
||||||
|
* |
||||||
|
* Will do nothing if service is already in the process of testing |
||||||
|
* (`IsRunningTests() == true`). |
||||||
|
* |
||||||
|
* @return Caller `TestService` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function TestingService FilterByName(string caseName) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<TestCase> > preFiltered; |
||||||
|
if (runningTests) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
preFiltered = testCasesToRun; |
||||||
|
testCasesToRun.length = 0; |
||||||
|
for (i = 0; i < preFiltered.length; i += 1) |
||||||
|
{ |
||||||
|
if (preFiltered[i].static.GetName() ~= caseName) { |
||||||
|
testCasesToRun[testCasesToRun.length] = preFiltered[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Filters tests in current queue to only those that belong to |
||||||
|
* a specific group. Should be used after `PrepareTests()` call, |
||||||
|
* but before `Run()`. |
||||||
|
* |
||||||
|
* Will do nothing if service is already in the process of testing |
||||||
|
* (`IsRunningTests() == true`). |
||||||
|
* |
||||||
|
* @return Caller `TestService` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function TestingService FilterByGroup(string caseGroup) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<TestCase> > preFiltered; |
||||||
|
if (runningTests) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
preFiltered = testCasesToRun; |
||||||
|
testCasesToRun.length = 0; |
||||||
|
for (i = 0; i < preFiltered.length; i += 1) |
||||||
|
{ |
||||||
|
if (preFiltered[i].static.GetGroup() ~= caseGroup) { |
||||||
|
testCasesToRun[testCasesToRun.length] = preFiltered[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Makes `TestingService` run all tests in a current queue. |
||||||
|
* |
||||||
|
* Queue musty be build before hand: start with `PrepareTests()` call and |
||||||
|
* optionally use `FilterByName()` / `FilterByGroup()` before |
||||||
|
* `Run()` method call. |
||||||
|
* |
||||||
|
* @return `false` if service is already performing the testing |
||||||
|
* and `true` otherwise. Note that `TestingService` might be inactive even |
||||||
|
* after `Run()` call that returns `true`, if the testing queue was empty. |
||||||
|
*/ |
||||||
|
public final function bool Run() |
||||||
|
{ |
||||||
|
if (runningTests) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
nextTestCase = 0; |
||||||
|
runningTests = true; |
||||||
|
summarizedResults.length = 0; |
||||||
|
events.static.CallTestingBegan(testCasesToRun); |
||||||
|
if (testCasesToRun.length <= 0) { |
||||||
|
runningTests = false; |
||||||
|
events.static.CallTestingEnded(testCasesToRun, summarizedResults); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private final function DoTestingStep() |
||||||
|
{ |
||||||
|
local TestCaseSummary newResult; |
||||||
|
if (nextTestCase >= testCasesToRun.length) |
||||||
|
{ |
||||||
|
runningTests = false; |
||||||
|
default.summarizedResults = summarizedResults; |
||||||
|
events.static.CallTestingEnded(testCasesToRun, summarizedResults); |
||||||
|
return; |
||||||
|
} |
||||||
|
testCasesToRun[nextTestCase].static.PerformTests(); |
||||||
|
newResult = testCasesToRun[nextTestCase].static.GetSummary(); |
||||||
|
events.static.CallCaseTested(testCasesToRun[nextTestCase], newResult); |
||||||
|
summarizedResults[summarizedResults.length] = newResult; |
||||||
|
nextTestCase += 1; |
||||||
|
} |
||||||
|
|
||||||
|
event Tick(float delta) |
||||||
|
{ |
||||||
|
// This will destroy us on the next tick after we were |
||||||
|
// either created or finished performing tests |
||||||
|
if (!runningTests) { |
||||||
|
Destroy(); |
||||||
|
return; |
||||||
|
} |
||||||
|
DoTestingStep(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
runTestsOnStartUp = false |
||||||
|
events = class'TestingEvents' |
||||||
|
} |
@ -0,0 +1,226 @@ |
|||||||
|
/** |
||||||
|
* Base class aimed to contain sets of tests for various components of |
||||||
|
* Acedia and it's features. |
||||||
|
* Neither this class, nor it's children aren't supposed to |
||||||
|
* be instantiated. |
||||||
|
* 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 TestCase extends AcediaObject |
||||||
|
abstract; |
||||||
|
|
||||||
|
// Name by which this set of unit tests can be referred to. |
||||||
|
var protected const string caseName; |
||||||
|
// Name of group to which this set of unit tests belong. |
||||||
|
var protected const string caseGroup; |
||||||
|
|
||||||
|
// Were all tests performed? |
||||||
|
var private bool finishedTests; |
||||||
|
// Context under which we are currently performing our tests. |
||||||
|
var private string currentContext; |
||||||
|
// Error message that will be generated if some test will fail now. |
||||||
|
var private string currentIssue; |
||||||
|
|
||||||
|
// Summary where we are recording results of all our tests. |
||||||
|
var private TestCaseSummary currentSummary; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets context for any tests that will follow this call (but before the next |
||||||
|
* `Context()` call). |
||||||
|
* |
||||||
|
* Context is supposed to be a short description about what |
||||||
|
* exactly you are testing. When reporting failed tests, - failures will be |
||||||
|
* grouped up by a context. |
||||||
|
* |
||||||
|
* Changing current context will also reset current issue, to set it up |
||||||
|
* use `Issue()` method. |
||||||
|
* |
||||||
|
* @param context Context for the following tests. |
||||||
|
*/ |
||||||
|
public final static function Context(string context) |
||||||
|
{ |
||||||
|
default.currentContext = context; |
||||||
|
default.currentIssue = ""; // Reset issue. |
||||||
|
} |
||||||
|
|
||||||
|
// Call this function to define an error message for tests that |
||||||
|
// would fail after it. |
||||||
|
// Message is reset by another call of `Issue()` or |
||||||
|
// by changing the context via `Context()`. |
||||||
|
/** |
||||||
|
* Changes an issue that any following tests (but before the next `Issue()` or |
||||||
|
* `Context()` call) will test for. |
||||||
|
* |
||||||
|
* Issue is the message that will be displayed to the user if any relevant |
||||||
|
* tests have failed. |
||||||
|
* |
||||||
|
* NOTE: Current issue will be reset by any `Context()` call. |
||||||
|
* |
||||||
|
* @param issue Issue that following tests will test for. |
||||||
|
*/ |
||||||
|
public final static function Issue(string issue) |
||||||
|
{ |
||||||
|
default.currentIssue = issue; |
||||||
|
} |
||||||
|
|
||||||
|
// Following functions provide simple test primitives |
||||||
|
|
||||||
|
/** |
||||||
|
* This call will record either one success or one failure for the caller |
||||||
|
* `TestCase` class, depending on passed `bool` argument. |
||||||
|
* |
||||||
|
* @param result Your test's result as a `bool` value: `true` will record a |
||||||
|
* success and `false` a failure. |
||||||
|
*/ |
||||||
|
public final static function TEST_ExpectTrue(bool result) |
||||||
|
{ |
||||||
|
RecordTestResult(result); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This call will record either one success or one failure for the caller |
||||||
|
* `TestCase` class, depending on passed `bool` argument. |
||||||
|
* |
||||||
|
* @param result Your test's result as a `bool` value: `false` will result in |
||||||
|
* recording a success and `true` in a failure. |
||||||
|
*/ |
||||||
|
public final static function TEST_ExpectFalse(bool result) |
||||||
|
{ |
||||||
|
RecordTestResult(!result); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This call will record either one success or one failure for the caller |
||||||
|
* `TestCase` class, depending on passed `Object` argument. |
||||||
|
* |
||||||
|
* @param result Your test's result as an `Object` value: `none` will result |
||||||
|
* in recording success and any non-`none` value in failure. |
||||||
|
*/ |
||||||
|
public final static function TEST_ExpectNone(Object object) |
||||||
|
{ |
||||||
|
RecordTestResult(object == none); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This call will record either one success or one failure for the caller |
||||||
|
* `TestCase` class, depending on passed `Object` argument. |
||||||
|
* |
||||||
|
* @param result Your test's result as an `Object` value: any non-`none` |
||||||
|
* value will result in recording success and `none` in failure. |
||||||
|
*/ |
||||||
|
public final static function TEST_ExpectNotNone(Object object) |
||||||
|
{ |
||||||
|
RecordTestResult(object != none); |
||||||
|
} |
||||||
|
|
||||||
|
// Records (in current context summary) that another test was performed and |
||||||
|
// succeeded/failed, along with given error message. |
||||||
|
private final static function RecordTestResult(bool isSuccessful) |
||||||
|
{ |
||||||
|
if (default.finishedTests) return; |
||||||
|
if (default.currentSummary == none) return; |
||||||
|
default.currentSummary.AddTestResult( default.currentContext, |
||||||
|
default.currentIssue, |
||||||
|
isSuccessful); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Once testing has finished returns compiled results as a |
||||||
|
* `TestCaseSummary` object. |
||||||
|
* |
||||||
|
* @return `TestCaseSummary` with compiled results if the testing has finished |
||||||
|
* and `none` otherwise. |
||||||
|
*/ |
||||||
|
public final static function TestCaseSummary GetSummary() |
||||||
|
{ |
||||||
|
if (!default.finishedTests) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
return default.currentSummary; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether this `TestCase` has already finished running all it's tests. |
||||||
|
* Finished testing means a prepared `TestCaseSummary` is available |
||||||
|
* (by `GetSummary()` method). |
||||||
|
* |
||||||
|
* @return `true` if this test case already did the testing |
||||||
|
* and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final static function bool HasFinishedTesting() |
||||||
|
{ |
||||||
|
return default.finishedTests; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns name of this `TestCase`. |
||||||
|
* |
||||||
|
* @return Name of this `TestCase`. |
||||||
|
*/ |
||||||
|
public final static function string GetName() |
||||||
|
{ |
||||||
|
return default.caseName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns group name of this `TestCase`. |
||||||
|
* |
||||||
|
* @return Group name of this `TestCase`. |
||||||
|
*/ |
||||||
|
public final static function string GetGroup() |
||||||
|
{ |
||||||
|
return default.caseGroup; |
||||||
|
} |
||||||
|
|
||||||
|
// Calling this function will perform unit tests defined in `TESTS()` |
||||||
|
// function of this test case and will prepare the summary, |
||||||
|
// obtainable through `GetSummary()` function. |
||||||
|
// Returns `true` if all tests have successfully passed |
||||||
|
// and `false` otherwise. |
||||||
|
/** |
||||||
|
* Performs all tests for this `TestCase`. |
||||||
|
* Guaranteed to be done after this finishes. |
||||||
|
* |
||||||
|
* @return `true` if all tests have finished successfully |
||||||
|
* and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final static function bool PerformTests() |
||||||
|
{ |
||||||
|
default.finishedTests = false; |
||||||
|
_().memory.Free(default.currentSummary); |
||||||
|
default.currentSummary = new class'TestCaseSummary'; |
||||||
|
default.currentSummary.Initialize(default.class); |
||||||
|
TESTS(); |
||||||
|
default.finishedTests = true; |
||||||
|
return default.currentSummary.HasPassedAllTests(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Any tests that your `TestCase` class needs to perform should be put in |
||||||
|
* this function. |
||||||
|
* To separate tests into groups it's recommended (as a style |
||||||
|
* consideration) to put them in separate function calls and give these |
||||||
|
* functions names starting with "Test_". They can have further folded |
||||||
|
* functions with prefix "SubTest_", which can contain "SubSubTest_", etc.. |
||||||
|
*/ |
||||||
|
protected static function TESTS(){} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
caseName = "" |
||||||
|
caseGroup = "" |
||||||
|
} |
@ -0,0 +1,542 @@ |
|||||||
|
/** |
||||||
|
* Class for storing and processing the information about how well testing |
||||||
|
* for a certain `TestCase` went. That information is stored as |
||||||
|
* a collection of `IssueSummary`s, that can be accessed all at once |
||||||
|
* or by their context. |
||||||
|
* `TestCaseSummary` must be initialized for some `TestCase` before it can |
||||||
|
* be used for anything (unlike `IssueSummary`). |
||||||
|
* 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 TestCaseSummary extends AcediaObject; |
||||||
|
|
||||||
|
// Case for which this summary was initialized. |
||||||
|
// `none` if it was not. |
||||||
|
var private class<TestCase> ownerCase; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* We will store issue summaries for different contexts separately. |
||||||
|
* INVARIANT: any function that adds records to `contextRecords` |
||||||
|
* must guarantee that: |
||||||
|
* 1. No two distinct records will have the same `context`; |
||||||
|
* 2. All the `IssueSummary`s in `issueSummaries` array have different |
||||||
|
* issue descriptions. |
||||||
|
* Comparisons of `string`s for two above conditions are case-insensitive. |
||||||
|
*/ |
||||||
|
struct ContextRecord |
||||||
|
{ |
||||||
|
var string context; |
||||||
|
var array<IssueSummary> issueSummaries; |
||||||
|
}; |
||||||
|
var private array<ContextRecord> contextRecords; |
||||||
|
|
||||||
|
// String literals used for displaying array of test case summaries |
||||||
|
var private const string indent, smallIndent; |
||||||
|
var private const string reportHeader; |
||||||
|
var private const string reportSuccessfulEnding; |
||||||
|
var private const string reportUnsuccessfulEnding; |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes caller summary for given `TestCase` class. |
||||||
|
* Can only be successfully done once, but will fail if |
||||||
|
* passed a `none` reference. |
||||||
|
* |
||||||
|
* @param targetCase `TestCase` class for which this summary will be |
||||||
|
* recording test results. |
||||||
|
* @return `true` if initialization was successful and `false otherwise |
||||||
|
* (either summary already initialized or passed reference is `none`). |
||||||
|
*/ |
||||||
|
public final function bool Initialize(class<TestCase> targetCase) |
||||||
|
{ |
||||||
|
if (ownerCase != none) return false; |
||||||
|
if (targetCase == none) return false; |
||||||
|
ownerCase = targetCase; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns index of a context record with a given description |
||||||
|
* (`context`) in `contextRecords`. |
||||||
|
* Creates one if missing. Never fails. |
||||||
|
* |
||||||
|
* @param context Context that desired record must match. |
||||||
|
* @return Index of the context record that matches `context`. |
||||||
|
* Returned index is always valid. |
||||||
|
*/ |
||||||
|
private final function int TouchContext(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local ContextRecord newRecord; |
||||||
|
// Try to find existing record with given context description |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (context ~= contextRecords[i].context) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
// If there is none - make a new one |
||||||
|
newRecord.context = context; |
||||||
|
contextRecords[contextRecords.length] = newRecord; |
||||||
|
return (contextRecords.length - 1); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Finds indices of a context record and an `IssueSummary` in |
||||||
|
* a nested array that have matching `context` |
||||||
|
* and `issueDescription`. |
||||||
|
* Creates records and/or `IssueSummary` if missing. Never fails. |
||||||
|
* |
||||||
|
* @param context Context description that |
||||||
|
* desired record must match. |
||||||
|
* @param issueDescription Issue description that |
||||||
|
* desired `IssueSummary`must match. |
||||||
|
* @param recordIndex Index of the context record that matches |
||||||
|
* `context` description will be recorded here. |
||||||
|
* Returned value is always valid. Passed value is discarded. |
||||||
|
* @param recordIndex Index of the `IssueSummary` that matches |
||||||
|
* `issueDescription` description will be recorded here. |
||||||
|
* Returned value is always valid. Passed value is discarded. |
||||||
|
*/ |
||||||
|
private final function TouchIssue( |
||||||
|
string context, |
||||||
|
string issueDescription, |
||||||
|
out int recordIndex, |
||||||
|
out int issueIndex |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<IssueSummary> issueSummaries; |
||||||
|
recordIndex = TouchContext(context); |
||||||
|
issueSummaries = contextRecords[recordIndex].issueSummaries; |
||||||
|
// Try to find existing issue summary with a given description |
||||||
|
for (i = 0; i < issueSummaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (issueSummaries[i] == none) continue; |
||||||
|
if (issueDescription ~= issueSummaries[i].GetDescription()) |
||||||
|
{ |
||||||
|
issueIndex = i; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
// If there is none - add a new one |
||||||
|
issueIndex = issueSummaries.length; |
||||||
|
issueSummaries[issueIndex] = new class'IssueSummary'; |
||||||
|
issueSummaries[issueIndex].SetIssue(ownerCase, context, issueDescription); |
||||||
|
contextRecords[recordIndex].issueSummaries = issueSummaries; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if caller summary was correctly initialized. |
||||||
|
* |
||||||
|
* @return `true` if summary was correctly initialized and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool IsInitialized() |
||||||
|
{ |
||||||
|
return (ownerCase != none); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds result of another test (success or not) to the records of this summary. |
||||||
|
* |
||||||
|
* @param context Context under which test was performed. |
||||||
|
* @param issueDescription Description of issue, |
||||||
|
* for which test was performed. |
||||||
|
* @param success `true` if test was successful and had passed, |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function AddTestResult( |
||||||
|
string context, |
||||||
|
string issueDescription, |
||||||
|
bool success |
||||||
|
) |
||||||
|
{ |
||||||
|
local int recordIndex, issueIndex; |
||||||
|
TouchIssue(context, issueDescription, recordIndex, issueIndex); |
||||||
|
contextRecords[recordIndex] |
||||||
|
.issueSummaries[issueIndex] |
||||||
|
.AddTestResult(success); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all contexts, for which caller summary has any records of tests |
||||||
|
* being performed. |
||||||
|
* |
||||||
|
* To check if particular context exists you can use `DoesContextExists()`. |
||||||
|
* |
||||||
|
* @return Array of `string`s, each representing one of the contexts, |
||||||
|
* used in tests. |
||||||
|
* Guarantees no duplicates (equality without accounting for case). |
||||||
|
*/ |
||||||
|
public final function array<string> GetContexts() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<string> result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) { |
||||||
|
result[result.length] = contextRecords[i].context; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if given context has any records about performing tests |
||||||
|
* (whether they ended in success or a failure) under it. |
||||||
|
* |
||||||
|
* To get an array of all existing contexts use `GetContexts()`. |
||||||
|
* |
||||||
|
* @param context A context to check for existing in records. |
||||||
|
* @return `true` if there was a record about a test being performed under |
||||||
|
* a given context and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool DoesContextExists(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (contextRecords[i].context ~= context) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* `IssueSummary`s for every issue that was tested and recorded in |
||||||
|
* the caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* @return Array of `IssueSummary`s for every tested and recorded issue. |
||||||
|
*/ |
||||||
|
public final function array<IssueSummary> GetIssueSummaries() |
||||||
|
{ |
||||||
|
local int i, j; |
||||||
|
local array<IssueSummary> recordedSummaries; |
||||||
|
local array<IssueSummary> result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
recordedSummaries = contextRecords[i].issueSummaries; |
||||||
|
for (j = 0; j < recordedSummaries.length; j += 1) { |
||||||
|
result[result.length] = recordedSummaries[j]; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns `IssueSummary`s for every issue that was tested under |
||||||
|
* a given context and recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* @param context Context under which issues of interest were tested. |
||||||
|
* @return Array of `IssueSummary`s for every issue that was tested under |
||||||
|
* given context. |
||||||
|
*/ |
||||||
|
public final function array<IssueSummary> GetIssueSummariesForContext( |
||||||
|
string context |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<IssueSummary> emptyResult; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (contextRecords[i].context ~= context) { |
||||||
|
return contextRecords[i].issueSummaries; |
||||||
|
} |
||||||
|
} |
||||||
|
return emptyResult; |
||||||
|
} |
||||||
|
|
||||||
|
// Counts total amount of tests performed under the contexts |
||||||
|
// corresponding to `contextRecords[recordIndex]` record. |
||||||
|
private final function int GetTotalTestsAmountForRecord(int recordIndex) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
local array<IssueSummary> issueSummaries; |
||||||
|
issueSummaries = contextRecords[recordIndex].issueSummaries; |
||||||
|
result = 0; |
||||||
|
for (i = 0; i < issueSummaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (issueSummaries[i] == none) continue; |
||||||
|
result += issueSummaries[i].GetTotalTestsAmount(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of performed tests, recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in amount of test under a specific context, - |
||||||
|
* use `GetTotalTestsAmountForContext()` instead. |
||||||
|
* |
||||||
|
* @return Total amount of performed tests. |
||||||
|
*/ |
||||||
|
public final function int GetTotalTestsAmount() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
result += GetTotalTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of tests, performed under a context `context` and |
||||||
|
* recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in total amount of test under all contexts, - |
||||||
|
* use `GetTotalTestsAmount()` instead. |
||||||
|
* |
||||||
|
* @param context Context for which method must count amount of |
||||||
|
* performed tests. |
||||||
|
* @return Total amount of tests, performed under given context. |
||||||
|
* If given context does not exist in records, - returns `-1`. |
||||||
|
*/ |
||||||
|
public final function int GetTotalTestsAmountForContext(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (context ~= contextRecords[i].context) { |
||||||
|
return GetTotalTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// Counts total amount of successful tests performed under the contexts |
||||||
|
// corresponding to `contextRecords[recordIndex]` record. |
||||||
|
private final function int GetSuccessfulTestsAmountForRecord(int recordIndex) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
local array<IssueSummary> issueSummaries; |
||||||
|
issueSummaries = contextRecords[recordIndex].issueSummaries; |
||||||
|
result = 0; |
||||||
|
for (i = 0; i < issueSummaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (issueSummaries[i] == none) continue; |
||||||
|
result += issueSummaries[i].GetSuccessfulTestsAmount(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of successfully performed tests, |
||||||
|
* recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in amount of successful test under a specific context, |
||||||
|
* - use `GetSuccessfulTestsAmountForContext()` instead. |
||||||
|
* |
||||||
|
* @return Total amount of successfully performed tests. |
||||||
|
*/ |
||||||
|
public final function int GetSuccessfulTestsAmount() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
result += GetSuccessfulTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of tests, performed under a context `context` and |
||||||
|
* recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in total amount of successful test under all contexts, |
||||||
|
* - use `GetSuccessfulTestsAmount()` instead. |
||||||
|
* |
||||||
|
* @param context Context for which we method must count amount of |
||||||
|
* successful tests. |
||||||
|
* @return Total amount of successful tests, performed under given context. |
||||||
|
* If given context does not exist in records, - returns `-1`. |
||||||
|
*/ |
||||||
|
public final function int GetSuccessfulTestsAmountForContext(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (context ~= contextRecords[i].context) { |
||||||
|
return GetSuccessfulTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// Counts total amount of tests, failed under the contexts |
||||||
|
// corresponding to `contextRecords[recordIndex]` record. |
||||||
|
private final function int GetFailedTestsAmountForRecord(int recordIndex) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
local array<IssueSummary> issueSummaries; |
||||||
|
issueSummaries = contextRecords[recordIndex].issueSummaries; |
||||||
|
result = 0; |
||||||
|
for (i = 0; i < issueSummaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (issueSummaries[i] == none) continue; |
||||||
|
result += issueSummaries[i].GetFailedTestsAmount(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of failed tests, recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in amount of failed test under a specific context, - |
||||||
|
* use `GetFailedTestsAmountForContext()` instead. |
||||||
|
* |
||||||
|
* @return Total amount of failed tests. |
||||||
|
*/ |
||||||
|
public final function int GetFailedTestsAmount() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
result += GetFailedTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of failed tests, performed under a context `context` and |
||||||
|
* recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in total amount of failed test under all contexts, - |
||||||
|
* use `GetFailedTestsAmount()` instead. |
||||||
|
* |
||||||
|
* @param context Context for which method must count amount of |
||||||
|
* failed tests. |
||||||
|
* @return Total amount of failed tests, performed under given context. |
||||||
|
* If given context does not exist in records, - returns `-1`. |
||||||
|
*/ |
||||||
|
public final function int GetFailedTestsAmountForContext(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (context ~= contextRecords[i].context) { |
||||||
|
return GetFailedTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether all tests recorded in this summary have passed. |
||||||
|
* |
||||||
|
* @return `true` if all tests have passed, `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool HasPassedAllTests() |
||||||
|
{ |
||||||
|
return (GetFailedTestsAmount() <= 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether all tests, performed under given context and |
||||||
|
* recorded in this summary, have passed. |
||||||
|
* |
||||||
|
* @return `true` if all tests under given context have passed, |
||||||
|
* `false` otherwise. |
||||||
|
* If given context does not exists - it did not fail any tests. |
||||||
|
*/ |
||||||
|
public final function bool HasPassedAllTestsForContext(string context) |
||||||
|
{ |
||||||
|
return (GetFailedTestsAmountForContext(context) <= 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a text summary for a set of results, given as array of |
||||||
|
* `TestCaseSummary`s (exactly how results are returned by `TestingService`). |
||||||
|
* |
||||||
|
* @param summaries `TestCase` summaries (obtained as a result of testing) |
||||||
|
* that we want to display. |
||||||
|
* @return Test representation of `summaries` as an array of |
||||||
|
* formatted strings, where each string corresponds to it's own line. |
||||||
|
*/ |
||||||
|
public final static function array<string> GenerateStringSummary( |
||||||
|
array<TestCaseSummary> summaries) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool allTestsPassed; |
||||||
|
local array<string> result; |
||||||
|
allTestsPassed = true; |
||||||
|
result[0] = default.reportHeader; |
||||||
|
for (i = 0; i < summaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (summaries[i] == none) continue; |
||||||
|
summaries[i].AppendCaseSummary(result); |
||||||
|
allTestsPassed = allTestsPassed && summaries[i].HasPassedAllTests(); |
||||||
|
} |
||||||
|
if (allTestsPassed) { |
||||||
|
result[result.length] = default.reportSuccessfulEnding; |
||||||
|
} |
||||||
|
else { |
||||||
|
result[result.length] = default.reportUnsuccessfulEnding; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// Add text representation of caller `TestCase` to the existing array `result`. |
||||||
|
private final function AppendCaseSummary(out array<string> result) |
||||||
|
{ |
||||||
|
local int i, j; |
||||||
|
local array<string> contexts; |
||||||
|
local string testCaseAnnouncement; |
||||||
|
local array<IssueSummary> issues; |
||||||
|
if (ownerCase == none) return; |
||||||
|
// Announce case |
||||||
|
testCaseAnnouncement = "{$text_default Test case {$text_emphasis"; |
||||||
|
if (ownerCase.static.GetGroup() != "") { |
||||||
|
testCaseAnnouncement @= "[" $ ownerCase.static.GetGroup() $ "]"; |
||||||
|
} |
||||||
|
testCaseAnnouncement @= ownerCase.static.GetName() $ "}:}"; |
||||||
|
if (GetFailedTestsAmount() > 0) { |
||||||
|
testCaseAnnouncement @= "{$text_failure failed}!"; |
||||||
|
} |
||||||
|
else { |
||||||
|
testCaseAnnouncement @= "{$text_ok passed}!"; |
||||||
|
} |
||||||
|
result[result.length] = testCaseAnnouncement; |
||||||
|
// Report failed tests |
||||||
|
contexts = GetContexts(); |
||||||
|
for (i = 0;i < contexts.length; i += 1) |
||||||
|
{ |
||||||
|
if (GetFailedTestsAmountForContext(contexts[i]) <= 0) continue; |
||||||
|
result[result.length] = |
||||||
|
smallIndent $ "{$text_warning " $ contexts[i] $ "}"; |
||||||
|
issues = GetIssueSummariesForContext(contexts[i]); |
||||||
|
for (j = 0; j < issues.length; j += 1) |
||||||
|
{ |
||||||
|
if (issues[j] == none) continue; |
||||||
|
if (issues[j].GetFailedTestsAmount() <= 0) continue; |
||||||
|
result[result.length] = indent $ issues[j].ToString(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
smallIndent = " " |
||||||
|
indent = " " |
||||||
|
reportHeader = "{$text_default ############################## {$text_emphasis Test summary} ###############################}" |
||||||
|
reportSuccessfulEnding = "{$text_default ########################### {$text_ok All tests have passed!} ############################}" |
||||||
|
reportUnsuccessfulEnding = "{$text_default ########################## {$text_failure Some tests have failed :(} ###########################}" |
||||||
|
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,290 @@ |
|||||||
|
/** |
||||||
|
* Text object, meant as Acedia's replacement for a `string` type, |
||||||
|
* that is supposed to provide a better (although by no means full) |
||||||
|
* Unicode support than what is available from built-in unrealscript functions. |
||||||
|
* Main differences with `string` are: |
||||||
|
* 1. Text is a reference type, that doesn't copy it's contents with each |
||||||
|
* assignment. |
||||||
|
* 2. It's functions such as `ToUpper()` work with larger sets of |
||||||
|
* symbols than native functions such as `Caps()` that only work with |
||||||
|
* ASCII Latin; |
||||||
|
* 3. Can store a wider range of characters than `string`, although |
||||||
|
* the only way to actually add them to `Text` is via directly |
||||||
|
* inputting Unicode code points. |
||||||
|
* 4. Since it's functionality implemented in unrealscript, |
||||||
|
* Text is slower that a string; |
||||||
|
* 5. Once created, Text object won't disappear until garbage collection |
||||||
|
* is performed, even if it is not referenced anywhere. |
||||||
|
|
||||||
|
* API that provides extended text handling with extended Cyrillic (Russian) |
||||||
|
* support (native functions like `Caps` only work with Latin letters). |
||||||
|
* 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 Text extends AcediaObject; |
||||||
|
|
||||||
|
// Used to store a result of a `ParseSign()` function. |
||||||
|
enum StringType |
||||||
|
{ |
||||||
|
STRING_Plain, |
||||||
|
STRING_Colored, |
||||||
|
STRING_Formatted |
||||||
|
}; |
||||||
|
|
||||||
|
enum LetterCase |
||||||
|
{ |
||||||
|
LCASE_Lower, |
||||||
|
LCASE_Upper |
||||||
|
}; |
||||||
|
|
||||||
|
enum StringColorType |
||||||
|
{ |
||||||
|
STRCOLOR_Default, |
||||||
|
STRCOLOR_Struct, |
||||||
|
STRCOLOR_Alias |
||||||
|
}; |
||||||
|
|
||||||
|
struct Character |
||||||
|
{ |
||||||
|
var int codePoint; |
||||||
|
// `false` if relevant character has a particular color, |
||||||
|
// `true` if it does not (use context-dependent default color). |
||||||
|
var StringColorType colorType; |
||||||
|
// Color of the relevant character if `isDefaultColor == false`. |
||||||
|
var Color color; |
||||||
|
var string colorAlias; |
||||||
|
}; |
||||||
|
// We will store our string data in two different ways at once to make getters |
||||||
|
// faster at the cost of doing more work in functions that change the string. |
||||||
|
var private array<Character> contents; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets new value of the `Text` object, that has called this method, |
||||||
|
* to be equal to the given `Text`. Does not change given `Text`. |
||||||
|
* |
||||||
|
* @param source After this function caller `Text` will have exactly |
||||||
|
* the same contents as given parameter. |
||||||
|
* @return Returns the calling `Text` object, to allow for function chaining. |
||||||
|
*/ |
||||||
|
public final function Text Copy(Text otherText) |
||||||
|
{ |
||||||
|
contents = otherText.contents; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Replaces data of caller `Text` object with data given by the array of |
||||||
|
* Unicode code points, preserving the order of characters where it matters |
||||||
|
* (some modifier code points are allowed arbitrary order in Unicode standard). |
||||||
|
* |
||||||
|
* `Text` isn't a simple wrapper around array of Unicode code points, so |
||||||
|
* this function call should be assumed to be more expensive than |
||||||
|
* a simple copy. |
||||||
|
* |
||||||
|
* @param source New contents of the `Text`. |
||||||
|
* @return Returns the calling object, to allow for function chaining. |
||||||
|
*/ |
||||||
|
public final function Text CopyRaw(array<Character> rawSource) |
||||||
|
{ |
||||||
|
contents = rawSource; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copies contents of the given string into caller `Text`. |
||||||
|
* |
||||||
|
* `Text` isn't a simple wrapper around unrealscript's `string`, so |
||||||
|
* this function call should be assumed to be more expensive than simple |
||||||
|
* `string` copy. |
||||||
|
* |
||||||
|
* @param source New contents of the caller `Text`. |
||||||
|
* @return Returns the calling `Text` object, to allow for function chaining. |
||||||
|
*/ |
||||||
|
public final function Text CopyString(string source) |
||||||
|
{ |
||||||
|
CopyRaw(_().text.StringToRaw(source)); |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns data in the caller `Text` object in form of an array of |
||||||
|
* Unicode code points, preserving the order of characters where it matters |
||||||
|
* (some modifier code points are allowed arbitrary order in Unicode standard). |
||||||
|
*/ |
||||||
|
public final function array<Character> ToRaw() |
||||||
|
{ |
||||||
|
return contents; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the `string` representation of contents of the caller `Text`. |
||||||
|
* |
||||||
|
* Unreal Engine doesn't seem to store code points higher than 2^16 in |
||||||
|
* `string`, so some data might be lost in the process. |
||||||
|
* (To check if it concerns you, refer to the Unicode symbol table, |
||||||
|
* but it is not a problem for most people). |
||||||
|
*/ |
||||||
|
public final function string ToString(optional StringType resultType) |
||||||
|
{ |
||||||
|
return _().text.RawToString(contents, resultType); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if the caller `Text` and a given `Text` have contain equal text |
||||||
|
* content, according to Unicode standard. By default case-sensitive. |
||||||
|
*/ |
||||||
|
public final function bool IsEqual |
||||||
|
( |
||||||
|
Text otherText, |
||||||
|
optional bool caseInsensitive |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<Character> otherContentsCopy; |
||||||
|
local TextAPI api; |
||||||
|
if (contents.length != otherText.contents.length) return false; |
||||||
|
|
||||||
|
api = _().text; |
||||||
|
// There's some evidence that UnrealEngine might copy the whole |
||||||
|
// `otherText.contents` each time we access any element, |
||||||
|
// so just copy it once. |
||||||
|
otherContentsCopy = otherText.contents; |
||||||
|
for (i = 0; i < contents.length; i += 1) |
||||||
|
{ |
||||||
|
if (!api.AreEqual(contents[i], otherContentsCopy[i], caseInsensitive)) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if the caller `Text` contains the same text content as the given |
||||||
|
* `string`. By default case-sensitive. |
||||||
|
* |
||||||
|
* If text contains Unicode code points that can't be stored in |
||||||
|
* a given `string`, equality should be considered impossible. |
||||||
|
*/ |
||||||
|
public final function bool IsEqualToString |
||||||
|
( |
||||||
|
string source, |
||||||
|
optional bool caseInsensitive, |
||||||
|
optional StringType sourceType |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<Character> rawSource; |
||||||
|
local TextAPI api; |
||||||
|
api = _().text; |
||||||
|
rawSource = api.StringToRaw(source, sourceType); |
||||||
|
if (contents.length != rawSource.length) return false; |
||||||
|
|
||||||
|
for (i = 0; i < contents.length; i += 1) |
||||||
|
{ |
||||||
|
if (!api.AreEqual(contents[i], rawSource[i], caseInsensitive)) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns `true` if the string has no characters, otherwise returns `false`. |
||||||
|
*/ |
||||||
|
public final function bool IsEmpty() |
||||||
|
{ |
||||||
|
return (contents.length == 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Attempts to returns Unicode code point, stored in caller `Text` at the |
||||||
|
* given `index`. |
||||||
|
* |
||||||
|
* Doesn't properly work if `Text` contains characters consisting of |
||||||
|
* multiple code points. |
||||||
|
* |
||||||
|
* @return For a valid index (non-negative, not exceeding the length, |
||||||
|
* given by `GetLength()` of the `Text`) returns Unicode code point, |
||||||
|
* stored in caller `Text` at the given `index`; otherwise - returns `-1`. |
||||||
|
*/ |
||||||
|
public final function Character GetCharacter(optional int index) |
||||||
|
{ |
||||||
|
if (index < 0) return _().text.GetInvalidCharacter(); |
||||||
|
if (index >= contents.length) return _().text.GetInvalidCharacter(); |
||||||
|
|
||||||
|
return contents[index]; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Converts caller `Text` to lower case. |
||||||
|
* |
||||||
|
* Changes every symbol contained in caller `Text` to it's lower case folding |
||||||
|
* (according to Unicode standard). Symbols without lower case folding |
||||||
|
* (like "&" or "!") are left unchanged. |
||||||
|
* |
||||||
|
* @return Returns the calling object, to allow for function chaining. |
||||||
|
*/ |
||||||
|
public final function Text ToLower() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local TextAPI api; |
||||||
|
api = _().text; |
||||||
|
for (i = 0; i < contents.length; i += 1) |
||||||
|
{ |
||||||
|
contents[i] = api.ToLower(contents[i]); |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Converts caller `Text` to upper case. |
||||||
|
* |
||||||
|
* Changes every symbol contained in caller `Text` to it's upper case folding |
||||||
|
* (according to Unicode standard). Symbols without upper case folding |
||||||
|
* (like "&" or "!") are left unchanged. |
||||||
|
* |
||||||
|
* @return Returns the calling object, to allow for function chaining. |
||||||
|
*/ |
||||||
|
public final function Text ToUpper() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local TextAPI api; |
||||||
|
api = _().text; |
||||||
|
for (i = 0; i < contents.length; i += 1) |
||||||
|
{ |
||||||
|
contents[i] = api.ToUpper(contents[i]); |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
public final function int GetHash() { |
||||||
|
return _().text.GetHashRaw(contents); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns amount of symbols in the caller `Text`. |
||||||
|
*/ |
||||||
|
public final function int GetLength() |
||||||
|
{ |
||||||
|
return contents.length; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
/** |
||||||
|
* `Manifest` is meant to describe contents of the Acedia's package. |
||||||
|
* This is the base class, every package's `Manifest` must directly extend it. |
||||||
|
* 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 Object |
||||||
|
abstract; |
||||||
|
|
||||||
|
// List of alias sources in this manifest's package. |
||||||
|
var public const array< class<AliasSource> > aliasSources; |
||||||
|
|
||||||
|
// List of features in this manifest's package. |
||||||
|
var public const array< class<Feature> > features; |
||||||
|
|
||||||
|
// List of test cases in this manifest's package. |
||||||
|
var public const array< class<TestCase> > testCases; |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
Loading…
Reference in new issue