Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b20a2f74c4 | ||
|
|
5c748fbe54 | ||
|
|
8eb2a13efd | ||
|
|
2dd67c229c | ||
|
|
d0783f352d | ||
|
|
664b2c3ced | ||
|
|
357fc37a79 | ||
|
|
788f46cc55 | ||
|
|
4bafa32549 | ||
|
|
d5493e6149 | ||
|
|
216b3969ed | ||
|
|
ba7be0293c | ||
|
|
5323ef68ca | ||
|
|
58ed9754a4 | ||
|
|
2eae473429 | ||
|
|
12686460c1 | ||
|
|
3903750ba2 | ||
|
|
e2effa1c5d | ||
|
|
5846af9453 | ||
|
|
ac4369ce3b | ||
|
|
13619a14dc | ||
|
|
b9f565f5fc | ||
|
|
4cb500277b | ||
|
|
4dff6d834a | ||
|
|
2037ebaaba | ||
|
|
ae89a004a6 | ||
|
|
95db857fb5 | ||
|
|
d0b414b61d | ||
|
|
f056edded7 | ||
|
|
1ab9961cfe |
687
LICENSE
@@ -1,21 +1,674 @@
|
|||||||
MIT License
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (c) 2017 Anton Kramskoi
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Preamble
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The GNU General Public License is a free, copyleft license for
|
||||||
copies or substantial portions of the Software.
|
software and other kinds of works.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
The licenses for most software and other practical works are designed
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
share and change all versions of a program--to make sure it remains free
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
GNU General Public License for most of our software; it applies also to
|
||||||
SOFTWARE.
|
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 <http://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
|
||||||
|
<http://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
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ allprojects {
|
|||||||
version = 'release'
|
version = 'release'
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
versionNumber = '3.4'
|
versionNumber = '3.5'
|
||||||
versionType = 'release'
|
versionType = 'release'
|
||||||
appName = 'Mindustry'
|
appName = 'Mindustry'
|
||||||
gdxVersion = '1.9.8'
|
gdxVersion = '1.9.8'
|
||||||
aiVersion = '1.8.1'
|
aiVersion = '1.8.1'
|
||||||
uCoreVersion = 'cdfa32d'
|
uCoreVersion = '238babe'
|
||||||
|
|
||||||
getVersionString = {
|
getVersionString = {
|
||||||
String buildVersion = getBuildVersion()
|
String buildVersion = getBuildVersion()
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 59 KiB |
BIN
core/assets-raw/sprites/ui/discord-banner-over.png
Normal file
|
After Width: | Height: | Size: 341 B |
BIN
core/assets-raw/sprites/ui/discord-banner.png
Normal file
|
After Width: | Height: | Size: 341 B |
BIN
core/assets-raw/sprites/ui/icons/icon-dev-builds.png
Normal file
|
After Width: | Height: | Size: 196 B |
BIN
core/assets-raw/sprites/ui/icons/icon-exit.png
Normal file
|
After Width: | Height: | Size: 186 B |
BIN
core/assets-raw/sprites/ui/icons/icon-github.png
Normal file
|
After Width: | Height: | Size: 204 B |
BIN
core/assets-raw/sprites/ui/icons/icon-google-play.png
Normal file
|
After Width: | Height: | Size: 187 B |
BIN
core/assets-raw/sprites/ui/icons/icon-itch.io.png
Normal file
|
After Width: | Height: | Size: 198 B |
BIN
core/assets-raw/sprites/ui/icons/icon-link.png
Normal file
|
After Width: | Height: | Size: 195 B |
BIN
core/assets-raw/sprites/ui/icons/icon-trello.png
Normal file
|
After Width: | Height: | Size: 171 B |
BIN
core/assets-raw/sprites/ui/icons/icon-wiki.png
Normal file
|
After Width: | Height: | Size: 174 B |
@@ -1,6 +1,17 @@
|
|||||||
text.about=Created by [ROYAL]Anuken.[]\nOriginally an entry in the [orange]GDL[] MM Jam.\n\nCredits:\n- SFX made with [YELLOW]bfxr[]\n- Music made by [GREEN]RoccoW[] / found on [lime]FreeMusicArchive.org[]\n\nSpecial thanks to:\n- [coral]MitchellFJN[]: extensive playtesting and feedback\n- [sky]Luxray5474[]: wiki work, code contributions\n- [lime]Epowerj[]: code build system, icon\n- All the beta testers on itch.io and Google Play\n
|
text.about=Created by [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\nOriginally an entry in the [orange]GDL[] Metal Monstrosity Jam.\n\nCredits:\n- SFX made with [YELLOW]bfxr[]\n- Music made by [GREEN]RoccoW[] / found on [lime]FreeMusicArchive.org[]\n\nSpecial thanks to:\n- [coral]MitchellFJN[]: extensive playtesting and feedback\n- [sky]Luxray5474[]: wiki work, code contributions\n- [lime]Epowerj[]: code build system, icon\n- All the beta testers on itch.io and Google Play\n
|
||||||
|
text.credits=Credits
|
||||||
text.discord=Join the mindustry discord!
|
text.discord=Join the mindustry discord!
|
||||||
text.changes=[SCARLET]Attention!\n[]Some important game mechanics have been changed.\n\n- [accent]Teleporters[] now use power.\n- [accent]Smelteries[] and [accent]crucibles[] now have a maximum item capacity.\n- [accent]Crucibles[] now require coal as fuel.
|
text.changes=[SCARLET]Attention!\n[]Some important game mechanics have been changed.\n\n- [accent]Teleporters[] now use power.\n- [accent]Smelteries[] and [accent]crucibles[] now have a maximum item capacity.\n- [accent]Crucibles[] now require coal as fuel.
|
||||||
|
text.link.discord.description=the official Mindustry discord chatroom
|
||||||
|
text.link.github.description=Game source code
|
||||||
|
text.link.dev-builds.description=Unstable development builds
|
||||||
|
text.link.trello.description=Official trello board for planned features
|
||||||
|
text.link.itch.io.description=itch.io page with PC downloads and web version
|
||||||
|
text.link.google-play.description=Google Play store listing
|
||||||
|
text.link.wiki.description=official Mindustry wiki
|
||||||
|
text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard.
|
||||||
|
text.editor.web=The web version does not support the editor!\nDownload the game to use it.
|
||||||
|
text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page.
|
||||||
text.gameover=The core was destroyed.
|
text.gameover=The core was destroyed.
|
||||||
text.highscore=[YELLOW]New highscore!
|
text.highscore=[YELLOW]New highscore!
|
||||||
text.lasted=You lasted until wave
|
text.lasted=You lasted until wave
|
||||||
@@ -12,6 +23,7 @@ text.level.mode=Gamemode:
|
|||||||
text.savegame=Save Game
|
text.savegame=Save Game
|
||||||
text.loadgame=Load Game
|
text.loadgame=Load Game
|
||||||
text.joingame=Join Game
|
text.joingame=Join Game
|
||||||
|
text.newgame=New Game
|
||||||
text.quit=Quit
|
text.quit=Quit
|
||||||
text.about.button=About
|
text.about.button=About
|
||||||
text.name=Name:
|
text.name=Name:
|
||||||
@@ -26,9 +38,12 @@ text.server.kicked.invalidPassword=Invalid password!
|
|||||||
text.server.kicked.clientOutdated=Outdated client! Update your game!
|
text.server.kicked.clientOutdated=Outdated client! Update your game!
|
||||||
text.server.kicked.serverOutdated=Outdated server! Ask the host to update!
|
text.server.kicked.serverOutdated=Outdated server! Ask the host to update!
|
||||||
text.server.kicked.banned=You are banned on this server.
|
text.server.kicked.banned=You are banned on this server.
|
||||||
|
text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again.
|
||||||
text.server.connected={0} has joined.
|
text.server.connected={0} has joined.
|
||||||
text.server.disconnected={0} has disconnected.
|
text.server.disconnected={0} has disconnected.
|
||||||
text.nohost=Can't host server on a custom map!
|
text.nohost=Can't host server on a custom map!
|
||||||
|
text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings.
|
||||||
|
text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP.
|
||||||
text.hostserver=Host Server
|
text.hostserver=Host Server
|
||||||
text.host=Host
|
text.host=Host
|
||||||
text.hosting=[accent]Opening server...
|
text.hosting=[accent]Opening server...
|
||||||
@@ -70,6 +85,7 @@ text.joingame.byip=Join by IP...
|
|||||||
text.joingame.title=Join Game
|
text.joingame.title=Join Game
|
||||||
text.joingame.ip=IP:
|
text.joingame.ip=IP:
|
||||||
text.disconnect=Disconnected.
|
text.disconnect=Disconnected.
|
||||||
|
text.disconnect.data=Failed to load world data!
|
||||||
text.connecting=[accent]Connecting...
|
text.connecting=[accent]Connecting...
|
||||||
text.connecting.data=[accent]Loading world data...
|
text.connecting.data=[accent]Loading world data...
|
||||||
text.connectfail=[crimson]Failed to connect to server: [orange]{0}
|
text.connectfail=[crimson]Failed to connect to server: [orange]{0}
|
||||||
@@ -112,6 +128,7 @@ text.ok=OK
|
|||||||
text.open=Open
|
text.open=Open
|
||||||
text.cancel=Cancel
|
text.cancel=Cancel
|
||||||
text.openlink=Open Link
|
text.openlink=Open Link
|
||||||
|
text.copylink=Copy Link
|
||||||
text.back=Back
|
text.back=Back
|
||||||
text.quit.confirm=Are you sure you want to quit?
|
text.quit.confirm=Are you sure you want to quit?
|
||||||
text.changelog.title=Changelog
|
text.changelog.title=Changelog
|
||||||
@@ -249,7 +266,7 @@ setting.sensitivity.name=Controller Sensitivity
|
|||||||
setting.saveinterval.name=Autosave Interval
|
setting.saveinterval.name=Autosave Interval
|
||||||
setting.seconds={0} Seconds
|
setting.seconds={0} Seconds
|
||||||
setting.fullscreen.name=Fullscreen
|
setting.fullscreen.name=Fullscreen
|
||||||
setting.multithread.name=Multithreading [scarlet](unstable!)
|
setting.multithread.name=Multithreading
|
||||||
setting.fps.name=Show FPS
|
setting.fps.name=Show FPS
|
||||||
setting.vsync.name=VSync
|
setting.vsync.name=VSync
|
||||||
setting.lasers.name=Show Power Lasers
|
setting.lasers.name=Show Power Lasers
|
||||||
@@ -339,9 +356,13 @@ keybind.weapon_3.name=weapon_3
|
|||||||
keybind.weapon_4.name=weapon_4
|
keybind.weapon_4.name=weapon_4
|
||||||
keybind.weapon_5.name=weapon_5
|
keybind.weapon_5.name=weapon_5
|
||||||
keybind.weapon_6.name=weapon_6
|
keybind.weapon_6.name=weapon_6
|
||||||
|
mode.text.help.title=Description of modes
|
||||||
mode.waves.name=waves
|
mode.waves.name=waves
|
||||||
|
mode.waves.description=the normal mode. limited resources and automatic incoming waves.
|
||||||
mode.sandbox.name=sandbox
|
mode.sandbox.name=sandbox
|
||||||
|
mode.sandbox.description=infinite resources and no timer for waves.
|
||||||
mode.freebuild.name=freebuild
|
mode.freebuild.name=freebuild
|
||||||
|
mode.freebuild.description=limited resources and no timer for waves.
|
||||||
upgrade.standard.name=standard
|
upgrade.standard.name=standard
|
||||||
upgrade.standard.description=The standard mech.
|
upgrade.standard.description=The standard mech.
|
||||||
upgrade.blaster.name=blaster
|
upgrade.blaster.name=blaster
|
||||||
|
|||||||
500
core/assets/bundles/bundle_uk_UA.properties
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
text.about = Створено [ROYAL] Anuken. []\nСпочатку запис у [orange] GDL [] MM Jam.\nТворці:\n- SFX зроблено з [YELLOW] bfxr []\n- Музика зроблена [GREEN] RoccoW [] / Знайдено на [lime] FreeMusicArchive.org [] \nОсоблива подяка:\n- [coral] MitchellFJN []: екстенсивне тестування та відгуки\n- [sky] Luxray5474 []: робота з вікі, вклади коду\n- Всі бета-тестери на itch.io та Google Play\n
|
||||||
|
text.discord = Приєднуйтесь до нашого Discord!
|
||||||
|
text.changes = [SCARLET] Увага! \n[] Деякі важливі механіки гри були змінені.\n- [accent] Телепорти [] тепер використовують електроенергію. \n- [accent] Домінна піч [] та [accent] Тиглі [] тепер мають ліміт. \n- [accent] Тиглі [] зараз вимагають вугілля як паливо.
|
||||||
|
text.gameover = Ядро було зруйновано.
|
||||||
|
text.highscore = [YELLOW] Новий рекорд!
|
||||||
|
text.lasted = Ви тримались до хвилі
|
||||||
|
text.level.highscore = Рекорд: [accent] {0}
|
||||||
|
text.level.delete.title = Підтвердьте видалення
|
||||||
|
text.level.delete = Ви впевнені, що хочете видалити карту \"[orange] {0} \"?
|
||||||
|
text.level.select = Вибір рівня
|
||||||
|
text.level.mode = Ігровий режим
|
||||||
|
text.savegame = Зберегти гру
|
||||||
|
text.loadgame = Завантажити гру
|
||||||
|
text.joingame = Приєднатися\nдо гри
|
||||||
|
text.newgame=Нова гра
|
||||||
|
text.quit = Вийти
|
||||||
|
text.about.button = Про
|
||||||
|
text.name = Назва:
|
||||||
|
text.public = Публічний
|
||||||
|
text.players = {0} гравців онлайн
|
||||||
|
text.server.player.host = {0} (host)
|
||||||
|
text.players.single = {0} гравців онлайн
|
||||||
|
text.server.mismatch = Пакетна помилка: невідповідність версії версії клієнта / сервера. Переконайтеся, що ви та хост мають останню версію Mindustry!
|
||||||
|
text.server.closing = [accent] Закриття сервера ...
|
||||||
|
text.server.kicked.kick = Ви були вигнані з сервера!
|
||||||
|
text.server.kicked.invalidPassword = Невірний пароль!
|
||||||
|
text.server.kicked.clientOutdated = Застарілий клієнт! Оновіть свою гру!
|
||||||
|
text.server.kicked.serverOutdated = Застарілий сервер! Попросіть хост оновити!
|
||||||
|
text.server.connected = {0} приєднався.
|
||||||
|
text.server.disconnected = {0} від'єднано.
|
||||||
|
text.nohost = Неможливо розмістити сервер на власній карті!
|
||||||
|
text.hostserver = Хост-сервер
|
||||||
|
text.host = Хост
|
||||||
|
text.hosting = [accent] Відкриття сервера ...
|
||||||
|
text.hosts.refresh = Оновити
|
||||||
|
text.hosts.discovering = Знайомство з мережевими іграми
|
||||||
|
text.server.refreshing = Оновити сервери
|
||||||
|
text.hosts.none = [lightgray] Ніяких ігор у мережі не знайдено!
|
||||||
|
text.host.invalid = [scarlet] Неможливо підключитися до хосту.
|
||||||
|
text.server.friendlyfire = Дружній вогонь
|
||||||
|
text.server.add = Додати сервер
|
||||||
|
text.server.delete = Ви впевнені, що хочете видалити цей сервер?
|
||||||
|
text.server.hostname = Хост: {0}
|
||||||
|
text.server.edit = Редагувати сервер
|
||||||
|
text.joingame.byip = [] Приєднатися по IP ...[]
|
||||||
|
text.joingame.title = Приєднатися до гри
|
||||||
|
text.joingame.ip = IP
|
||||||
|
text.disconnect = Роз'єднано
|
||||||
|
text.connecting = [accent] Підключення ...
|
||||||
|
text.connecting.data = [accent] Завантаження світових даних ...
|
||||||
|
text.connectfail = [crimson] Не вдалося підключитися до сервера: [orange] {0}
|
||||||
|
text.server.port = Порт
|
||||||
|
text.server.addressinuse = Адреса вже використовується!
|
||||||
|
text.server.invalidport = Недійсний номер порту.
|
||||||
|
text.server.error = [crimson] Помилка хостингу сервера: [orange] {0}
|
||||||
|
text.tutorial.back = < Попер.
|
||||||
|
text.tutorial.next = Далі >
|
||||||
|
text.save.new = Нове збереження
|
||||||
|
text.save.overwrite = Ви впевнені, що хочете перезаписати цей слот для збереження?
|
||||||
|
text.overwrite = Перезаписати
|
||||||
|
text.save.none = Не знайдено жодних збережень!
|
||||||
|
text.saveload = [accent] Збереження ...
|
||||||
|
text.savefail = Не вдалося зберегти гру!
|
||||||
|
text.save.delete.confirm = Ви впевнені, що хочете видалити це збереження?
|
||||||
|
text.save.delete = Видалити
|
||||||
|
text.save.export = Експорт збереження
|
||||||
|
text.save.import.invalid = [orange] Це збереження недійсне!
|
||||||
|
text.save.import.fail = [crimson] Не вдалося імпортувати збереження: [orange] {0}
|
||||||
|
text.save.export.fail = [crimson] Не вдалося експортувати збереження: [orange] {0}
|
||||||
|
text.save.import = Імпортувати збереження
|
||||||
|
text.save.newslot = Назва збереження:
|
||||||
|
text.save.rename = Переіменувати
|
||||||
|
text.save.rename.text = Нова назва:
|
||||||
|
text.selectslot = Виберіть збереження.
|
||||||
|
text.slot = [accent] слот {0}
|
||||||
|
text.save.corrupted = [orange] Збережений файл пошкоджений або він невірний!
|
||||||
|
text.empty = <порожньо>
|
||||||
|
text.on = Увімкнути
|
||||||
|
text.off = Вимкнути
|
||||||
|
text.save.autosave = Автозбереження: {0}
|
||||||
|
text.save.map = Карта
|
||||||
|
text.save.wave = Хвиля {0}
|
||||||
|
text.save.difficulty = Складність
|
||||||
|
text.save.date = Останнє збережено: {0}
|
||||||
|
text.confirm = Підтвердити
|
||||||
|
text.delete = Видалити
|
||||||
|
text.ok = ОК
|
||||||
|
text.open = Відкрити
|
||||||
|
text.cancel = Скасувати
|
||||||
|
text.openlink = Відкрити посилання
|
||||||
|
text.back = Назад
|
||||||
|
text.quit.confirm = Ти впевнений що хочеш піти?
|
||||||
|
text.loading = [accent] Завантаження ...
|
||||||
|
text.wave = [orange] хвиля {0}
|
||||||
|
text.wave.waiting = Хвиля через {0}
|
||||||
|
text.waiting = Очікування…
|
||||||
|
text.enemies = {0} Вороги
|
||||||
|
text.enemies.single = Противник
|
||||||
|
text.loadimage = Завантажити зображення
|
||||||
|
text.saveimage = Зберегти зображення
|
||||||
|
text.oregen = Генерація руд
|
||||||
|
text.editor.badsize = [orange] Недійсні розміри зображення! [] Дійсні розміри карти: {0}
|
||||||
|
text.editor.errorimageload = Помилка завантаження файлу зображень: [orange] {0}
|
||||||
|
text.editor.errorimagesave = Помилка збереження файлу зображення: [orange] {0}
|
||||||
|
text.editor.generate = Генератор
|
||||||
|
text.editor.resize = Змінити розмір
|
||||||
|
text.editor.loadmap = // Завантажити карту
|
||||||
|
text.editor.savemap = Зберегти карту
|
||||||
|
text.editor.loadimage = Завантажити зображення
|
||||||
|
text.editor.saveimage = Зберегти зображення
|
||||||
|
text.editor.unsaved = [scarlet] У вас є незбережені зміни! [] Ви впевнені, що хочете вийти?
|
||||||
|
text.editor.brushsize = Розмір пензля: {0}
|
||||||
|
text.editor.noplayerspawn = Ця карта не має ігрового поля для гравця!
|
||||||
|
text.editor.manyplayerspawns = Карти не можуть мати більше одного ігрового поля для гравців!
|
||||||
|
text.editor.manyenemyspawns = Не може бути більше ніж {0} ворожих точок!
|
||||||
|
text.editor.resizemap = Змінити розмір карти
|
||||||
|
text.editor.resizebig = [scarlet] Попередження! [] Карти, розмір яких перевищує 256 одиниць, можуть виснути і можуть бути нестабільними.
|
||||||
|
text.editor.mapname = Назва карти:
|
||||||
|
text.editor.overwrite = [accent] Попередження! Це перезаписує існуючу карту.
|
||||||
|
text.editor.failoverwrite = [crimson] Неможливо перезаписати карту за замовчуванням!
|
||||||
|
text.editor.selectmap = Виберіть карту для завантаження:
|
||||||
|
text.width = Ширина
|
||||||
|
text.height = Висота
|
||||||
|
text.randomize = Рандомізувати
|
||||||
|
text.apply = Застосувати
|
||||||
|
text.update = Оновити
|
||||||
|
text.menu = Меню
|
||||||
|
text.play = Відтворити
|
||||||
|
text.load = Завантаження
|
||||||
|
text.save = Зберегти
|
||||||
|
text.language.restart = Будь ласка, перезапустіть свою гру, щоб налаштування мови набули чинності.
|
||||||
|
text.settings.language = Мова
|
||||||
|
text.settings = Налаштування
|
||||||
|
text.tutorial = Навчальний\nпосібник
|
||||||
|
text.editor = Редактор
|
||||||
|
text.mapeditor = Редактор карт
|
||||||
|
text.donate = Підтримати проект
|
||||||
|
text.settings.reset = Скинути до стандартних
|
||||||
|
text.settings.controls = Елементи управління
|
||||||
|
text.settings.game = Гра
|
||||||
|
text.settings.sound = Звук
|
||||||
|
text.settings.graphics = Графіка
|
||||||
|
text.upgrades = Оновлення
|
||||||
|
text.purchased = [LIME] Створено!
|
||||||
|
text.weapons = Зброя
|
||||||
|
text.paused = Пауза
|
||||||
|
text.respawn = Відновлення за
|
||||||
|
text.info.title = [accent] інформація
|
||||||
|
text.error.title = [crimson] Виникла помилка
|
||||||
|
text.error.crashmessage = [SCARLET] Виникла несподівана помилка, що призвела до збою. [] Будь ласка, повідомте про конкретні обставини, розробнику: [ORANGE] anukendev@gmail.com []
|
||||||
|
text.error.crashtitle = Виникла помилка
|
||||||
|
text.mode.break = Режим зносу: {0}
|
||||||
|
text.mode.place = Режим будівництва: {0}
|
||||||
|
placemode.hold.name = Лінія
|
||||||
|
placemode.areadelete.name = Площа
|
||||||
|
placemode.touchdelete.name = Дотик
|
||||||
|
placemode.holddelete.name = Утримування.
|
||||||
|
placemode.none.name = (None)
|
||||||
|
placemode.touch.name = Дотик
|
||||||
|
placemode.cursor.name = курсор
|
||||||
|
text.blocks.extrainfo = [accent] додатковий інформаційний блок:
|
||||||
|
text.blocks.blockinfo = Блокування інформації
|
||||||
|
text.blocks.powercapacity = Потужність
|
||||||
|
text.blocks.powershot = Потужність / постріл
|
||||||
|
text.blocks.powersecond = Потужність / секунда
|
||||||
|
text.blocks.powerdraindamage = Потужність дренажу / пошкодження
|
||||||
|
text.blocks.shieldradius = Радіус щита
|
||||||
|
text.blocks.itemspeedsecond = Швидкість / секунда
|
||||||
|
text.blocks.range = Радіус
|
||||||
|
text.blocks.size = Розмір
|
||||||
|
text.blocks.powerliquid = Потужність / Рідина
|
||||||
|
text.blocks.maxliquidsecond = Макс. Рідина / секунда
|
||||||
|
text.blocks.liquidcapacity = Ємкість рідини
|
||||||
|
text.blocks.liquidsecond = Рідина / секунда
|
||||||
|
text.blocks.damageshot = Пошкодження / постріл
|
||||||
|
text.blocks.ammocapacity = Місткість боєприпасів
|
||||||
|
text.blocks.ammo = Набої
|
||||||
|
text.blocks.ammoitem = Боєприпаси / предмет
|
||||||
|
text.blocks.maxitemssecond = Макс. Елементи / секунду
|
||||||
|
text.blocks.powerrange = Радіус потужності
|
||||||
|
text.blocks.lasertilerange = Радіус лазерних плиток
|
||||||
|
text.blocks.capacity = Ємкість
|
||||||
|
text.blocks.itemcapacity = Ємкість предмету
|
||||||
|
text.blocks.maxpowergenerationsecond = Максимальна потужність / секунда
|
||||||
|
text.blocks.powergenerationsecond = Потужність / секунда
|
||||||
|
text.blocks.generationsecondsitem = Генерація за секунду / предмет
|
||||||
|
text.blocks.input = Ввід
|
||||||
|
text.blocks.inputliquid = Ввід речовини
|
||||||
|
text.blocks.inputitem = Вхідний матеріал
|
||||||
|
text.blocks.output = Вивід
|
||||||
|
text.blocks.secondsitem = Секунда / предмет
|
||||||
|
text.blocks.maxpowertransfersecond = Максимальна передача потужності / секунда
|
||||||
|
text.blocks.explosive = Вибухонебезпечний!
|
||||||
|
text.blocks.repairssecond = Ремонт / секунда
|
||||||
|
text.blocks.health = Здоров'я
|
||||||
|
text.blocks.inaccuracy = Неточність
|
||||||
|
text.blocks.shots = Постріли
|
||||||
|
text.blocks.shotssecond = Постріли / секунду
|
||||||
|
text.blocks.fuel = Паливо:
|
||||||
|
text.blocks.fuelduration = Тривалість палива
|
||||||
|
text.blocks.maxoutputsecond = Макс. Вихід / секунду
|
||||||
|
text.blocks.inputcapacity = Вхідна ємність
|
||||||
|
text.blocks.outputcapacity = Випускна ємність
|
||||||
|
text.blocks.poweritem = Потужність / виріб
|
||||||
|
text.placemode = Місцевий режим
|
||||||
|
text.breakmode = Перерваний режим
|
||||||
|
text.health = Здоров'я
|
||||||
|
setting.difficulty.easy = Легкий
|
||||||
|
setting.difficulty.normal = Нормальний
|
||||||
|
setting.difficulty.hard = Важкий
|
||||||
|
setting.difficulty.insane = Божевільний
|
||||||
|
setting.difficulty.purge = Очистити
|
||||||
|
setting.difficulty.name = Складність
|
||||||
|
setting.screenshake.name = Тряска екрана
|
||||||
|
setting.smoothcam.name = Гладка камера
|
||||||
|
setting.indicators.name = Індикатори ворога
|
||||||
|
setting.effects.name = Ефекти відображення
|
||||||
|
setting.sensitivity.name = Чутливість контролера
|
||||||
|
setting.saveinterval.name = Інтервал автозбереження
|
||||||
|
setting.seconds = {0} секунд
|
||||||
|
setting.fullscreen.name = Повноекранний
|
||||||
|
setting.multithread.name = Багатопотоковий [scarlet] (нестабільний!)
|
||||||
|
setting.fps.name = Показати FPS
|
||||||
|
setting.vsync.name = VSunc
|
||||||
|
setting.lasers.name = Показати енергетичні лазери
|
||||||
|
setting.healthbars.name = Показати здоров'я
|
||||||
|
setting.pixelate.name = Пікселяція екрану
|
||||||
|
setting.musicvol.name = Гучність музики
|
||||||
|
setting.mutemusic.name = Вимкнути музику
|
||||||
|
setting.sfxvol.name = Гучність ефектів
|
||||||
|
setting.mutesound.name = Вимкнути звук
|
||||||
|
map.maze.name = Лабіринт
|
||||||
|
map.fortress.name = Фортеця
|
||||||
|
map.sinkhole.name = Свердловина
|
||||||
|
map.caves.name = Печери
|
||||||
|
map.volcano.name = Вулкан
|
||||||
|
map.caldera.name = Кальдера
|
||||||
|
map.scorch.name = Мертва земля
|
||||||
|
map.desert.name = Пустеля
|
||||||
|
map.island.name = Острів
|
||||||
|
map.grassland.name = Пасовища
|
||||||
|
map.tundra.name = Тундра
|
||||||
|
map.spiral.name = Спіраль
|
||||||
|
map.tutorial.name = Навчання
|
||||||
|
tutorial.intro.text = [yellow] Ласкаво просимо до підручника. [] Для початку натисніть \"далі\".
|
||||||
|
tutorial.moveDesktop.text = Для переміщення використовуйте клавіші [orange] [[WASD] []. Утримуйте [orange] SHIFT[], для прискорення. Утримуйте [orange] CTRL [], використовуючи [orange] колесо прокручування [] для збільшення або зменшення.
|
||||||
|
tutorial.shoot.text = Використовуйте мишу, щоб націлитись, утримуйте [orange] ліву кнопку миші [], щоб стріляти. Попрактикуйтесь на [yellow] мішені [].
|
||||||
|
tutorial.moveAndroid.text = Щоб перетягнути панораму, перетягніть один палець по екрану. Використовуйте два пальця, щоб збільшити чи зменшити маштаб.
|
||||||
|
tutorial.placeSelect.text = Спробуйте вибрати [yellow] конвеєр [] у меню блоку внизу справа.
|
||||||
|
tutorial.placeConveyorDesktop.text = Використовуйте [orange] [[колесико миші] [], щоб повернути конвеєр [orange] вперед [], а потім помістіть його в [yellow] позначене місце [], використовуючи [orange] [[ліву кнопку миші] [].
|
||||||
|
tutorial.placeConveyorAndroid.text = Використовуйте [orange] [[кнопку оберту] [], щоб обернути конвеєр [оранжевий] вперед [], перетягуйте його одним пальцем, а потім помістіть його в [yellow] позначене місце [], використовуючи [orange] [[галочка][].
|
||||||
|
tutorial.placeConveyorAndroidInfo.text = Крім того, ви можете натиснути піктограму перехрестя внизу ліворуч, щоб переключитися на [orange] [[сенсорний режим]] [], і помістити блоки, натиснувши на екран. У сенсорному режимі блоки можна повертати зі стрілкою внизу ліворуч. Натисніть [yellow] наступний [], щоб спробувати.
|
||||||
|
tutorial.placeDrill.text = Тепер виберіть та розмістіть [yellow] кам'яне свердло [] у зазначеному місці.
|
||||||
|
tutorial.blockInfo.text = Якщо ви хочете дізнатись більше про блок, ви можете торкнутися [orange] знак питання [] у верхньому правому куті, щоб прочитати його опис.
|
||||||
|
tutorial.deselectDesktop.text = Ви можете вимкнути блок, використовуючи [orange] [[клацання правою кнопкою миші] [].
|
||||||
|
tutorial.deselectAndroid.text = Ви можете скасувати вибір блоку, натиснувши кнопку [orange] X [].
|
||||||
|
tutorial.drillPlaced.text = Дриль тепер видобуває [yellow] камінь, [] та виведе його на конвеєр, а потім переміщає його в [yellow] ядро [].
|
||||||
|
tutorial.drillInfo.text = Різні руди потребують різних дрилі. Камінь вимагає кам'яні свердла, залізо вимагає залізні свердла та ін
|
||||||
|
tutorial.drillPlaced2.text = Переміщення елементів у ядро вказує їх у ваш [yellow] предметний інвентар [] у верхньому лівому куті. Розміщення блоків використовує предмети з вашого інвентарю.
|
||||||
|
tutorial.moreDrills.text = Ви можете пов'язати багато свердлів і конвеєрів разом в одну гілку конвеєра.
|
||||||
|
tutorial.deleteBlock.text = Ви можете видалити блоки, натиснувши правою клавішею [orange] правою кнопкою миші [] по блоці, який ви хочете видалити. Спробуйте видалити цей конвеєр.
|
||||||
|
tutorial.deleteBlockAndroid.text = Ви можете видалити блоки за допомогою [orange], перехрестя [] в меню [mode] зламу [orange] у нижньому лівому куті та натиснувши на блок. Спробуйте видалити цей конвеєр.
|
||||||
|
tutorial.placeTurret.text = Тепер виділіть та розмістіть [yellow] турель [] у [yellow] позначеному місці [].
|
||||||
|
tutorial.placedTurretAmmo.text = Ця турель тепер приймає [yellow] боєприпас [] з конвеєра. Ви можете побачити, скільки боєприпасів вона має, натискаючи на неї і перевіряючи [green] зелену полоску [].
|
||||||
|
tutorial.turretExplanation.text = Турелі будуть автоматично стріляти у найближчого ворога, якщо вони мають достатню кількість боєприпасів.
|
||||||
|
tutorial.waves.text = Кожні [yellow] 60 [] секунд, хвиля [coral] ворогів [] буде виникати в певних місцях і намагатися знищити ядро.
|
||||||
|
tutorial.coreDestruction.text = Ваша мета полягає в тому, щоб [yellow] захищати ядро []. Якщо ядро знищено, ви [coral] програєте[].
|
||||||
|
tutorial.pausingDesktop.text = Якщо вам коли-небудь потрібно зробити перерву, натисніть кнопку [orange] паузи [] у верхньому лівому куті або на кнопку [orange] пропуск [], щоб призупинити гру. Ви можете вибрати і розмістити блоки під час призупинення, але не можете переміщатися чи стріляти.
|
||||||
|
tutorial.pausingAndroid.text = Якщо вам коли-небудь потрібно зробити перерву, натисніть кнопку [orange] пауза [] у верхньому лівому куті, щоб призупинити гру. Ти можеш ще знищувати та будувати блоки під час призупинення.
|
||||||
|
tutorial.purchaseWeapons.text = Ви можете придбати нову [yellow] зброю [] для вашого механізму, відкривши меню оновлення в лівому нижньому кутку.
|
||||||
|
tutorial.switchWeapons.text = Перемикати зброю будь-яким натисканням його піктограми внизу ліворуч або за допомогою цифр [orange] [[1-9] [].
|
||||||
|
tutorial.spawnWave.text = Ось хвиля зараз. Знищи їх
|
||||||
|
tutorial.pumpDesc.text = У пізніших хвилях, можливо, доведеться використовувати [yellow] насоси [] для розподілу рідин для генераторів або екстракторів.
|
||||||
|
tutorial.pumpPlace.text = Насоси працюють аналогічно свердлам, за винятком того, що вони виробляють рідини замість предметів. Спробуйте встановити насос на [yellow] призначене мастило [].
|
||||||
|
tutorial.conduitUse.text = Тепер покладіть [orange] трубопровід [], віддаляючись від насоса.
|
||||||
|
tutorial.conduitUse2.text = І ще кілька ...
|
||||||
|
tutorial.conduitUse3.text = І ще кілька ...
|
||||||
|
tutorial.generator.text = Тепер, помістіть блок [orange] базовий генератор енергії [] в кінці каналу.
|
||||||
|
tutorial.generatorExplain.text = Цей генератор тепер створить [yellow] енергію [] від масла.
|
||||||
|
tutorial.lasers.text = Потужність розподіляється за допомогою [yellow] лазерів потужності []. Поверніть і помістіть його тут.
|
||||||
|
tutorial.laserExplain.text = Тепер генератор переведе енергію в лазерний блок. Промінь [yellow] непрозорий [] означає, що в даний час він передає потужність, а промінь [yellow] прозорий [] означає, що це не так.
|
||||||
|
tutorial.laserMore.text = Ви можете перевірити, скільки енергії в блоку, наведіть курсор миші на нього і перевірте [yellow] жовту стрічку [] у верхній частині екрана.
|
||||||
|
tutorial.healingTurret.text = Цей лазер може бути використаний для живлення турелі для ремонту [lime] []. Помістіть одну тут.
|
||||||
|
tutorial.healingTurretExplain.text = Поки вона має енергію, ця турель може [lime] відремонтувати блоки. [] Під час гри постарайтеся збудувати одну таку чим швидше!
|
||||||
|
tutorial.smeltery.text = Для багатьох блоків потрібна [orange] сталь [], для цього потрібна[orange] доминна піч [] . Місце тут.
|
||||||
|
tutorial.smelterySetup.text = Ця піч буде тепер виробляти [orange] сталь [] із вхідного заліза, використовуючи вугілля як паливо.
|
||||||
|
tutorial.tunnelExplain.text = Також зауважте, що елементи проходять через [yellow] тунельний блок [] і з'являються з іншого боку, проходячи через кам'яний блок. Майте на увазі, що тунелі можуть проходити лише до 2 блоків.
|
||||||
|
tutorial.end.text = Ви завершили підручник! Удачі!
|
||||||
|
text.keybind.title = Ключ перемотки
|
||||||
|
keybind.move_x.name = move_x
|
||||||
|
keybind.move_y.name = move_y
|
||||||
|
keybind.select.name = Вибрати
|
||||||
|
keybind.break.name = {0}break{/0}{1}; {/1}
|
||||||
|
keybind.shoot.name = Постріл
|
||||||
|
keybind.zoom_hold.name = zoom_hold
|
||||||
|
keybind.zoom.name = Збільшити
|
||||||
|
keybind.block_info.name = Інформація про блок
|
||||||
|
keybind.menu.name = Меню
|
||||||
|
keybind.pause.name = Пауза
|
||||||
|
keybind.dash.name = Тире
|
||||||
|
keybind.chat.name = Чат
|
||||||
|
keybind.player_list.name = Список гравців
|
||||||
|
keybind.console.name = // Консоль 1
|
||||||
|
keybind.rotate_alt.name = rotate_alt
|
||||||
|
keybind.rotate.name = Повернути
|
||||||
|
keybind.weapon_1.name = Зброя!
|
||||||
|
keybind.weapon_2.name = Зброя!
|
||||||
|
keybind.weapon_3.name = Зброя!
|
||||||
|
keybind.weapon_4.name = Зброя!
|
||||||
|
keybind.weapon_5.name = Зброя!
|
||||||
|
keybind.weapon_6.name = Зброя!
|
||||||
|
mode.waves.name = Хвилі
|
||||||
|
mode.sandbox.name = Пісочниця
|
||||||
|
mode.freebuild.name = Вільний режим
|
||||||
|
upgrade.standard.name = Стандартний
|
||||||
|
upgrade.standard.description = Стандартний механ.
|
||||||
|
upgrade.blaster.name = Бластер
|
||||||
|
upgrade.blaster.description = Стріляє повільно, слабкі кулі.
|
||||||
|
upgrade.triblaster.name = Трипластер
|
||||||
|
upgrade.triblaster.description = Вистрілює 3 кулі в розповсюдженні.
|
||||||
|
upgrade.clustergun.name = Касетна гармата
|
||||||
|
upgrade.clustergun.description = Вистрілює неточними вибуховими гранатами.
|
||||||
|
upgrade.beam.name = Пушечна гармата
|
||||||
|
upgrade.beam.description = Вистрілює далекобійним,пробірний лазерний промінь.
|
||||||
|
upgrade.vulcan.name = Вулкан
|
||||||
|
upgrade.vulcan.description = Вистрілює шквал швидких куль.
|
||||||
|
upgrade.shockgun.name = Шок-пушка
|
||||||
|
upgrade.shockgun.description = Стріляє руйнівним вибухом заряженої шрапнелі.
|
||||||
|
item.stone.name = Камінь
|
||||||
|
item.iron.name = Залізо
|
||||||
|
item.coal.name = Вугівалля
|
||||||
|
item.steel.name = Сталь
|
||||||
|
item.titanium.name = Титан
|
||||||
|
item.dirium.name = Дириум
|
||||||
|
item.uranium.name = Уран
|
||||||
|
item.sand.name = Пісок
|
||||||
|
liquid.water.name = Вода
|
||||||
|
liquid.plasma.name = Плазма
|
||||||
|
liquid.lava.name = Лава
|
||||||
|
liquid.oil.name = Нафта
|
||||||
|
block.weaponfactory.name = Фабрика зброї
|
||||||
|
block.weaponfactory.fulldescription = Використовується для створення зброї для гравця mech. Натисніть, щоб використати. Автоматично приймає ресурси з основного ядра.
|
||||||
|
block.air.name = Повітря
|
||||||
|
block.blockpart.name = Блокчастина
|
||||||
|
block.deepwater.name = Глибока вода
|
||||||
|
block.water.name = Вода
|
||||||
|
block.lava.name = Лава
|
||||||
|
block.oil.name = Нафта
|
||||||
|
block.stone.name = Камінь
|
||||||
|
block.blackstone.name = Чорний камінь
|
||||||
|
block.iron.name = Залізо
|
||||||
|
block.coal.name = Вугілля
|
||||||
|
block.titanium.name = Титан
|
||||||
|
block.uranium.name = Уран
|
||||||
|
block.dirt.name = Бруд
|
||||||
|
block.sand.name = Пісок
|
||||||
|
block.ice.name = Лід
|
||||||
|
block.snow.name = Сніг
|
||||||
|
block.grass.name = Трава
|
||||||
|
block.sandblock.name = Блок піску
|
||||||
|
block.snowblock.name = Блок снігу
|
||||||
|
block.stoneblock.name = Блок камню
|
||||||
|
block.blackstoneblock.name = Блок чорного камню
|
||||||
|
block.grassblock.name = Блок бруду
|
||||||
|
block.mossblock.name = Моссблок
|
||||||
|
block.shrub.name = Чагарник
|
||||||
|
block.rock.name = Камень
|
||||||
|
block.icerock.name = Ледяний камень
|
||||||
|
block.blackrock.name = Чорний камінь
|
||||||
|
block.dirtblock.name = Блок землі
|
||||||
|
block.stonewall.name = Кам'яна стіна
|
||||||
|
block.stonewall.fulldescription = Недорогий захисний блок. Корисно для захисту ядра та турелі в перші кілька хвиль.
|
||||||
|
block.ironwall.name = Залізна стіна
|
||||||
|
block.ironwall.fulldescription = Основний захисний блок. Забезпечує захист від ворогів.
|
||||||
|
block.steelwall.name = Сталева стіна
|
||||||
|
block.steelwall.fulldescription = Стандартний захисний блок. адекватний захист від ворогів.
|
||||||
|
block.titaniumwall.name = Титанова стіна
|
||||||
|
block.titaniumwall.fulldescription = Сильний захисний блок. Забезпечує захист від ворогів.
|
||||||
|
block.duriumwall.name = Діріумова стіна
|
||||||
|
block.duriumwall.fulldescription = Дуже сильний захисний блок. Забезпечує захист від ворогів.
|
||||||
|
block.compositewall.name = Композитна стіна
|
||||||
|
block.steelwall-large.name = Велика сталева стіна
|
||||||
|
block.steelwall-large.fulldescription = Стандартний захисний блок. Поєднує в собі кілька блоків.
|
||||||
|
block.titaniumwall-large.name = Велика титанова стіна
|
||||||
|
block.titaniumwall-large.fulldescription = Сильний захисний блок. Поєднує в собі кілька блоків.
|
||||||
|
block.duriumwall-large.name = Велика дирмітова стіна
|
||||||
|
block.duriumwall-large.fulldescription = Дуже сильний захисний блок.Поєднує в собі кілька блоків.
|
||||||
|
block.titaniumshieldwall.name = Стіна з щитом
|
||||||
|
block.titaniumshieldwall.fulldescription = Сильний захисний блок з додатковим вбудованим щитом. Потрібна енергія. Використовує енергію для поглинання ворожих куль. Рекомендується використовувати силові пристосування для забезпечення енергії цього блоку.
|
||||||
|
block.repairturret.name = Ремонтна турель
|
||||||
|
block.repairturret.fulldescription = Ремонтує недалекі пошкодженні блоки.Повільний темп. Використовує невелику кількість енергії.
|
||||||
|
block.megarepairturret.name = Ремонтна турель II
|
||||||
|
block.megarepairturret.fulldescription = Ремонтує недалекі пошкодженні блоки.Збільшений радіус та швидший темп ремонту . Використовує багато енергії.
|
||||||
|
block.shieldgenerator.name = Генератор щиту
|
||||||
|
block.shieldgenerator.fulldescription = Передовий захисний блок. Захищає всі блоки в радіусі від нападу. Не вкористовує енергію при бездіяльності, але швидко витрачає енергію на захист від куль.
|
||||||
|
block.door.name = Двері
|
||||||
|
block.door.fulldescription = Блок, який можна відкрити та закрити, торкнувшись його.
|
||||||
|
block.door-large.name = Великі двері
|
||||||
|
block.door-large.fulldescription = Блок, який можна відкрити та закрити, торкнувшись його.
|
||||||
|
block.conduit.name = Трубопровід
|
||||||
|
block.conduit.fulldescription = Основний транспортний блок. Працює як конвеєр, але з рідинами. Найкраще використовується з насосами або іншими трубопроводами. Може використовуватися як міст через рідини для ворогів та гравців.
|
||||||
|
block.pulseconduit.name = Імпульсний канал
|
||||||
|
block.pulseconduit.fulldescription = Покращенний блок перевезення рідин. Транспортує рідини швидше і зберігає більше стандартних каналів.
|
||||||
|
block.liquidrouter.name = маршрутизатор для рідини
|
||||||
|
block.liquidrouter.fulldescription = Працює аналогічно маршрутизатору. Приймає рідину ввід з одного боку і виводить його на інші сторони. Корисний для розщеплення рідини з одного каналу на кілька інших трубопроводів.
|
||||||
|
block.conveyor.name = Конвеєр
|
||||||
|
block.conveyor.fulldescription = Базовий транспортний блок. Переміщує предмети вперед і автоматично вкладає їх у турелі або ремісники. Поворотний Може використовуватися як міст через рідину для ворогів та гравців.
|
||||||
|
block.steelconveyor.name = Сталевий конвеєр
|
||||||
|
block.steelconveyor.fulldescription = Розширений блок транспортування предметів. Переміщення елементів швидше, ніж стандартні конвеєри.
|
||||||
|
block.poweredconveyor.name = Імпульсний конвеєр
|
||||||
|
block.poweredconveyor.fulldescription = Кінцевий транспортний блок. Переміщення елементів швидше, ніж сталеві конвеєри.
|
||||||
|
block.router.name = Маршрутизатор
|
||||||
|
block.router.fulldescription = Приймає елементи з одного напрямку і виводить їх на 3 інших напрямках. Можна також зберігати певну кількість предметів. Використовується для розщеплення матеріалів з одного свердла на декілька башточок.
|
||||||
|
block.junction.name = Міст
|
||||||
|
block.junction.fulldescription = Виступає як міст для двох перехресних конвеєрних стрічок. Корисне у ситуаціях з двома різними конвеєрами, що несуть різні матеріали в різних місцях.
|
||||||
|
block.conveyortunnel.name = Конвеєрний тунель
|
||||||
|
block.conveyortunnel.fulldescription = Транспортує предмети під блоками. Щоб використати, помістіть один тунель, що веде у блок, щоб бути підсвіченим, а один - з іншого боку. Переконайтеся, що обидва тунелі стикаються з протилежними напрямками, тобто до блоків, які вони вводять або виводять.
|
||||||
|
block.liquidjunction.name = Міст для рідини
|
||||||
|
block.liquidjunction.fulldescription = Діє як міст для двох перехресних трубопроводів. Корисно в ситуаціях з двома різними трубами, що несуть різні рідини в різних місцях.
|
||||||
|
block.liquiditemjunction.name = Перехрестя рідкого пункту
|
||||||
|
block.liquiditemjunction.fulldescription = Виступає як міст для перетину трубопроводів і конвеєрів.
|
||||||
|
block.powerbooster.name = Підсилювач потужності
|
||||||
|
block.powerbooster.fulldescription = Поширює енергію на всі блоки в межах його радіуса.
|
||||||
|
block.powerlaser.name = Енергетичний лазер
|
||||||
|
block.powerlaser.fulldescription = Створює лазер, який передає енергію блоку перед ним. Не створює жодної сили сама. Найкраще використовується з генераторами або іншими лазерами.
|
||||||
|
block.powerlaserrouter.name = Лазерний маршрутизатор
|
||||||
|
block.powerlaserrouter.fulldescription = Лазер, який розподіляє енергію у три напрямки одночасно. Корисно в ситуаціях, коли потрібно живити кілька блоків від одного генератора.
|
||||||
|
block.powerlasercorner.name = Лазерний кут
|
||||||
|
block.powerlasercorner.fulldescription = Лазер, який розподіляє енергію одночасно на два напрямки. Корисно в ситуаціях, коли потрібно живити кілька блоків від одного генератора, а маршрутизатор неточний.
|
||||||
|
block.teleporter.name = Телепорт
|
||||||
|
block.teleporter.fulldescription = Продвинутий блок транспортування предметів.Щоб телепортувати предмети з одного місця в інше потрібно збудувати 2 телепорти і назначити на них одинаковий колір. Використовує енергію. Натисніть, щоб змінити колір.
|
||||||
|
block.sorter.name = Сортувальник
|
||||||
|
block.sorter.fulldescription = Сортує предмети за типом матеріалу. Матеріал для прийняття позначається кольором у блоці. Всі елементи, що відповідають матеріалу сортування, виводяться вперед, а все інше виводить ліворуч і праворуч.
|
||||||
|
block.core.name = Ядро
|
||||||
|
block.pump.name = Насос
|
||||||
|
block.pump.fulldescription = Насоси рідини з вихідного блоку - зазвичай вода, лава чи олія. Виводить рідину в сусідні трубопроводи.
|
||||||
|
block.fluxpump.name = Флюсовий насос
|
||||||
|
block.fluxpump.fulldescription = Розширений варіант насоса. Зберігає більше рідини та перекачує швидше.
|
||||||
|
block.smelter.name = Плавильня
|
||||||
|
block.smelter.fulldescription = Основний ремісничий блок. Коли вводиться 1 залізо та 1 вугілля в якості палива, виводить одну сталь. Рекомендується вводити залізо та вугілля на різних поясах, щоб запобігти засміченню.
|
||||||
|
block.crucible.name = Тигель
|
||||||
|
block.crucible.fulldescription = Розширений блок обробки. При введенні 1 титану, 1 сталі та 1 вугілля в якості пального, виводить один дирний. Рекомендується вводити вугілля, сталь та титан на різних поясах, щоб запобігти засміченню.
|
||||||
|
block.coalpurifier.name = вугільний екстрактор
|
||||||
|
block.coalpurifier.fulldescription = Основний екстрактор. Виходить вугілля при постачанні великої кількості води та каменю.
|
||||||
|
block.titaniumpurifier.name = Титановий екстрактор
|
||||||
|
block.titaniumpurifier.fulldescription = Стандартний блок екстрактора. Виходить титан при постачанні великої кількості води та заліза.
|
||||||
|
block.oilrefinery.name = Нафтопереробний завод
|
||||||
|
block.oilrefinery.fulldescription = Очищує велику кількість нафти і перетворює на вугілля. Корисний для заправки вугільних башточок, коли вугільні родовища є дефіцитними.
|
||||||
|
block.stoneformer.name = Кам'янний екстрактор
|
||||||
|
block.stoneformer.fulldescription = Здавлюється рідка лава в камінь. Корисно для виготовлення великої кількості каменю для очищувачів вугілля.
|
||||||
|
block.lavasmelter.name = Лавовий завод
|
||||||
|
block.lavasmelter.fulldescription = Використовує лаву для перетворення залізо на сталь. Альтернатива плавильні. Корисно в ситуаціях, коли вугілля є дефіцитним.
|
||||||
|
block.stonedrill.name = Кам'янна свердловина
|
||||||
|
block.stonedrill.fulldescription = Основна свердловина.Розміщюється на кам'яній плитці виводить камінь повільними темпами.
|
||||||
|
block.irondrill.name = Залізна свердловина
|
||||||
|
block.irondrill.fulldescription = Базова свердловина.Розміщюється на родовищі залізної руди, випускає залізо в повільному темпі.
|
||||||
|
block.coaldrill.name = Вугільна свердловина
|
||||||
|
block.coaldrill.fulldescription = Базова свердловина.Розміщюється на родовищі вугільної руди ,видобуває вугілля повільними темпами.
|
||||||
|
block.uraniumdrill.name = Уранова свердловина
|
||||||
|
block.uraniumdrill.fulldescription = Продвинута свердловина. Розміщюється на родовищі уранової руди.Видобуток урану відбувається повільними темпами.
|
||||||
|
block.titaniumdrill.name = Титанова свердловина
|
||||||
|
block.titaniumdrill.fulldescription = Продвинута свердловина.Розміщюється на родовищі титанової руди, Видобуток титану відбувається повільним темпом.
|
||||||
|
block.omnidrill.name = Убер свердловина
|
||||||
|
block.omnidrill.fulldescription = Кінцева свердловина.Дуже швидко видобуває будь-який вид руди.
|
||||||
|
block.coalgenerator.name = Вугільний генератор
|
||||||
|
block.coalgenerator.fulldescription = Основний генератор. Генерує енергію з вугілля. Виводиться потужність лазерів на 4 сторони.
|
||||||
|
block.thermalgenerator.name = Теплогенератор
|
||||||
|
block.thermalgenerator.fulldescription = Генерує енергію від лави. Виводиться потужність лазерів на 4 сторони.
|
||||||
|
block.combustiongenerator.name = Генератор горіння
|
||||||
|
block.combustiongenerator.fulldescription = Генерує енергію з нафти. Виводиться потужність лазерів на 4 сторони.
|
||||||
|
block.rtgenerator.name = RTG генератор
|
||||||
|
block.rtgenerator.fulldescription = Генерує невелику кількість енергії з радіоактивного розпаду урану. Виводиться потужність лазерів на 4 сторони.
|
||||||
|
block.nuclearreactor.name = Ядерний реактор
|
||||||
|
block.nuclearreactor.fulldescription = Розширений варіант RTG Generator і кінцевий генератор електроенергії. Генерує енергію з урану. Потребує постійного водяного охолодження.Сильно вибухне якщо не буде постачання води у великії кількості
|
||||||
|
block.turret.name = Турель
|
||||||
|
block.turret.fulldescription = Базова, дешева турель. Використовує камінь для боєприпасів. Має трохи більше діапазону, ніж подвійна турель.
|
||||||
|
block.doubleturret.name = Подвійна турель
|
||||||
|
block.doubleturret.fulldescription = Дещо потужна версія турель. Використовує камінь для боєприпасів. Значно більший урон, але менший діапазон. Вистрілює дві кулі.
|
||||||
|
block.machineturret.name = Кулеметна турель
|
||||||
|
block.machineturret.fulldescription = Стандартна всеосяжна турель. Використовує залізо для боєприпасів. Має швидку швидкість пострілу і гідну шкоду.
|
||||||
|
block.shotgunturret.name = Розріджуюча турель
|
||||||
|
block.shotgunturret.fulldescription = Стандартна турель. Використовує залізо для боєприпасів. Вистрілює 7 куль навколо себе.Наносить значних ушкоджень,звісно якщо поцілить :)
|
||||||
|
block.flameturret.name = Вогнемет
|
||||||
|
block.flameturret.fulldescription = Продвинута турель ближнього діапазону. Використовує вугілля для боєприпасів. Має дуже низький радіус, але дуже високий збиток. Добре для близьких дистанцій. Рекомендується використовувати за стінами.
|
||||||
|
block.sniperturret.name = Лазерна турель.
|
||||||
|
block.sniperturret.fulldescription = Продвинута далекобійна турель. Використовує сталь для боєприпасів. Дуже високий збиток, але низький рівень урону. Дорогі для використання, але можуть бути розташовані далеко від ліній ворога через його радіус.
|
||||||
|
block.mortarturret.name = Флак турель
|
||||||
|
block.mortarturret.fulldescription = Продвинута,неточна турель. Використовує вугілля для боєприпасів. Стріляє кулями, що вибухають у шрапнеллв. Корисне для великих натовпів ворогів.
|
||||||
|
block.laserturret.name = Лазерна турель
|
||||||
|
block.laserturret.fulldescription = Продвинута однопушечна турель. Використовує енергію. Хороша на середніх дистанціях. Ніколи не пропускає.
|
||||||
|
block.waveturret.name = Тесла
|
||||||
|
block.waveturret.fulldescription = Передова багатоцільова турель. Використовує енергію. Середній радіус. Ніколи не пропускає. Активно знижує, але може вражати декількох ворогів одночасно з ланцюговим освітленням.
|
||||||
|
block.plasmaturret.name = Плазмова турель
|
||||||
|
block.plasmaturret.fulldescription = Дуже продвинута версія Вогнеметної турелі. Використовує вугілля як боєприпаси. Дуже високий урон, від близької до середньої дистанції.
|
||||||
|
block.chainturret.name = Уранова турель
|
||||||
|
block.chainturret.fulldescription = Остаточна швидкістна вежа. Використовує уран як боєприпаси. Вистрілює великі кулі при високій швидкості вогню. Середній радіус. Промінь кілька плиток. Надзвичайно жорсткий.
|
||||||
|
block.titancannon.name = Титанова гармата
|
||||||
|
block.titancannon.fulldescription = Найбільш далекобійна турель. Використовує уран як боєприпаси. Вистрілює великі снаряди. Далекобійний. Промінь кілька плиток. Надзвичайно жорсткий.
|
||||||
|
block.playerspawn.name = Спавн Гравця
|
||||||
|
block.enemyspawn.name = Спавн ворогів
|
||||||
BIN
core/assets/cursors/cursor.png
Normal file
|
After Width: | Height: | Size: 201 B |
BIN
core/assets/cursors/hand.png
Normal file
|
After Width: | Height: | Size: 276 B |
BIN
core/assets/cursors/ibar.png
Normal file
|
After Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 91 KiB |
@@ -70,10 +70,13 @@ char id=98 x=204 y=102 width=22 height=22 xoffset=-1 yoffset=15 x
|
|||||||
char id=99 x=226 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=99 x=226 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
char id=100 x=248 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=100 x=248 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
char id=101 x=270 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=101 x=270 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
|
char id=1108 x=270 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
char id=102 x=292 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=102 x=292 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
char id=103 x=314 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=103 x=314 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
char id=104 x=336 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=104 x=336 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
char id=105 x=358 y=102 width=14 height=22 xoffset=-1 yoffset=15 xadvance=16 page=0 chnl=0
|
char id=105 x=358 y=102 width=14 height=22 xoffset=-1 yoffset=15 xadvance=16 page=0 chnl=0
|
||||||
|
char id=1110 x=358 y=102 width=14 height=22 xoffset=-1 yoffset=15 xadvance=16 page=0 chnl=0
|
||||||
|
char id=1111 x=358 y=102 width=14 height=22 xoffset=-1 yoffset=15 xadvance=16 page=0 chnl=0
|
||||||
char id=106 x=372 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=106 x=372 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
char id=107 x=394 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=107 x=394 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
char id=108 x=416 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
char id=108 x=416 y=102 width=22 height=22 xoffset=-1 yoffset=15 xadvance=24 page=0 chnl=0
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ io.anuke.ucore.scene.ui.Button$ButtonStyle: {
|
|||||||
},
|
},
|
||||||
io.anuke.ucore.scene.ui.TextButton$TextButtonStyle: {
|
io.anuke.ucore.scene.ui.TextButton$TextButtonStyle: {
|
||||||
default: {over: button-over, disabled: button, font: default-font, fontColor: white, disabledFontColor: grey, down: button-down, up: button, transition: 0 },
|
default: {over: button-over, disabled: button, font: default-font, fontColor: white, disabledFontColor: grey, down: button-down, up: button, transition: 0 },
|
||||||
|
discord: {over: discord-banner-over, font: default-font, fontColor: white, up: discord-banner},
|
||||||
clear: {down: clear-down, up: clear, over: clear-over, font: default-font, fontColor: white, disabledFontColor: grey },
|
clear: {down: clear-down, up: clear, over: clear-over, font: default-font, fontColor: white, disabledFontColor: grey },
|
||||||
empty: {font: default-font},
|
empty: {font: default-font},
|
||||||
toggle: {font: default-font, fontColor: white, checked: button-down, down: button-down, up: button, over: button-over, disabled: button, disabledFontColor: grey }
|
toggle: {font: default-font, fontColor: white, checked: button-down, down: button-down, up: button, over: button-over, disabled: button, disabledFontColor: grey }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#Autogenerated file. Do not modify.
|
#Autogenerated file. Do not modify.
|
||||||
#Tue Mar 13 13:31:03 EDT 2018
|
#Sat Mar 24 14:20:21 EDT 2018
|
||||||
version=release
|
version=release
|
||||||
androidBuildCode=380
|
androidBuildCode=473
|
||||||
name=Mindustry
|
name=Mindustry
|
||||||
code=3.4
|
code=3.5
|
||||||
build=34
|
build=35
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class Vars{
|
|||||||
public static float fontscale = Math.max(Unit.dp.scl(1f)/2f, 0.5f);
|
public static float fontscale = Math.max(Unit.dp.scl(1f)/2f, 0.5f);
|
||||||
//camera zoom displayed on startup
|
//camera zoom displayed on startup
|
||||||
public static final int baseCameraScale = Math.round(Unit.dp.scl(4));
|
public static final int baseCameraScale = Math.round(Unit.dp.scl(4));
|
||||||
//how much the zoom changes every zoom button press
|
//how much the zoom changes every zoom button press (unused?)
|
||||||
public static final int zoomScale = Math.round(Unit.dp.scl(1));
|
public static final int zoomScale = Math.round(Unit.dp.scl(1));
|
||||||
//if true, player speed will be increased, massive amounts of resources will be given on start, and other debug options will be available
|
//if true, player speed will be increased, massive amounts of resources will be given on start, and other debug options will be available
|
||||||
public static boolean debug = false;
|
public static boolean debug = false;
|
||||||
@@ -92,7 +92,7 @@ public class Vars{
|
|||||||
|
|
||||||
public static final int tilesize = 8;
|
public static final int tilesize = 8;
|
||||||
|
|
||||||
public static final Locale[] locales = {new Locale("en"), new Locale("fr", "FR"), new Locale("ru"), new Locale("pl", "PL"),
|
public static final Locale[] locales = {new Locale("en"), new Locale("fr", "FR"), new Locale("ru"), new Locale("uk", "UA"), new Locale("pl", "PL"),
|
||||||
new Locale("de"), new Locale("es", "LA"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID")};
|
new Locale("de"), new Locale("es", "LA"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID")};
|
||||||
|
|
||||||
public static final Color[] playerColors = {
|
public static final Color[] playerColors = {
|
||||||
|
|||||||
@@ -16,11 +16,15 @@ import io.anuke.mindustry.net.Net.SendMode;
|
|||||||
import io.anuke.mindustry.net.NetworkIO;
|
import io.anuke.mindustry.net.NetworkIO;
|
||||||
import io.anuke.mindustry.net.Packets.*;
|
import io.anuke.mindustry.net.Packets.*;
|
||||||
import io.anuke.mindustry.resource.Item;
|
import io.anuke.mindustry.resource.Item;
|
||||||
|
import io.anuke.mindustry.resource.Upgrade;
|
||||||
|
import io.anuke.mindustry.resource.UpgradeRecipes;
|
||||||
|
import io.anuke.mindustry.resource.Weapon;
|
||||||
import io.anuke.mindustry.world.Block;
|
import io.anuke.mindustry.world.Block;
|
||||||
import io.anuke.mindustry.world.Map;
|
import io.anuke.mindustry.world.Map;
|
||||||
import io.anuke.mindustry.world.Placement;
|
import io.anuke.mindustry.world.Placement;
|
||||||
import io.anuke.mindustry.world.Tile;
|
import io.anuke.mindustry.world.Tile;
|
||||||
import io.anuke.mindustry.world.blocks.ProductionBlocks;
|
import io.anuke.mindustry.world.blocks.ProductionBlocks;
|
||||||
|
import io.anuke.ucore.core.Effects;
|
||||||
import io.anuke.ucore.core.Timers;
|
import io.anuke.ucore.core.Timers;
|
||||||
import io.anuke.ucore.entities.BaseBulletType;
|
import io.anuke.ucore.entities.BaseBulletType;
|
||||||
import io.anuke.ucore.entities.Entities;
|
import io.anuke.ucore.entities.Entities;
|
||||||
@@ -40,10 +44,10 @@ public class NetClient extends Module {
|
|||||||
|
|
||||||
private Timer timer = new Timer(5);
|
private Timer timer = new Timer(5);
|
||||||
private boolean connecting = false;
|
private boolean connecting = false;
|
||||||
private boolean gotData = false;
|
|
||||||
private boolean kicked = false;
|
private boolean kicked = false;
|
||||||
private IntSet recieved = new IntSet();
|
private IntSet recieved = new IntSet();
|
||||||
private IntMap<Entity> recent = new IntMap<>();
|
private IntMap<Entity> recent = new IntMap<>();
|
||||||
|
private float timeoutTime = 0f; //data timeout counter
|
||||||
|
|
||||||
public NetClient(){
|
public NetClient(){
|
||||||
|
|
||||||
@@ -53,8 +57,8 @@ public class NetClient extends Module {
|
|||||||
Net.setClientLoaded(false);
|
Net.setClientLoaded(false);
|
||||||
recieved.clear();
|
recieved.clear();
|
||||||
recent.clear();
|
recent.clear();
|
||||||
|
timeoutTime = 0f;
|
||||||
connecting = true;
|
connecting = true;
|
||||||
gotData = false;
|
|
||||||
kicked = false;
|
kicked = false;
|
||||||
|
|
||||||
ui.chatfrag.clearMessages();
|
ui.chatfrag.clearMessages();
|
||||||
@@ -77,14 +81,6 @@ public class NetClient extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Net.send(c, SendMode.tcp);
|
Net.send(c, SendMode.tcp);
|
||||||
|
|
||||||
Timers.runTask(dataTimeout, () -> {
|
|
||||||
if (!gotData) {
|
|
||||||
Log.err("Failed to load data!");
|
|
||||||
ui.loadfrag.hide();
|
|
||||||
Net.disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Net.handleClient(Disconnect.class, packet -> {
|
Net.handleClient(Disconnect.class, packet -> {
|
||||||
@@ -105,8 +101,6 @@ public class NetClient extends Module {
|
|||||||
NetworkIO.loadWorld(data.stream);
|
NetworkIO.loadWorld(data.stream);
|
||||||
player.set(world.getSpawnX(), world.getSpawnY());
|
player.set(world.getSpawnX(), world.getSpawnY());
|
||||||
|
|
||||||
gotData = true;
|
|
||||||
|
|
||||||
finishConnecting();
|
finishConnecting();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -123,7 +117,7 @@ public class NetClient extends Module {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Net.handleClient(SyncPacket.class, packet -> {
|
Net.handleClient(SyncPacket.class, packet -> {
|
||||||
if (!gotData) return;
|
if (connecting) return;
|
||||||
int players = 0;
|
int players = 0;
|
||||||
int enemies = 0;
|
int enemies = 0;
|
||||||
|
|
||||||
@@ -180,9 +174,8 @@ public class NetClient extends Module {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Net.handleClient(BreakPacket.class, (packet) -> {
|
Net.handleClient(BreakPacket.class, (packet) ->
|
||||||
Placement.breakBlock(packet.x, packet.y, true, Timers.get("breakblocksound", 10));
|
Placement.breakBlock(packet.x, packet.y, true, Timers.get("breakblocksound", 10)));
|
||||||
});
|
|
||||||
|
|
||||||
Net.handleClient(EntitySpawnPacket.class, packet -> {
|
Net.handleClient(EntitySpawnPacket.class, packet -> {
|
||||||
EntityGroup group = packet.group;
|
EntityGroup group = packet.group;
|
||||||
@@ -246,7 +239,7 @@ public class NetClient extends Module {
|
|||||||
kicked = true;
|
kicked = true;
|
||||||
Net.disconnect();
|
Net.disconnect();
|
||||||
state.set(State.menu);
|
state.set(State.menu);
|
||||||
ui.showError("$text.server.kicked." + packet.reason.name());
|
if(!packet.reason.quiet) ui.showError("$text.server.kicked." + packet.reason.name());
|
||||||
ui.loadfrag.hide();
|
ui.loadfrag.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -309,6 +302,15 @@ public class NetClient extends Module {
|
|||||||
Player player = playerGroup.getByID(packet.info.playerid);
|
Player player = playerGroup.getByID(packet.info.playerid);
|
||||||
ui.traces.show(player, packet.info);
|
ui.traces.show(player, packet.info);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Net.handleClient(UpgradePacket.class, packet -> {
|
||||||
|
Weapon weapon = (Weapon) Upgrade.getByID(packet.id);
|
||||||
|
|
||||||
|
state.inventory.removeItems(UpgradeRecipes.get(weapon));
|
||||||
|
control.upgrades().addWeapon(weapon);
|
||||||
|
ui.hudfrag.updateWeapons();
|
||||||
|
Effects.sound("purchase");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -316,14 +318,20 @@ public class NetClient extends Module {
|
|||||||
if(!Net.client()) return;
|
if(!Net.client()) return;
|
||||||
|
|
||||||
if(!state.is(State.menu)){
|
if(!state.is(State.menu)){
|
||||||
if(gotData) sync();
|
if(!connecting) sync();
|
||||||
}else if(!connecting){
|
}else if(!connecting){
|
||||||
Net.disconnect();
|
Net.disconnect();
|
||||||
|
}else{ //...must be connecting
|
||||||
|
timeoutTime += Timers.delta();
|
||||||
|
if(timeoutTime > dataTimeout){
|
||||||
|
Log.err("Failed to load data!");
|
||||||
|
ui.loadfrag.hide();
|
||||||
|
kicked = true;
|
||||||
|
ui.showError("$text.disconnect.data");
|
||||||
|
Net.disconnect();
|
||||||
|
timeoutTime = 0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasData(){
|
|
||||||
return gotData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConnecting(){
|
public boolean isConnecting(){
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import io.anuke.mindustry.entities.SyncEntity;
|
|||||||
import io.anuke.mindustry.game.EventType.GameOverEvent;
|
import io.anuke.mindustry.game.EventType.GameOverEvent;
|
||||||
import io.anuke.mindustry.io.Platform;
|
import io.anuke.mindustry.io.Platform;
|
||||||
import io.anuke.mindustry.io.Version;
|
import io.anuke.mindustry.io.Version;
|
||||||
import io.anuke.mindustry.net.Administration;
|
import io.anuke.mindustry.net.*;
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.net.Administration.PlayerInfo;
|
||||||
import io.anuke.mindustry.net.Net.SendMode;
|
import io.anuke.mindustry.net.Net.SendMode;
|
||||||
import io.anuke.mindustry.net.NetworkIO;
|
|
||||||
import io.anuke.mindustry.net.Packets.*;
|
import io.anuke.mindustry.net.Packets.*;
|
||||||
import io.anuke.mindustry.resource.*;
|
import io.anuke.mindustry.resource.*;
|
||||||
import io.anuke.mindustry.world.Block;
|
import io.anuke.mindustry.world.Block;
|
||||||
@@ -30,7 +29,7 @@ import java.nio.ByteBuffer;
|
|||||||
import static io.anuke.mindustry.Vars.*;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
|
|
||||||
public class NetServer extends Module{
|
public class NetServer extends Module{
|
||||||
private final static float serverSyncTime = 4, itemSyncTime = 10;
|
private final static float serverSyncTime = 4, itemSyncTime = 10, kickDuration = 30 * 1000;
|
||||||
|
|
||||||
private final static int timerEntitySync = 0;
|
private final static int timerEntitySync = 0;
|
||||||
private final static int timerStateSync = 1;
|
private final static int timerStateSync = 1;
|
||||||
@@ -49,40 +48,48 @@ public class NetServer extends Module{
|
|||||||
|
|
||||||
Net.handleServer(Connect.class, (id, connect) -> {
|
Net.handleServer(Connect.class, (id, connect) -> {
|
||||||
if(admins.isIPBanned(connect.addressTCP)){
|
if(admins.isIPBanned(connect.addressTCP)){
|
||||||
Net.kickConnection(id, KickReason.banned);
|
kick(id, KickReason.banned);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Net.handleServer(ConnectPacket.class, (id, packet) -> {
|
Net.handleServer(ConnectPacket.class, (id, packet) -> {
|
||||||
String uuid = new String(Base64Coder.encode(packet.uuid));
|
String uuid = new String(Base64Coder.encode(packet.uuid));
|
||||||
|
|
||||||
if(Net.getConnection(id) == null ||
|
if(Net.getConnection(id) == null ||
|
||||||
admins.isIPBanned(Net.getConnection(id).address)) return;
|
admins.isIPBanned(Net.getConnection(id).address)) return;
|
||||||
|
|
||||||
|
TraceInfo trace = admins.getTrace(Net.getConnection(id).address);
|
||||||
|
PlayerInfo info = admins.getInfo(uuid);
|
||||||
|
trace.uuid = uuid;
|
||||||
|
trace.android = packet.android;
|
||||||
|
|
||||||
if(admins.isIDBanned(uuid)){
|
if(admins.isIDBanned(uuid)){
|
||||||
Net.kickConnection(id, KickReason.banned);
|
kick(id, KickReason.banned);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(TimeUtils.millis() - info.lastKicked < kickDuration){
|
||||||
|
kick(id, KickReason.recentKick);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String ip = Net.getConnection(id).address;
|
String ip = Net.getConnection(id).address;
|
||||||
|
|
||||||
admins.setKnownName(ip, packet.name);
|
admins.updatePlayerJoined(uuid, ip, packet.name);
|
||||||
admins.setKnownIP(uuid, ip);
|
|
||||||
admins.getTrace(ip).uuid = uuid;
|
|
||||||
admins.getTrace(ip).android = packet.android;
|
|
||||||
|
|
||||||
if(packet.version != Version.build && Version.build != -1 && packet.version != -1){
|
if(packet.version != Version.build && Version.build != -1 && packet.version != -1){
|
||||||
Net.kickConnection(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated);
|
kick(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(packet.version == -1){
|
if(packet.version == -1){
|
||||||
admins.getTrace(ip).modclient = true;
|
trace.modclient = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info("Sending data to player '{0}' / {1}", packet.name, id);
|
Log.info("Sending data to player '{0}' / {1}", packet.name, id);
|
||||||
|
|
||||||
Player player = new Player();
|
Player player = new Player();
|
||||||
player.isAdmin = admins.isAdmin(Net.getConnection(id).address);
|
player.isAdmin = admins.isAdmin(uuid, ip);
|
||||||
player.clientid = id;
|
player.clientid = id;
|
||||||
player.name = packet.name;
|
player.name = packet.name;
|
||||||
player.isAndroid = packet.android;
|
player.isAndroid = packet.android;
|
||||||
@@ -92,7 +99,7 @@ public class NetServer extends Module{
|
|||||||
player.color.set(packet.color);
|
player.color.set(packet.color);
|
||||||
connections.put(id, player);
|
connections.put(id, player);
|
||||||
|
|
||||||
admins.getTrace(ip).playerid = player.id;
|
trace.playerid = player.id;
|
||||||
|
|
||||||
if(world.getMap().custom){
|
if(world.getMap().custom){
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
@@ -114,7 +121,7 @@ public class NetServer extends Module{
|
|||||||
Player player = connections.get(id);
|
Player player = connections.get(id);
|
||||||
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
NetworkIO.writeWorld(player, weapons.get(player.name, new ByteArray()), stream);
|
NetworkIO.writeWorld(player, weapons.get(admins.getTrace(Net.getConnection(id).address).uuid, new ByteArray()), stream);
|
||||||
WorldData data = new WorldData();
|
WorldData data = new WorldData();
|
||||||
data.stream = new ByteArrayInputStream(stream.toByteArray());
|
data.stream = new ByteArrayInputStream(stream.toByteArray());
|
||||||
Net.sendStream(id, data);
|
Net.sendStream(id, data);
|
||||||
@@ -150,6 +157,7 @@ public class NetServer extends Module{
|
|||||||
Net.send(dc, SendMode.tcp);
|
Net.send(dc, SendMode.tcp);
|
||||||
|
|
||||||
Platform.instance.updateRPC();
|
Platform.instance.updateRPC();
|
||||||
|
admins.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
Net.handleServer(PositionPacket.class, (id, packet) -> {
|
Net.handleServer(PositionPacket.class, (id, packet) -> {
|
||||||
@@ -161,6 +169,22 @@ public class NetServer extends Module{
|
|||||||
});
|
});
|
||||||
|
|
||||||
Net.handleServer(ShootPacket.class, (id, packet) -> {
|
Net.handleServer(ShootPacket.class, (id, packet) -> {
|
||||||
|
TraceInfo info = admins.getTrace(Net.getConnection(id).address);
|
||||||
|
Weapon weapon = (Weapon)Upgrade.getByID(packet.weaponid);
|
||||||
|
|
||||||
|
float wtrc = 40f;
|
||||||
|
|
||||||
|
if(!Timers.get(info.ip + "-weapontrace", wtrc)){
|
||||||
|
info.fastShots ++;
|
||||||
|
}else{
|
||||||
|
|
||||||
|
if(info.fastShots - 1 > (int)(wtrc / (weapon.getReload() / 2f))){
|
||||||
|
kick(id, KickReason.kick);
|
||||||
|
}
|
||||||
|
|
||||||
|
info.fastShots = 0;
|
||||||
|
}
|
||||||
|
|
||||||
packet.playerid = connections.get(id).id;
|
packet.playerid = connections.get(id).id;
|
||||||
Net.sendExcept(id, packet, SendMode.udp);
|
Net.sendExcept(id, packet, SendMode.udp);
|
||||||
});
|
});
|
||||||
@@ -182,6 +206,7 @@ public class NetServer extends Module{
|
|||||||
|
|
||||||
admins.getTrace(Net.getConnection(id).address).lastBlockPlaced = block;
|
admins.getTrace(Net.getConnection(id).address).lastBlockPlaced = block;
|
||||||
admins.getTrace(Net.getConnection(id).address).totalBlocksPlaced ++;
|
admins.getTrace(Net.getConnection(id).address).totalBlocksPlaced ++;
|
||||||
|
admins.getInfo(admins.getTrace(Net.getConnection(id).address).uuid).totalBlockPlaced ++;
|
||||||
|
|
||||||
Net.send(packet, SendMode.tcp);
|
Net.send(packet, SendMode.tcp);
|
||||||
});
|
});
|
||||||
@@ -196,6 +221,7 @@ public class NetServer extends Module{
|
|||||||
if(block != null) {
|
if(block != null) {
|
||||||
admins.getTrace(Net.getConnection(id).address).lastBlockBroken = block;
|
admins.getTrace(Net.getConnection(id).address).lastBlockBroken = block;
|
||||||
admins.getTrace(Net.getConnection(id).address).totalBlocksBroken++;
|
admins.getTrace(Net.getConnection(id).address).totalBlocksBroken++;
|
||||||
|
admins.getInfo(admins.getTrace(Net.getConnection(id).address).uuid).totalBlocksBroken ++;
|
||||||
if (block.update || block.destructible)
|
if (block.update || block.destructible)
|
||||||
admins.getTrace(Net.getConnection(id).address).structureBlocksBroken++;
|
admins.getTrace(Net.getConnection(id).address).structureBlocksBroken++;
|
||||||
}
|
}
|
||||||
@@ -220,11 +246,22 @@ public class NetServer extends Module{
|
|||||||
Player player = connections.get(id);
|
Player player = connections.get(id);
|
||||||
|
|
||||||
Weapon weapon = (Weapon) Upgrade.getByID(packet.id);
|
Weapon weapon = (Weapon) Upgrade.getByID(packet.id);
|
||||||
|
String uuid = admins.getTrace(Net.getConnection(id).address).uuid;
|
||||||
|
|
||||||
if (!weapons.containsKey(player.name)) weapons.put(player.name, new ByteArray());
|
if(!state.inventory.hasItems(UpgradeRecipes.get(weapon))){
|
||||||
if (!weapons.get(player.name).contains(weapon.id)) weapons.get(player.name).add(weapon.id);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!weapons.containsKey(uuid)) weapons.put(uuid, new ByteArray());
|
||||||
|
|
||||||
|
if (!weapons.get(uuid).contains(weapon.id)){
|
||||||
|
weapons.get(uuid).add(weapon.id);
|
||||||
|
}else{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.inventory.removeItems(UpgradeRecipes.get(weapon));
|
state.inventory.removeItems(UpgradeRecipes.get(weapon));
|
||||||
|
Net.sendTo(id, packet, SendMode.tcp);
|
||||||
});
|
});
|
||||||
|
|
||||||
Net.handleServer(WeaponSwitchPacket.class, (id, packet) -> {
|
Net.handleServer(WeaponSwitchPacket.class, (id, packet) -> {
|
||||||
@@ -277,10 +314,10 @@ public class NetServer extends Module{
|
|||||||
|
|
||||||
if(packet.action == AdminAction.ban){
|
if(packet.action == AdminAction.ban){
|
||||||
admins.banPlayerIP(ip);
|
admins.banPlayerIP(ip);
|
||||||
Net.kickConnection(other.clientid, KickReason.banned);
|
kick(other.clientid, KickReason.banned);
|
||||||
Log.info("&lc{0} has banned {1}.", player.name, other.name);
|
Log.info("&lc{0} has banned {1}.", player.name, other.name);
|
||||||
}else if(packet.action == AdminAction.kick){
|
}else if(packet.action == AdminAction.kick){
|
||||||
Net.kickConnection(other.clientid, KickReason.kick);
|
kick(other.clientid, KickReason.kick);
|
||||||
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
|
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
|
||||||
}else if(packet.action == AdminAction.trace){
|
}else if(packet.action == AdminAction.trace){
|
||||||
TracePacket trace = new TracePacket();
|
TracePacket trace = new TracePacket();
|
||||||
@@ -313,6 +350,30 @@ public class NetServer extends Module{
|
|||||||
admins.clearTraces();
|
admins.clearTraces();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void kick(int connection, KickReason reason){
|
||||||
|
NetConnection con = Net.getConnection(connection);
|
||||||
|
if(con == null){
|
||||||
|
Log.err("Cannot kick unknown player!");
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
Log.info("Kicking connection #{0} / IP: {1}. Reason: {2}", connection, con.address, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
if((reason == KickReason.kick || reason == KickReason.banned) && admins.getTrace(con.address).uuid != null){
|
||||||
|
PlayerInfo info = admins.getInfo(admins.getTrace(con.address).uuid);
|
||||||
|
info.timesKicked ++;
|
||||||
|
info.lastKicked = TimeUtils.millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
KickPacket p = new KickPacket();
|
||||||
|
p.reason = reason;
|
||||||
|
|
||||||
|
con.send(p, SendMode.tcp);
|
||||||
|
Timers.runTask(2f, con::close);
|
||||||
|
|
||||||
|
admins.save();
|
||||||
|
}
|
||||||
|
|
||||||
void sync(){
|
void sync(){
|
||||||
|
|
||||||
if(timer.get(timerEntitySync, serverSyncTime)){
|
if(timer.get(timerEntitySync, serverSyncTime)){
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import io.anuke.ucore.function.Callable;
|
|||||||
import io.anuke.ucore.graphics.*;
|
import io.anuke.ucore.graphics.*;
|
||||||
import io.anuke.ucore.modules.RendererModule;
|
import io.anuke.ucore.modules.RendererModule;
|
||||||
import io.anuke.ucore.scene.ui.layout.Unit;
|
import io.anuke.ucore.scene.ui.layout.Unit;
|
||||||
|
import io.anuke.ucore.scene.utils.Cursors;
|
||||||
import io.anuke.ucore.util.Angles;
|
import io.anuke.ucore.util.Angles;
|
||||||
import io.anuke.ucore.util.Mathf;
|
import io.anuke.ucore.util.Mathf;
|
||||||
import io.anuke.ucore.util.Tmp;
|
import io.anuke.ucore.util.Tmp;
|
||||||
@@ -68,6 +69,12 @@ public class Renderer extends RendererModule{
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cursors.cursorScaling = 3;
|
||||||
|
Cursors.outlineColor = Color.valueOf("444444");
|
||||||
|
Cursors.arrow = Cursors.loadCursor("cursor");
|
||||||
|
Cursors.hand = Cursors.loadCursor("hand");
|
||||||
|
Cursors.ibeam = Cursors.loadCursor("ibar");
|
||||||
|
|
||||||
clearColor = Hue.lightness(0.4f);
|
clearColor = Hue.lightness(0.4f);
|
||||||
clearColor.a = 1f;
|
clearColor.a = 1f;
|
||||||
|
|
||||||
@@ -408,7 +415,8 @@ public class Renderer extends RendererModule{
|
|||||||
if((input.recipe != null && state.inventory.hasItems(input.recipe.requirements) && (!ui.hasMouse() || android)
|
if((input.recipe != null && state.inventory.hasItems(input.recipe.requirements) && (!ui.hasMouse() || android)
|
||||||
&& control.input().drawPlace())){
|
&& control.input().drawPlace())){
|
||||||
|
|
||||||
input.placeMode.draw(control.input().getBlockX(), control.input().getBlockY(), control.input().getBlockEndX(), control.input().getBlockEndY());
|
input.placeMode.draw(control.input().getBlockX(), control.input().getBlockY(),
|
||||||
|
control.input().getBlockEndX(), control.input().getBlockEndY());
|
||||||
|
|
||||||
Lines.stroke(1f);
|
Lines.stroke(1f);
|
||||||
Draw.color(Color.SCARLET);
|
Draw.color(Color.SCARLET);
|
||||||
@@ -424,7 +432,13 @@ public class Renderer extends RendererModule{
|
|||||||
if(input.breakMode == PlaceMode.holdDelete)
|
if(input.breakMode == PlaceMode.holdDelete)
|
||||||
input.breakMode.draw(tilex, tiley, 0, 0);
|
input.breakMode.draw(tilex, tiley, 0, 0);
|
||||||
|
|
||||||
}else if(input.breakMode.delete && control.input().drawPlace() && input.recipe == null){
|
}else if(input.breakMode.delete && control.input().drawPlace()
|
||||||
|
&& (input.recipe == null || !state.inventory.hasItems(input.recipe.requirements))
|
||||||
|
&& (input.placeMode.delete || input.breakMode.both || !android)){
|
||||||
|
|
||||||
|
if(input.breakMode == PlaceMode.holdDelete)
|
||||||
|
input.breakMode.draw(tilex, tiley, 0, 0);
|
||||||
|
else
|
||||||
input.breakMode.draw(control.input().getBlockX(), control.input().getBlockY(),
|
input.breakMode.draw(control.input().getBlockX(), control.input().getBlockY(),
|
||||||
control.input().getBlockEndX(), control.input().getBlockEndY());
|
control.input().getBlockEndX(), control.input().getBlockEndY());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,12 +154,12 @@ public class UI extends SceneModule{
|
|||||||
language = new LanguageDialog();
|
language = new LanguageDialog();
|
||||||
settings = new SettingsMenuDialog();
|
settings = new SettingsMenuDialog();
|
||||||
paused = new PausedDialog();
|
paused = new PausedDialog();
|
||||||
|
changelog = new ChangelogDialog();
|
||||||
about = new AboutDialog();
|
about = new AboutDialog();
|
||||||
host = new HostDialog();
|
host = new HostDialog();
|
||||||
bans = new BansDialog();
|
bans = new BansDialog();
|
||||||
admins = new AdminsDialog();
|
admins = new AdminsDialog();
|
||||||
traces = new TraceDialog();
|
traces = new TraceDialog();
|
||||||
changelog = new ChangelogDialog();
|
|
||||||
|
|
||||||
build.begin(scene);
|
build.begin(scene);
|
||||||
|
|
||||||
@@ -221,8 +221,8 @@ public class UI extends SceneModule{
|
|||||||
|
|
||||||
public void showInfo(String info){
|
public void showInfo(String info){
|
||||||
new Dialog("$text.info.title", "dialog"){{
|
new Dialog("$text.info.title", "dialog"){{
|
||||||
content().margin(15).add(info);
|
content().margin(15).add(info).width(600f).get().setWrap(true);
|
||||||
buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4).get().getLabelCell().width(400f).get().setWrap(true);
|
buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4);
|
||||||
}}.show();
|
}}.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ public enum GameMode{
|
|||||||
public boolean infiniteResources;
|
public boolean infiniteResources;
|
||||||
public boolean disableWaveTimer;
|
public boolean disableWaveTimer;
|
||||||
|
|
||||||
|
public String description(){
|
||||||
|
return Bundles.get("mode."+name()+".description");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return Bundles.get("mode."+name()+".name");
|
return Bundles.get("mode."+name()+".name");
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ public class AndroidInput extends InputHandler{
|
|||||||
public float lmousex, lmousey;
|
public float lmousex, lmousey;
|
||||||
public float mousex, mousey;
|
public float mousex, mousey;
|
||||||
public boolean brokeBlock = false;
|
public boolean brokeBlock = false;
|
||||||
|
public boolean placing = false;
|
||||||
|
|
||||||
private boolean enableHold = false;
|
private boolean enableHold = false;
|
||||||
private boolean placing = false;
|
|
||||||
private float warmup;
|
private float warmup;
|
||||||
private float warmupDelay = 20;
|
private float warmupDelay = 20;
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ public class AndroidInput extends InputHandler{
|
|||||||
}else if(pointer == 0 && !breakMode.pan && breaking() && drawPlace()){
|
}else if(pointer == 0 && !breakMode.pan && breaking() && drawPlace()){
|
||||||
breakMode.released(getBlockX(), getBlockY(), getBlockEndX(), getBlockEndY());
|
breakMode.released(getBlockX(), getBlockY(), getBlockEndX(), getBlockEndY());
|
||||||
}
|
}
|
||||||
|
|
||||||
placing = false;
|
placing = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public class DefaultKeybinds {
|
|||||||
"block_info", Input.CONTROL_LEFT,
|
"block_info", Input.CONTROL_LEFT,
|
||||||
"player_list", Input.TAB,
|
"player_list", Input.TAB,
|
||||||
"chat", Input.ENTER,
|
"chat", Input.ENTER,
|
||||||
|
"chat_scroll_up", Input.UP,
|
||||||
|
"chat_scroll_down", Input.DOWN,
|
||||||
"console", Input.GRAVE,
|
"console", Input.GRAVE,
|
||||||
"weapon_1", Input.NUM_1,
|
"weapon_1", Input.NUM_1,
|
||||||
"weapon_2", Input.NUM_2,
|
"weapon_2", Input.NUM_2,
|
||||||
@@ -53,6 +55,8 @@ public class DefaultKeybinds {
|
|||||||
"rotate", new Axis(Input.CONTROLLER_A, Input.CONTROLLER_B),
|
"rotate", new Axis(Input.CONTROLLER_A, Input.CONTROLLER_B),
|
||||||
"player_list", Input.CONTROLLER_START,
|
"player_list", Input.CONTROLLER_START,
|
||||||
"chat", Input.ENTER,
|
"chat", Input.ENTER,
|
||||||
|
"chat_scroll_up", Input.UP,
|
||||||
|
"chat_scroll_down", Input.DOWN,
|
||||||
"console", Input.GRAVE,
|
"console", Input.GRAVE,
|
||||||
"weapon_1", Input.NUM_1,
|
"weapon_1", Input.NUM_1,
|
||||||
"weapon_2", Input.NUM_2,
|
"weapon_2", Input.NUM_2,
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ public class DesktopInput extends InputHandler{
|
|||||||
|
|
||||||
if((Inputs.keyTap("select") && recipe != null) || Inputs.keyTap("break")){
|
if((Inputs.keyTap("select") && recipe != null) || Inputs.keyTap("break")){
|
||||||
Vector2 vec = Graphics.world(Gdx.input.getX(), Gdx.input.getY());
|
Vector2 vec = Graphics.world(Gdx.input.getX(), Gdx.input.getY());
|
||||||
mousex = (int)vec.x;
|
mousex = vec.x;
|
||||||
mousey = (int)vec.y;
|
mousey = vec.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Inputs.keyDown("select") && !Inputs.keyDown("break")){
|
if(!Inputs.keyDown("select") && !Inputs.keyDown("break")){
|
||||||
@@ -108,6 +108,10 @@ public class DesktopInput extends InputHandler{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(target != null && target.block().isConfigurable(target)){
|
||||||
|
showCursor = true;
|
||||||
|
}
|
||||||
|
|
||||||
if(target != null && Inputs.keyTap("select") && !ui.hasMouse()){
|
if(target != null && Inputs.keyTap("select") && !ui.hasMouse()){
|
||||||
if(target.block().isConfigurable(target)){
|
if(target.block().isConfigurable(target)){
|
||||||
ui.configfrag.showConfig(target);
|
ui.configfrag.showConfig(target);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class GestureHandler extends GestureAdapter{
|
|||||||
if(control.showCursor() && !Inputs.keyDown("select")) return false;
|
if(control.showCursor() && !Inputs.keyDown("select")) return false;
|
||||||
|
|
||||||
if(!control.showCursor() && !(control.input().recipe != null
|
if(!control.showCursor() && !(control.input().recipe != null
|
||||||
&& state.inventory.hasItems(control.input().recipe.requirements) && control.input().placeMode.lockCamera) &&
|
&& control.input().placeMode.lockCamera) &&
|
||||||
!(control.input().recipe == null && control.input().breakMode.lockCamera)){
|
!(control.input().recipe == null && control.input().breakMode.lockCamera)){
|
||||||
float dx = deltaX*Core.camera.zoom/Core.cameraScale, dy = deltaY*Core.camera.zoom/Core.cameraScale;
|
float dx = deltaX*Core.camera.zoom/Core.cameraScale, dy = deltaY*Core.camera.zoom/Core.cameraScale;
|
||||||
player.x -= dx;
|
player.x -= dx;
|
||||||
|
|||||||
@@ -115,19 +115,23 @@ public abstract class InputHandler extends InputAdapter{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
|
public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
|
||||||
if(!Net.client()){
|
if(!Net.client()){ //is server or singleplayer
|
||||||
Placement.placeBlock(x, y, result, rotation, effects, sound);
|
Placement.placeBlock(x, y, result, rotation, effects, sound);
|
||||||
Tile tile = world.tile(x, y);
|
|
||||||
if(tile != null) result.placed(tile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Net.active()){
|
if(Net.active()){
|
||||||
NetEvents.handlePlace(x, y, result, rotation);
|
NetEvents.handlePlace(x, y, result, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!Net.client()){
|
||||||
|
Tile tile = world.tile(x, y);
|
||||||
|
if(tile != null) result.placed(tile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void breakBlock(int x, int y, boolean sound){
|
public void breakBlock(int x, int y, boolean sound){
|
||||||
if(!Net.client()) Placement.breakBlock(x, y, true, sound);
|
if(!Net.client())
|
||||||
|
Placement.breakBlock(x, y, true, sound);
|
||||||
|
|
||||||
if(Net.active()){
|
if(Net.active()){
|
||||||
NetEvents.handleBreak(x, y);
|
NetEvents.handleBreak(x, y);
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ public enum PlaceMode{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void released(int tilex, int tiley, int endx, int endy){
|
public void released(int tilex, int tiley, int endx, int endy){
|
||||||
|
|
||||||
process(tilex, tiley, endx, endy);
|
process(tilex, tiley, endx, endy);
|
||||||
tilex = this.tilex; tiley = this.tiley;
|
tilex = this.tilex; tiley = this.tiley;
|
||||||
endx = this.endx; endy = this.endy;
|
endx = this.endx; endy = this.endy;
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ import io.anuke.ucore.core.Settings;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.gwt;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
import static io.anuke.mindustry.Vars.logic;
|
|
||||||
import static io.anuke.mindustry.Vars.saveDirectory;
|
|
||||||
|
|
||||||
public class SaveIO{
|
public class SaveIO{
|
||||||
public static final IntMap<SaveFileVersion> versions = new IntMap<>();
|
public static final IntMap<SaveFileVersion> versions = new IntMap<>();
|
||||||
|
|||||||
@@ -7,177 +7,228 @@ import io.anuke.ucore.core.Settings;
|
|||||||
|
|
||||||
public class Administration {
|
public class Administration {
|
||||||
private Json json = new Json();
|
private Json json = new Json();
|
||||||
|
/**All player info. Maps UUIDs to info. This persists throughout restarts.*/
|
||||||
|
private ObjectMap<String, PlayerInfo> playerInfo = new ObjectMap<>();
|
||||||
|
/**Maps UUIDs to trace infos. This is wiped when a player logs off.*/
|
||||||
|
private ObjectMap<String, TraceInfo> traceInfo = new ObjectMap<>();
|
||||||
private Array<String> bannedIPs = new Array<>();
|
private Array<String> bannedIPs = new Array<>();
|
||||||
private Array<String> bannedIDs = new Array<>();
|
|
||||||
private Array<String> admins = new Array<>();
|
|
||||||
private ObjectMap<String, String> ipNames = new ObjectMap<>();
|
|
||||||
private ObjectMap<String, String> idIPs = new ObjectMap<>();
|
|
||||||
private ObjectMap<String, TraceInfo> traces = new ObjectMap<>();
|
|
||||||
|
|
||||||
public Administration(){
|
public Administration(){
|
||||||
Settings.defaultList(
|
Settings.defaults("playerInfo", "{}");
|
||||||
"bans", "{}",
|
Settings.defaults("bannedIPs", "{}");
|
||||||
"bannedIDs", "{}",
|
|
||||||
"admins", "{}",
|
|
||||||
"knownIPs", "{}",
|
|
||||||
"knownIDs", "{}"
|
|
||||||
);
|
|
||||||
|
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceInfo getTrace(String ip){
|
/**Call when a player joins to update their information here.*/
|
||||||
if(!traces.containsKey(ip)) traces.put(ip, new TraceInfo(ip));
|
public void updatePlayerJoined(String id, String ip, String name){
|
||||||
|
PlayerInfo info = getCreateInfo(id);
|
||||||
|
info.lastName = name;
|
||||||
|
info.lastIP = ip;
|
||||||
|
info.timesJoined ++;
|
||||||
|
if(!info.names.contains(name, false)) info.names.add(name);
|
||||||
|
if(!info.ips.contains(ip, false)) info.ips.add(ip);
|
||||||
|
}
|
||||||
|
|
||||||
return traces.get(ip);
|
/**Returns trace info by IP.*/
|
||||||
|
public TraceInfo getTrace(String ip){
|
||||||
|
if(!traceInfo.containsKey(ip)) traceInfo.put(ip, new TraceInfo(ip));
|
||||||
|
|
||||||
|
return traceInfo.get(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearTraces(){
|
public void clearTraces(){
|
||||||
traces.clear();
|
traceInfo.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Sets last known name for an IP.*/
|
/**Bans a player by IP; returns whether this player was already banned.
|
||||||
public void setKnownName(String ip, String name){
|
* If there are players who at any point had this IP, they will be UUID banned as well.*/
|
||||||
ipNames.put(ip, name);
|
|
||||||
saveKnown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Sets last known UUID for an IP.*/
|
|
||||||
public void setKnownIP(String id, String ip){
|
|
||||||
idIPs.put(id, ip);
|
|
||||||
saveKnown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Returns the last known name for an IP. Returns 'unknown' if this IP has an unknown username.*/
|
|
||||||
public String getLastName(String ip){
|
|
||||||
return ipNames.get(ip, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Returns the last known IP for a UUID. Returns 'unknown' if this IP has an unknown IP.*/
|
|
||||||
public String getLastIP(String id){
|
|
||||||
return idIPs.get(id, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Return the last known device ID associated with an IP. Returns 'unknown' if this IP has an unknown device.*/
|
|
||||||
public String getLastID(String ip){
|
|
||||||
for(String id : idIPs.keys()){
|
|
||||||
if(idIPs.get(id).equals(ip)){
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Returns list of banned IPs.*/
|
|
||||||
public Array<String> getBanned(){
|
|
||||||
return bannedIPs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Returns list of banned IDs.*/
|
|
||||||
public Array<String> getBannedIDs(){
|
|
||||||
return bannedIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Bans a player by IP; returns whether this player was already banned.*/
|
|
||||||
public boolean banPlayerIP(String ip){
|
public boolean banPlayerIP(String ip){
|
||||||
if(bannedIPs.contains(ip, false))
|
if(bannedIPs.contains(ip, false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
for(PlayerInfo info : playerInfo.values()){
|
||||||
|
if(info.ips.contains(ip, false)){
|
||||||
|
info.banned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bannedIPs.add(ip);
|
bannedIPs.add(ip);
|
||||||
saveBans();
|
save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Bans a player by UUID.*/
|
/**Bans a player by UUID; returns whether this player was already banned.*/
|
||||||
public boolean banPlayerID(String id){
|
public boolean banPlayerID(String id){
|
||||||
if(bannedIDs.contains(id, false))
|
if(playerInfo.containsKey(id) && playerInfo.get(id).banned)
|
||||||
return false;
|
return false;
|
||||||
bannedIDs.add(id);
|
|
||||||
saveBans();
|
getCreateInfo(id).banned = true;
|
||||||
|
|
||||||
|
save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Unbans a player by IP; returns whether this player was banned in the first place..*/
|
/**Unbans a player by IP; returns whether this player was banned in the first place.
|
||||||
|
* This method also unbans any player that was banned and had this IP.*/
|
||||||
public boolean unbanPlayerIP(String ip){
|
public boolean unbanPlayerIP(String ip){
|
||||||
if(!bannedIPs.contains(ip, false))
|
boolean found = bannedIPs.contains(ip, false);
|
||||||
return false;
|
|
||||||
|
for(PlayerInfo info : playerInfo.values()){
|
||||||
|
if(info.ips.contains(ip, false)){
|
||||||
|
info.banned = false;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bannedIPs.removeValue(ip, false);
|
bannedIPs.removeValue(ip, false);
|
||||||
saveBans();
|
|
||||||
|
|
||||||
return true;
|
if(found) save();
|
||||||
|
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Unbans a player by IP; returns whether this player was banned in the first place..*/
|
/**Unbans a player by ID; returns whether this player was banned in the first place.
|
||||||
public boolean unbanPlayerID(String ip){
|
* This also unbans all IPs the player used.*/
|
||||||
if(!bannedIDs.contains(ip, false))
|
public boolean unbanPlayerID(String id){
|
||||||
|
PlayerInfo info = getCreateInfo(id);
|
||||||
|
|
||||||
|
if(!info.banned)
|
||||||
return false;
|
return false;
|
||||||
bannedIDs.removeValue(ip, false);
|
|
||||||
saveBans();
|
info.banned = false;
|
||||||
|
bannedIPs.removeAll(info.ips, false);
|
||||||
|
save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Returns list of banned IPs.*/
|
/**Returns list of all players with admin status*/
|
||||||
public Array<String> getAdmins(){
|
public Array<PlayerInfo> getAdmins(){
|
||||||
return admins;
|
Array<PlayerInfo> result = new Array<>();
|
||||||
|
for(PlayerInfo info : playerInfo.values()){
|
||||||
|
if(info.admin){
|
||||||
|
result.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**Returns list of all players with admin status*/
|
||||||
|
public Array<PlayerInfo> getBanned(){
|
||||||
|
Array<PlayerInfo> result = new Array<>();
|
||||||
|
for(PlayerInfo info : playerInfo.values()){
|
||||||
|
if(info.banned){
|
||||||
|
result.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**Returns all banned IPs. This does not include the IPs of ID-banned players.*/
|
||||||
|
public Array<String> getBannedIPs(){
|
||||||
|
return bannedIPs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Makes a player an admin. Returns whether this player was already an admin.*/
|
/**Makes a player an admin. Returns whether this player was already an admin.*/
|
||||||
public boolean adminPlayer(String ip){
|
public boolean adminPlayer(String id, String ip){
|
||||||
if(admins.contains(ip, false))
|
PlayerInfo info = getCreateInfo(id);
|
||||||
|
|
||||||
|
if(info.admin)
|
||||||
return false;
|
return false;
|
||||||
admins.add(ip);
|
|
||||||
saveAdmins();
|
info.validAdminIP = ip;
|
||||||
|
info.admin = true;
|
||||||
|
save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Makes a player no longer an admin. Returns whether this player was an admin in the first place.*/
|
/**Makes a player no longer an admin. Returns whether this player was an admin in the first place.*/
|
||||||
public boolean unAdminPlayer(String ip){
|
public boolean unAdminPlayer(String id){
|
||||||
if(!admins.contains(ip, false))
|
PlayerInfo info = getCreateInfo(id);
|
||||||
|
|
||||||
|
if(!info.admin)
|
||||||
return false;
|
return false;
|
||||||
admins.removeValue(ip, false);
|
|
||||||
saveAdmins();
|
info.admin = false;
|
||||||
|
save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIPBanned(String ip){
|
public boolean isIPBanned(String ip){
|
||||||
return bannedIPs.contains(ip, false);
|
return bannedIPs.contains(ip, false) || (findByIP(ip) != null && findByIP(ip).banned);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIDBanned(String uuid){
|
public boolean isIDBanned(String uuid){
|
||||||
return bannedIDs.contains(uuid, false);
|
return getCreateInfo(uuid).banned;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAdmin(String ip){
|
public boolean isAdmin(String id, String ip){
|
||||||
return admins.contains(ip, false);
|
PlayerInfo info = getCreateInfo(id);
|
||||||
|
return info.admin && ip.equals(info.validAdminIP);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveKnown(){
|
public PlayerInfo getInfo(String id){
|
||||||
Settings.putString("knownIPs", json.toJson(ipNames));
|
return getCreateInfo(id);
|
||||||
Settings.putString("knownIDs", json.toJson(idIPs));
|
|
||||||
Settings.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveBans(){
|
public PlayerInfo getInfoOptional(String id){
|
||||||
Settings.putString("bans", json.toJson(bannedIPs));
|
return playerInfo.get(id);
|
||||||
Settings.putString("bannedIDs", json.toJson(bannedIDs));
|
|
||||||
Settings.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAdmins(){
|
public PlayerInfo findByIP(String ip){
|
||||||
Settings.putString("admins", json.toJson(admins));
|
for(PlayerInfo info : playerInfo.values()){
|
||||||
|
if(info.ips.contains(ip, false)){
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerInfo getCreateInfo(String id){
|
||||||
|
if(playerInfo.containsKey(id)){
|
||||||
|
return playerInfo.get(id);
|
||||||
|
}else{
|
||||||
|
PlayerInfo info = new PlayerInfo(id);
|
||||||
|
playerInfo.put(id, info);
|
||||||
|
save();
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(){
|
||||||
|
Settings.putString("playerInfo", json.toJson(playerInfo));
|
||||||
|
Settings.putString("bannedIPs", json.toJson(bannedIPs));
|
||||||
Settings.save();
|
Settings.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load(){
|
private void load(){
|
||||||
bannedIPs = json.fromJson(Array.class, Settings.getString("bans"));
|
playerInfo = json.fromJson(ObjectMap.class, Settings.getString("playerInfo"));
|
||||||
bannedIDs = json.fromJson(Array.class, Settings.getString("bannedIDs"));
|
bannedIPs = json.fromJson(Array.class, Settings.getString("bannedIPs"));
|
||||||
admins = json.fromJson(Array.class, Settings.getString("admins"));
|
}
|
||||||
ipNames = json.fromJson(ObjectMap.class, Settings.getString("knownIPs"));
|
|
||||||
idIPs = json.fromJson(ObjectMap.class, Settings.getString("knownIDs"));
|
public static class PlayerInfo{
|
||||||
|
public String id;
|
||||||
|
public String lastName = "<unknown>", lastIP = "<unknown>";
|
||||||
|
public String validAdminIP;
|
||||||
|
public Array<String> ips = new Array<>();
|
||||||
|
public Array<String> names = new Array<>();
|
||||||
|
public int timesKicked; //TODO not implemented!
|
||||||
|
public int timesJoined;
|
||||||
|
public int totalBlockPlaced;
|
||||||
|
public int totalBlocksBroken;
|
||||||
|
public boolean banned, admin;
|
||||||
|
public long lastKicked; //last kicked timestamp
|
||||||
|
|
||||||
|
PlayerInfo(String id){
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerInfo(){}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import com.badlogic.gdx.utils.reflect.ClassReflection;
|
|||||||
import io.anuke.mindustry.io.Platform;
|
import io.anuke.mindustry.io.Platform;
|
||||||
import io.anuke.mindustry.net.Packet.ImportantPacket;
|
import io.anuke.mindustry.net.Packet.ImportantPacket;
|
||||||
import io.anuke.mindustry.net.Packet.UnimportantPacket;
|
import io.anuke.mindustry.net.Packet.UnimportantPacket;
|
||||||
import io.anuke.mindustry.net.Packets.KickReason;
|
|
||||||
import io.anuke.mindustry.net.Streamable.StreamBegin;
|
import io.anuke.mindustry.net.Streamable.StreamBegin;
|
||||||
import io.anuke.mindustry.net.Streamable.StreamBuilder;
|
import io.anuke.mindustry.net.Streamable.StreamBuilder;
|
||||||
import io.anuke.mindustry.net.Streamable.StreamChunk;
|
import io.anuke.mindustry.net.Streamable.StreamChunk;
|
||||||
@@ -101,11 +100,6 @@ public class Net{
|
|||||||
clientProvider.discover(cons);
|
clientProvider.discover(cons);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Kick a specified connection from the server.*/
|
|
||||||
public static void kickConnection(int id, KickReason reason){
|
|
||||||
serverProvider.kick(id, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Returns a list of all connections IDs.*/
|
/**Returns a list of all connections IDs.*/
|
||||||
public static Array<NetConnection> getConnections(){
|
public static Array<NetConnection> getConnections(){
|
||||||
return (Array<NetConnection>)serverProvider.getConnections();
|
return (Array<NetConnection>)serverProvider.getConnections();
|
||||||
@@ -307,10 +301,6 @@ public class Net{
|
|||||||
Array<? extends NetConnection> getConnections();
|
Array<? extends NetConnection> getConnections();
|
||||||
/**Returns a connection by ID.*/
|
/**Returns a connection by ID.*/
|
||||||
NetConnection getByID(int id);
|
NetConnection getByID(int id);
|
||||||
/**Kick a certain connection.*/
|
|
||||||
void kick(int connection, KickReason reason);
|
|
||||||
/**Returns the ping for a certain connection.*/
|
|
||||||
int getPingFor(NetConnection connection);
|
|
||||||
/**Close all connections.*/
|
/**Close all connections.*/
|
||||||
void dispose();
|
void dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,9 +96,13 @@ public class NetEvents {
|
|||||||
public static void handleSendMessage(String message){
|
public static void handleSendMessage(String message){
|
||||||
ChatPacket packet = new ChatPacket();
|
ChatPacket packet = new ChatPacket();
|
||||||
packet.text = message;
|
packet.text = message;
|
||||||
packet.name = Vars.player.name;
|
packet.name = player.name;
|
||||||
packet.id = Vars.player.id;
|
packet.id = player.id;
|
||||||
Net.send(packet, SendMode.tcp);
|
Net.send(packet, SendMode.tcp);
|
||||||
|
|
||||||
|
if(Net.server() && !headless){
|
||||||
|
ui.chatfrag.addMessage(message, netCommon.colorizeName(player.id, player.name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void handleShoot(Weapon weapon, float x, float y, float angle){
|
public static void handleShoot(Weapon weapon, float x, float y, float angle){
|
||||||
|
|||||||
@@ -377,7 +377,14 @@ public class Packets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum KickReason{
|
public enum KickReason{
|
||||||
kick, invalidPassword, clientOutdated, serverOutdated, banned
|
kick, invalidPassword, clientOutdated, serverOutdated, banned, gameover(true), recentKick;
|
||||||
|
public final boolean quiet;
|
||||||
|
|
||||||
|
KickReason(){ quiet = false; }
|
||||||
|
|
||||||
|
KickReason(boolean quiet){
|
||||||
|
this.quiet = quiet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class UpgradePacket implements Packet{
|
public static class UpgradePacket implements Packet{
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public class TraceInfo {
|
|||||||
public boolean modclient;
|
public boolean modclient;
|
||||||
public boolean android;
|
public boolean android;
|
||||||
|
|
||||||
|
public int fastShots;
|
||||||
|
|
||||||
public int totalBlocksBroken;
|
public int totalBlocksBroken;
|
||||||
public int structureBlocksBroken;
|
public int structureBlocksBroken;
|
||||||
public Block lastBlockBroken = Blocks.air;
|
public Block lastBlockBroken = Blocks.air;
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ public class Weapon extends Upgrade{
|
|||||||
Effects.sound(shootsound, x, y);
|
Effects.sound(shootsound, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getReload(){
|
||||||
|
return reload;
|
||||||
|
}
|
||||||
|
|
||||||
public void shoot(Player p, float x, float y, float angle){
|
public void shoot(Player p, float x, float y, float angle){
|
||||||
shootInternal(p, x, y, angle);
|
shootInternal(p, x, y, angle);
|
||||||
|
|
||||||
|
|||||||
32
core/src/io/anuke/mindustry/ui/Links.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package io.anuke.mindustry.ui;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import io.anuke.ucore.util.Bundles;
|
||||||
|
|
||||||
|
public class Links {
|
||||||
|
private static final LinkEntry[] links = {
|
||||||
|
new LinkEntry("discord", "https://discord.gg/BKADYds", Color.valueOf("7289da")),
|
||||||
|
new LinkEntry("trello", "https://trello.com/b/aE2tcUwF", Color.valueOf("026aa7")),
|
||||||
|
new LinkEntry("wiki", "http://mindustry.wikia.com/wiki/Mindustry_Wiki", Color.valueOf("0f142f")),
|
||||||
|
new LinkEntry("itch.io", "https://anuke.itch.io/mindustry", Color.valueOf("fa5c5c")),
|
||||||
|
new LinkEntry("google-play", "https://play.google.com/store/apps/details?id=io.anuke.mindustry", Color.valueOf("689f38")),
|
||||||
|
new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Color.valueOf("24292e")),
|
||||||
|
new LinkEntry("dev-builds", "https://github.com/Anuken/Mindustry/wiki", Color.valueOf("fafbfc")),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static LinkEntry[] getLinks(){
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LinkEntry{
|
||||||
|
public final String name, description, link;
|
||||||
|
public final Color color;
|
||||||
|
|
||||||
|
public LinkEntry(String name, String link, Color color) {
|
||||||
|
this.name = name;
|
||||||
|
this.color = color;
|
||||||
|
this.description = Bundles.getNotNull("text.link." + name +".description");
|
||||||
|
this.link = link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +1,37 @@
|
|||||||
package io.anuke.mindustry.ui;
|
package io.anuke.mindustry.ui;
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
|
||||||
import io.anuke.ucore.core.Core;
|
|
||||||
import io.anuke.ucore.function.Listenable;
|
import io.anuke.ucore.function.Listenable;
|
||||||
import io.anuke.ucore.scene.ui.Button;
|
import io.anuke.ucore.scene.ui.TextButton;
|
||||||
import io.anuke.ucore.scene.ui.layout.Unit;
|
|
||||||
import io.anuke.ucore.util.Bundles;
|
|
||||||
|
|
||||||
public class MenuButton extends Button{
|
public class MenuButton extends TextButton{
|
||||||
private static boolean hasInvalid = false;
|
|
||||||
private String text;
|
|
||||||
private boolean added = false;
|
|
||||||
|
|
||||||
public MenuButton(String text, PressGroup group, Listenable clicked){
|
public MenuButton(String icon, String text, Listenable clicked){
|
||||||
super("menu");
|
this(icon, text, null, clicked);
|
||||||
this.text = text;
|
|
||||||
BitmapFont font = Core.skin.getFont("title");
|
|
||||||
for(char c : Bundles.get(text.substring(1)).toCharArray()){
|
|
||||||
if(!font.getData().hasGlyph(c)){
|
|
||||||
hasInvalid = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MenuButton(String icon, String text, String description, Listenable clicked){
|
||||||
|
super("default");
|
||||||
|
float s = 70f;
|
||||||
|
|
||||||
clicked(clicked);
|
clicked(clicked);
|
||||||
group.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
clearChildren();
|
||||||
public void layout() {
|
|
||||||
super.layout();
|
|
||||||
if(added)
|
|
||||||
return;
|
|
||||||
added = true;
|
|
||||||
String style = "title";
|
|
||||||
float scale = 4f;
|
|
||||||
if(hasInvalid){
|
|
||||||
style = "default";
|
|
||||||
scale = Unit.dp.scl(1f);
|
|
||||||
}
|
|
||||||
add(text, style, scale).color(hasInvalid ? Color.DARK_GRAY : Color.WHITE);
|
|
||||||
|
|
||||||
if(hasInvalid){
|
margin(0);
|
||||||
row();
|
|
||||||
add(text, style, scale).padTop(Unit.dp.scl(-Core.font.getData().lineHeight * scale * 2f - 4f)).color(Color.WHITE);
|
table(t -> {
|
||||||
|
t.addImage(icon).size(14*3);
|
||||||
|
t.update(() -> t.setBackground(getClickListener().isOver() || getClickListener().isVisualPressed() ? "button-over" : "button"));
|
||||||
|
}).size(s - 5, s);
|
||||||
|
|
||||||
|
|
||||||
|
table(t -> {
|
||||||
|
t.add(text);
|
||||||
|
if(description != null){
|
||||||
|
t.row();
|
||||||
|
t.add(description).color(Color.LIGHT_GRAY);
|
||||||
}
|
}
|
||||||
|
}).padLeft(5).growX();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,71 @@
|
|||||||
package io.anuke.mindustry.ui.dialogs;
|
package io.anuke.mindustry.ui.dialogs;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import io.anuke.mindustry.ui.Links;
|
||||||
|
import io.anuke.mindustry.ui.Links.LinkEntry;
|
||||||
|
import io.anuke.ucore.core.Core;
|
||||||
|
import io.anuke.ucore.core.Timers;
|
||||||
|
import io.anuke.ucore.scene.ui.ScrollPane;
|
||||||
|
import io.anuke.ucore.scene.ui.layout.Table;
|
||||||
|
|
||||||
|
import static io.anuke.mindustry.Vars.ui;
|
||||||
|
|
||||||
public class AboutDialog extends FloatingDialog {
|
public class AboutDialog extends FloatingDialog {
|
||||||
|
|
||||||
public AboutDialog(){
|
public AboutDialog(){
|
||||||
super("$text.about.button");
|
super("$text.about.button");
|
||||||
|
|
||||||
addCloseButton();
|
addCloseButton();
|
||||||
content().add("$text.about");
|
|
||||||
|
float h = 80f;
|
||||||
|
float w = 600f;
|
||||||
|
|
||||||
|
Table in = new Table();
|
||||||
|
ScrollPane pane = new ScrollPane(in, "clear");
|
||||||
|
|
||||||
|
for(LinkEntry link : Links.getLinks()){
|
||||||
|
Table table = new Table("button");
|
||||||
|
table.margin(0);
|
||||||
|
table.table(img -> {
|
||||||
|
img.addImage("white").height(h - 5).width(40f).color(link.color);
|
||||||
|
img.row();
|
||||||
|
img.addImage("white").height(5).width(40f).color(link.color.cpy().mul(0.8f, 0.8f, 0.8f, 1f));
|
||||||
|
}).expandY();
|
||||||
|
|
||||||
|
table.table(i -> {
|
||||||
|
i.background("button");
|
||||||
|
i.addImage("icon-" + link.name).size(14*3f);
|
||||||
|
}).size(h-5, h);
|
||||||
|
|
||||||
|
table.table(inset -> {
|
||||||
|
inset.add("[accent]"+link.name.replace("-", " ")).growX().left();
|
||||||
|
inset.row();
|
||||||
|
inset.labelWrap(link.description).width(w - 100f).color(Color.LIGHT_GRAY).growX();
|
||||||
|
}).padLeft(8);
|
||||||
|
|
||||||
|
table.addImageButton("icon-link", 14*3, () -> {
|
||||||
|
if(!Gdx.net.openURI(link.link)){
|
||||||
|
ui.showError("$text.linkfail");
|
||||||
|
Gdx.app.getClipboard().setContents(link.link);
|
||||||
|
}
|
||||||
|
}).size(h-5, h);
|
||||||
|
|
||||||
|
in.add(table).size(w, h).padTop(5).row();
|
||||||
|
}
|
||||||
|
|
||||||
|
shown(() -> Timers.run(1f, () -> Core.scene.setScrollFocus(pane)));
|
||||||
|
|
||||||
|
content().add(pane).growX();
|
||||||
|
|
||||||
|
buttons().addButton("$text.credits", this::showCredits).size(200f, 64f);
|
||||||
|
buttons().addButton("$text.changelog.title", ui.changelog::show).size(200f, 64f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCredits(){
|
||||||
|
FloatingDialog dialog = new FloatingDialog("$text.credits");
|
||||||
|
dialog.addCloseButton();
|
||||||
|
dialog.content().add("$text.about");
|
||||||
|
dialog.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.anuke.mindustry.ui.dialogs;
|
package io.anuke.mindustry.ui.dialogs;
|
||||||
|
|
||||||
import io.anuke.mindustry.entities.Player;
|
import io.anuke.mindustry.entities.Player;
|
||||||
|
import io.anuke.mindustry.net.Administration.PlayerInfo;
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.net.Net;
|
||||||
import io.anuke.mindustry.net.NetConnection;
|
import io.anuke.mindustry.net.NetConnection;
|
||||||
import io.anuke.mindustry.net.NetEvents;
|
import io.anuke.mindustry.net.NetEvents;
|
||||||
@@ -36,15 +37,15 @@ public class AdminsDialog extends FloatingDialog {
|
|||||||
table.add("$text.server.admins.none");
|
table.add("$text.server.admins.none");
|
||||||
}
|
}
|
||||||
|
|
||||||
for(String ip : netServer.admins.getAdmins()){
|
for(PlayerInfo info : netServer.admins.getAdmins()){
|
||||||
Table res = new Table("button");
|
Table res = new Table("button");
|
||||||
res.margin(14f);
|
res.margin(14f);
|
||||||
|
|
||||||
res.labelWrap("[LIGHT_GRAY]" + netServer.admins.getLastName(ip)).width(w - h - 24f);
|
res.labelWrap("[LIGHT_GRAY]" + info.lastName).width(w - h - 24f);
|
||||||
res.add().growX();
|
res.add().growX();
|
||||||
res.addImageButton("icon-cancel", 14*3, () -> {
|
res.addImageButton("icon-cancel", 14*3, () -> {
|
||||||
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
|
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
|
||||||
netServer.admins.unAdminPlayer(ip);
|
netServer.admins.unAdminPlayer(info.id);
|
||||||
for(Player player : playerGroup.all()){
|
for(Player player : playerGroup.all()){
|
||||||
NetConnection c = Net.getConnection(player.clientid);
|
NetConnection c = Net.getConnection(player.clientid);
|
||||||
if(c != null){
|
if(c != null){
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.anuke.mindustry.ui.dialogs;
|
package io.anuke.mindustry.ui.dialogs;
|
||||||
|
|
||||||
|
import io.anuke.mindustry.net.Administration.PlayerInfo;
|
||||||
import io.anuke.ucore.scene.ui.ScrollPane;
|
import io.anuke.ucore.scene.ui.ScrollPane;
|
||||||
import io.anuke.ucore.scene.ui.layout.Table;
|
import io.anuke.ucore.scene.ui.layout.Table;
|
||||||
|
|
||||||
@@ -33,15 +34,15 @@ public class BansDialog extends FloatingDialog {
|
|||||||
table.add("$text.server.bans.none");
|
table.add("$text.server.bans.none");
|
||||||
}
|
}
|
||||||
|
|
||||||
for(String ip : netServer.admins.getBanned()){
|
for(PlayerInfo info : netServer.admins.getBanned()){
|
||||||
Table res = new Table("button");
|
Table res = new Table("button");
|
||||||
res.margin(14f);
|
res.margin(14f);
|
||||||
|
|
||||||
res.labelWrap("IP: [LIGHT_GRAY]" + ip + "\n[]Name: [LIGHT_GRAY]" + netServer.admins.getLastName(ip)).width(w - h - 24f);
|
res.labelWrap("IP: [LIGHT_GRAY]" + info.lastIP + "\n[]Name: [LIGHT_GRAY]" + info.lastName).width(w - h - 24f);
|
||||||
res.add().growX();
|
res.add().growX();
|
||||||
res.addImageButton("icon-cancel", 14*3, () -> {
|
res.addImageButton("icon-cancel", 14*3, () -> {
|
||||||
ui.showConfirm("$text.confirm", "$text.confirmunban", () -> {
|
ui.showConfirm("$text.confirm", "$text.confirmunban", () -> {
|
||||||
netServer.admins.unbanPlayerIP(ip);
|
netServer.admins.unbanPlayerID(info.id);
|
||||||
setup();
|
setup();
|
||||||
});
|
});
|
||||||
}).size(h).pad(-14f);
|
}).size(h).pad(-14f);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class ChangelogDialog extends FloatingDialog{
|
|||||||
if(info.build == Version.build){
|
if(info.build == Version.build){
|
||||||
in.row();
|
in.row();
|
||||||
in.add("$text.changelog.current");
|
in.add("$text.changelog.current");
|
||||||
}else if(info == versions.peek()){
|
}else if(info == versions.first()){
|
||||||
in.row();
|
in.row();
|
||||||
in.add("$text.changelog.latest");
|
in.add("$text.changelog.latest");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,52 @@
|
|||||||
package io.anuke.mindustry.ui.dialogs;
|
package io.anuke.mindustry.ui.dialogs;
|
||||||
|
|
||||||
import com.badlogic.gdx.Gdx;
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import com.badlogic.gdx.graphics.Colors;
|
||||||
import io.anuke.ucore.scene.ui.Dialog;
|
import io.anuke.ucore.scene.ui.Dialog;
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.discordURL;
|
import static io.anuke.mindustry.Vars.discordURL;
|
||||||
|
import static io.anuke.mindustry.Vars.ui;
|
||||||
|
|
||||||
public class DiscordDialog extends Dialog {
|
public class DiscordDialog extends Dialog {
|
||||||
|
|
||||||
public DiscordDialog(){
|
public DiscordDialog(){
|
||||||
super("Discord", "dialog");
|
super("", "dialog");
|
||||||
|
|
||||||
|
float h = 70f;
|
||||||
|
|
||||||
content().margin(12f);
|
content().margin(12f);
|
||||||
content().add("$text.discord");
|
|
||||||
content().row();
|
Color color = Color.valueOf("7289da");
|
||||||
content().add("[orange]"+ discordURL);
|
|
||||||
buttons().defaults().size(200f, 50);
|
content().table(t -> {
|
||||||
buttons().addButton("$text.openlink", () -> Gdx.net.openURI(discordURL));
|
t.background("button").margin(0);
|
||||||
|
|
||||||
|
t.table(img -> {
|
||||||
|
img.addImage("white").height(h - 5).width(40f).color(color);
|
||||||
|
img.row();
|
||||||
|
img.addImage("white").height(5).width(40f).color(color.cpy().mul(0.8f, 0.8f, 0.8f, 1f));
|
||||||
|
}).expandY();
|
||||||
|
|
||||||
|
t.table(i -> {
|
||||||
|
i.background("button");
|
||||||
|
i.addImage("icon-discord").size(14 * 3);
|
||||||
|
}).size(h).left();
|
||||||
|
|
||||||
|
t.add("$text.discord").color(Colors.get("accent")).growX().padLeft(10f);
|
||||||
|
}).size(470f, h).pad(10f);
|
||||||
|
|
||||||
|
buttons().defaults().size(170f, 50);
|
||||||
|
|
||||||
buttons().addButton("$text.back", this::hide);
|
buttons().addButton("$text.back", this::hide);
|
||||||
|
buttons().addButton("$text.copylink", () ->{
|
||||||
|
Gdx.app.getClipboard().setContents(discordURL);
|
||||||
|
});
|
||||||
|
buttons().addButton("$text.openlink", () ->{
|
||||||
|
if(!Gdx.net.openURI(discordURL)){
|
||||||
|
ui.showError("$text.linkfail");
|
||||||
|
Gdx.app.getClipboard().setContents(discordURL);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,10 +40,12 @@ public class HostDialog extends FloatingDialog{
|
|||||||
});
|
});
|
||||||
}).size(50f, 54f).get();
|
}).size(50f, 54f).get();
|
||||||
button.update(() -> button.getStyle().imageUpColor = player.getColor());
|
button.update(() -> button.getStyle().imageUpColor = player.getColor());
|
||||||
}).width(w).height(70f).pad(4);
|
}).width(w).height(70f).pad(4).colspan(3);
|
||||||
|
|
||||||
content().row();
|
content().row();
|
||||||
|
|
||||||
|
content().add().width(65f);
|
||||||
|
|
||||||
content().addButton("$text.host", () -> {
|
content().addButton("$text.host", () -> {
|
||||||
ui.loadfrag.show("$text.hosting");
|
ui.loadfrag.show("$text.hosting");
|
||||||
Timers.runTask(5f, () -> {
|
Timers.runTask(5f, () -> {
|
||||||
@@ -57,5 +59,7 @@ public class HostDialog extends FloatingDialog{
|
|||||||
hide();
|
hide();
|
||||||
});
|
});
|
||||||
}).width(w).height(70f);
|
}).width(w).height(70f);
|
||||||
|
|
||||||
|
content().addButton("?", () -> ui.showInfo("$text.host.info")).size(65f, 70f).padLeft(6f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,14 @@ public class JoinDialog extends FloatingDialog {
|
|||||||
|
|
||||||
loadServers();
|
loadServers();
|
||||||
|
|
||||||
|
buttons().add().width(60f);
|
||||||
|
buttons().add().growX();
|
||||||
|
|
||||||
addCloseButton();
|
addCloseButton();
|
||||||
|
|
||||||
|
buttons().add().growX();
|
||||||
|
buttons().addButton("?", () -> ui.showInfo("$text.join.info")).size(60f, 64f);
|
||||||
|
|
||||||
add = new FloatingDialog("$text.joingame.title");
|
add = new FloatingDialog("$text.joingame.title");
|
||||||
add.content().add("$text.joingame.ip").padRight(5f).left();
|
add.content().add("$text.joingame.ip").padRight(5f).left();
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import static io.anuke.mindustry.Vars.*;
|
|||||||
|
|
||||||
public class LevelDialog extends FloatingDialog{
|
public class LevelDialog extends FloatingDialog{
|
||||||
private Map selectedMap = world.maps().getMap(0);
|
private Map selectedMap = world.maps().getMap(0);
|
||||||
private TextureRegion region = new TextureRegion();
|
|
||||||
private ScrollPane pane;
|
private ScrollPane pane;
|
||||||
|
|
||||||
public LevelDialog(){
|
public LevelDialog(){
|
||||||
@@ -56,6 +55,7 @@ public class LevelDialog extends FloatingDialog{
|
|||||||
group.add(b[0]);
|
group.add(b[0]);
|
||||||
selmode.add(b[0]).size(130f, 54f);
|
selmode.add(b[0]).size(130f, 54f);
|
||||||
}
|
}
|
||||||
|
selmode.addButton("?", this::displayGameModeHelp).size(50f, 54f).padLeft(18f);
|
||||||
|
|
||||||
content().add(selmode);
|
content().add(selmode);
|
||||||
content().row();
|
content().row();
|
||||||
@@ -171,4 +171,23 @@ public class LevelDialog extends FloatingDialog{
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void displayGameModeHelp() {
|
||||||
|
FloatingDialog d = new FloatingDialog(Bundles.get("mode.text.help.title"));
|
||||||
|
d.setFillParent(false);
|
||||||
|
Table table = new Table();
|
||||||
|
table.defaults().pad(1f);
|
||||||
|
ScrollPane pane = new ScrollPane(table, "clear");
|
||||||
|
pane.setFadeScrollBars(false);
|
||||||
|
table.row();
|
||||||
|
for(GameMode mode : GameMode.values()){
|
||||||
|
table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(600f);
|
||||||
|
table.row();
|
||||||
|
}
|
||||||
|
|
||||||
|
d.content().add(pane);
|
||||||
|
d.buttons().addButton("$text.ok", d::hide).size(110, 50).pad(10f);
|
||||||
|
d.show();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ public class BackgroundFragment implements Fragment {
|
|||||||
Draw.color();
|
Draw.color();
|
||||||
|
|
||||||
TextureRegion back = Draw.region("background");
|
TextureRegion back = Draw.region("background");
|
||||||
float backscl = Math.max(Gdx.graphics.getWidth() / (float)back.getRegionWidth() * 1.5f, Unit.dp.scl(5f));
|
float backscl = (int)Math.max(Gdx.graphics.getWidth() / (float)back.getRegionWidth() * 1.5f, Unit.dp.scl(5f));
|
||||||
|
|
||||||
Draw.alpha(0.7f);
|
Draw.alpha(0.5f);
|
||||||
Core.batch.draw(back, w/2 - back.getRegionWidth()*backscl/2 +240f, h/2 - back.getRegionHeight()*backscl/2 + 250f,
|
Core.batch.draw(back, w/2 - back.getRegionWidth()*backscl/2, h/2 - back.getRegionHeight()*backscl/2,
|
||||||
back.getRegionWidth()*backscl, back.getRegionHeight()*backscl);
|
back.getRegionWidth()*backscl, back.getRegionHeight()*backscl);
|
||||||
|
|
||||||
boolean portrait = Gdx.graphics.getWidth() < Gdx.graphics.getHeight();
|
boolean portrait = Gdx.graphics.getWidth() < Gdx.graphics.getHeight();
|
||||||
@@ -31,7 +31,7 @@ public class BackgroundFragment implements Fragment {
|
|||||||
float logoh = logo.getRegionHeight()*logoscl;
|
float logoh = logo.getRegionHeight()*logoscl;
|
||||||
|
|
||||||
Draw.color();
|
Draw.color();
|
||||||
Core.batch.draw(logo, w/2 - logow/2, h - logoh + 15 + (portrait ? -Unit.dp.scl(30f) : 0f), logow, logoh);
|
Core.batch.draw(logo, (int)(w/2 - logow/2), (int)(h - logoh + 15 - Unit.dp.scl(portrait ? 30f : 0)), logow, logoh);
|
||||||
}).visible(() -> state.is(State.menu)).grow();
|
}).visible(() -> state.is(State.menu)).grow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import io.anuke.ucore.scene.ui.TextField;
|
|||||||
import io.anuke.ucore.scene.ui.layout.Table;
|
import io.anuke.ucore.scene.ui.layout.Table;
|
||||||
import io.anuke.ucore.scene.ui.layout.Unit;
|
import io.anuke.ucore.scene.ui.layout.Unit;
|
||||||
import io.anuke.ucore.util.Log;
|
import io.anuke.ucore.util.Log;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.state;
|
import static io.anuke.mindustry.Vars.state;
|
||||||
import static io.anuke.ucore.core.Core.scene;
|
import static io.anuke.ucore.core.Core.scene;
|
||||||
@@ -39,6 +40,8 @@ public class ChatFragment extends Table implements Fragment{
|
|||||||
private float textWidth = Unit.dp.scl(600);
|
private float textWidth = Unit.dp.scl(600);
|
||||||
private Color shadowColor = new Color(0, 0, 0, 0.4f);
|
private Color shadowColor = new Color(0, 0, 0, 0.4f);
|
||||||
private float textspacing = Unit.dp.scl(10);
|
private float textspacing = Unit.dp.scl(10);
|
||||||
|
private Array<String> history = new Array<String>();
|
||||||
|
private int historyPos = 0;
|
||||||
|
|
||||||
public ChatFragment(){
|
public ChatFragment(){
|
||||||
super();
|
super();
|
||||||
@@ -57,8 +60,21 @@ public class ChatFragment extends Table implements Fragment{
|
|||||||
if(Net.active() && Inputs.keyTap("chat")){
|
if(Net.active() && Inputs.keyTap("chat")){
|
||||||
toggle();
|
toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chatOpen) {
|
||||||
|
if (Inputs.keyTap("chat_scroll_up") && historyPos < history.size - 1) {
|
||||||
|
if (historyPos == 0) history.set(0, chatfield.getText());
|
||||||
|
historyPos++;
|
||||||
|
updateChat();
|
||||||
|
}
|
||||||
|
if (Inputs.keyTap("chat_scroll_down") && historyPos > 0) {
|
||||||
|
historyPos--;
|
||||||
|
updateChat();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
history.insert(0, "");
|
||||||
setup();
|
setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +85,8 @@ public class ChatFragment extends Table implements Fragment{
|
|||||||
|
|
||||||
public void clearMessages(){
|
public void clearMessages(){
|
||||||
messages.clear();
|
messages.clear();
|
||||||
|
history.clear();
|
||||||
|
history.insert(0, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setup(){
|
private void setup(){
|
||||||
@@ -144,7 +162,8 @@ public class ChatFragment extends Table implements Fragment{
|
|||||||
|
|
||||||
private void sendMessage(){
|
private void sendMessage(){
|
||||||
String message = chatfield.getText();
|
String message = chatfield.getText();
|
||||||
chatfield.clearText();
|
clearChatInput();
|
||||||
|
history.insert(1, message);
|
||||||
|
|
||||||
if(message.replaceAll(" ", "").isEmpty()) return;
|
if(message.replaceAll(" ", "").isEmpty()) return;
|
||||||
|
|
||||||
@@ -170,6 +189,17 @@ public class ChatFragment extends Table implements Fragment{
|
|||||||
public void hide(){
|
public void hide(){
|
||||||
scene.setKeyboardFocus(null);
|
scene.setKeyboardFocus(null);
|
||||||
chatOpen = false;
|
chatOpen = false;
|
||||||
|
clearChatInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateChat() {
|
||||||
|
chatfield.setText(history.get(historyPos));
|
||||||
|
chatfield.setCursorPosition(chatfield.getText().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearChatInput() {
|
||||||
|
historyPos = 0;
|
||||||
|
history.set(0, "");
|
||||||
chatfield.setText("");
|
chatfield.setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,8 +130,7 @@ public class DebugFragment implements Fragment {
|
|||||||
Net.client() ?
|
Net.client() ?
|
||||||
"chat.open: " + ui.chatfrag.chatOpen() + "\n" +
|
"chat.open: " + ui.chatfrag.chatOpen() + "\n" +
|
||||||
"chat.messages: " + ui.chatfrag.getMessagesSize() + "\n" +
|
"chat.messages: " + ui.chatfrag.getMessagesSize() + "\n" +
|
||||||
"client.connecting: " + netClient.isConnecting() + "\n" +
|
"client.connecting: " + netClient.isConnecting() + "\n" : "",
|
||||||
"client.hasdata: " + netClient.hasData() : "",
|
|
||||||
"players: " + playerGroup.size(),
|
"players: " + playerGroup.size(),
|
||||||
"enemies: " + enemyGroup.size(),
|
"enemies: " + enemyGroup.size(),
|
||||||
"tiles: " + tileGroup.size(),
|
"tiles: " + tileGroup.size(),
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package io.anuke.mindustry.ui.fragments;
|
package io.anuke.mindustry.ui.fragments;
|
||||||
|
|
||||||
import com.badlogic.gdx.Gdx;
|
import com.badlogic.gdx.Gdx;
|
||||||
import io.anuke.mindustry.Vars;
|
|
||||||
import io.anuke.mindustry.core.GameState.State;
|
import io.anuke.mindustry.core.GameState.State;
|
||||||
import io.anuke.mindustry.io.Platform;
|
import io.anuke.mindustry.io.Platform;
|
||||||
import io.anuke.mindustry.io.Version;
|
import io.anuke.mindustry.io.Version;
|
||||||
import io.anuke.mindustry.ui.MenuButton;
|
import io.anuke.mindustry.ui.MenuButton;
|
||||||
import io.anuke.mindustry.ui.PressGroup;
|
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||||
import io.anuke.ucore.scene.builders.imagebutton;
|
import io.anuke.ucore.scene.builders.imagebutton;
|
||||||
import io.anuke.ucore.scene.builders.label;
|
import io.anuke.ucore.scene.builders.label;
|
||||||
import io.anuke.ucore.scene.builders.table;
|
import io.anuke.ucore.scene.builders.table;
|
||||||
@@ -21,36 +20,38 @@ public class MenuFragment implements Fragment{
|
|||||||
|
|
||||||
if(!android){
|
if(!android){
|
||||||
new table(){{
|
new table(){{
|
||||||
PressGroup group = new PressGroup();
|
|
||||||
|
|
||||||
float scale = 4f;
|
float w = 200f;
|
||||||
defaults().size(140*scale, 21*scale).pad(-10f);
|
float bw = w * 2f + 10f;
|
||||||
|
|
||||||
|
defaults().size(w, 70f).padTop(5).padRight(5);
|
||||||
|
|
||||||
|
add(new MenuButton("icon-play-2", "$text.play", MenuFragment.this::showPlaySelect)).width(bw).colspan(2);
|
||||||
|
|
||||||
add(new MenuButton("$text.play", group, ui.levels::show));
|
|
||||||
row();
|
row();
|
||||||
|
|
||||||
if(Platform.instance.canJoinGame()) {
|
add(new MenuButton("icon-editor", "$text.editor", () -> {
|
||||||
add(new MenuButton("$text.joingame", group, ui.join::show));
|
if(gwt){
|
||||||
row();
|
ui.showInfo("$text.editor.web");
|
||||||
|
}else{
|
||||||
|
ui.editor.show();
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
add(new MenuButton("icon-tools", "$text.settings", ui.settings::show));
|
||||||
|
|
||||||
add(new MenuButton("$text.tutorial", group, ()-> control.playMap(world.maps().getMap("tutorial"))));
|
|
||||||
row();
|
row();
|
||||||
|
|
||||||
add(new MenuButton("$text.loadgame", group, ui.load::show));
|
add(new MenuButton("icon-info", "$text.about.button", ui.about::show));
|
||||||
|
|
||||||
|
add(new MenuButton("icon-menu", "$text.changelog.title", ui.changelog::show));
|
||||||
|
|
||||||
row();
|
row();
|
||||||
|
|
||||||
if(!gwt){
|
if(!gwt){
|
||||||
add(new MenuButton("$text.editor", group, ui.editor::show));
|
add(new MenuButton("icon-exit", "$text.quit", Gdx.app::exit)).width(bw).colspan(2);
|
||||||
row();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(new MenuButton("$text.settings", group, ui.settings::show));
|
|
||||||
row();
|
|
||||||
|
|
||||||
if(!gwt){
|
|
||||||
add(new MenuButton("$text.quit", group, Gdx.app::exit));
|
|
||||||
}
|
|
||||||
get().margin(16);
|
get().margin(16);
|
||||||
}}.end();
|
}}.end();
|
||||||
|
|
||||||
@@ -80,17 +81,13 @@ public class MenuFragment implements Fragment{
|
|||||||
}
|
}
|
||||||
}}.end();
|
}}.end();
|
||||||
|
|
||||||
//extra icons in top right
|
//discord icon in top right
|
||||||
new table(){{
|
|
||||||
atop().aright();
|
|
||||||
if(Platform.instance.hasDiscord()) {
|
if(Platform.instance.hasDiscord()) {
|
||||||
new imagebutton("icon-discord", 30f, ui.discord::show).margin(14);
|
new table() {{
|
||||||
}
|
abottom().atop().aright();
|
||||||
if(!Vars.android) {
|
get().addButton("", "discord", ui.discord::show);
|
||||||
new imagebutton("icon-info", 30f, ui.about::show).margin(14);
|
|
||||||
}
|
|
||||||
new imagebutton("icon-menu", 30f, ui.changelog::show).margin(14);
|
|
||||||
}}.end().visible(() -> state.is(State.menu));
|
}}.end().visible(() -> state.is(State.menu));
|
||||||
|
}
|
||||||
|
|
||||||
//version info
|
//version info
|
||||||
new table(){{
|
new table(){{
|
||||||
@@ -99,4 +96,41 @@ public class MenuFragment implements Fragment{
|
|||||||
new label("Mindustry " + Version.code + " " + Version.type + " / " + Version.buildName);
|
new label("Mindustry " + Version.code + " " + Version.type + " / " + Version.buildName);
|
||||||
}}.end();
|
}}.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showPlaySelect(){
|
||||||
|
float w = 200f;
|
||||||
|
float bw = w * 2f + 10f;
|
||||||
|
|
||||||
|
FloatingDialog dialog = new FloatingDialog("$text.play");
|
||||||
|
dialog.addCloseButton();
|
||||||
|
dialog.content().defaults().height(70f).width(w).padRight(5f);
|
||||||
|
|
||||||
|
dialog.content().add(new MenuButton("icon-play-2", "$text.newgame", () -> {
|
||||||
|
dialog.hide();
|
||||||
|
ui.levels.show();
|
||||||
|
})).width(bw).colspan(2);
|
||||||
|
dialog.content().row();
|
||||||
|
|
||||||
|
dialog.content().add(new MenuButton("icon-add", "$text.joingame", () -> {
|
||||||
|
if(Platform.instance.canJoinGame()){
|
||||||
|
ui.join.show();
|
||||||
|
dialog.hide();
|
||||||
|
}else{
|
||||||
|
ui.showInfo("$text.multiplayer.web");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
dialog.content().add(new MenuButton("icon-tutorial", "$text.tutorial", ()-> {
|
||||||
|
control.playMap(world.maps().getMap("tutorial"));
|
||||||
|
dialog.hide();
|
||||||
|
}));
|
||||||
|
|
||||||
|
dialog.content().row();
|
||||||
|
|
||||||
|
dialog.content().add(new MenuButton("icon-load", "$text.loadgame", () -> {
|
||||||
|
ui.load.show();
|
||||||
|
dialog.hide();
|
||||||
|
})).width(bw).colspan(2);
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color;
|
|||||||
import com.badlogic.gdx.math.Interpolation;
|
import com.badlogic.gdx.math.Interpolation;
|
||||||
import com.badlogic.gdx.utils.Align;
|
import com.badlogic.gdx.utils.Align;
|
||||||
import io.anuke.mindustry.core.GameState.State;
|
import io.anuke.mindustry.core.GameState.State;
|
||||||
|
import io.anuke.mindustry.input.AndroidInput;
|
||||||
import io.anuke.mindustry.input.InputHandler;
|
import io.anuke.mindustry.input.InputHandler;
|
||||||
import io.anuke.mindustry.input.PlaceMode;
|
import io.anuke.mindustry.input.PlaceMode;
|
||||||
import io.anuke.ucore.core.Core;
|
import io.anuke.ucore.core.Core;
|
||||||
@@ -128,7 +129,7 @@ public class PlacementFragment implements Fragment{
|
|||||||
|
|
||||||
defaults().padBottom(-5.5f);
|
defaults().padBottom(-5.5f);
|
||||||
|
|
||||||
new imagebutton("icon-" + mode.name(), "toggle", 10 * 3, () -> {
|
ImageButton button = new imagebutton("icon-" + mode.name(), "toggle", 10 * 3, () -> {
|
||||||
control.input().resetCursor();
|
control.input().resetCursor();
|
||||||
input.breakMode = mode;
|
input.breakMode = mode;
|
||||||
input.lastBreakMode = mode;
|
input.lastBreakMode = mode;
|
||||||
@@ -138,7 +139,15 @@ public class PlacementFragment implements Fragment{
|
|||||||
input.placeMode = input.lastPlaceMode;
|
input.placeMode = input.lastPlaceMode;
|
||||||
}
|
}
|
||||||
modeText(Bundles.format("text.mode.break", mode.toString()));
|
modeText(Bundles.format("text.mode.break", mode.toString()));
|
||||||
}).group(breakGroup).get().setName(mode.name());
|
}).group(breakGroup).get();
|
||||||
|
|
||||||
|
button.setName(mode.name());
|
||||||
|
button.released(() -> {
|
||||||
|
//TODO hack
|
||||||
|
if(mode == PlaceMode.areaDelete){
|
||||||
|
((AndroidInput)input).placing = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}}.end().get();
|
}}.end().get();
|
||||||
@@ -215,6 +224,7 @@ public class PlacementFragment implements Fragment{
|
|||||||
|
|
||||||
if(!show){
|
if(!show){
|
||||||
control.input().breakMode = PlaceMode.none;
|
control.input().breakMode = PlaceMode.none;
|
||||||
|
if(control.input().placeMode.delete) control.input().placeMode = PlaceMode.none;
|
||||||
breaktable.actions(Actions.translateBy(-breaktable.getWidth() - 5, 0, dur, in), Actions.call(() -> shown = false));
|
breaktable.actions(Actions.translateBy(-breaktable.getWidth() - 5, 0, dur, in), Actions.call(() -> shown = false));
|
||||||
}else{
|
}else{
|
||||||
shown = true;
|
shown = true;
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ public class PlayerListFragment implements Fragment{
|
|||||||
ui.showConfirm("$text.confirm", "$text.confirmban", () -> {
|
ui.showConfirm("$text.confirm", "$text.confirmban", () -> {
|
||||||
if(Net.server()) {
|
if(Net.server()) {
|
||||||
netServer.admins.banPlayerIP(connection.address);
|
netServer.admins.banPlayerIP(connection.address);
|
||||||
Net.kickConnection(player.clientid, KickReason.banned);
|
netServer.kick(player.clientid, KickReason.banned);
|
||||||
}else{
|
}else{
|
||||||
NetEvents.handleAdministerRequest(player, AdminAction.ban);
|
NetEvents.handleAdministerRequest(player, AdminAction.ban);
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ public class PlayerListFragment implements Fragment{
|
|||||||
|
|
||||||
t.addImageButton("icon-cancel", 14*2, () -> {
|
t.addImageButton("icon-cancel", 14*2, () -> {
|
||||||
if(Net.server()) {
|
if(Net.server()) {
|
||||||
Net.kickConnection(player.clientid, KickReason.kick);
|
netServer.kick(player.clientid, KickReason.kick);
|
||||||
}else{
|
}else{
|
||||||
NetEvents.handleAdministerRequest(player, AdminAction.kick);
|
NetEvents.handleAdministerRequest(player, AdminAction.kick);
|
||||||
}
|
}
|
||||||
@@ -150,14 +150,16 @@ public class PlayerListFragment implements Fragment{
|
|||||||
t.addImageButton("icon-admin", "toggle", 14*2, () -> {
|
t.addImageButton("icon-admin", "toggle", 14*2, () -> {
|
||||||
if(Net.client()) return;
|
if(Net.client()) return;
|
||||||
|
|
||||||
if(netServer.admins.isAdmin(connection.address)){
|
String id = netServer.admins.getTrace(connection.address).uuid;
|
||||||
|
|
||||||
|
if(netServer.admins.isAdmin(id, connection.address)){
|
||||||
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
|
ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> {
|
||||||
netServer.admins.unAdminPlayer(connection.address);
|
netServer.admins.unAdminPlayer(id);
|
||||||
NetEvents.handleAdminSet(player, false);
|
NetEvents.handleAdminSet(player, false);
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
ui.showConfirm("$text.confirm", "$text.confirmadmin", () -> {
|
ui.showConfirm("$text.confirm", "$text.confirmadmin", () -> {
|
||||||
netServer.admins.adminPlayer(connection.address);
|
netServer.admins.adminPlayer(id, connection.address);
|
||||||
NetEvents.handleAdminSet(player, true);
|
NetEvents.handleAdminSet(player, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public class WeaponBlocks{
|
|||||||
reload = 13f;
|
reload = 13f;
|
||||||
bullet = BulletType.stone;
|
bullet = BulletType.stone;
|
||||||
ammo = Item.stone;
|
ammo = Item.stone;
|
||||||
health = 55;
|
|
||||||
health = 45;
|
health = 45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class Teleporter extends PowerBlock{
|
|||||||
@Override
|
@Override
|
||||||
public void placed(Tile tile){
|
public void placed(Tile tile){
|
||||||
tile.<TeleporterEntity>entity().color = lastColor;
|
tile.<TeleporterEntity>entity().color = lastColor;
|
||||||
Timers.run(1f, () -> setConfigure(tile, lastColor));
|
setConfigure(tile, lastColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -89,14 +89,15 @@ public class WeaponFactory extends Block{
|
|||||||
tip.setInstant(true);
|
tip.setInstant(true);
|
||||||
|
|
||||||
ImageButton button = content.addImageButton("white", 8*4, () -> {
|
ImageButton button = content.addImageButton("white", 8*4, () -> {
|
||||||
|
|
||||||
|
if(Net.client()){
|
||||||
|
NetEvents.handleUpgrade(weapon);
|
||||||
|
}else{
|
||||||
state.inventory.removeItems(requirements);
|
state.inventory.removeItems(requirements);
|
||||||
control.upgrades().addWeapon(weapon);
|
control.upgrades().addWeapon(weapon);
|
||||||
ui.hudfrag.updateWeapons();
|
ui.hudfrag.updateWeapons();
|
||||||
run.listen();
|
run.listen();
|
||||||
Effects.sound("purchase");
|
Effects.sound("purchase");
|
||||||
|
|
||||||
if(Net.client()){
|
|
||||||
NetEvents.handleUpgrade(weapon);
|
|
||||||
}
|
}
|
||||||
}).size(49f, 54f).padBottom(-5).get();
|
}).size(49f, 54f).padBottom(-5).get();
|
||||||
|
|
||||||
|
|||||||
@@ -134,23 +134,6 @@ public class KryoServer implements ServerProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void kick(int connection, KickReason reason) {
|
|
||||||
KryoConnection con = getByID(connection);
|
|
||||||
if(con == null){
|
|
||||||
Log.err("Cannot kick unknown player!");
|
|
||||||
return;
|
|
||||||
}else{
|
|
||||||
Log.info("Kicking connection #{0} / IP: {1}. Reason: {2}", connection, con.address, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
KickPacket p = new KickPacket();
|
|
||||||
p.reason = reason;
|
|
||||||
|
|
||||||
con.send(p, SendMode.tcp);
|
|
||||||
Timers.runTask(2f, con::close);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void host(int port) throws IOException {
|
public void host(int port) throws IOException {
|
||||||
lastconnection = 0;
|
lastconnection = 0;
|
||||||
@@ -278,12 +261,6 @@ public class KryoServer implements ServerProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPingFor(NetConnection con) {
|
|
||||||
KryoConnection k = (KryoConnection)con;
|
|
||||||
return k.connection == null ? 0 : k.connection.getReturnTripTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose(){
|
public void dispose(){
|
||||||
close();
|
close();
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import io.anuke.mindustry.game.EventType.GameOverEvent;
|
|||||||
import io.anuke.mindustry.game.GameMode;
|
import io.anuke.mindustry.game.GameMode;
|
||||||
import io.anuke.mindustry.io.SaveIO;
|
import io.anuke.mindustry.io.SaveIO;
|
||||||
import io.anuke.mindustry.io.Version;
|
import io.anuke.mindustry.io.Version;
|
||||||
|
import io.anuke.mindustry.net.Administration.PlayerInfo;
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.net.Net;
|
||||||
|
import io.anuke.mindustry.net.NetConnection;
|
||||||
import io.anuke.mindustry.net.NetEvents;
|
import io.anuke.mindustry.net.NetEvents;
|
||||||
import io.anuke.mindustry.net.Packets.ChatPacket;
|
import io.anuke.mindustry.net.Packets.ChatPacket;
|
||||||
import io.anuke.mindustry.net.Packets.KickReason;
|
import io.anuke.mindustry.net.Packets.KickReason;
|
||||||
@@ -75,9 +77,9 @@ public class ServerControl extends Module {
|
|||||||
Events.on(GameOverEvent.class, () -> {
|
Events.on(GameOverEvent.class, () -> {
|
||||||
info("Game over!");
|
info("Game over!");
|
||||||
|
|
||||||
Timers.runTask(10f, () -> {
|
for(NetConnection connection : Net.getConnections()){
|
||||||
state.set(State.menu);
|
netServer.kick(connection.id, KickReason.gameover);
|
||||||
Net.closeServer();
|
}
|
||||||
|
|
||||||
if (mode != ShuffleMode.off) {
|
if (mode != ShuffleMode.off) {
|
||||||
Array<Map> maps = mode == ShuffleMode.both ? world.maps().getAllMaps() :
|
Array<Map> maps = mode == ShuffleMode.both ? world.maps().getAllMaps() :
|
||||||
@@ -91,10 +93,11 @@ public class ServerControl extends Module {
|
|||||||
state.set(State.playing);
|
state.set(State.playing);
|
||||||
logic.reset();
|
logic.reset();
|
||||||
world.loadMap(map);
|
world.loadMap(map);
|
||||||
host();
|
}else{
|
||||||
|
state.set(State.menu);
|
||||||
|
Net.closeServer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
info("&lcServer loaded. Type &ly'help'&lc for help.");
|
info("&lcServer loaded. Type &ly'help'&lc for help.");
|
||||||
}
|
}
|
||||||
@@ -124,14 +127,16 @@ public class ServerControl extends Module {
|
|||||||
Log.info("Stopped server.");
|
Log.info("Stopped server.");
|
||||||
});
|
});
|
||||||
|
|
||||||
handler.register("host", "<mapname> <mode>", "Open the server with a specific map.", arg -> {
|
handler.register("host", "[mapname] [mode]", "Open the server with a specific map.", arg -> {
|
||||||
if(state.is(State.playing)){
|
if(state.is(State.playing)){
|
||||||
err("Already hosting. Type 'stop' to stop hosting first.");
|
err("Already hosting. Type 'stop' to stop hosting first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String search = arg[0];
|
|
||||||
Map result = null;
|
Map result = null;
|
||||||
|
|
||||||
|
if(arg.length > 0) {
|
||||||
|
String search = arg[0];
|
||||||
for (Map map : world.maps().list()) {
|
for (Map map : world.maps().list()) {
|
||||||
if (map.name.equalsIgnoreCase(search))
|
if (map.name.equalsIgnoreCase(search))
|
||||||
result = map;
|
result = map;
|
||||||
@@ -141,10 +146,15 @@ public class ServerControl extends Module {
|
|||||||
err("No map with name &y'{0}'&lr found.", search);
|
err("No map with name &y'{0}'&lr found.", search);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
while(result == null || !result.visible)
|
||||||
|
result = world.maps().getAllMaps().random();
|
||||||
|
Log.info("&ly&fiNo map specified, so &lb{0}&ly was chosen randomly.", result.name);
|
||||||
|
}
|
||||||
|
|
||||||
GameMode mode = null;
|
GameMode mode;
|
||||||
try{
|
try{
|
||||||
mode = GameMode.valueOf(arg[1]);
|
mode = arg.length < 2 ? GameMode.waves : GameMode.valueOf(arg[1]);
|
||||||
}catch (IllegalArgumentException e){
|
}catch (IllegalArgumentException e){
|
||||||
err("No gamemode '{0}' found.", arg[1]);
|
err("No gamemode '{0}' found.", arg[1]);
|
||||||
return;
|
return;
|
||||||
@@ -270,7 +280,7 @@ public class ServerControl extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(target != null){
|
if(target != null){
|
||||||
Net.kickConnection(target.clientid, KickReason.kick);
|
netServer.kick(target.clientid, KickReason.kick);
|
||||||
info("It is done.");
|
info("It is done.");
|
||||||
}else{
|
}else{
|
||||||
info("Nobody with that name could be found...");
|
info("Nobody with that name could be found...");
|
||||||
@@ -296,7 +306,7 @@ public class ServerControl extends Module {
|
|||||||
String ip = Net.getConnection(target.clientid).address;
|
String ip = Net.getConnection(target.clientid).address;
|
||||||
netServer.admins.banPlayerIP(ip);
|
netServer.admins.banPlayerIP(ip);
|
||||||
netServer.admins.banPlayerID(netServer.admins.getTrace(ip).uuid);
|
netServer.admins.banPlayerID(netServer.admins.getTrace(ip).uuid);
|
||||||
Net.kickConnection(target.clientid, KickReason.banned);
|
netServer.kick(target.clientid, KickReason.banned);
|
||||||
info("Banned player by IP and ID: {0} / {1}", ip, netServer.admins.getTrace(ip).uuid);
|
info("Banned player by IP and ID: {0} / {1}", ip, netServer.admins.getTrace(ip).uuid);
|
||||||
}else{
|
}else{
|
||||||
info("Nobody with that name could be found.");
|
info("Nobody with that name could be found.");
|
||||||
@@ -304,26 +314,26 @@ public class ServerControl extends Module {
|
|||||||
});
|
});
|
||||||
|
|
||||||
handler.register("bans", "List all banned IPs and IDs.", arg -> {
|
handler.register("bans", "List all banned IPs and IDs.", arg -> {
|
||||||
Array<String> bans = netServer.admins.getBanned();
|
Array<PlayerInfo> bans = netServer.admins.getBanned();
|
||||||
|
|
||||||
if(bans.size == 0){
|
if(bans.size == 0){
|
||||||
Log.info("No IP-banned players have been found.");
|
|
||||||
}else{
|
|
||||||
Log.info("&lyBanned players [IP]:");
|
|
||||||
for(String string : bans){
|
|
||||||
Log.info(" &ly {0} / Last known name: '{1}'", string, netServer.admins.getLastName(string));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Array<String> idbans = netServer.admins.getBannedIDs();
|
|
||||||
|
|
||||||
if(idbans.size == 0){
|
|
||||||
Log.info("No ID-banned players have been found.");
|
Log.info("No ID-banned players have been found.");
|
||||||
}else{
|
}else{
|
||||||
Log.info("&lmBanned players [ID]:");
|
Log.info("&lyBanned players [ID]:");
|
||||||
for(String string : idbans){
|
for(PlayerInfo info : bans){
|
||||||
Log.info(" &lm '{0}' / Last known name: '{1}' / Last known IP: '{2}'", string,
|
Log.info(" &ly {0} / Last known name: '{1}'", info.id, info.lastName);
|
||||||
netServer.admins.getLastName(netServer.admins.getLastIP(string)), netServer.admins.getLastIP(string));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<String> ipbans = netServer.admins.getBannedIPs();
|
||||||
|
|
||||||
|
if(ipbans.size == 0){
|
||||||
|
Log.info("No IP-banned players have been found.");
|
||||||
|
}else{
|
||||||
|
Log.info("&lmBanned players [IP]:");
|
||||||
|
for(String string : ipbans){
|
||||||
|
PlayerInfo info = netServer.admins.findByIP(string);
|
||||||
|
Log.info(" &lm '{0}' / Last known name: '{1}' / ID: '{2}'", string, info.lastName, info.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -334,7 +344,7 @@ public class ServerControl extends Module {
|
|||||||
|
|
||||||
for(Player player : playerGroup.all()){
|
for(Player player : playerGroup.all()){
|
||||||
if(Net.getConnection(player.clientid).address.equals(arg[0])){
|
if(Net.getConnection(player.clientid).address.equals(arg[0])){
|
||||||
Net.kickConnection(player.clientid, KickReason.banned);
|
netServer.kick(player.clientid, KickReason.banned);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +359,7 @@ public class ServerControl extends Module {
|
|||||||
|
|
||||||
for(Player player : playerGroup.all()){
|
for(Player player : playerGroup.all()){
|
||||||
if(netServer.admins.getTrace(Net.getConnection(player.clientid).address).uuid.equals(arg[0])){
|
if(netServer.admins.getTrace(Net.getConnection(player.clientid).address).uuid.equals(arg[0])){
|
||||||
Net.kickConnection(player.clientid, KickReason.banned);
|
netServer.kick(player.clientid, KickReason.banned);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,12 +371,6 @@ public class ServerControl extends Module {
|
|||||||
handler.register("unbanip", "<ip>", "Completely unban a person by IP.", arg -> {
|
handler.register("unbanip", "<ip>", "Completely unban a person by IP.", arg -> {
|
||||||
if(netServer.admins.unbanPlayerIP(arg[0])) {
|
if(netServer.admins.unbanPlayerIP(arg[0])) {
|
||||||
info("Unbanned player by IP: {0}.", arg[0]);
|
info("Unbanned player by IP: {0}.", arg[0]);
|
||||||
for(String s : netServer.admins.getBannedIDs()){
|
|
||||||
if(netServer.admins.getLastIP(s).equals(arg[0])){
|
|
||||||
netServer.admins.unbanPlayerID(s);
|
|
||||||
Log.info("Also unbanned UUID '{0}' as it corresponds to this IP.", s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
}else{
|
||||||
err("That IP is not banned!");
|
err("That IP is not banned!");
|
||||||
}
|
}
|
||||||
@@ -375,11 +379,6 @@ public class ServerControl extends Module {
|
|||||||
handler.register("unbanid", "<id>", "Completely unban a person by ID.", arg -> {
|
handler.register("unbanid", "<id>", "Completely unban a person by ID.", arg -> {
|
||||||
if(netServer.admins.unbanPlayerID(arg[0])) {
|
if(netServer.admins.unbanPlayerID(arg[0])) {
|
||||||
info("&lmUnbanned player by ID: {0}.", arg[0]);
|
info("&lmUnbanned player by ID: {0}.", arg[0]);
|
||||||
String ip = netServer.admins.getLastIP(arg[0]);
|
|
||||||
if(!ip.equals("unknown")) {
|
|
||||||
netServer.admins.unbanPlayerIP(ip);
|
|
||||||
Log.info("Also unbanned IP '{0}' as it corresponds to this ID.", ip);
|
|
||||||
}
|
|
||||||
}else{
|
}else{
|
||||||
err("That IP is not banned!");
|
err("That IP is not banned!");
|
||||||
}
|
}
|
||||||
@@ -401,10 +400,10 @@ public class ServerControl extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(target != null){
|
if(target != null){
|
||||||
String ip = Net.getConnection(target.clientid).address;
|
String id = netServer.admins.getTrace(Net.getConnection(target.clientid).address).uuid;
|
||||||
netServer.admins.adminPlayer(ip);
|
netServer.admins.adminPlayer(id, Net.getConnection(target.clientid).address);
|
||||||
NetEvents.handleAdminSet(target, true);
|
NetEvents.handleAdminSet(target, true);
|
||||||
info("Admin-ed player by IP: {0} / {1}", ip, arg[0]);
|
info("Admin-ed player by ID: {0} / {1}", id, arg[0]);
|
||||||
}else{
|
}else{
|
||||||
info("Nobody with that name could be found.");
|
info("Nobody with that name could be found.");
|
||||||
}
|
}
|
||||||
@@ -426,24 +425,24 @@ public class ServerControl extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(target != null){
|
if(target != null){
|
||||||
String ip = Net.getConnection(target.clientid).address;
|
String id = netServer.admins.getTrace(Net.getConnection(target.clientid).address).uuid;
|
||||||
netServer.admins.unAdminPlayer(ip);
|
netServer.admins.unAdminPlayer(id);
|
||||||
NetEvents.handleAdminSet(target, false);
|
NetEvents.handleAdminSet(target, false);
|
||||||
info("Un-admin-ed player by IP: {0} / {1}", ip, arg[0]);
|
info("Un-admin-ed player by ID: {0} / {1}", id, arg[0]);
|
||||||
}else{
|
}else{
|
||||||
info("Nobody with that name could be found.");
|
info("Nobody with that name could be found.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
handler.register("admins", "List all admins.", arg -> {
|
handler.register("admins", "List all admins.", arg -> {
|
||||||
Array<String> admins = netServer.admins.getAdmins();
|
Array<PlayerInfo> admins = netServer.admins.getAdmins();
|
||||||
|
|
||||||
if(admins.size == 0){
|
if(admins.size == 0){
|
||||||
Log.info("No admins have been found.");
|
Log.info("No admins have been found.");
|
||||||
}else{
|
}else{
|
||||||
Log.info("&lyAdmins:");
|
Log.info("&lyAdmins:");
|
||||||
for(String string : admins){
|
for(PlayerInfo info : admins){
|
||||||
Log.info(" &luy {0} / Name: '{1}'", string, netServer.admins.getLastName(string));
|
Log.info(" &lm {0} / ID: '{1}' / IP: '{2}'", info.lastName, info.id, info.lastIP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -507,7 +506,7 @@ public class ServerControl extends Module {
|
|||||||
info("Core destroyed.");
|
info("Core destroyed.");
|
||||||
});
|
});
|
||||||
|
|
||||||
handler.register("info", "Print debug info", arg -> {
|
handler.register("debug", "Print debug info", arg -> {
|
||||||
info(DebugFragment.debugInfo());
|
info(DebugFragment.debugInfo());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -538,6 +537,24 @@ public class ServerControl extends Module {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
handler.register("info", "<UUID>", "Get global info for a player's UUID.", arg -> {
|
||||||
|
PlayerInfo info = netServer.admins.getInfoOptional(arg[0]);
|
||||||
|
|
||||||
|
if(info != null){
|
||||||
|
Log.info("&lcTrace info for player '{0}':", info.lastName);
|
||||||
|
Log.info(" &lyall names used: {0}", info.names);
|
||||||
|
Log.info(" &lyIP: {0}", info.lastIP);
|
||||||
|
Log.info(" &lyall IPs used: {0}", info.ips);
|
||||||
|
Log.info(" &lytimes joined: {0}", info.timesJoined);
|
||||||
|
Log.info(" &lytimes kicked: {0}", info.timesKicked);
|
||||||
|
Log.info("");
|
||||||
|
Log.info(" &lytotal blocks broken: {0}", info.totalBlocksBroken);
|
||||||
|
Log.info(" &lytotal blocks placed: {0}", info.totalBlockPlaced);
|
||||||
|
}else{
|
||||||
|
info("Nobody with that UUID could be found.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
handler.register("trace", "<username...>", "Trace a player's actions", arg -> {
|
handler.register("trace", "<username...>", "Trace a player's actions", arg -> {
|
||||||
if(!state.is(State.playing)) {
|
if(!state.is(State.playing)) {
|
||||||
err("Open the server first.");
|
err("Open the server first.");
|
||||||
|
|||||||